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
|
||||
relative "ago" column honest while the tab is in view. The backing
|
||||
`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
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
// BUILD — all-agents build log history, backed by GET /api/build-logs
|
||||
// AGENT — per-container journald viewer, backed by GET /api/journal/{name}
|
||||
// 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.
|
||||
//
|
||||
|
|
@ -245,6 +246,7 @@ import { createTabStrip } from '@hive/shared/tabs.js';
|
|||
let ev;
|
||||
try { ev = JSON.parse(e.data); } catch { return; }
|
||||
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 (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 ─────────────────────────────────────────────────────────────
|
||||
|
||||
// Wire the shared tab strip now that fetchSystem + the element refs it
|
||||
|
|
|
|||
Loading…
Reference in a new issue