import { useState, useEffect, useCallback } from "react"; import { Download, Search, Filter, RefreshCw, ChevronLeft, ChevronRight, LogIn, Key, UserPlus, Shield, Settings, AlertTriangle, Terminal, Loader2, CheckCircle2, XCircle, X, Globe, Lock, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { api, AuditLogEntry, ApiError } from "@/lib/api"; import { formatDateTime } from "@/lib/date"; // ─── category helpers ──────────────────────────────────────────────────────── type Category = "auth" | "ssh" | "org" | "user" | "security" | "token" | "admin" | "other"; const getCategory = (action: string): Category => { const a = action.toLowerCase(); if (a.startsWith("session") || a === "user.login" || a === "user.logout" || a.startsWith("external_auth.login")) return "auth"; if (a.startsWith("ssh")) return "ssh"; if (a.startsWith("admin.")) return "admin"; if (a.startsWith("org") || a.includes("member") || a.includes("department") || a.includes("invite")) return "org"; if (a.startsWith("user")) return "user"; if (a.includes("mfa") || a.includes("totp") || a.includes("webauthn") || a.includes("passkey") || a.includes("password")) return "security"; if (a.includes("token") || a.includes("oidc") || a.includes("client") || a.startsWith("external_auth")) return "token"; return "other"; }; const CATEGORY_META: Record = { auth: { label: "Auth", color: "bg-blue-500/10 text-blue-600 dark:text-blue-400" }, ssh: { label: "SSH", color: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400" }, org: { label: "Org", color: "bg-violet-500/10 text-violet-600 dark:text-violet-400" }, user: { label: "User", color: "bg-amber-500/10 text-amber-600 dark:text-amber-400" }, security: { label: "Security", color: "bg-orange-500/10 text-orange-600 dark:text-orange-400" }, token: { label: "Token", color: "bg-cyan-500/10 text-cyan-600 dark:text-cyan-400" }, admin: { label: "Admin", color: "bg-red-500/10 text-red-600 dark:text-red-400" }, other: { label: "Other", color: "bg-muted text-muted-foreground" }, }; const getCategoryIcon = (category: Category) => { const cls = "w-4 h-4"; switch (category) { case "auth": return ; case "ssh": return ; case "org": return ; case "user": return ; case "security": return ; case "token": return ; case "admin": return ; default: return ; } }; const getActionLabel = (action: string) => action .replace(/_/g, " ") .replace(/\./g, " › ") .replace(/\b\w/g, (c) => c.toUpperCase()); // ─── component ─────────────────────────────────────────────────────────────── const ACTION_FILTER_OPTIONS = [ { value: "all", label: "All actions" }, { value: "session.create", label: "Login" }, { value: "session.revoke", label: "Logout" }, { value: "external_auth.login", label: "OAuth Login" }, { value: "external_auth.login.failed", label: "OAuth Failed" }, { value: "external_auth.link.completed", label: "OAuth Account Linked" }, { value: "external_auth.unlink", label: "OAuth Account Unlinked" }, { value: "user.register", label: "Register" }, { value: "ssh.key.added", label: "SSH Key Added" }, { value: "ssh.key.verified", label: "SSH Key Verified" }, { value: "ssh.key.deleted", label: "SSH Key Deleted" }, { value: "ssh.cert.issued", label: "SSH Cert Issued" }, { value: "ssh.cert.revoked", label: "SSH Cert Revoked" }, { value: "ssh.cert.failed", label: "SSH Cert Failed" }, { value: "org.create", label: "Org Created" }, { value: "org.member.add", label: "Member Added" }, { value: "org.member.remove", label: "Member Removed" }, { value: "org.member.role_change", label: "Role Changed" }, { value: "org.security_policy.update", label: "Security Policy Updated" }, { value: "admin.mfa.remove", label: "MFA Removed (Admin)" }, { value: "admin.oauth.unlink", label: "OAuth Unlinked (Admin)" }, { value: "admin.password.set", label: "Password Set (Admin)" }, { value: "totp.enroll.completed", label: "TOTP Enrolled" }, { value: "totp.disabled", label: "TOTP Disabled" }, { value: "webauthn.register.completed", label: "Passkey Registered" }, { value: "webauthn.credential.deleted", label: "Passkey Removed" }, { value: "user.password_change", label: "Password Changed" }, { value: "user.password_reset", label: "Password Reset" }, { value: "user.suspend", label: "User Suspended" }, ]; export default function SystemAuditPage() { const [logs, setLogs] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isExporting, setIsExporting] = useState(false); const [error, setError] = useState(null); const [accessDenied, setAccessDenied] = useState(false); const [isAdminView, setIsAdminView] = useState(false); // filters const [search, setSearch] = useState(""); const [debouncedSearch, setDebouncedSearch] = useState(""); const [actionFilter, setActionFilter] = useState("all"); const [successFilter, setSuccessFilter] = useState("all"); const [userFilter, setUserFilter] = useState(null); const [userFilterLabel, setUserFilterLabel] = useState(null); // pagination const [page, setPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [totalCount, setTotalCount] = useState(0); const PER_PAGE = 50; // debounce search useEffect(() => { const t = setTimeout(() => setDebouncedSearch(search), 400); return () => clearTimeout(t); }, [search]); const fetchLogs = useCallback(async () => { setIsLoading(true); setError(null); setAccessDenied(false); try { const params: Record = { page: String(page), per_page: String(PER_PAGE), }; if (actionFilter !== "all") params.action = actionFilter; if (successFilter !== "all") params.success = successFilter; if (userFilter) params.user_id = userFilter; if (debouncedSearch) params.q = debouncedSearch; const resp = await api.admin.getAuditLogs(params); setLogs(resp.audit_logs ?? []); setTotalCount(resp.count ?? 0); setTotalPages(resp.pages ?? 1); setIsAdminView(resp.is_admin_view ?? false); } catch (err) { if (err instanceof ApiError && err.code === 403) { setAccessDenied(true); } else { console.error("Failed to fetch system audit logs:", err); setError("Failed to load audit logs. Please try again."); } } finally { setIsLoading(false); } }, [page, actionFilter, successFilter, userFilter, debouncedSearch]); useEffect(() => { fetchLogs(); }, [fetchLogs]); // reset to page 1 when filters change useEffect(() => { setPage(1); }, [actionFilter, successFilter, userFilter, debouncedSearch]); const formatDate = (dateString: string) => formatDateTime(dateString); const formatUserAgent = (ua: string | null) => { if (!ua) return null; const m = ua.match(/\(([^)]+)\)/); if (m) return m[1].split(";")[0].trim(); return ua.slice(0, 40); }; const handleExport = useCallback(async () => { setIsExporting(true); try { const EXPORT_PER_PAGE = 200; const buildParams = (p: number) => { const params: Record = { page: String(p), per_page: String(EXPORT_PER_PAGE) }; if (actionFilter !== "all") params.action = actionFilter; if (successFilter !== "all") params.success = successFilter; if (userFilter) params.user_id = userFilter; if (debouncedSearch) params.q = debouncedSearch; return params; }; const first = await api.admin.getAuditLogs(buildParams(1)); const allLogs = [...(first.audit_logs ?? [])]; const totalPages = first.pages ?? 1; if (totalPages > 1) { const remaining = await Promise.all( Array.from({ length: totalPages - 1 }, (_, i) => api.admin.getAuditLogs(buildParams(i + 2)) ) ); for (const r of remaining) allLogs.push(...(r.audit_logs ?? [])); } const esc = (v: string) => `"${v.replace(/"/g, '""')}"`; const header = ["ID","Action","Description","User Email","User ID","Resource Type","Resource ID","IP Address","User Agent","Success","Error Message","Created At","Updated At"]; const rows = allLogs.map((l) => [ l.id, l.action, l.description ?? "", l.user?.email ?? "", l.user_id ?? "", l.resource_type ?? "", l.resource_id ?? "", l.ip_address ?? "", l.user_agent ?? "", l.success ? "Yes" : "No", l.error_message ?? "", l.created_at, l.updated_at ?? "", ].map(esc).join(",")); const csv = [header.map(esc).join(","), ...rows].join("\n"); const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `audit-logs-${new Date().toISOString().slice(0, 10)}.csv`; a.click(); URL.revokeObjectURL(url); } catch (err) { console.error("Export failed:", err); } finally { setIsExporting(false); } }, [actionFilter, successFilter, userFilter, debouncedSearch]); return (
{/* Header */}

System Audit Log

{isAdminView ? `All system events — ${totalCount.toLocaleString()} total` : "Your account events"}

{/* Filters */}
setSearch(e.target.value)} className="pl-10" />
{/* Active filter chips */} {(actionFilter !== "all" || successFilter !== "all" || userFilter) && (
{actionFilter !== "all" && ( Action: {getActionLabel(actionFilter)} setActionFilter("all")} /> )} {userFilter && ( User: {userFilterLabel ?? userFilter.slice(0, 8) + "…"} { setUserFilter(null); setUserFilterLabel(null); }} /> )} {successFilter !== "all" && ( Status: {successFilter === "true" ? "Success only" : "Failures only"} setSuccessFilter("all")} /> )}
)} {/* Table */} {isLoading ? (
Loading…
) : accessDenied ? (

Access Restricted

You don't have permission to view system-wide audit logs. Contact your administrator to request access.

) : error ? (

{error}

) : logs.length === 0 ? (
No audit events match the current filters.
) : (
{logs.map((log) => { const cat = getCategory(log.action); const meta = CATEGORY_META[cat]; return (
{/* Icon */}
{log.success ? getCategoryIcon(cat) : }
{/* Body */}
setActionFilter((prev) => (prev === log.action ? "all" : log.action)) } > {getActionLabel(log.action)} {meta.label} {!log.success && ( Failed )} {log.resource_type && ( {log.resource_type} )}
{/* Description */} {log.description && (

{log.description}

)} {log.error_message && (

{log.error_message}

)} {/* Meta row */}
{log.user?.email ? ( { if (log.user_id) { setUserFilter((prev) => (prev === log.user_id ? null : log.user_id)); setUserFilterLabel((prev) => (prev === log.user.email ? null : log.user.email)); } }} >{log.user.email} ) : log.user_id ? ( { setUserFilter((prev) => (prev === log.user_id ? null : log.user_id)); setUserFilterLabel((prev) => prev === log.user_id ? null : `${log.user_id!.slice(0, 8)}…`); }} >{log.user_id.slice(0, 8)}… ) : ( System )} {log.ip_address && ( {log.ip_address} )} {log.user_agent && ( {formatUserAgent(log.user_agent)} )} {log.resource_id && ( {log.resource_id.slice(0, 8)}… )}
{/* Timestamp */}

{formatDate(log.created_at)}

{log.success ? ( ) : ( )}
); })}
)}
{/* Pagination */} {totalPages > 1 && (

Page {page} of {totalPages}  ·  {totalCount.toLocaleString()} events

)}
); }