Add comprehensive ISAC audit logging

This commit is contained in:
JB
2026-07-25 03:48:22 -04:00
parent bb9ee3785c
commit 6be31de986
19 changed files with 252 additions and 40 deletions
+35
View File
@@ -0,0 +1,35 @@
<script lang="ts">
import KpiFilters from '$lib/components/KpiFilters.svelte';
import RegistryPagination from '$lib/components/RegistryPagination.svelte';
let { data } = $props();
const date = (value: unknown) => new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'medium' }).format(new Date(String(value)));
const pretty = (value: unknown) => {
if (!value) return 'NO SNAPSHOT';
try { return JSON.stringify(JSON.parse(String(value)), null, 2); } catch { return String(value); }
};
</script>
<svelte:head><title>Audit history — ISAC</title></svelte:head>
<div class="module-toolbar"><div><p class="eyebrow">SYSTEM OVERSIGHT</p><h2>AUDIT HISTORY</h2><p>Immutable operator activity, target records, and before/after change evidence.</p></div></div>
<KpiFilters parameter="category" active={data.filters.category} items={[
{value:'',label:'ALL EVENTS',count:data.summary.total,description:'Complete audit history'},
{value:'game_data',label:'GAME DATA',count:data.summary.game_data,description:'Builder-source mutations'},
{value:'user',label:'USER OPERATIONS',count:data.summary.users,description:'Access, moderation, identities'},
{value:'access_policy',label:'ACCESS POLICY',count:data.summary.access,description:'Role definition changes'}
]}/>
<form class="audit-filter">
<input name="q" value={data.filters.query} placeholder="ACTOR, TARGET ID, OR CHANGED VALUE"/>
<select name="category"><option value="">ALL CATEGORIES</option><option value="user" selected={data.filters.category === 'user'}>USER OPERATIONS · ALL</option><option value="access_policy" selected={data.filters.category === 'access_policy'}>ACCESS POLICY · ALL</option>{#each data.categories as item}<option value={item.entity_type} selected={data.filters.category === item.entity_type}>{String(item.entity_type).replaceAll('_',' ').toUpperCase()} · {item.count}</option>{/each}</select>
<select name="action"><option value="">ALL ACTIONS</option>{#each data.actions as item}<option value={item.action} selected={data.filters.action === item.action}>{String(item.action).toUpperCase()} · {item.count}</option>{/each}</select>
<button class="secondary">SEARCH AUDIT</button>
</form>
<RegistryPagination pagination={data.pagination}/>
<div class="dashboard-table">
<div class="dashboard-row audit-row table-heading"><span>EVENT</span><span>OPERATOR</span><span>TARGET</span><span>TIME</span><span></span></div>
{#each data.entries as entry}
<details class="audit-entry">
<summary class="dashboard-row audit-row"><div><strong>{String(entry.action).toUpperCase()}</strong><small>{String(entry.entity_type).replaceAll('_',' ').toUpperCase()}</small></div><div><strong>{entry.actor_name}</strong><small>{entry.actor_login ?? entry.actor_id}</small></div><div><strong>{entry.entity_id}</strong><small>EVENT {String(entry.id).slice(0,8).toUpperCase()}</small></div><span>{date(entry.created_at)}</span><b>DETAILS +</b></summary>
<div class="audit-comparison"><section><span>BEFORE</span><pre>{pretty(entry.before_json)}</pre></section><section><span>AFTER</span><pre>{pretty(entry.after_json)}</pre></section></div>
</details>
{:else}<div class="empty-dashboard"><strong>NO AUDIT EVENTS FOUND</strong><span>Adjust the category, action, or search value.</span></div>{/each}
</div>