"use client"; import * as React from "react"; import Link from "next/link"; import { X } from "lucide-react"; import { useAdminQuery } from "@/components/admin/use-query"; import type { SecurityEventRow, SecurityEventsResponse } from "@/components/admin/types"; import { PageHeader, RefreshButton, Panel, DataTable, ErrorState, TableSkeleton, DenseInput, DenseSelect, SeverityPill, JsonToggle, Mono, type Column } from "@/components/admin/primitives"; import { dateTime, int, num } from "@/components/admin/format"; import { cn } from "@/lib/utils"; const SEVERITIES = ["", "info", "warn", "high"] as const; const LIMITS = [50, 100, 200] as const; const cols: Column[] = [ { key: "at", header: "Time", render: (e) => {dateTime(e.createdAt)} }, { key: "type", header: "Type", mono: true, render: (e) => {e.type} }, { key: "sev", header: "Severity", render: (e) => }, { key: "user", header: "User", render: (e) => (e.userId ? {e.username ?? e.userId.slice(0, 8)} : e.adminId ? admin : —) }, { key: "ip", header: "IP", mono: true, render: (e) => {e.ip ?? "—"} }, { key: "ua", header: "Agent", render: (e) => {e.userAgent ?? "—"} }, { key: "meta", header: "Meta", render: (e) => }, ]; export default function AdminSecurityPage() { const [type, setType] = React.useState(""); const [severity, setSeverity] = React.useState<(typeof SEVERITIES)[number]>(""); const [limit, setLimit] = React.useState<(typeof LIMITS)[number]>(100); const params = new URLSearchParams({ limit: String(limit) }); if (type) params.set("type", type); if (severity) params.set("severity", severity); const q = useAdminQuery(`/api/admin/security/events?${params.toString()}`, { refreshMs: 30_000 }); const d = q.data; const high = d?.events.filter((e) => e.severity === "high").length ?? 0; const total24h = d?.summary24h.reduce((a, s) => a + num(s.n), 0) ?? 0; return ( <> void q.refresh()} loading={q.refreshing} />} /> {d ? (
Last 24 h · {int(total24h)} {d.summary24h.length === 0 ? quiet — no events : null} {d.summary24h.map((s) => ( ))}
) : null}
setType(e.target.value.trim())} aria-label="Event type" list="security-types" className={type ? "pr-8" : undefined} /> {type ? ( ) : null} {d?.summary24h.map((s) =>
setSeverity(e.target.value as (typeof SEVERITIES)[number])} aria-label="Severity"> {SEVERITIES.map((s) => ( ))} setLimit(Number(e.target.value) as (typeof LIMITS)[number])} aria-label="Limit"> {LIMITS.map((l) => ( ))} {d ? ( {int(d.events.length)} shown · {int(high)} high ) : null}
{q.error && !d ? (
void q.refresh()} />
) : !d ? ( ) : ( e.id} dense stale={q.stale} empty={type || severity ? "No events match these filters." : "No security events recorded."} rowClassName={(e) => (e.severity === "high" ? "bg-danger/5" : undefined)} /> )}
); }