логирование
This commit is contained in:
123
index.html
123
index.html
@@ -187,6 +187,10 @@
|
||||
<i data-lucide="users" class="w-4 h-4"></i>
|
||||
Users
|
||||
</button>
|
||||
<button id="auditButton" onclick="app.showAuditModal()" class="hidden flex items-center gap-2 px-4 py-2 bg-slate-100 text-slate-700 rounded-lg hover:bg-slate-200 transition-colors text-sm font-medium">
|
||||
<i data-lucide="history" class="w-4 h-4"></i>
|
||||
Audit
|
||||
</button>
|
||||
<button id="logsButton" onclick="app.showLogsPanel()" class="hidden flex items-center gap-2 px-4 py-2 bg-slate-100 text-slate-700 rounded-lg hover:bg-slate-200 transition-colors text-sm font-medium">
|
||||
<i data-lucide="scroll-text" class="w-4 h-4"></i>
|
||||
Logs
|
||||
@@ -571,7 +575,7 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
||||
<div class="p-6 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Folder</label>
|
||||
<input type="text" id="moveTableFolder" class="w-full px-4 py-2 border border-slate-300 rounded-lg" placeholder="frontend">
|
||||
<select id="moveTableFolder" class="w-full px-4 py-2 border border-slate-300 rounded-lg"></select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Table name</label>
|
||||
@@ -627,15 +631,24 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Readable folders</label>
|
||||
<input type="text" id="userViewFolders" class="w-full px-4 py-2 border border-slate-300 rounded-lg" placeholder="frontend, backend">
|
||||
<details class="border border-slate-300 rounded-lg p-3">
|
||||
<summary class="cursor-pointer text-sm text-slate-700">Choose folders</summary>
|
||||
<div id="userViewFoldersList" class="mt-3 grid gap-2"></div>
|
||||
</details>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Editable tables</label>
|
||||
<input type="text" id="userEditTables" class="w-full px-4 py-2 border border-slate-300 rounded-lg" placeholder="users, frontend__users">
|
||||
<details class="border border-slate-300 rounded-lg p-3">
|
||||
<summary class="cursor-pointer text-sm text-slate-700">Choose tables</summary>
|
||||
<div id="userEditTablesList" class="mt-3 grid gap-2 max-h-48 overflow-auto"></div>
|
||||
</details>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Deletable folders</label>
|
||||
<input type="text" id="userDeleteFolders" class="w-full px-4 py-2 border border-slate-300 rounded-lg" placeholder="frontend">
|
||||
<details class="border border-slate-300 rounded-lg p-3">
|
||||
<summary class="cursor-pointer text-sm text-slate-700">Choose folders</summary>
|
||||
<div id="userDeleteFoldersList" class="mt-3 grid gap-2"></div>
|
||||
</details>
|
||||
</div>
|
||||
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||||
<input type="checkbox" id="userDisabled" class="w-4 h-4">
|
||||
@@ -650,6 +663,23 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="auditModal" class="hidden fixed inset-0 z-50 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
|
||||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-5xl max-h-[90vh] overflow-hidden flex flex-col fade-in">
|
||||
<div class="p-6 border-b border-slate-200 flex items-center justify-between">
|
||||
<h3 class="text-xl font-bold text-slate-800">Audit log</h3>
|
||||
<button onclick="app.closeModal('auditModal')" class="p-2 hover:bg-slate-100 rounded-lg transition-colors">
|
||||
<i data-lucide="x" class="w-5 h-5 text-slate-500"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-6 overflow-auto">
|
||||
<div class="flex justify-end mb-4">
|
||||
<button onclick="app.loadAuditLog()" class="px-4 py-2 bg-slate-100 text-slate-700 rounded-lg">Refresh</button>
|
||||
</div>
|
||||
<div id="auditList" class="space-y-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast Notifications -->
|
||||
<div id="toastContainer" class="fixed bottom-6 right-6 z-50 flex flex-col gap-2"></div>
|
||||
|
||||
@@ -807,6 +837,7 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
||||
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('auditButton').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';
|
||||
|
||||
@@ -1677,7 +1708,34 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
||||
return value.split(',').map(item => item.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
getAvailableFolders() {
|
||||
return Array.from(new Set(this.tables.map(table => {
|
||||
const parts = table.name.split('__');
|
||||
return parts.length > 1 ? parts[0] : 'default';
|
||||
}))).sort();
|
||||
}
|
||||
|
||||
renderOptionChecklist(containerId, values, selected = []) {
|
||||
const container = document.getElementById(containerId);
|
||||
const selectedSet = new Set(selected || []);
|
||||
container.innerHTML = values.length
|
||||
? values.map(value => `
|
||||
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||||
<input type="checkbox" value="${value}" ${selectedSet.has(value) ? 'checked' : ''} class="w-4 h-4">
|
||||
<span>${value}</span>
|
||||
</label>
|
||||
`).join('')
|
||||
: '<div class="text-sm text-slate-500">No options available</div>';
|
||||
}
|
||||
|
||||
getCheckedValues(containerId) {
|
||||
return Array.from(document.querySelectorAll(`#${containerId} input[type="checkbox"]:checked`)).map(input => input.value);
|
||||
}
|
||||
|
||||
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');
|
||||
@@ -1714,9 +1772,9 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
||||
document.getElementById('userUsername').disabled = true;
|
||||
document.getElementById('userPassword').value = '';
|
||||
document.getElementById('userRole').value = user.role;
|
||||
document.getElementById('userViewFolders').value = (user.access?.view?.folders || []).join(', ');
|
||||
document.getElementById('userEditTables').value = (user.access?.edit?.tables || []).join(', ');
|
||||
document.getElementById('userDeleteFolders').value = (user.access?.delete?.folders || []).join(', ');
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1726,9 +1784,9 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
||||
document.getElementById('userUsername').disabled = false;
|
||||
document.getElementById('userPassword').value = '';
|
||||
document.getElementById('userRole').value = 'viewer';
|
||||
document.getElementById('userViewFolders').value = '';
|
||||
document.getElementById('userEditTables').value = '';
|
||||
document.getElementById('userDeleteFolders').value = '';
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1742,10 +1800,10 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
||||
role: document.getElementById('userRole').value,
|
||||
disabled: document.getElementById('userDisabled').checked,
|
||||
access: {
|
||||
view: { folders: this.parseList(document.getElementById('userViewFolders').value), tables: [] },
|
||||
view: { folders: this.getCheckedValues('userViewFoldersList'), tables: [] },
|
||||
create: { folders: [], tables: [] },
|
||||
edit: { folders: [], tables: this.parseList(document.getElementById('userEditTables').value) },
|
||||
delete: { folders: this.parseList(document.getElementById('userDeleteFolders').value), tables: [] },
|
||||
edit: { folders: [], tables: this.getCheckedValues('userEditTablesList') },
|
||||
delete: { folders: this.getCheckedValues('userDeleteFoldersList'), tables: [] },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1784,7 +1842,12 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
||||
|
||||
showMoveTableModal() {
|
||||
const parts = (this.currentTable || '').split('__');
|
||||
document.getElementById('moveTableFolder').value = parts.length > 1 ? parts[0] : '';
|
||||
const currentFolder = parts.length > 1 ? parts[0] : 'default';
|
||||
const folders = this.getAvailableFolders();
|
||||
document.getElementById('moveTableFolder').innerHTML = ['<option value="default">Common folder</option>']
|
||||
.concat(folders.filter(folder => folder !== 'default').map(folder => `<option value="${folder}">${folder}</option>`))
|
||||
.join('');
|
||||
document.getElementById('moveTableFolder').value = currentFolder;
|
||||
document.getElementById('moveTableName').value = parts.length > 1 ? parts.slice(1).join('__') : this.currentTable || '';
|
||||
document.getElementById('moveTableModal').classList.remove('hidden');
|
||||
}
|
||||
@@ -1801,7 +1864,7 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
||||
const response = await fetch(`/api/tables/${encodeURIComponent(this.currentTable)}/move`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ folder, name }),
|
||||
body: JSON.stringify({ folder: folder === 'default' ? '' : folder, name }),
|
||||
});
|
||||
const data = await response.json();
|
||||
if (!response.ok) {
|
||||
@@ -1817,6 +1880,36 @@ SELECT * FROM users LIMIT 10;"></textarea>
|
||||
}
|
||||
}
|
||||
|
||||
async showAuditModal() {
|
||||
await this.loadAuditLog();
|
||||
document.getElementById('auditModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
async loadAuditLog() {
|
||||
try {
|
||||
const response = await fetch('/api/audit');
|
||||
const entries = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error(entries.error || 'Failed to load audit log');
|
||||
}
|
||||
|
||||
document.getElementById('auditList').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">
|
||||
<div class="font-medium text-slate-800">${entry.event}</div>
|
||||
<div class="text-xs text-slate-500">${entry.timestamp}</div>
|
||||
</div>
|
||||
<div class="text-sm text-slate-600 mt-1">Actor: ${entry.actor}</div>
|
||||
<pre class="mt-3 text-xs bg-slate-50 rounded-lg p-3 overflow-auto">${JSON.stringify(entry.details, null, 2)}</pre>
|
||||
</div>
|
||||
`).join('')
|
||||
: '<div class="text-sm text-slate-500">Audit log is empty.</div>';
|
||||
} catch (err) {
|
||||
this.showToast(err.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
showIndexesModal() {
|
||||
document.getElementById('indexesTableName').textContent = this.currentTable;
|
||||
this.loadIndexes();
|
||||
|
||||
Reference in New Issue
Block a user