'use client'; import { useEffect, useState } from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { ClientRequestError, requestJson } from '@/lib/client-request'; import { AUDIT_ACTIONS, AUDIT_CATEGORIES, adminAuditResponse, type AdminAuditPage } from '@/lib/admin-audit'; import '../admin.css'; type Filters = { q: string; category: string; from: string; to: string }; const empty: Filters = { q:'',category:'all',from:'',to:'' }; export default function ActionHistory() { const [filters,setFilters] = useState(empty); const [applied,setApplied] = useState(empty); const [result,setResult] = useState(null); const [loading,setLoading] = useState(true); const [denied,setDenied] = useState(false); const [notice,setNotice] = useState(''); const [reload,setReload] = useState(0); useEffect(() => { let canceled = false; const query = new URLSearchParams(window.location.search); const selected = { q:query.get('q') ?? '',category:query.get('category') || 'all',from:query.get('from') ?? '',to:query.get('to') ?? '' }; // oxlint-disable-next-line react/react-compiler -- Restore shareable, non-secret history filters after SSR. setFilters(selected); setApplied(selected); setLoading(true); setResult(null); setDenied(false); setNotice(''); requestJson<{ admin: { id: string } }>('/api/admin/session',{cache:'no-store'}) .then(async session => adminAuditResponse(await requestJson('/api/admin/audit?'+query,{cache:'no-store',headers:{'X-Dune-Admin-Id':session.admin.id}}))) .then(page => { if (!canceled) setResult(page); }) .catch(error => { if (!canceled) { setDenied(error instanceof ClientRequestError && [401,403].includes(error.status ?? 0)); setNotice(error instanceof Error ? error.message : 'Action history could not be loaded.'); } }) .finally(() => { if (!canceled) setLoading(false); }); return () => { canceled = true; }; },[reload]); useEffect(() => { const refresh = () => { if (document.visibilityState !== 'hidden') { setResult(null); setReload(value => value+1); } }; window.addEventListener('focus',refresh); window.addEventListener('pageshow',refresh); document.addEventListener('visibilitychange',refresh); return () => { window.removeEventListener('focus',refresh); window.removeEventListener('pageshow',refresh); document.removeEventListener('visibilitychange',refresh); }; },[]); const link = (page: number) => '/admin/audit?'+new URLSearchParams({...applied,page:String(page)}); return
Administration

Action history

Recorded administrator actions, newest first. This history describes what was applied; later actions may have changed the room again.

{notice &&

{notice}

} {denied ?

Sign in to administration

: <>
Clear filters
{loading && Loading action history…} {result &&

{result.total.toLocaleString()} recorded {result.total === 1 ? 'action' : 'actions'}

{result.reasonsVisible ? 'Operational reasons are visible to owners and operators.' : 'Your viewer role hides operational reasons.'} Search matches room/seat/account identifiers and current administrator names; it does not search reasons or private game contents.

{result.events.length === 0 ?

No records on this page. Adjust the filters or return to the first page.

:
    {result.events.map(event =>
  1. {AUDIT_ACTIONS[event.action]}

    {event.createdAt === null ? Time unavailable : }

    Recorded {AUDIT_CATEGORIES[event.category]}{event.roomCode && <> · Room {event.roomCode}}

    Administrator
    {event.actorId ? <>{event.actorName ?? 'Account no longer available'} ({event.actorId}) : 'External account operation; no administrator attribution was recorded'}
    {event.targetAdminId && <>
    Account affected
    {event.targetAdminName ?? 'Account no longer available'} ({event.targetAdminId})
    } {event.targetSeatId && <>
    Seat affected
    {event.targetSeatId}
    } {event.reason !== null && <>
    Reason
    {event.reason}
    }
    {event.changes.length > 0 &&
    Recorded settings
    {event.changes.map(change =>
    {change.field}
    {change.before !== null ? <>Before: {change.before}
    : null}{change.after !== null ? <>After: {change.after} : null}
    )}
    }
  2. )}
}

Times use your device’s time zone; date filters use UTC. Names reflect current administrator accounts. External CLI account operations may lack actor attribution; web-managed account changes record the acting owner. Rejected attempts are not yet recorded here. New actions may move records between pages.

} }
; }