// Administration Overview // Purpose: Summarizes configuration state, revision history, audit history, and links to existing health/report surfaces. // Scope: Presents persisted administration metadata without duplicating operational service implementations. import { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import CardFrame from '../../components/CardFrame/index.jsx'; import { checkForApplicationUpdate, getApplicationLifecycleStatus, restartApplication, restoreConfigurationRevision, updateApplication, } from '../api.js'; function formatDate(value) { return Number.isFinite(Number(value)) ? new Date(Number(value)).toLocaleString() : 'unknown'; } export default function AdminOverview({ snapshot, socket, runSensitive, onSnapshot }) { const config = snapshot.configuration; const [restartRequested, setRestartRequested] = useState(false); const [lifecycle, setLifecycle] = useState(null); const [lifecycleError, setLifecycleError] = useState(''); useEffect(() => { let mounted = true; async function refreshLifecycle() { if (!socket.connected) return; try { const response = await getApplicationLifecycleStatus(socket); if (!mounted) return; setLifecycle(response.lifecycle); setLifecycleError(''); } catch (error) { if (mounted) setLifecycleError(error.message); } } refreshLifecycle(); // Update work continues while this application is being replaced. Polling // the controller gives the page current progress before disconnect and the // persisted final result immediately after Socket.IO reconnects. const timer = window.setInterval(refreshLifecycle, 2500); return () => { mounted = false; window.clearInterval(timer); }; }, [socket]); useEffect(() => { if (!restartRequested) return undefined; // The socket reconnect is the simplest authoritative success signal: the // replacement process is accepting browser connections again. const handleReconnect = () => setRestartRequested(false); socket.once('connect', handleReconnect); return () => socket.off('connect', handleReconnect); }, [restartRequested, socket]); async function restore(revision) { if (!window.confirm(`Restore configuration revision ${revision}? This creates and immediately applies a new active revision.`)) return; try { const response = await runSensitive(() => restoreConfigurationRevision(socket, { revision, expectedRevision: config.revision, })); onSnapshot(response.snapshot); } catch (error) { window.alert(error.message); } } async function restart() { if (!window.confirm('Restart the MultiRover application now? Connected rovers and browsers will disconnect briefly; the server host will not reboot.')) return; setRestartRequested(true); try { await runSensitive(() => restartApplication(socket)); } catch (error) { setRestartRequested(false); window.alert(error.message); } } async function checkForUpdate() { try { const response = await runSensitive(() => checkForApplicationUpdate(socket)); setLifecycle(response.lifecycle); } catch (error) { setLifecycleError(error.message); } } async function update() { if (!window.confirm('Pull the configured MultiRover image and replace the application container now? The page will reconnect automatically.')) return; try { const response = await runSensitive(() => updateApplication(socket)); setLifecycle(response.lifecycle); } catch (error) { setLifecycleError(error.message); } } const lifecycleBusy = lifecycle?.state === 'running'; const lifecycleAvailable = Boolean(lifecycle?.available); const updateSummary = lifecycle?.updateAvailable === true ? 'An update is available.' : lifecycle?.updateAvailable === false ? 'The running application matches the latest checked image.' : 'Check for updates to compare the running container with the configured image.'; return (

Active revision

{config.revision}

Administrators

{snapshot.administrators.length}

Last configuration save

{formatDate(config.createdAt)}

Open fleet reports Open driver application

{lifecycleAvailable ? updateSummary : 'Container updates are unavailable until the lifecycle service is running.'}

{lifecycle?.message ?

{lifecycle.message}

: null} {lifecycle?.rollback ?

Rollback: {lifecycle.rollback.status}{lifecycle.rollback.error ? ` — ${lifecycle.rollback.error}` : ''}

: null} {lifecycleError ?

{lifecycleError}

: null}
{snapshot.revisions.map((revision) => (
#{revision.revision}{formatDate(revision.createdAt)}{revision.actor}{revision.source}
))}
{snapshot.auditEvents.map((event) => (
{formatDate(event.createdAt)}{event.actor}{event.action}
))}
); }