spb/tools-spboucher-ai
Public
1import { sql } from "@/db";2import { fmtDateTime } from "@/lib/utils";3export default async function Audit({ searchParams }: { searchParams: Promise<{ page?: string }> }) {4 const page = Math.max(1, Number((await searchParams).page ?? 1));5 const rows = await sql`SELECT id, actor, action, entity_type AS "entityType", entity_id AS "entityId", details, ip, created_at AS "createdAt" FROM audit_log ORDER BY id DESC LIMIT 100 OFFSET ${(page - 1) * 100}`;6 return <div><h1 className="display h1">Audit log</h1><p className="mt-1 text-sm text-fg-2">Append-only (database trigger forbids UPDATE/DELETE). Every editorial change, publication, merge, login and security event.</p>7 <div className="mt-4 overflow-x-auto rounded-2xl border border-line bg-bg-2"><table className="table"><thead><tr><th>When</th><th>Actor</th><th>Action</th><th>Entity</th><th>Details</th><th>IP</th></tr></thead><tbody>{rows.map((r) => <tr key={r.id}><td className="whitespace-nowrap text-fg-3">{fmtDateTime(r.createdAt)}</td><td>{r.actor}</td><td className="font-mono text-xs">{r.action}</td><td className="font-mono text-xs">{r.entityType ? `${r.entityType}:${String(r.entityId).slice(0, 8)}` : ""}</td><td className="max-w-md truncate font-mono text-xs">{JSON.stringify(r.details)}</td><td className="text-fg-3">{r.ip}</td></tr>)}</tbody></table></div>8 <div className="mt-3 flex gap-2 text-sm">{page > 1 && <a className="btn-ghost" href={`?page=${page - 1}`}>Newer</a>}{rows.length === 100 && <a className="btn-ghost" href={`?page=${page + 1}`}>Older</a>}</div></div>;9}10