import { useEffect, useMemo, useState } from 'react'; import { useSession } from '../context/SessionContext.jsx'; import RoverRoster from './RoverRoster.jsx'; const MODES = [ { key: 'open', label: 'Open' }, { key: 'turns', label: 'Turns' }, { key: 'admin', label: 'Admin' }, { key: 'lockdown', label: 'Lockdown' }, ]; export default function AdminPanel() { const { session, lockRover, setMode, requestControl, setCommunityGoal, setAdminReason, rebootRover, rebootServer, llmControl, adminLogs, llmCommentaryStatus, } = useSession(); const roster = useMemo(() => session?.roster ?? [], [session?.roster]); const [lockStates, setLockStates] = useState({}); const [rebootStates, setRebootStates] = useState({}); const [serverRebooting, setServerRebooting] = useState(false); const [clearingLlmHistory, setClearingLlmHistory] = useState(false); const health = session?.health || null; const currentGoal = session?.communityGoal?.text || ''; const goalUpdatedAt = session?.communityGoal?.updatedAt || null; const [goalDraft, setGoalDraft] = useState(currentGoal); const currentReason = session?.adminReason?.text || ''; const reasonUpdatedAt = session?.adminReason?.updatedAt || null; const [reasonDraft, setReasonDraft] = useState(currentReason); const isAdmin = session?.role === 'admin' || session?.role === 'lockdown' || session?.role === 'lockdown-admin'; const currentMode = session?.mode ?? 'open'; const handleLockToggle = async (roverId, locked) => { try { await lockRover(roverId, locked); setLockStates((prev) => ({ ...prev, [roverId]: locked })); } catch (err) { alert(err.message); } }; const handleModeChange = async (event) => { const mode = event.target.value; try { await setMode(mode); } catch (err) { alert(err.message); } }; const handleForceControl = async (roverId) => { try { await requestControl(roverId, { force: true }); } catch (err) { alert(err.message); } }; const handleReboot = async (rover) => { if (!rover?.id) return; const ok = window.confirm(`Reboot rover "${rover.name || rover.id}" now?`); if (!ok) return; setRebootStates((prev) => ({ ...prev, [rover.id]: true })); try { await rebootRover(rover.id); } catch (err) { alert(err.message); } finally { setRebootStates((prev) => ({ ...prev, [rover.id]: false })); } }; const handleServerReboot = async () => { const ok = window.confirm('Reboot the server host now? This will disconnect all users.'); if (!ok) return; setServerRebooting(true); try { await rebootServer(); } catch (err) { alert(err.message); setServerRebooting(false); } }; const handleClearLlmHistory = async () => { const ok = window.confirm( 'Clear LLM commentary history now? This resets chat context, bot memory, and rover activity metrics for narration.', ); if (!ok) return; setClearingLlmHistory(true); try { await llmControl('clearHistory'); } catch (err) { alert(err.message); } finally { setClearingLlmHistory(false); } }; const handleGoalSave = async () => { try { await setCommunityGoal(goalDraft); } catch (err) { alert(err.message); } }; const handleGoalClear = async () => { try { await setCommunityGoal(null); } catch (err) { alert(err.message); } }; const handleReasonSave = async () => { try { await setAdminReason(reasonDraft); } catch (err) { alert(err.message); } }; const handleReasonClear = async () => { try { await setAdminReason(null); } catch (err) { alert(err.message); } }; useEffect(() => { setGoalDraft(currentGoal); }, [currentGoal]); useEffect(() => { setReasonDraft(currentReason); }, [currentReason]); const lockMap = useMemo(() => { const map = {}; roster.forEach((rover) => { map[rover.id] = lockStates[rover.id] ?? rover.locked; }); return map; }, [roster, lockStates]); if (!isAdmin) return null; return (
Admin controls
Community goal {goalUpdatedAt ? ( Updated {new Date(goalUpdatedAt).toLocaleString()} ) : null}
setGoalDraft(event.target.value)} placeholder="Set a community goal" className="field-input text-sm" />
Admin mode reason {reasonUpdatedAt ? ( Updated {new Date(reasonUpdatedAt).toLocaleString()} ) : null}