feat(dashboard): live-append the LOGS AUDIT tab via audit_entry_added
Completes the audit log end-to-end. The backend now emits an audit_entry_added event on /dashboard/stream per privileged action (the new row flattened at top level). logs.js already subscribes to that stream for the BUILD tab; add a branch that prepends the new row to the AUDIT table's cached list (de-duped by id against the cold fetch), bumps the 'latest N of M' total, and re-renders while the AUDIT tab is in view — so a restart shows up without a manual refresh. Docs updated to match.
This commit is contained in:
parent
f21a812f5f
commit
24cbafc64a
2 changed files with 24 additions and 2 deletions
|
|
@ -568,7 +568,10 @@ refusal) shown amber and labelled `denied` so it reads apart from an
|
||||||
execution failure. `ts_unix` is unix seconds; a 30 s ticker keeps the
|
execution failure. `ts_unix` is unix seconds; a 30 s ticker keeps the
|
||||||
relative "ago" column honest while the tab is in view. The backing
|
relative "ago" column honest while the tab is in view. The backing
|
||||||
`audit_log` store records every privileged-action attempt (ok / err /
|
`audit_log` store records every privileged-action attempt (ok / err /
|
||||||
denied); a live-append off `/dashboard/stream` is a planned follow-up.
|
denied). New entries live-append without a refresh: an `audit_entry_added`
|
||||||
|
event on `/dashboard/stream` (the flattened row) is prepended to the table
|
||||||
|
and the "latest N of M" count bumped, de-duped by id against the cold
|
||||||
|
fetch.
|
||||||
|
|
||||||
## Container row
|
## Container row
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
// BUILD — all-agents build log history, backed by GET /api/build-logs
|
// BUILD — all-agents build log history, backed by GET /api/build-logs
|
||||||
// AGENT — per-container journald viewer, backed by GET /api/journal/{name}
|
// AGENT — per-container journald viewer, backed by GET /api/journal/{name}
|
||||||
// SYSTEM — host service logs, backed by GET /api/journal-host
|
// SYSTEM — host service logs, backed by GET /api/journal-host
|
||||||
// AUDIT — agent-initiated privileged-action trail, GET /api/audit-log
|
// AUDIT — agent-initiated privileged-action trail, GET /api/audit-log;
|
||||||
|
// live-appends via the `audit_entry_added` /dashboard/stream event
|
||||||
//
|
//
|
||||||
// Tab routing via URL hash (#build, #agent, #system, #audit). Default: #build.
|
// Tab routing via URL hash (#build, #agent, #system, #audit). Default: #build.
|
||||||
//
|
//
|
||||||
|
|
@ -245,6 +246,7 @@ import { createTabStrip } from '@hive/shared/tabs.js';
|
||||||
let ev;
|
let ev;
|
||||||
try { ev = JSON.parse(e.data); } catch { return; }
|
try { ev = JSON.parse(e.data); } catch { return; }
|
||||||
if (ev.kind === 'rebuild_queue_changed') debouncedRefreshBuild();
|
if (ev.kind === 'rebuild_queue_changed') debouncedRefreshBuild();
|
||||||
|
else if (ev.kind === 'audit_entry_added') onAuditEntryAdded(ev);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -489,6 +491,23 @@ import { createTabStrip } from '@hive/shared/tabs.js';
|
||||||
if (auditRefresh) auditRefresh.addEventListener('click', fetchAudit);
|
if (auditRefresh) auditRefresh.addEventListener('click', fetchAudit);
|
||||||
if (auditFilter) auditFilter.addEventListener('input', renderAudit);
|
if (auditFilter) auditFilter.addEventListener('input', renderAudit);
|
||||||
|
|
||||||
|
// Live-append: an `audit_entry_added` event on /dashboard/stream carries a
|
||||||
|
// new row flattened at the top level ({ kind, seq, id, ts_unix, agent,
|
||||||
|
// action, target, outcome, detail }). Prepend it (newest-first), de-duped
|
||||||
|
// by id against whatever the cold fetch already returned, and bump the
|
||||||
|
// total so the "latest N of M" header stays right. Re-render only while
|
||||||
|
// the AUDIT tab is in view; otherwise the next tab-show fetch is
|
||||||
|
// authoritative anyway. Wired into the shared stream onmessage above.
|
||||||
|
function onAuditEntryAdded(ev) {
|
||||||
|
if (auditEntries.some((e) => e.id === ev.id)) return;
|
||||||
|
auditEntries.unshift({
|
||||||
|
id: ev.id, ts_unix: ev.ts_unix, agent: ev.agent, action: ev.action,
|
||||||
|
target: ev.target, outcome: ev.outcome, detail: ev.detail,
|
||||||
|
});
|
||||||
|
auditTotal += 1;
|
||||||
|
if (logTabs.active() === 'audit') renderAudit();
|
||||||
|
}
|
||||||
|
|
||||||
// ─── init ─────────────────────────────────────────────────────────────
|
// ─── init ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// Wire the shared tab strip now that fetchSystem + the element refs it
|
// Wire the shared tab strip now that fetchSystem + the element refs it
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue