Add compliance audit log

Immutable audit_log table in the scanner DB records every significant
admin action (profile save/delete, token create/revoke, PIN changes,
source add/update/delete, scheduler job changes, scan start/stop, SMTP
save, dispositions, item delete/redact). GET /api/audit_log exposes
entries newest-first. New Audit Log tab in the Settings modal renders
the table on demand. Settings modal widened 540→640 px and tab labels
set to white-space:nowrap so the six-tab row fits on one line.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
StyxX65andClaude Sonnet 4.6 committed 2026-05-28 10:51:23 +02:00
1 parent 4ef2dfb352
commit 744813f4ac
18 files changed
+236 -11

No files matched your search

+28 -1
View File
@@ -237,7 +237,7 @@ function closeSettings() {
}
function switchSettingsTab(tab) {
['general','security','scheduler','email','database'].forEach(function(t) {
['general','security','scheduler','email','database','auditlog'].forEach(function(t) {
var cap = t.charAt(0).toUpperCase() + t.slice(1);
var pane = document.getElementById('stPane' + cap);
var btn = document.getElementById('stTab' + cap);
@@ -248,6 +248,32 @@ function switchSettingsTab(tab) {
if (tab === 'email') stLoadSmtp();
if (tab === 'database') stLoadDbStats();
if (tab === 'scheduler') schedLoad();
if (tab === 'auditlog') stLoadAuditLog();
}
async function stLoadAuditLog() {
const tbody = document.getElementById('stAuditTableBody');
if (!tbody) return;
tbody.innerHTML = `<tr><td colspan="4" style="padding:8px;color:var(--muted)">${t('m365_audit_loading')}</td></tr>`;
try {
const rows = await fetch('/api/audit_log?limit=200').then(r => r.json());
if (!Array.isArray(rows) || !rows.length) {
tbody.innerHTML = `<tr><td colspan="4" style="padding:8px;color:var(--muted)">${t('m365_audit_empty')}</td></tr>`;
return;
}
tbody.innerHTML = rows.map(function(r) {
const d = new Date(r.ts * 1000);
const ts = d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
return '<tr style="border-bottom:1px solid var(--border)">'
+ '<td style="padding:4px 8px;white-space:nowrap;color:var(--muted);font-size:11px">' + window._escHtml(ts) + '</td>'
+ '<td style="padding:4px 8px"><span style="font-family:monospace;background:var(--bg);border:1px solid var(--border);border-radius:3px;padding:1px 4px;font-size:11px">' + window._escHtml(r.action) + '</span></td>'
+ '<td style="padding:4px 8px;color:var(--text);font-size:12px">' + window._escHtml(r.detail) + '</td>'
+ '<td style="padding:4px 8px;color:var(--muted);font-size:11px">' + window._escHtml(r.ip) + '</td>'
+ '</tr>';
}).join('');
} catch(e) {
tbody.innerHTML = '<tr><td colspan="4" style="padding:8px;color:var(--danger)">' + window._escHtml(String(e)) + '</td></tr>';
}
}
// ── Window exports (HTML handlers + cross-module calls) ─────────────────────
@@ -266,5 +292,6 @@ window.confirmPinPrompt = confirmPinPrompt;
window.openSettings = openSettings;
window.closeSettings = closeSettings;
window.switchSettingsTab = switchSettingsTab;
window.stLoadAuditLog = stLoadAuditLog;
window._M365_SOURCES = _M365_SOURCES;
window._pinCallback = _pinCallback;
+3 -3
View File
@@ -361,17 +361,17 @@
.settings-backdrop.open { display:flex; }
.settings-modal {
background:var(--surface); border:1px solid var(--border);
border-radius:10px; width:min(540px,96vw);
border-radius:10px; width:min(640px,96vw);
display:flex; flex-direction:column; overflow:hidden;
font-size:12px; color:var(--text);
}
.settings-header { padding:16px 20px 0; display:flex; align-items:center; justify-content:space-between; }
.settings-header h2 { font-size:14px; font-weight:700; margin:0; }
.settings-tabs { display:flex; border-bottom:1px solid var(--border); padding:0 20px; margin-top:12px; }
.settings-tabs { display:flex; border-bottom:1px solid var(--border); padding:0 20px; margin-top:12px; flex-wrap:wrap; }
.settings-tab {
height:36px; padding:0 14px; font-size:12px; cursor:pointer; border:none;
background:none; color:var(--muted); border-bottom:2px solid transparent;
margin-bottom:-1px; font-weight:500;
margin-bottom:-1px; font-weight:500; white-space:nowrap;
}
.settings-tab.active { color:var(--accent); border-bottom-color:var(--accent); font-weight:600; }
.settings-body { padding:16px 20px; overflow-y:auto; max-height:65vh; display:flex; flex-direction:column; gap:14px; }