● Zion Boggan
repos/Sightline/frontend/src/app/audit/page.tsx
zionboggan.com ↗
51 lines · tsx
History for this file →
1
"use client";
2
import { useEffect, useState } from "react";
3
import { api, AuditEntry } from "@/lib/api";
4
 
5
export default function AuditPage() {
6
  const [rows, setRows] = useState<AuditEntry[]>([]);
7
  const [entityType, setEntityType] = useState("");
8
  const [err, setErr] = useState<string | null>(null);
9
 
10
  function load() {
11
    api.audit({ entity_type: entityType || undefined, limit: 200 })
12
      .then(setRows)
13
      .catch((e) => setErr(e.status === 401 ? "Sign in to view the audit log." : String(e)));
14
  }
15
  useEffect(load, [entityType]);
16
 
17
  const types = ["", "investigation", "import", "watchlist", "watchlist_entry", "user", "report", "enrichment_job", "alert"];
18
 
19
  return (
20
    <div>
21
      <h1>Audit Log</h1>
22
      <p className="muted">Immutable record of every mutation - actor, action, entity, and metadata.</p>
23
      <div className="row" style={{ margin: "10px 0" }}>
24
        <select value={entityType} onChange={(e) => setEntityType(e.target.value)}>
25
          {types.map((t) => <option key={t} value={t}>{t || "all entity types"}</option>)}
26
        </select>
27
        <button onClick={load}>Refresh</button>
28
      </div>
29
      {err && <div className="badge danger">{err}</div>}
30
      <div className="card">
31
        <table>
32
          <thead><tr><th>When</th><th>Action</th><th>Entity</th><th>Actor</th><th>Metadata</th></tr></thead>
33
          <tbody>
34
            {rows.map((r) => (
35
              <tr key={r.id}>
36
                <td className="muted" style={{ whiteSpace: "nowrap" }}>{new Date(r.at).toLocaleString()}</td>
37
                <td><span className="badge">{r.action}</span></td>
38
                <td className="muted">{r.entity_type}{r.entity_id ? ` · ${r.entity_id.slice(0, 8)}` : ""}</td>
39
                <td className="muted">{r.actor ? r.actor.slice(0, 8) : "-"}</td>
40
                <td className="muted" style={{ fontSize: 12, maxWidth: 320, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
41
                  {r.metadata && Object.keys(r.metadata).length ? JSON.stringify(r.metadata) : "-"}
42
                </td>
43
              </tr>
44
            ))}
45
            {!rows.length && !err && <tr><td colSpan={5} className="muted">No audit entries.</td></tr>}
46
          </tbody>
47
        </table>
48
      </div>
49
    </div>
50
  );
51
}