import { useDeferredValue, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { listAuditLogs } from '@/lib/api-client'; import { useDemoMode } from '@/lib/demo-mode'; import { type FilterConfig } from '@/lib/dynamic-filter'; import { useDynamicFilter } from '@/lib/use-dynamic-filter'; import { formatDate, formatRelative } from '@/lib/time'; import { DemoRestricted, FilterBar } from '@/shared/components'; import { ScrollText, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'; const PAGE_SIZE = 50; const rangeOptions = [ { value: '', label: 'All time' }, { value: '24h', label: 'Last 24 hours', ms: 24 * 60 * 60 * 1000 }, { value: '7d', label: 'Last 7 days', ms: 7 * 24 * 60 * 60 * 1000 }, { value: '30d', label: 'Last 30 days', ms: 30 * 24 * 60 * 60 * 1000 }, ] as const; // Server-backed filters — the engine runs over empty data; values map to // API params. URL state makes filtered views shareable. const AUDIT_SCHEMA: FilterConfig[] = [ { key: 'resource', label: 'Resource', type: 'search' }, { key: 'action', label: 'Action', type: 'search' }, { key: 'actor', label: 'Actor', type: 'search' }, { key: 'range', label: 'Time', type: 'select', options: rangeOptions.filter((o) => o.value !== '').map((o) => ({ value: o.value, label: o.label })), }, ]; export function AuditLogsPage() { const isDemoMode = useDemoMode(); const filter = useDynamicFilter({ data: [], schema: AUDIT_SCHEMA }); const [page, setPage] = useState(1); // Back to page 1 whenever the filter set changes. const filtersJson = JSON.stringify(filter.filters); const [prevFiltersJson, setPrevFiltersJson] = useState(filtersJson); if (prevFiltersJson !== filtersJson) { setPrevFiltersJson(filtersJson); setPage(1); } const deferredResource = useDeferredValue(String(filter.filters.resource ?? '')); const deferredAction = useDeferredValue(String(filter.filters.action ?? '')); const deferredActor = useDeferredValue(String(filter.filters.actor ?? '')); const range = filter.filters.range === 'all' ? '' : String(filter.filters.range ?? ''); const filters = { resource: deferredResource.trim() || undefined, action: deferredAction.trim() || undefined, actor: deferredActor.trim() || undefined, page, limit: PAGE_SIZE, }; const logsQuery = useQuery({ queryKey: ['audit-logs', filters, range], queryFn: () => { const option = rangeOptions.find((o) => o.value === range); const since = option && 'ms' in option ? new Date(Date.now() - option.ms).toISOString() : undefined; return listAuditLogs({ ...filters, since }); }, enabled: !isDemoMode, }); if (isDemoMode) { return ; } const logs = logsQuery.data ?? []; const hasNext = logs.length === PAGE_SIZE; const hasFilters = filter.activeCount > 0; return (

Audit Logs_

Every authenticated action recorded by the platform

{logsQuery.isLoading ? ( ) : logsQuery.isError ? ( ) : logs.length === 0 ? ( ) : ( logs.map((log) => ( )) )}
Time Actor Resource Action Resource ID IP Details
Failed to load audit logs.

No audit events{hasFilters ? ' match these filters' : ' recorded yet'}.

{log.createdAt ? formatRelative(log.createdAt) : '—'} {log.userEmail || (log.userId ? log.userId.slice(0, 8) : 'system')} {log.resource} {log.action} {log.resourceId || '—'} {log.ipAddress || '—'} {log.details && log.details !== '{}' ? log.details : '—'}

Page {page}{logs.length > 0 ? ` — ${logs.length} entries` : ''}

); }