'use client'; import { useEffect, useRef, useState } from 'react'; import { Button } from '@/components/ui/button'; import { ClientRequestError, requestJson } from '@/lib/client-request'; import type { AdminAttempt, AdminAttemptReason } from '@/db/admin-attempts'; import type { AdminIntegrity, AdminOperations, AdminStalledDecision } from '@/db/admin-operations'; import '../admin.css'; import './operations.css'; type Account = { id: string; role: 'owner' | 'operator' | 'viewer' }; type Snapshot = AdminOperations & { revision: string }; type Stalled = { observedAt: number; stallMs: number; stalled: AdminStalledDecision[] }; type Attempts = { observedAt: number; windowMs: number; total: number; byReason: { reason: AdminAttemptReason; count: number }[]; recent: AdminAttempt[]; }; const ATTEMPT_LABELS: Record = { invalid_key_format: 'Malformed access key', unknown_or_disabled_key: 'Unknown or disabled access key', missing_session: 'Missing or malformed session', unknown_or_expired_session: 'Unknown, expired or revoked session', role_denied: 'Role does not permit the action', }; const message = (error: unknown) => error instanceof Error ? error.message : 'Operational status is unavailable.'; const idle = (idleMs: number) => `${Math.floor(idleMs / 3_600_000)}h ${Math.floor(idleMs % 3_600_000 / 60_000)}m`; export default function OperationsPage() { const [snapshot, setSnapshot] = useState(null); const [notice, setNotice] = useState(''); const [loading, setLoading] = useState(true); const [refresh, setRefresh] = useState(0); const [integrity, setIntegrity] = useState(null); const [integrityNotice, setIntegrityNotice] = useState(''); const [checking, setChecking] = useState(false); const integrityEpoch = useRef(0); const [stalled, setStalled] = useState(null); const [stalledNotice, setStalledNotice] = useState(''); const [loadingStalled, setLoadingStalled] = useState(false); const stalledEpoch = useRef(0); const [attempts, setAttempts] = useState(null); const [attemptsNotice, setAttemptsNotice] = useState(''); const [loadingAttempts, setLoadingAttempts] = useState(false); const attemptsEpoch = useRef(0); useEffect(() => { let canceled = false; const epoch = integrityEpoch; requestJson<{ admin: Account }>('/api/admin/session', { cache: 'no-store' }) .then(async ({ admin }) => { if (admin.role !== 'owner') throw new Error('Operations are available only to an owner.'); const result = await requestJson('/api/admin/operations', { cache: 'no-store', headers: { 'X-Dune-Admin-Id': admin.id }, }); if (!canceled) { setSnapshot(result); setNotice(''); } }) .catch(error => { if (canceled) return; setSnapshot(null); setNotice(error instanceof ClientRequestError && [401, 403].includes(error.status ?? 0) ? 'Owner access changed or expired. Sign in to administration again.' : message(error)); }) .finally(() => { if (!canceled) setLoading(false); }); return () => { canceled = true; epoch.current++; }; }, [refresh]); async function checkIntegrity() { const epoch = ++integrityEpoch.current; setChecking(true); setIntegrity(null); setIntegrityNotice(''); try { const { admin } = await requestJson<{ admin: Account }>('/api/admin/session', { cache: 'no-store' }); if (admin.role !== 'owner') throw new Error('Operations are available only to an owner.'); const result = await requestJson('/api/admin/operations?integrity=1', { cache: 'no-store', headers: { 'X-Dune-Admin-Id': admin.id }, }); if (epoch === integrityEpoch.current) setIntegrity(result); } catch (error) { if (epoch === integrityEpoch.current) setIntegrityNotice(error instanceof ClientRequestError && [401, 403].includes(error.status ?? 0) ? 'Owner access changed or expired. Sign in to administration again.' : message(error)); } finally { if (epoch === integrityEpoch.current) setChecking(false); } } async function checkStalled() { const epoch = ++stalledEpoch.current; setLoadingStalled(true); setStalled(null); setStalledNotice(''); try { const { admin } = await requestJson<{ admin: Account }>('/api/admin/session', { cache: 'no-store' }); if (admin.role !== 'owner') throw new Error('Operations are available only to an owner.'); const result = await requestJson('/api/admin/operations?stalled=1', { cache: 'no-store', headers: { 'X-Dune-Admin-Id': admin.id }, }); if (epoch === stalledEpoch.current) setStalled(result); } catch (error) { if (epoch === stalledEpoch.current) setStalledNotice(error instanceof ClientRequestError && [401, 403].includes(error.status ?? 0) ? 'Owner access changed or expired. Sign in to administration again.' : message(error)); } finally { if (epoch === stalledEpoch.current) setLoadingStalled(false); } } async function checkAttempts() { const epoch = ++attemptsEpoch.current; setLoadingAttempts(true); setAttempts(null); setAttemptsNotice(''); try { const { admin } = await requestJson<{ admin: Account }>('/api/admin/session', { cache: 'no-store' }); if (admin.role !== 'owner') throw new Error('Operations are available only to an owner.'); const result = await requestJson('/api/admin/operations?attempts=1', { cache: 'no-store', headers: { 'X-Dune-Admin-Id': admin.id }, }); if (epoch === attemptsEpoch.current) setAttempts(result); } catch (error) { if (epoch === attemptsEpoch.current) setAttemptsNotice(error instanceof ClientRequestError && [401, 403].includes(error.status ?? 0) ? 'Owner access changed or expired. Sign in to administration again.' : message(error)); } finally { if (epoch === attemptsEpoch.current) setLoadingAttempts(false); } } return
Administration

Operations

Owner-only, read-only counts from the current database and the revision baked into this server build. Refresh to take a new sample; no monitoring or automatic repair runs here.

{notice &&

{notice}

} {loading ? Checking owner access and database… : snapshot ?

Sample at {new Date(snapshot.observedAt).toLocaleString()}

Server build revision
{snapshot.revision}
Saved rooms (including removed/archived)
{snapshot.rooms}
Removed rooms
{snapshot.removedRooms}
Archived rooms
{snapshot.archivedRooms}
Paused rooms
{snapshot.pausedRooms}
Closed rooms
{snapshot.closedRooms}
Active seat credentials
{snapshot.activeSeats}
Invalid JSON room states
{snapshot.unreadableRooms}
Latest room write
{snapshot.lastRoomChange === null ? 'No rooms' : new Date(snapshot.lastRoomChange).toLocaleString()}
Room backup snapshots
{snapshot.backupSnapshots}
Backup snapshot payload bytes
{snapshot.backupBytes.toLocaleString()}
Recorded private exports
{snapshot.backupDownloads}
Rooms waiting on a stalled interaction
{snapshot.stalledRooms}
Oldest stalled room write
{snapshot.oldestStalledChange === null ? 'None stalled' : new Date(snapshot.oldestStalledChange).toLocaleString()}
Database size
{(snapshot.databaseBytes / 1048576).toFixed(1)} MB
Database pages
{snapshot.databasePages.toLocaleString()}

These counters do not check SQLite integrity, disk free space, backup recoverability or server health outside this request. The invalid JSON count detects syntax only, not rule-level save validity; this page cannot repair a room.

Read-only sample of up to ten live rooms whose public pending interaction has been idle longest. It excludes paused, closed, removed and archived rooms and shows no private game contents.

{loadingStalled && Sampling stalled rooms…} {stalledNotice &&

{stalledNotice}

} {stalled &&

Sampled {new Date(stalled.observedAt).toLocaleString()}; idle means no room write for {Math.round(stalled.stallMs / 3_600_000)} hours.

{stalled.stalled.length === 0 ?

No live room is waiting on a stalled interaction.

: {stalled.stalled.map(room => )}
Stalled rooms, longest idle first
RoomWaiting onTurnPhaseIdle
{room.code} {room.kind} {room.turn ?? '—'} {room.phase ?? '—'} {idle(room.idleMs)}
}

This sample changes nothing. Pause, resume, close or remove a room only through its own controls and audit.

}

Read-only counters and the twenty most recent rejected administrator requests. Only a fixed reason, the attempted role and an already-known account id are stored: never a key, session token, request body or address.

{loadingAttempts && Sampling rejected requests…} {attemptsNotice &&

{attemptsNotice}

} {attempts &&

Sampled {new Date(attempts.observedAt).toLocaleString()}; counters cover the last {Math.round(attempts.windowMs / 86_400_000)} days ({attempts.total} rejected requests).

{attempts.byReason.length === 0 ?

No rejected administrator request was recorded in the window.

:
    {attempts.byReason.map(row =>
  • {ATTEMPT_LABELS[row.reason]}: {row.count}
  • )}
} {attempts.recent.length === 0 ? null : {attempts.recent.map((row, index) => )}
Most recent rejected requests, newest first
WhenKindReasonRoleAccount
{new Date(row.createdAt).toLocaleString()} {row.kind} {ATTEMPT_LABELS[row.reason]} {row.role ?? '—'} {row.accountId ? `${row.accountId.slice(0, 8)}…` : '—'}
}

This sample changes nothing and identifies no credential. Use it to notice repeated failures, not to lock out a seat or account automatically.

}

This manual, read-only SQLite quick check and foreign-key check may take time. It reports pass/fail only: not disk capacity, backup recoverability or game-rule validity.

{checking && Checking database structure and references…} {integrityNotice &&

{integrityNotice}

} {integrity &&

Checked {new Date(integrity.observedAt).toLocaleString()}.

SQLite structure: {integrity.sqlite === 'passed' ? 'Passed' : 'Failed'}. Foreign keys: {integrity.foreignKeys === 'passed' ? 'Passed' : 'Failed'}.

{integrity.sqlite === 'failed' || integrity.foreignKeys === 'failed' ?

Do not repair or restore this database automatically. Preserve a protected snapshot and investigate privately.

: null}
}
:

Sign in to administration

}
; }