что то амт
This commit is contained in:
@@ -151,16 +151,27 @@
|
||||
`${dbInfo.host}:${dbInfo.port}/${dbInfo.database}`;
|
||||
document.getElementById('roleBadge').textContent = this.currentUser.role || 'viewer';
|
||||
document.getElementById('logsButton').classList.toggle('hidden', !this.getPermissions().canViewLogs);
|
||||
document.getElementById('usersButton').classList.toggle('hidden', !this.getPermissions().canManageUsers);
|
||||
document.getElementById('backupsButton').classList.toggle('hidden', !this.getPermissions().canManageUsers);
|
||||
document.getElementById('settingsButton').classList.toggle('hidden', !this.getPermissions().canManageUsers);
|
||||
document.getElementById('auditButton').classList.toggle('hidden', !this.getPermissions().canManageUsers);
|
||||
document.getElementById('managementButton').classList.toggle('hidden', !this.getPermissions().canManageUsers);
|
||||
document.querySelector('button[onclick="app.showSQLPanel()"]').style.display = this.getPermissions().canRunSql ? '' : 'none';
|
||||
document.querySelector('button[onclick="app.showCreateTableModal()"]').style.display = this.getPermissions().canCreate ? '' : 'none';
|
||||
|
||||
this.loadTables();
|
||||
}
|
||||
|
||||
hideWorkspacePanels() {
|
||||
['emptyState', 'dataGrid', 'sqlPanel', 'logsPanel', 'managementPanel'].forEach((id) => {
|
||||
const node = document.getElementById(id);
|
||||
if (node) {
|
||||
node.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setToolbarMode(mode) {
|
||||
document.getElementById('tableActions').classList.toggle('hidden', mode !== 'table');
|
||||
document.getElementById('recordControls').classList.toggle('hidden', mode !== 'table');
|
||||
}
|
||||
|
||||
getPermissions() {
|
||||
if (this.currentUser?.role === 'superadmin') {
|
||||
return {
|
||||
@@ -293,10 +304,8 @@
|
||||
this.closeSidebar();
|
||||
|
||||
document.getElementById('currentTableTitle').textContent = tableName;
|
||||
document.getElementById('tableActions').classList.remove('hidden');
|
||||
document.getElementById('emptyState').classList.add('hidden');
|
||||
document.getElementById('sqlPanel').classList.add('hidden');
|
||||
document.getElementById('logsPanel').classList.add('hidden');
|
||||
this.setToolbarMode('table');
|
||||
this.hideWorkspacePanels();
|
||||
document.getElementById('dataGrid').classList.remove('hidden');
|
||||
|
||||
// Update action buttons based on permissions
|
||||
@@ -841,10 +850,10 @@
|
||||
this.showToast('SQL доступ разрешен только администраторам', 'error');
|
||||
return;
|
||||
}
|
||||
document.getElementById('emptyState').classList.add('hidden');
|
||||
document.getElementById('dataGrid').classList.add('hidden');
|
||||
document.getElementById('logsPanel').classList.add('hidden');
|
||||
this.setToolbarMode('workspace');
|
||||
this.hideWorkspacePanels();
|
||||
document.getElementById('sqlPanel').classList.remove('hidden');
|
||||
document.getElementById('currentTableTitle').textContent = 'SQL Query';
|
||||
}
|
||||
|
||||
executeSQL() {
|
||||
@@ -936,10 +945,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('emptyState').classList.add('hidden');
|
||||
document.getElementById('dataGrid').classList.add('hidden');
|
||||
document.getElementById('sqlPanel').classList.add('hidden');
|
||||
this.setToolbarMode('workspace');
|
||||
this.hideWorkspacePanels();
|
||||
document.getElementById('logsPanel').classList.remove('hidden');
|
||||
document.getElementById('currentTableTitle').textContent = 'Container logs';
|
||||
await this.loadContainers();
|
||||
}
|
||||
|
||||
@@ -1070,13 +1079,41 @@
|
||||
return Array.from(document.querySelectorAll(`#${containerId} input[type="checkbox"]:checked`)).map(input => input.value);
|
||||
}
|
||||
|
||||
async showManagementPanel(section = 'settings') {
|
||||
if (!this.getPermissions().canManageUsers) {
|
||||
this.showToast('Management is available only for admins', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
this.setToolbarMode('workspace');
|
||||
this.hideWorkspacePanels();
|
||||
document.getElementById('managementPanel').classList.remove('hidden');
|
||||
document.getElementById('currentTableTitle').textContent = 'Management';
|
||||
|
||||
['settings', 'backups', 'users', 'audit'].forEach((item) => {
|
||||
document.getElementById(`managementSection-${item}`).classList.toggle('hidden', item !== section);
|
||||
document.getElementById(`managementTab-${item}`).className = item === section
|
||||
? 'w-full flex items-center gap-3 px-4 py-3 rounded-xl text-left bg-slate-100 text-slate-900'
|
||||
: 'w-full flex items-center gap-3 px-4 py-3 rounded-xl text-left text-slate-700 hover:bg-slate-100';
|
||||
});
|
||||
|
||||
if (section === 'settings') {
|
||||
await this.loadSettings();
|
||||
} else if (section === 'backups') {
|
||||
await this.loadBackups();
|
||||
} else if (section === 'users') {
|
||||
this.renderOptionChecklist('managementUserViewFoldersList', this.getAvailableFolders().filter(folder => folder !== 'default'));
|
||||
this.renderOptionChecklist('managementUserDeleteFoldersList', this.getAvailableFolders().filter(folder => folder !== 'default'));
|
||||
this.renderOptionChecklist('managementUserEditTablesList', this.tables.map(table => table.name));
|
||||
await this.loadUsers();
|
||||
this.resetUserForm();
|
||||
} else if (section === 'audit') {
|
||||
await this.loadAuditLog();
|
||||
}
|
||||
}
|
||||
|
||||
async showUsersModal() {
|
||||
this.renderOptionChecklist('userViewFoldersList', this.getAvailableFolders().filter(folder => folder !== 'default'));
|
||||
this.renderOptionChecklist('userDeleteFoldersList', this.getAvailableFolders().filter(folder => folder !== 'default'));
|
||||
this.renderOptionChecklist('userEditTablesList', this.tables.map(table => table.name));
|
||||
await this.loadUsers();
|
||||
this.resetUserForm();
|
||||
document.getElementById('usersModal').classList.remove('hidden');
|
||||
await this.showManagementPanel('users');
|
||||
}
|
||||
|
||||
async loadUsers() {
|
||||
@@ -1087,7 +1124,7 @@
|
||||
throw new Error(users.error || 'Failed to load users');
|
||||
}
|
||||
|
||||
document.getElementById('usersTableBody').innerHTML = users.map(user => `
|
||||
document.getElementById('managementUsersTableBody').innerHTML = users.map(user => `
|
||||
<tr class="border-b border-slate-200">
|
||||
<td class="p-3">${user.username}</td>
|
||||
<td class="p-3">${user.role}</td>
|
||||
@@ -1105,43 +1142,43 @@
|
||||
|
||||
editUser(serializedUser) {
|
||||
const user = JSON.parse(serializedUser);
|
||||
document.getElementById('userEditMode').value = user.username;
|
||||
document.getElementById('userUsername').value = user.username;
|
||||
document.getElementById('userUsername').disabled = true;
|
||||
document.getElementById('userPassword').value = '';
|
||||
document.getElementById('userRole').value = user.role;
|
||||
this.renderOptionChecklist('userViewFoldersList', this.getAvailableFolders().filter(folder => folder !== 'default'), user.access?.view?.folders || []);
|
||||
this.renderOptionChecklist('userDeleteFoldersList', this.getAvailableFolders().filter(folder => folder !== 'default'), user.access?.delete?.folders || []);
|
||||
this.renderOptionChecklist('userEditTablesList', this.tables.map(table => table.name), user.access?.edit?.tables || []);
|
||||
document.getElementById('userDisabled').checked = Boolean(user.disabled);
|
||||
document.getElementById('managementUserEditMode').value = user.username;
|
||||
document.getElementById('managementUserUsername').value = user.username;
|
||||
document.getElementById('managementUserUsername').disabled = true;
|
||||
document.getElementById('managementUserPassword').value = '';
|
||||
document.getElementById('managementUserRole').value = user.role;
|
||||
this.renderOptionChecklist('managementUserViewFoldersList', this.getAvailableFolders().filter(folder => folder !== 'default'), user.access?.view?.folders || []);
|
||||
this.renderOptionChecklist('managementUserDeleteFoldersList', this.getAvailableFolders().filter(folder => folder !== 'default'), user.access?.delete?.folders || []);
|
||||
this.renderOptionChecklist('managementUserEditTablesList', this.tables.map(table => table.name), user.access?.edit?.tables || []);
|
||||
document.getElementById('managementUserDisabled').checked = Boolean(user.disabled);
|
||||
}
|
||||
|
||||
resetUserForm() {
|
||||
document.getElementById('userEditMode').value = '';
|
||||
document.getElementById('userUsername').value = '';
|
||||
document.getElementById('userUsername').disabled = false;
|
||||
document.getElementById('userPassword').value = '';
|
||||
document.getElementById('userRole').value = 'viewer';
|
||||
this.renderOptionChecklist('userViewFoldersList', this.getAvailableFolders().filter(folder => folder !== 'default'));
|
||||
this.renderOptionChecklist('userDeleteFoldersList', this.getAvailableFolders().filter(folder => folder !== 'default'));
|
||||
this.renderOptionChecklist('userEditTablesList', this.tables.map(table => table.name));
|
||||
document.getElementById('userDisabled').checked = false;
|
||||
document.getElementById('managementUserEditMode').value = '';
|
||||
document.getElementById('managementUserUsername').value = '';
|
||||
document.getElementById('managementUserUsername').disabled = false;
|
||||
document.getElementById('managementUserPassword').value = '';
|
||||
document.getElementById('managementUserRole').value = 'viewer';
|
||||
this.renderOptionChecklist('managementUserViewFoldersList', this.getAvailableFolders().filter(folder => folder !== 'default'));
|
||||
this.renderOptionChecklist('managementUserDeleteFoldersList', this.getAvailableFolders().filter(folder => folder !== 'default'));
|
||||
this.renderOptionChecklist('managementUserEditTablesList', this.tables.map(table => table.name));
|
||||
document.getElementById('managementUserDisabled').checked = false;
|
||||
}
|
||||
|
||||
async saveUser() {
|
||||
const username = document.getElementById('userUsername').value.trim();
|
||||
const editMode = document.getElementById('userEditMode').value;
|
||||
const password = document.getElementById('userPassword').value;
|
||||
const username = document.getElementById('managementUserUsername').value.trim();
|
||||
const editMode = document.getElementById('managementUserEditMode').value;
|
||||
const password = document.getElementById('managementUserPassword').value;
|
||||
const payload = {
|
||||
username,
|
||||
password,
|
||||
role: document.getElementById('userRole').value,
|
||||
disabled: document.getElementById('userDisabled').checked,
|
||||
role: document.getElementById('managementUserRole').value,
|
||||
disabled: document.getElementById('managementUserDisabled').checked,
|
||||
access: {
|
||||
view: { folders: this.getCheckedValues('userViewFoldersList'), tables: [] },
|
||||
create: { folders: this.getCheckedValues('userViewFoldersList'), tables: [] },
|
||||
edit: { folders: [], tables: this.getCheckedValues('userEditTablesList') },
|
||||
delete: { folders: this.getCheckedValues('userDeleteFoldersList'), tables: [] },
|
||||
view: { folders: this.getCheckedValues('managementUserViewFoldersList'), tables: [] },
|
||||
create: { folders: this.getCheckedValues('managementUserViewFoldersList'), tables: [] },
|
||||
edit: { folders: [], tables: this.getCheckedValues('managementUserEditTablesList') },
|
||||
delete: { folders: this.getCheckedValues('managementUserDeleteFoldersList'), tables: [] },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1219,18 +1256,15 @@
|
||||
}
|
||||
|
||||
async showAuditModal() {
|
||||
await this.loadAuditLog();
|
||||
document.getElementById('auditModal').classList.remove('hidden');
|
||||
await this.showManagementPanel('audit');
|
||||
}
|
||||
|
||||
async showBackupsModal() {
|
||||
await this.loadBackups();
|
||||
document.getElementById('backupsModal').classList.remove('hidden');
|
||||
await this.showManagementPanel('backups');
|
||||
}
|
||||
|
||||
async showSettingsModal() {
|
||||
await this.loadSettings();
|
||||
document.getElementById('settingsModal').classList.remove('hidden');
|
||||
await this.showManagementPanel('settings');
|
||||
}
|
||||
|
||||
async loadBackups() {
|
||||
@@ -1241,14 +1275,17 @@
|
||||
throw new Error(backups.error || 'Failed to load backups');
|
||||
}
|
||||
|
||||
document.getElementById('backupsList').innerHTML = backups.length
|
||||
document.getElementById('managementBackupsList').innerHTML = backups.length
|
||||
? backups.map(backup => `
|
||||
<div class="border border-slate-200 rounded-xl p-4 flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<div class="font-medium text-slate-800">${backup.filename}</div>
|
||||
<div class="text-sm text-slate-500">${backup.createdAt} - ${backup.kind} - ${backup.size} bytes</div>
|
||||
</div>
|
||||
<a href="/api/backups/${encodeURIComponent(backup.filename)}/download" class="px-4 py-2 bg-slate-100 text-slate-700 rounded-lg">Download</a>
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick='app.restoreBackup(${JSON.stringify(backup.filename)})' class="px-4 py-2 bg-amber-50 text-amber-700 rounded-lg">Restore</button>
|
||||
<a href="/api/backups/${encodeURIComponent(backup.filename)}/download" class="px-4 py-2 bg-slate-100 text-slate-700 rounded-lg">Download</a>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')
|
||||
: '<div class="text-sm text-slate-500">No backups yet.</div>';
|
||||
@@ -1267,13 +1304,44 @@
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || 'Failed to create backup');
|
||||
}
|
||||
this.showToast('Backup created', 'success');
|
||||
this.showToast('Archive created', 'success');
|
||||
this.loadBackups();
|
||||
} catch (err) {
|
||||
this.showToast(err.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async restoreBackup(filename) {
|
||||
if (!confirm(`Restore backup ${filename}? The current database will be replaced.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/backups/${encodeURIComponent(filename)}/restore`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-Source': 'WEB',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
restoreAppSnapshot: document.getElementById('managementRestoreAppSnapshot').checked,
|
||||
}),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || 'Failed to restore backup');
|
||||
}
|
||||
|
||||
this.showToast('Backup restored', 'success');
|
||||
await this.loadTables();
|
||||
if (this.currentTable) {
|
||||
await this.selectTable(this.currentTable);
|
||||
}
|
||||
} catch (err) {
|
||||
this.showToast(err.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
try {
|
||||
const response = await fetch('/api/settings');
|
||||
@@ -1283,32 +1351,32 @@
|
||||
}
|
||||
|
||||
this.currentSettings = settings;
|
||||
document.getElementById('settingsBackupsEnabled').checked = Boolean(settings.backups?.enabled);
|
||||
document.getElementById('settingsBackupTime').value = `${String(settings.backups?.hour ?? 3).padStart(2, '0')}:${String(settings.backups?.minute ?? 0).padStart(2, '0')}`;
|
||||
document.getElementById('settingsKeepLast').value = settings.backups?.keepLast ?? 14;
|
||||
document.getElementById('settingsIncludeAppSnapshot').checked = settings.backups?.includeAppSnapshot !== false;
|
||||
document.getElementById('settingsTelegramEnabled').checked = Boolean(settings.telegram?.enabled);
|
||||
document.getElementById('settingsTelegramToken').value = settings.telegram?.botToken || '';
|
||||
document.getElementById('settingsTelegramChatId').value = settings.telegram?.chatId || '';
|
||||
document.getElementById('managementSettingsBackupsEnabled').checked = Boolean(settings.backups?.enabled);
|
||||
document.getElementById('managementSettingsBackupTime').value = `${String(settings.backups?.hour ?? 3).padStart(2, '0')}:${String(settings.backups?.minute ?? 0).padStart(2, '0')}`;
|
||||
document.getElementById('managementSettingsKeepLast').value = settings.backups?.keepLast ?? 14;
|
||||
document.getElementById('managementSettingsIncludeAppSnapshot').checked = settings.backups?.includeAppSnapshot !== false;
|
||||
document.getElementById('managementSettingsTelegramEnabled').checked = Boolean(settings.telegram?.enabled);
|
||||
document.getElementById('managementSettingsTelegramToken').value = settings.telegram?.botToken || '';
|
||||
document.getElementById('managementSettingsTelegramChatId').value = settings.telegram?.chatId || '';
|
||||
} catch (err) {
|
||||
this.showToast(err.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
const [hour, minute] = (document.getElementById('settingsBackupTime').value || '03:00').split(':').map(Number);
|
||||
const [hour, minute] = (document.getElementById('managementSettingsBackupTime').value || '03:00').split(':').map(Number);
|
||||
const payload = {
|
||||
backups: {
|
||||
enabled: document.getElementById('settingsBackupsEnabled').checked,
|
||||
enabled: document.getElementById('managementSettingsBackupsEnabled').checked,
|
||||
hour,
|
||||
minute,
|
||||
keepLast: Number(document.getElementById('settingsKeepLast').value || 14),
|
||||
includeAppSnapshot: document.getElementById('settingsIncludeAppSnapshot').checked,
|
||||
keepLast: Number(document.getElementById('managementSettingsKeepLast').value || 14),
|
||||
includeAppSnapshot: document.getElementById('managementSettingsIncludeAppSnapshot').checked,
|
||||
},
|
||||
telegram: {
|
||||
enabled: document.getElementById('settingsTelegramEnabled').checked,
|
||||
botToken: document.getElementById('settingsTelegramToken').value.trim(),
|
||||
chatId: document.getElementById('settingsTelegramChatId').value.trim(),
|
||||
enabled: document.getElementById('managementSettingsTelegramEnabled').checked,
|
||||
botToken: document.getElementById('managementSettingsTelegramToken').value.trim(),
|
||||
chatId: document.getElementById('managementSettingsTelegramChatId').value.trim(),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1328,7 +1396,6 @@
|
||||
|
||||
this.currentSettings = result.settings;
|
||||
this.showToast('Settings saved', 'success');
|
||||
this.closeModal('settingsModal');
|
||||
} catch (err) {
|
||||
this.showToast(err.message, 'error');
|
||||
}
|
||||
@@ -1342,7 +1409,7 @@
|
||||
throw new Error(entries.error || 'Failed to load audit log');
|
||||
}
|
||||
|
||||
document.getElementById('auditList').innerHTML = entries.length
|
||||
document.getElementById('managementAuditList').innerHTML = entries.length
|
||||
? entries.map(entry => `
|
||||
<div class="border border-slate-200 rounded-xl p-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
|
||||
Reference in New Issue
Block a user