import { Fragment, useEffect, useMemo, useRef, useState } from 'react'; import { buildFleetTableViewModel, type FleetAuthFilter, type FleetFilter, type FleetInstance, type FleetSortColumn, type FleetStatus, type SortDirection, } from './fleet-table-view-model.js'; export interface FleetSnapshot { readonly instances: readonly FleetInstance[]; readonly statuses: ReadonlyMap; } export interface FleetDataSource { load(): Promise; } export interface FleetPageProps { readonly dataSource?: FleetDataSource; readonly initialData?: FleetSnapshot; readonly refreshSignal?: number; } const EMPTY_SNAPSHOT: FleetSnapshot = { instances: [], statuses: new Map() }; const STATUS_LABELS: Readonly> = { online: 'Online', auth: 'Authentication required', offline: 'Offline', unknown: 'Unknown', }; const COLUMN_LABELS: Readonly> = { name: 'Name', status: 'Status', latency: 'Latency', version: 'Version', capabilities: 'Capabilities', tags: 'Tags', freshness: 'Freshness', anomalies: 'Anomalies', origin: 'Origin', }; const ALL_COLUMNS = Object.keys(COLUMN_LABELS) as readonly (FleetSortColumn | 'origin')[]; export function canonicalHttpOrigin(value: string): string | null { try { const url = new URL(value); if (url.protocol !== 'http:' && url.protocol !== 'https:') return null; url.username = ''; url.password = ''; return url.origin; } catch { return null; } } export function FleetPage({ dataSource, initialData, refreshSignal = 0 }: FleetPageProps) { const [snapshot, setSnapshot] = useState(initialData ?? null); const [error, setError] = useState(null); const [attempt, setAttempt] = useState(0); const [query, setQuery] = useState(''); const [filter, setFilter] = useState('all'); const [auth, setAuth] = useState('all'); const [capability, setCapability] = useState(''); const [version, setVersion] = useState(''); const [tag, setTag] = useState(''); const [page, setPage] = useState(1); const [sort, setSort] = useState<{ column: FleetSortColumn; direction: SortDirection }>({ column: 'name', direction: 'asc', }); const [selectedIds, setSelectedIds] = useState>(new Set()); const [shownColumns, setShownColumns] = useState>(new Set(ALL_COLUMNS)); const [columnsOpen, setColumnsOpen] = useState(false); const [batchOpen, setBatchOpen] = useState(false); const selectAllRef = useRef(null); useEffect(() => { if (initialData && refreshSignal === 0) { setSnapshot(initialData); setError(null); return; } if (!dataSource) { setSnapshot(EMPTY_SNAPSHOT); return; } let active = true; setSnapshot(null); setError(null); void dataSource.load().then( (data) => { if (active) setSnapshot(data); }, (reason: unknown) => { if (active) setError(reason instanceof Error ? reason.message : 'Unable to load instances.'); }, ); return () => { active = false; }; }, [attempt, dataSource, initialData, refreshSignal]); const model = useMemo( () => buildFleetTableViewModel(snapshot?.instances ?? [], snapshot?.statuses ?? new Map(), { query, filter, auth, ...(capability ? { capability } : {}), ...(version ? { version } : {}), ...(tag ? { tag } : {}), sort, selectedIds, page, }), [auth, capability, filter, page, query, selectedIds, snapshot, sort, tag, version], ); useEffect(() => { if (selectAllRef.current) selectAllRef.current.indeterminate = model.visibleSelection.indeterminate; }, [model.visibleSelection.indeterminate]); useEffect(() => { if (page !== model.page) setPage(model.page); }, [model.page, page]); function resetPage(action: () => void): void { action(); setPage(1); } function changeSort(column: FleetSortColumn): void { setSort((current) => ({ column, direction: current.column === column && current.direction === 'asc' ? 'desc' : 'asc', })); setPage(1); } function toggleOne(id: string, checked: boolean): void { setSelectedIds((current) => { const next = new Set(current); if (checked) next.add(id); else next.delete(id); return next; }); } function toggleVisible(checked: boolean): void { setSelectedIds((current) => { const next = new Set(current); for (const row of model.rows) { if (checked) next.add(row.id); else next.delete(row.id); } return next; }); } function toggleColumn(column: string): void { setShownColumns((current) => { const next = new Set(current); if (next.has(column)) next.delete(column); else next.add(column); return next; }); } const selectFilter = ( label: string, value: string, setValue: (value: string) => void, choices: readonly string[], allLabel: string, ) => ( ); return (

Fleet

Find, compare, and manage SimAdmin instances.

{model.selectedIds.length} selected
{batchOpen ? (
Choose an action for {model.selectedIds.length} selected{' '} {model.selectedIds.length === 1 ? 'instance' : 'instances'}.
) : null}
{selectFilter( 'Capability', capability, setCapability, model.facets.capabilities, 'All capabilities', )} {selectFilter('Version', version, setVersion, model.facets.versions, 'All versions')} {selectFilter('Tag', tag, setTag, model.facets.tags, 'All tags')}
{columnsOpen ? (
Visible columns {ALL_COLUMNS.map((column) => ( ))}
) : null}
{!snapshot && !error ? (

Loading instances…

) : null} {error ? (

Could not load instances: {error}

) : null} {snapshot && model.emptyReason ? (
{model.emptyReason === 'config' ?

No instances are configured.

: null} {model.emptyReason === 'search' ? (

No instances match your search and filters.

) : null} {model.emptyReason === 'filter' ?

No instances match the active filters.

: null}
) : null} {snapshot && model.rows.length > 0 ? ( <>
{ALL_COLUMNS.filter((column) => shownColumns.has(column)).map((column) => ( ))} {model.rows.map((row) => { const origin = canonicalHttpOrigin(row.instance.url); const cells: Readonly> = { name: ( ), status: ( ), latency: , version: , capabilities: , tags: , freshness: , anomalies: , origin: ( ), }; return ( {ALL_COLUMNS.filter((column) => shownColumns.has(column)).map((column) => ( {cells[column]} ))} ); })}
Fleet instances
toggleVisible(event.currentTarget.checked)} /> {column === 'origin' ? ( COLUMN_LABELS[column] ) : ( )}
{row.displayName} {row.id} {STATUS_LABELS[row.statusKind]} {row.latencyMs === undefined ? '—' : `${row.latencyMs} ms`}{row.version ?? '—'}{row.capabilities.join(', ') || '—'}{row.tags.join(', ') || '—'}{row.freshness}{row.anomalies.join(', ') || '—'} {origin ? ( {origin} ) : ( Invalid origin )}
toggleOne(row.id, event.currentTarget.checked)} />
) : null}
); }