'use client'; import { useEffect, useState } from 'react'; import { apiFetch } from '@/lib/api'; import { Input } from '@/components/input'; interface AuditEntry { id: string; action: string; resourceType: string | null; resourceId: string | null; metadata: Record | null; ipAddress: string | null; userId: string | null; createdAt: string; } const ACTION_FILTERS = [ { value: '', label: 'All actions' }, { value: 'auth.login', label: 'Logins' }, { value: 'auth.logout', label: 'Logouts' }, { value: 'server.create', label: 'Server created' }, { value: 'server.iterate', label: 'Server iterated' }, { value: 'server.delete', label: 'Server deleted' }, ]; export default function AuditPage() { const [entries, setEntries] = useState(null); const [action, setAction] = useState(''); const [search, setSearch] = useState(''); useEffect(() => { const q = action ? `?action=${encodeURIComponent(action)}` : ''; apiFetch<{ entries: AuditEntry[] }>(`/v1/audit${q}`).then((r) => setEntries(r.entries)); }, [action]); const visible = entries?.filter((e) => search ? e.action.includes(search) || e.resourceId?.includes(search) || e.ipAddress?.includes(search) : true, ); return (

Audit log

Every privileged action in your workspace, with IP and metadata.

setSearch(e.target.value)} placeholder="Filter by resource id or ip…" className="w-72" />
{!visible && (
Loading…
)} {visible && visible.length === 0 && (
No matching entries.
)} {visible && visible.length > 0 && ( {visible.map((e) => ( ))}
When Action Resource IP Metadata
{new Date(e.createdAt).toLocaleString()} {e.action} {e.resourceType ? `${e.resourceType}/${e.resourceId?.slice(0, 8) ?? '—'}` : '—'} {e.ipAddress ?? '—'} {e.metadata ? JSON.stringify(e.metadata) : '—'}
)}
); }