Repository Public
1import { PageHead, when } from "@/components/studio/ui";2import { requireRole } from "@/lib/auth";3import { sql } from "@/lib/db";45export const metadata = { title: "Audit log" };6export default async function Audit() {7 await requireRole("editor");8 const rows = await sql<{ id: string; at: Date; email: string | null; action: string; entity: string | null; entity_id: string | null; detail: Record<string, unknown> }[]>`9 SELECT a.id, a.at, u.email, a.action, a.entity, a.entity_id, a.detail FROM audit_log a LEFT JOIN users u ON u.id = a.user_id ORDER BY a.at DESC LIMIT 300`;10 return (11 <>12 <PageHead title="Audit log" sub="Every sign-in and every studio mutation, newest first (last 300). The log is append-only." />13 <div className="card overflow-x-auto"><table className="studio-table"><thead><tr><th>When</th><th>Who</th><th>Action</th><th>Target</th><th>Detail</th></tr></thead><tbody>14 {rows.map((r) => <tr key={r.id}><td className="whitespace-nowrap text-[var(--color-muted)]">{when(r.at)}</td><td>{r.email ?? "system"}</td><td className="mono">{r.action}</td><td className="text-[var(--color-muted)]">{r.entity}{r.entity_id ? <span className="mono"> {r.entity_id.slice(0, 8)}</span> : ""}</td><td className="max-w-md break-words text-xs text-[var(--color-faint)]">{Object.keys(r.detail).length ? JSON.stringify(r.detail) : ""}</td></tr>)}15 {!rows.length && <tr><td colSpan={5} className="text-[var(--color-faint)]">Nothing logged yet.</td></tr>}16 </tbody></table></div>17 </>18 );19}20