import Link from "next/link"; import { ChevronLeft, ChevronRight, ScrollText } from "lucide-react"; import { getWorkspace } from "@/lib/session"; import { listAuditLogs } from "@/lib/queries/account"; import { formatDate, titleCase, truncate } from "@/lib/format"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { EmptyState } from "@/components/ui/empty-state"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { SimpleTooltip } from "@/components/ui/tooltip"; export const dynamic = "force-dynamic"; const PAGE_SIZE = 25; function actionVariant(action: string): "default" | "success" | "danger" | "warning" | "info" | "accent" { if (action.endsWith(".revoked") || action.endsWith(".deleted") || action.endsWith(".archived")) return "danger"; if (action.endsWith(".created") || action === "login" || action === "email.verified") return "success"; if (action.startsWith("password") || action.startsWith("email") || action.startsWith("session")) return "warning"; if (action.startsWith("admin")) return "accent"; return "default"; } function summarize(meta: Record | null): string { if (!meta) return "—"; const parts = Object.entries(meta) .filter(([, v]) => v !== null && v !== undefined && typeof v !== "object") .map(([k, v]) => `${k}: ${String(v)}`); return parts.length ? truncate(parts.join(" · "), 72) : "—"; } export default async function AuditLogPage({ searchParams }: { searchParams: Promise<{ page?: string }> }) { const sp = await searchParams; const page = Math.max(1, Number.parseInt(sp.page ?? "1", 10) || 1); const ws = await getWorkspace(); const { rows, total } = await listAuditLogs(ws.user.id, ws.organization.id, page, PAGE_SIZE); const pages = Math.max(1, Math.ceil(total / PAGE_SIZE)); return (

Audit log

Security-relevant events for your account and organization. Retained for 365 days.

{total.toLocaleString("en-US")} events

{rows.length === 0 ? ( 1 ? "Nothing on this page" : "No events yet"} description={page > 1 ? "Go back to the first page." : "Logins, key changes and settings updates will appear here."} action={page > 1 ? : undefined} /> ) : (
Time Action Target Details IP User agent {rows.map((r) => ( {formatDate(r.createdAt)} {titleCase(r.action.replace(/\./g, " "))} {r.target ? truncate(r.target, 28) : "—"} {summarize(r.metadata)} {r.ipAddress ?? "—"} {r.userAgent ? ( {r.userAgent}}> {truncate(r.userAgent, 36)} ) : ( "—" )} ))}
Page {page} of {pages}
)}
); }