import { Component, Suspense, lazy, type CSSProperties, type ErrorInfo, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { MetricWidget, metricStatus, RankedListWidget, StatusGridWidget, type MetricWidgetProps, type RankedListItem, type StatusGridItem } from './MetricWidgets'; import { DashboardRuntimeWidget, type RuntimeState } from './DashboardRuntimeWidget'; import { copy } from './copy'; import { routeFromLocation, type RoutePath } from './routes'; import { HostPage } from './HostPage'; import { ArrayPage } from './ArrayPage'; import { DiskDetailPage, DiskPage } from './DiskPage'; import { PoolPage } from './PoolPage'; import { SharePage } from './SharePage'; import { StoragePage } from './StoragePage'; import { CapacityPage } from './CapacityPage'; import { StorageMapWidget, TemperatureHeatmap, type HeatmapPoint, type StorageMapNode } from './StorageVisuals'; import { ContainerDetailPage, ContainerPage } from './ContainerPage'; import { ApplicationPage } from './ApplicationPage'; import { ServicePage } from './ServicePage'; import type { TopologyData } from './TopologyPage'; import { NetworkPage, NetworkHealthWidget, type NetworkData } from './NetworkPage'; import { IncidentPage } from './IncidentPage'; import { OnboardingPage } from './OnboardingPage'; import { SystemStatusPage } from './SystemStatusPage'; import { InventoryPage } from './InventoryPage'; import { EventsPage } from './EventsPage'; import { NotFoundPage } from './NotFoundPage'; import { formatDateTime } from './locale'; import { installSessionWatcher, onUnauthenticated } from './auth'; import { AuthNoticeBanner, SignInButton } from './SignIn'; import { aggregateStatus, refreshSystemStatus, statusProblems, useSystemStatus } from './systemStatus'; import { operationalStorageState, presentComponent, presentReason, presentStatus } from './presentation'; import { wallboardColumns, wallboardPlacement, wallboardSlideIndex } from './wallboardLayout'; import { OperationalSignalPath, type OperationalSignalStage } from './OperationalSignalPath'; import { containerSignalTone, signalToneFromState, signalToneRank, sourceSignalTone, worstSignalTone, type SignalTone } from './overviewSignals'; // Heavy, rarely-used surfaces. The wallboard and mobile personas never execute // the editor stack, the alert-rule editor, the topology graph or the process // explorer, so those stay out of the initial chunk (FRONTEND_STANDARDS "Charts"). const DashboardEditor = lazy(() => import('./DashboardEditor').then((module) => ({ default: module.DashboardEditor }))); const AlertRulesPage = lazy(() => import('./AlertRulesPage').then((module) => ({ default: module.AlertRulesPage }))); const TopologyPage = lazy(() => import('./TopologyPage').then((module) => ({ default: module.TopologyPage }))); const TopologyWidget = lazy(() => import('./TopologyPage').then((module) => ({ default: module.TopologyWidget }))); const ProcessPage = lazy(() => import('./ProcessPage').then((module) => ({ default: module.ProcessPage }))); const navigation = [ { path: '/', label: copy.navigation.overview, icon: '⌂', group: copy.navigation.groups.command }, { path: '/dashboards', label: copy.navigation.dashboards, icon: '▦', group: copy.navigation.groups.command }, { path: '/host', label: copy.navigation.host, icon: '▣', group: copy.navigation.groups.infrastructure }, { path: '/array', label: copy.navigation.array, icon: '▥', group: copy.navigation.groups.infrastructure }, { path: '/disks', label: copy.navigation.disks, icon: '◉', group: copy.navigation.groups.infrastructure }, { path: '/pools', label: copy.navigation.pools, icon: '◫', group: copy.navigation.groups.infrastructure }, { path: '/shares', label: copy.navigation.shares, icon: '⇄', group: copy.navigation.groups.infrastructure }, { path: '/storage', label: copy.navigation.storage, icon: '▤', group: copy.navigation.groups.infrastructure }, { path: '/capacity', label: copy.navigation.capacity, icon: '⌁', group: copy.navigation.groups.infrastructure }, { path: '/network', label: copy.navigation.network, icon: '⌘', group: copy.navigation.groups.infrastructure }, { path: '/processes', label: copy.navigation.processes, icon: '≋', group: copy.navigation.groups.workloads }, { path: '/containers', label: copy.navigation.containers, icon: '⬡', group: copy.navigation.groups.workloads }, { path: '/applications', label: copy.navigation.applications, icon: '◆', group: copy.navigation.groups.workloads }, { path: '/services', label: copy.navigation.services, icon: '◉', group: copy.navigation.groups.services }, { path: '/topology', label: copy.navigation.topology, icon: '⌬', group: copy.navigation.groups.services }, { path: '/alerts', label: copy.navigation.alerts, icon: '!', group: copy.navigation.groups.response }, { path: '/events', label: copy.navigation.events, icon: '≡', group: copy.navigation.groups.response }, { path: '/incidents', label: copy.navigation.incidents, icon: '△', group: copy.navigation.groups.response }, { path: '/inventory', label: copy.navigation.inventory, icon: '▥', group: copy.navigation.groups.manage }, { path: '/wallboard', label: copy.navigation.wallboard, icon: '▰', group: copy.navigation.groups.manage }, { path: '/settings', label: copy.navigation.settings, icon: '⚙', group: copy.navigation.groups.manage }, { path: '/status', label: copy.navigation.status, icon: '♥', group: copy.navigation.groups.manage }, { path: '/onboarding', label: copy.navigation.onboarding, icon: '→', group: copy.navigation.groups.manage }, ] as const satisfies ReadonlyArray<{ path: RoutePath; label: string; icon: string; group: string }>; const mobilePrimaryPaths = new Set(['/', '/incidents', '/containers', '/storage']); const mobilePrimaryNavigation = ['/', '/incidents', '/containers', '/storage'].map((path) => navigation.find((item) => item.path === path)).filter((item): item is NavigationItem => Boolean(item)); const mobileMoreNavigation = navigation.filter((item) => !mobilePrimaryPaths.has(item.path)); type NavigationItem = (typeof navigation)[number]; const navigationGroups = Array.from(new Set(navigation.map((item) => item.group))); function NavigationLink({ item, label = item.label }: { item: NavigationItem; label?: string }) { const active = routeFromLocation(window.location.pathname) === item.path; return
  • { event.preventDefault(); navigate(item.path); }}>{label}
  • ; } function DesktopNavigation({ route }: { route: RoutePath }) { const activeGroup = navigation.find((item) => route === item.path || (item.path !== '/' && route.startsWith(item.path + '/')))?.group ?? navigationGroups[0]; const [openGroups, setOpenGroups] = useState>(() => new Set([navigationGroups[0], activeGroup])); useEffect(() => setOpenGroups((current) => current.has(activeGroup) ? current : new Set([...current, activeGroup])), [activeGroup]); return ; } function navigate(path: string) { window.history.pushState({}, '', path); window.dispatchEvent(new PopStateEvent('popstate')); } function StatusBadge({ label, tone = 'unknown' }: { label: string; tone?: 'unknown' | 'ready' }) { return {label}; } function PageIntro({ eyebrow, title, intro }: { eyebrow: string; title: string; intro: string }) { return

    {eyebrow}

    {title}

    {intro}

    ; } type OverviewSource = { state?: string; freshness?: string; reason?: string }; type OverviewHost = { identity?: { name?: string }; cpu?: { totalPercent?: number }; memory?: { utilizationPercent?: number }; source?: OverviewSource }; type OverviewContainer = { id?: string; state?: string; health?: string; intentionalStop?: boolean }; type OverviewPool = { id: string; name: string; state: string; capacitySeverity?: string; utilizationPercent: number }; type OverviewService = { id: string; name: string; state: string }; type OverviewIncident = { id: string; title: string; severity: string; startedAt: string }; type OverviewContainerSnapshot = { source?: OverviewSource; containers?: OverviewContainer[]; total?: number; nextCursor?: string }; type OverviewPoolSnapshot = { source?: OverviewSource; pools?: OverviewPool[]; total?: number }; type OverviewServiceSnapshot = { capabilityState?: string; configurationState?: string; reason?: string; services?: OverviewService[]; total?: number }; type OverviewResourceState = 'loading' | 'ready' | 'unavailable' | 'unauthorized' | 'forbidden'; type OverviewResource = 'host' | 'containers' | 'pools' | 'services' | 'incidents'; type OverviewData = { host?: OverviewHost; containers: OverviewContainer[]; containerSource?: OverviewSource; containerTotal: number; containersPartial: boolean; pools: OverviewPool[]; poolSource?: OverviewSource; poolTotal: number; poolsPartial: boolean; services: OverviewService[]; serviceTotal: number; servicesPartial: boolean; serviceCapability?: string; serviceConfiguration?: string; incidents: OverviewIncident[]; incidentsPartial: boolean; resources: Record; }; const loadingOverviewResources: Record = { host: 'loading', containers: 'loading', pools: 'loading', services: 'loading', incidents: 'loading', }; const overviewInitialData: OverviewData = { containers: [], containerTotal: 0, containersPartial: false, pools: [], poolTotal: 0, poolsPartial: false, services: [], serviceTotal: 0, servicesPartial: false, incidents: [], incidentsPartial: false, resources: loadingOverviewResources, }; const OVERVIEW_REFRESH_MS = 30_000; const OVERVIEW_REQUEST_TIMEOUT_MS = 10_000; const CONTAINER_PAGE_LIMIT = 100; const CONTAINER_MAX_PAGES = 3; type ReadResult = { state: OverviewResourceState; data?: T }; function collectionExtent(reported: number | undefined, count: number): { total: number; partial: boolean } { const valid = Number.isInteger(reported) && (reported ?? -1) >= count; const total = valid ? reported as number : count; return { total, partial: !valid || total > count }; } function useOverviewData(): { data: OverviewData; refresh: () => void } { const [data, setData] = useState(overviewInitialData); const [generation, setGeneration] = useState(0); const refresh = useCallback(() => { setData((current) => ({ ...current, resources: { ...loadingOverviewResources } })); setGeneration((current) => current + 1); }, []); useEffect(() => { const controller = new AbortController(); const read = async (url: string): Promise> => { const requestController = new AbortController(); const abortRequest = () => requestController.abort(); if (controller.signal.aborted) abortRequest(); else controller.signal.addEventListener('abort', abortRequest, { once: true }); const timeout = window.setTimeout(abortRequest, OVERVIEW_REQUEST_TIMEOUT_MS); try { const response = await fetch(url, { signal: requestController.signal, cache: 'no-store' }); if (requestController.signal.aborted) return { state: 'unavailable' }; if (response.status === 401) return { state: 'unauthorized' }; if (response.status === 403) return { state: 'forbidden' }; return response.ok ? { state: 'ready', data: await response.json() as T } : { state: 'unavailable' }; } catch { return { state: 'unavailable' }; } finally { window.clearTimeout(timeout); controller.signal.removeEventListener('abort', abortRequest); } }; const readContainers = async (): Promise> => { const items: OverviewContainer[] = []; const seen = new Set(); let source: OverviewSource | undefined; let expectedTotal: number | undefined; let after = ''; let complete = false; let inconsistent = false; for (let page = 0; page < CONTAINER_MAX_PAGES; page += 1) { const params = new URLSearchParams({ limit: String(CONTAINER_PAGE_LIMIT), sort: 'name' }); if (after) params.set('after', after); const result = await read('/api/v1/containers?' + params); if (result.state !== 'ready' || !result.data) return { state: result.state }; const pageItems = result.data.containers ?? []; const reportedTotal = result.data.total; if (!Number.isInteger(reportedTotal) || (reportedTotal ?? -1) < pageItems.length) { inconsistent = true; } else if (expectedTotal == null) { expectedTotal = reportedTotal as number; } else { if (reportedTotal !== expectedTotal) inconsistent = true; expectedTotal = Math.max(expectedTotal, reportedTotal as number); } if (!source || signalToneRank[sourceSignalTone(result.data.source)] < signalToneRank[sourceSignalTone(source)]) source = result.data.source; for (const item of pageItems) { const id = item.id?.trim(); if (!id) { inconsistent = true; items.push(item); } else if (seen.has(id)) { inconsistent = true; } else { seen.add(id); items.push(item); } } const next = result.data.nextCursor?.trim() ?? ''; if (!next) { complete = true; break; } if (next === after) { inconsistent = true; break; } after = next; } const total = Math.max(expectedTotal ?? 0, items.length); return { state: 'ready', data: { source, items, total, partial: !complete || inconsistent || items.length < total } }; }; void read('/api/v1/host').then((result) => { if (controller.signal.aborted) return; setData((current) => ({ ...current, host: result.data, resources: { ...current.resources, host: result.state } })); }); void readContainers().then((result) => { if (controller.signal.aborted) return; setData((current) => ({ ...current, containers: result.data?.items ?? [], containerSource: result.data?.source, containerTotal: result.data?.total ?? 0, containersPartial: result.data?.partial ?? false, resources: { ...current.resources, containers: result.state } })); }); void read('/api/v1/pools?limit=64').then((result) => { if (controller.signal.aborted) return; const items = result.data?.pools ?? []; const extent = collectionExtent(result.data?.total, items.length); setData((current) => ({ ...current, pools: items, poolSource: result.data?.source, poolTotal: extent.total, poolsPartial: result.state === 'ready' && extent.partial, resources: { ...current.resources, pools: result.state } })); }); void read('/api/v1/services?limit=100').then((result) => { if (controller.signal.aborted) return; const items = result.data?.services ?? []; const extent = collectionExtent(result.data?.total, items.length); setData((current) => ({ ...current, services: items, serviceTotal: extent.total, servicesPartial: result.state === 'ready' && extent.partial, serviceCapability: result.data?.capabilityState, serviceConfiguration: result.data?.configurationState, resources: { ...current.resources, services: result.state } })); }); void read<{ items?: OverviewIncident[] }>('/api/v1/incidents?limit=100&status=open').then((result) => { if (controller.signal.aborted) return; const items = result.data?.items ?? []; setData((current) => ({ ...current, incidents: items, incidentsPartial: result.state === 'ready' && items.length >= 100, resources: { ...current.resources, incidents: result.state } })); }); return () => controller.abort(); }, [generation]); useEffect(() => { const timer = window.setInterval(() => { if (document.visibilityState === 'visible') setGeneration((current) => current + 1); }, OVERVIEW_REFRESH_MS); return () => window.clearInterval(timer); }, []); return { data, refresh }; } function signalResourceLabel(state: OverviewResourceState, tone: SignalTone): string { if (state === 'loading') return copy.overview.signalPathLoading; if (state === 'unauthorized') return copy.overview.signalPathUnauthorized; if (state === 'forbidden') return copy.overview.signalPathForbidden; if (state === 'unavailable') return copy.overview.signalPathUnavailable; return presentStatus(tone === 'attention' ? 'attention' : tone); } function resourceStateDetail(state: OverviewResourceState): string | undefined { if (state === 'loading') return copy.overview.resourceLoadingDetail; if (state === 'unauthorized') return copy.overview.resourceUnauthorizedDetail; if (state === 'forbidden') return copy.overview.resourceForbiddenDetail; if (state === 'unavailable') return copy.overview.resourceUnavailableDetail; return undefined; } function thresholdTone(values: Array): SignalTone { const usable = values.filter((value): value is number => value != null && Number.isFinite(value)); if (usable.length !== values.length) return 'unknown'; if (usable.some((value) => value >= 95)) return 'critical'; if (usable.some((value) => value >= 85)) return 'attention'; return 'healthy'; } function metric(value: number | undefined, suffix = '%'): string { return value == null || !Number.isFinite(value) ? '—' : value.toLocaleString('nl-BE', { maximumFractionDigits: 1 }) + suffix; } function boundedRatio(known: number, total: number, partial: boolean): string { return `${partial ? '≥' : ''}${known}/${total}`; } function signalCollectionLabel(state: OverviewResourceState, tone: SignalTone, options: { partial?: boolean; notConfigured?: boolean; emptyLabel?: string } = {}): string { if (state !== 'ready') return signalResourceLabel(state, tone); if (tone === 'critical' || tone === 'attention' || tone === 'stale') return presentStatus(tone); if (options.notConfigured) return copy.overview.signalPathNotConfigured; if (options.partial) return copy.overview.signalPathPartial; if (options.emptyLabel) return options.emptyLabel; return presentStatus(tone); } function OverviewPage() { const snapshot = useSystemStatus(); const status = aggregateStatus(snapshot); const { data: overview, refresh: refreshOverview } = useOverviewData(); const resourcesLoading = snapshot.state === 'loading' || Object.values(overview.resources).some((state) => state === 'loading'); const overviewNeedsAuthentication = snapshot.state === 'unauthorized' || Object.values(overview.resources).some((state) => state === 'unauthorized'); const systemResourceState: OverviewResourceState = snapshot.state === 'ready' ? 'ready' : snapshot.state === 'loading' ? 'loading' : snapshot.state === 'unauthorized' ? 'unauthorized' : snapshot.state === 'forbidden' ? 'forbidden' : 'unavailable'; const poolRank: Record = { critical: 0, faulted: 0, degraded: 1, attention: 2, unknown: 3 }; const poolProblems = (overview.resources.pools === 'ready' && sourceSignalTone(overview.poolSource) === 'healthy' ? overview.pools : []) .map((pool) => ({ pool, state: operationalStorageState(pool.state, pool.capacitySeverity) })) .filter(({ state }) => state !== 'healthy' && state !== 'normal') .sort((a, b) => (poolRank[a.state] ?? 4) - (poolRank[b.state] ?? 4)) .map(({ pool, state }) => ({ id: 'pool:' + pool.id, label: `${pool.name}: ${presentStatus(state)}`, reason: state === 'critical' || state === 'faulted' ? copy.overview.poolCapacityCritical : state === 'attention' ? copy.overview.poolCapacityAttention : presentReason('source_health_unknown') })); const resourceLabels: Record = { host: copy.navigation.host, containers: copy.overview.signalWorkloads, pools: copy.overview.signalStorage, services: copy.navigation.services, incidents: copy.overview.signalIncidents }; const resourceProblems = (Object.keys(overview.resources) as OverviewResource[]).flatMap((resource) => { const state = overview.resources[resource]; if (state !== 'unavailable' && state !== 'unauthorized' && state !== 'forbidden') return []; return [{ id: `resource:${resource}`, label: `${resourceLabels[resource]}: ${signalResourceLabel(state, 'unknown')}`, reason: resourceStateDetail(state) ?? copy.overview.resourceUnavailableDetail }]; }); const systemProblems = systemResourceState === 'ready' || systemResourceState === 'loading' ? [] : [{ id: 'resource:system-status', label: `${copy.overview.sources}: ${signalResourceLabel(systemResourceState, 'unknown')}`, reason: resourceStateDetail(systemResourceState) ?? copy.overview.resourceUnavailableDetail }]; const partialProblems: Array<{ id: string; label: string; reason: string }> = []; if (overview.containersPartial) partialProblems.push({ id: 'partial:containers', label: `${copy.overview.signalWorkloads}: ${copy.overview.signalPathPartial}`, reason: copy.overview.resourcePartialDetail }); if (overview.poolsPartial) partialProblems.push({ id: 'partial:pools', label: `${copy.overview.signalStorage}: ${copy.overview.signalPathPartial}`, reason: copy.overview.resourcePartialDetail }); if (overview.servicesPartial) partialProblems.push({ id: 'partial:services', label: `${copy.navigation.services}: ${copy.overview.signalPathPartial}`, reason: copy.overview.resourcePartialDetail }); if (overview.incidentsPartial) partialProblems.push({ id: 'partial:incidents', label: `${copy.overview.signalIncidents}: ${copy.overview.signalPathPartial}`, reason: copy.overview.resourcePartialDetail }); const problems = [...systemProblems, ...resourceProblems, ...partialProblems, ...poolProblems, ...statusProblems(snapshot.status)].slice(0, 10); const poolUsage = overview.pools.length ? Math.max(...overview.pools.map((pool) => pool.utilizationPercent)) : undefined; const runningContainers = overview.containers.filter((item) => item.state?.toLowerCase() === 'running').length; const availableServices = overview.services.filter((item) => item.state?.toLowerCase() === 'up').length; const sourceLags = snapshot.status?.sourceLag ?? []; const sourceTones = sourceLags.map((lag) => signalToneFromState(lag.state)); const healthySources = sourceTones.filter((tone) => tone === 'healthy').length; const sourceTone = snapshot.state !== 'ready' || sourceTones.length === 0 ? 'unknown' : status.stale ? 'stale' : worstSignalTone(sourceTones); const hostSourceTone = sourceSignalTone(overview.host?.source); const hostTone = overview.resources.host !== 'ready' || !overview.host ? 'unknown' : hostSourceTone !== 'healthy' ? hostSourceTone : thresholdTone([overview.host.cpu?.totalPercent, overview.host.memory?.utilizationPercent]); const poolSourceTone = sourceSignalTone(overview.poolSource); const knownStorageTone = overview.pools.length ? worstSignalTone(overview.pools.map((pool) => signalToneFromState(operationalStorageState(pool.state, pool.capacitySeverity)))) : 'unknown'; const storageTone = overview.resources.pools !== 'ready' ? 'unknown' : poolSourceTone !== 'healthy' ? poolSourceTone : overview.poolsPartial ? worstSignalTone([knownStorageTone, 'unknown']) : overview.poolTotal === 0 ? 'unknown' : knownStorageTone; const containerSourceTone = sourceSignalTone(overview.containerSource); const knownWorkloadTone = overview.containerTotal === 0 ? 'healthy' : worstSignalTone(overview.containers.map(containerSignalTone)); const workloadTone = overview.resources.containers !== 'ready' ? 'unknown' : containerSourceTone !== 'healthy' ? containerSourceTone : overview.containersPartial ? worstSignalTone([knownWorkloadTone, 'unknown']) : knownWorkloadTone; const serviceConfigured = overview.serviceCapability === 'available' && overview.serviceConfiguration === 'configured'; const knownServiceTone = overview.services.length ? worstSignalTone(overview.services.map((service) => signalToneFromState(service.state))) : 'unknown'; const serviceTone = overview.resources.services !== 'ready' || !serviceConfigured ? 'unknown' : overview.servicesPartial ? worstSignalTone([knownServiceTone, 'unknown']) : overview.serviceTotal === 0 ? 'unknown' : knownServiceTone; const incidentTone = overview.resources.incidents !== 'ready' ? 'unknown' : overview.incidents.length === 0 ? 'healthy' : overview.incidents.some((incident) => signalToneFromState(incident.severity) === 'critical') ? 'critical' : 'attention'; const orderedIncidents = [...overview.incidents].sort((left, right) => signalToneRank[signalToneFromState(left.severity)] - signalToneRank[signalToneFromState(right.severity)] || right.startedAt.localeCompare(left.startedAt) || left.id.localeCompare(right.id)); const highestIncident = orderedIncidents[0]; const incidentSeverity = highestIncident ? presentStatus(highestIncident.severity) : copy.overview.noOpenIncidents; const hostUsable = overview.resources.host === 'ready' && hostSourceTone === 'healthy'; const poolsUsable = overview.resources.pools === 'ready' && poolSourceTone === 'healthy'; const containersUsable = overview.resources.containers === 'ready' && containerSourceTone === 'healthy'; const servicesUsable = overview.resources.services === 'ready' && serviceConfigured; const signalStages: OperationalSignalStage[] = [ { id: 'sources', label: copy.overview.sources, icon: '◉', tone: sourceTone, statusLabel: signalResourceLabel(systemResourceState, sourceTone), primaryLabel: copy.overview.connectedSources, primaryValue: snapshot.state === 'ready' ? `${healthySources}/${sourceTones.length || '—'}` : '—', secondaryLabel: copy.overview.freshness, secondaryValue: signalResourceLabel(systemResourceState, sourceTone), detail: copy.overview.signalPathSourcesDetail, route: '/status' }, { id: 'host', label: copy.navigation.host, icon: '▣', tone: hostTone, statusLabel: signalResourceLabel(overview.resources.host, hostTone), primaryLabel: copy.overview.cpu, primaryValue: hostUsable ? metric(overview.host?.cpu?.totalPercent) : '—', secondaryLabel: copy.overview.memoryShort, secondaryValue: hostUsable ? metric(overview.host?.memory?.utilizationPercent) : '—', detail: copy.overview.signalPathHostDetail, route: '/host' }, { id: 'storage', label: copy.overview.signalStorage, icon: '▤', tone: storageTone, statusLabel: signalCollectionLabel(overview.resources.pools, storageTone, { partial: overview.poolsPartial }), primaryLabel: copy.overview.storage, primaryValue: poolsUsable ? (overview.poolsPartial && poolUsage != null ? `≥${metric(poolUsage)}` : metric(poolUsage)) : '—', secondaryLabel: copy.navigation.pools, secondaryValue: poolsUsable ? String(overview.poolTotal) : '—', detail: copy.overview.signalPathStorageDetail, route: '/storage' }, { id: 'workloads', label: copy.overview.signalWorkloads, icon: '⬡', tone: workloadTone, statusLabel: signalCollectionLabel(overview.resources.containers, workloadTone, { partial: overview.containersPartial, emptyLabel: overview.containerTotal === 0 ? copy.overview.signalPathNoWorkloads : undefined }), primaryLabel: copy.overview.activeContainers, primaryValue: containersUsable ? boundedRatio(runningContainers, overview.containerTotal, overview.containersPartial) : '—', secondaryLabel: copy.overview.total, secondaryValue: containersUsable ? String(overview.containerTotal) : '—', detail: copy.overview.signalPathWorkloadsDetail, route: '/containers' }, { id: 'services', label: copy.navigation.services, icon: '◇', tone: serviceTone, statusLabel: signalCollectionLabel(overview.resources.services, serviceTone, { partial: overview.servicesPartial, notConfigured: overview.serviceConfiguration === 'not_configured' }), primaryLabel: copy.overview.available, primaryValue: servicesUsable ? boundedRatio(availableServices, overview.serviceTotal, overview.servicesPartial) : '—', secondaryLabel: copy.overview.total, secondaryValue: servicesUsable ? String(overview.serviceTotal) : '—', detail: copy.overview.signalPathServicesDetail, route: '/services' }, { id: 'incidents', label: copy.overview.signalIncidents, icon: '△', tone: incidentTone, statusLabel: signalCollectionLabel(overview.resources.incidents, incidentTone, { partial: overview.incidentsPartial }), primaryLabel: copy.overview.openIncidents, primaryValue: overview.resources.incidents === 'ready' ? `${overview.incidents.length}${overview.incidentsPartial ? '+' : ''}` : '—', secondaryLabel: copy.overview.highestSeverity, secondaryValue: overview.resources.incidents === 'ready' ? incidentSeverity : '—', detail: copy.overview.signalPathIncidentsDetail, route: '/incidents' }, ]; const hasSignalAttention = signalStages.some((stage) => stage.tone === 'critical' || stage.tone === 'attention'); const hasSignalUncertainty = signalStages.some((stage) => stage.tone === 'stale' || stage.tone === 'unknown'); const heading = hasSignalAttention || problems.length > 0 || (poolUsage != null && poolUsage >= 90) ? copy.overview.attentionTitle : hasSignalUncertainty ? copy.overview.unknownTitle : copy.overview.title; return

    {copy.overview.eyebrow}

    {heading}

    {copy.overview.intro}

    {snapshot.status ? formatDateTime(snapshot.status.generatedAt) : copy.overview.statusLoading}
    {sourceLags.slice(0, 6).map((source) => {presentComponent(source.sourceId)}{presentStatus(source.state)})} {sourceLags.length === 0 && {copy.overview.sourceLag}{copy.overview.unknown}}

    {copy.overview.cpu}

    {hostUsable ? metric(overview.host?.cpu?.totalPercent) : '—'}{hostUsable ? presentStatus(overview.host?.source?.freshness ?? 'unknown') : signalResourceLabel(overview.resources.host, hostTone)}

    {copy.overview.memory}

    {hostUsable ? metric(overview.host?.memory?.utilizationPercent) : '—'}{hostUsable ? overview.host?.identity?.name ?? copy.overview.unknown : signalResourceLabel(overview.resources.host, hostTone)}

    {copy.overview.storage}

    {poolsUsable ? (overview.poolsPartial && poolUsage != null ? `≥${metric(poolUsage)}` : metric(poolUsage)) : '—'}{poolsUsable ? `${overview.poolTotal} ${copy.navigation.pools.toLowerCase()}` : signalResourceLabel(overview.resources.pools, storageTone)}

    {copy.overview.services}

    {servicesUsable ? boundedRatio(availableServices, overview.serviceTotal, overview.servicesPartial) : '—'}{overview.resources.services !== 'ready' ? signalResourceLabel(overview.resources.services, serviceTone) : !serviceConfigured ? copy.overview.signalPathNotConfigured : overview.servicesPartial ? copy.overview.signalPathPartial : copy.overview.available}

    {copy.overview.actionQueue}

    {copy.overview.problems}

    {problems.length}
    {problems.length ?
      {problems.map((problem, index) =>
    1. {index === 0 ? '!' : '?'}{problem.label}{problem.reason}
    2. )}
    :

    {resourcesLoading ? copy.overview.loadingResources : copy.overview.noProblems}

    }
    {overviewNeedsAuthentication && }

    {copy.overview.storagePools}

    {copy.overview.capacity}

    {overview.resources.pools !== 'ready' ?

    {resourceStateDetail(overview.resources.pools)}

    : poolSourceTone !== 'healthy' ?

    {copy.overview.resourceStaleDetail}

    : overview.pools.length ?
      {overview.pools.slice(0, 5).map((pool) => { const state = operationalStorageState(pool.state, pool.capacitySeverity); return
    • {pool.name}{presentStatus(state)} · device-health {presentStatus(pool.state).toLowerCase()}{metric(pool.utilizationPercent)}
    • ; })}
    :

    {copy.overview.noPools}

    }

    Nu

    {copy.navigation.containers}

    {containersUsable ? `${overview.containersPartial ? '≥' : ''}${runningContainers}` : '—'}{copy.overview.running}{containersUsable && !overview.containersPartial ? Math.max(0, overview.containerTotal - runningContainers) : '—'}{copy.overview.other}

    {overview.resources.containers !== 'ready' ? resourceStateDetail(overview.resources.containers) : containerSourceTone !== 'healthy' ? copy.overview.resourceStaleDetail : overview.containersPartial ? copy.overview.resourcePartialDetail : overview.containerTotal > 0 ? `${runningContainers} van ${overview.containerTotal} ${copy.overview.containersRunning}.` : copy.overview.signalPathNoWorkloads}

    {copy.overview.cpu}
    {hostUsable ? metric(overview.host?.cpu?.totalPercent) : '—'}
    {copy.overview.memory}
    {hostUsable ? metric(overview.host?.memory?.utilizationPercent) : '—'}
    Bron
    {hostUsable ? presentStatus(overview.host?.source?.freshness ?? 'unknown') : signalResourceLabel(overview.resources.host, hostTone)}

    {copy.overview.recentIncidents}

    {copy.navigation.incidents}

    {overview.resources.incidents !== 'ready' ?

    {resourceStateDetail(overview.resources.incidents)}

    : orderedIncidents.length ?
      {orderedIncidents.slice(0, 5).map((incident) =>
    • )}
    :

    {copy.overview.noIncidents}

    }
    ; } type ApiRecord = Record; type DashboardSummary = { id: string; slug: string; name: string; description: string; scope: string; revision: number; currentVersion: number }; type DashboardWidget = { id: string; type: string; title: string; description?: string; data?: ApiRecord; visualization?: ApiRecord; behavior?: ApiRecord; layouts?: ApiRecord }; type DashboardViewport = 'desktop' | 'tablet' | 'mobile' | 'wallboard'; type CrossFilter = { key: string; value: string; label: string; sourceWidgetId: string }; class ApiError extends Error { constructor(readonly status: number) { super('api request failed'); } } function field(record: ApiRecord | undefined, name: string): T | undefined { if (!record) return undefined; const upper = name.charAt(0).toUpperCase() + name.slice(1); return (record[name] ?? record[upper] ?? record[name.toUpperCase()]) as T | undefined; } function summaryFromApi(record: ApiRecord): DashboardSummary { return { id: String(field(record, 'id') ?? ''), slug: String(field(record, 'slug') ?? ''), name: String(field(record, 'name') ?? copy.dashboards.unnamed), description: String(field(record, 'description') ?? ''), scope: String(field(record, 'scope') ?? 'unknown'), revision: Number(field(record, 'revision') ?? 0), currentVersion: Number(field(record, 'currentVersion') ?? 0) }; } async function getJSON(url: string, signal: AbortSignal): Promise { const response = await fetch(url, { signal, cache: 'no-store' }); if (!response.ok) throw new ApiError(response.status); return response.json() as Promise; } function DashboardsPage() { const [state, setState] = useState<'loading' | 'error' | 'unauthorized' | 'empty' | 'ready'>('loading'); const [items, setItems] = useState([]); const [reload, setReload] = useState(0); useEffect(() => { const controller = new AbortController(); // Rotation is a background replacement once a dashboard is visible. Keep // the current view (and any identical shared live subscription) mounted // until the next document arrives instead of bouncing through `loading`. setState((current) => current === 'ready' ? current : 'loading'); getJSON<{ items?: ApiRecord[] }>('/api/v1/dashboards?limit=100', controller.signal).then((data) => { const next = (data.items ?? []).map(summaryFromApi).filter((item) => item.id !== ''); setItems(next); setState(next.length === 0 ? 'empty' : 'ready'); }).catch((error: unknown) => { if (error instanceof DOMException && error.name === 'AbortError') return; setState(error instanceof ApiError && error.status === 401 ? 'unauthorized' : 'error'); }); return () => controller.abort(); }, [reload]); const stateContent = state === 'loading' ?

    {copy.dashboards.loading}

    : state === 'unauthorized' ?

    {copy.dashboards.unauthorizedTitle}

    {copy.dashboards.unauthorizedDetail}

    : state === 'error' ?

    {copy.dashboards.errorTitle}

    {copy.dashboards.errorDetail}

    : state === 'empty' ?

    {copy.dashboards.empty}

    {copy.dashboards.emptyDetail}

    :
      {items.map((item) =>
    • )}
    ; return <>

    {copy.dashboards.catalog}

    {copy.dashboards.listTitle}

    {stateContent}
    {state === 'ready' &&

    {items.length} {copy.dashboards.available}

    }; } class WidgetBoundary extends Component<{ title: string; children: ReactNode }, { failed: boolean }> { state = { failed: false }; static getDerivedStateFromError(): { failed: boolean } { return { failed: true }; } componentDidCatch(_error: Error, _info: ErrorInfo): void {} render() { return this.state.failed ?

    {copy.dashboards.widgetError}

    {this.props.title}

    {copy.dashboards.widgetErrorDetail}

    : this.props.children; } } const widgetLabels: Record = { stat: copy.widgets.stat, timeseries: copy.widgets.timeseries, gauge: copy.widgets.gauge, 'ranked-list': copy.widgets.rankedList, 'status-grid': copy.widgets.statusGrid, table: copy.widgets.table, heatmap: copy.widgets.heatmap, 'event-timeline': copy.widgets.eventTimeline, 'storage-map': copy.widgets.storageMap, topology: copy.widgets.topology, 'service-matrix': copy.widgets.serviceMatrix, 'alert-summary': copy.widgets.alertSummary, text: copy.widgets.text, 'query-inspector': copy.widgets.queryInspector }; // Installed before any client captures the global `fetch`, so every API call in // the app funnels its 401s through one place. installSessionWatcher(); function responsiveViewport(): 'desktop' | 'tablet' | 'mobile' { return window.innerWidth <= 700 ? 'mobile' : window.innerWidth <= 900 ? 'tablet' : 'desktop'; } function widgetFilter(widget: DashboardWidget): { key: string; value: string; label: string } { const data = widget.data ?? {}; const scope = field(data, 'scope') ?? {}; const entityType = field(scope, 'entityType'); const sourceType = String(field(data, 'sourceType') ?? 'unknown'); return entityType ? { key: 'entityType', value: entityType, label: entityType } : { key: 'sourceType', value: sourceType, label: sourceType }; } function filterAllowed(document: ApiRecord, key: string, value: string): boolean { const safeSources = ['semantic-metric', 'inventory', 'events', 'alerts', 'incidents', 'text']; if (key === 'sourceType') return safeSources.includes(value); const variables = (field(document, 'variables') ?? []) as ApiRecord[]; return variables.some((variable) => { const options = field(variable, 'options') ?? []; return options.includes(value); }); } function filterFromURL(document: ApiRecord): CrossFilter | null { const params = new URLSearchParams(window.location.search); const key = params.get('filterKey'); const value = params.get('filterValue'); if (!key || !value || !filterAllowed(document, key, value)) return null; return { key, value, label: value, sourceWidgetId: '' }; } function compatibleWithFilter(widget: DashboardWidget, filter: CrossFilter | null): boolean { if (!filter || widget.id === filter.sourceWidgetId) return true; const next = widgetFilter(widget); return next.key === filter.key && next.value === filter.value; } function wallboardSlideFor(widget: DashboardWidget): number { const layout = field(widget.layouts ?? {}, 'wallboard') ?? field(widget.layouts ?? {}, 'desktop') ?? {}; return wallboardSlideIndex(field(layout, 'y')); } function widgetLayoutStyle(layout: ApiRecord, viewport: DashboardViewport): CSSProperties { const columns = viewport === 'wallboard' ? wallboardColumns : viewport === 'tablet' ? 8 : viewport === 'mobile' ? 1 : 18; const width = viewport === 'mobile' ? 1 : Math.min(columns, Math.max(1, Number(field(layout, 'w') ?? 6))); if (viewport !== 'wallboard') return { '--widget-span': String(width) } as CSSProperties; const placement = wallboardPlacement(layout); return { '--widget-span': String(placement.columnSpan), gridColumn: `${placement.columnStart} / span ${placement.columnSpan}`, gridRow: `${placement.rowStart} / span ${placement.rowSpan}` } as CSSProperties; } function DashboardWidgetView({ widget, viewport, onFilter, metric }: { widget: DashboardWidget; viewport: DashboardViewport; onFilter: (widget: DashboardWidget) => void; metric?: MetricWidgetProps }) { if (!widget.id || !widget.title) return

    {copy.dashboards.widgetError}

    {copy.widgets.unknown}

    {copy.dashboards.widgetErrorDetail}

    ; const behavior = widget.behavior ?? {}; if (field(behavior, 'hidden')) return null; const activeLayout = field(widget.layouts ?? {}, viewport) ?? field(widget.layouts ?? {}, 'desktop') ?? {}; if (field(activeLayout, 'visible') === false) return null; const typeLabel = widgetLabels[widget.type] ?? copy.widgets.unknown; const source = String(field(widget.data ?? {}, 'sourceType') ?? copy.dashboards.unknown); const status = metric ? metricStatus(metric) : { label: copy.dashboards.unknown, tone: 'unknown' as const }; const metricKind = metric && (widget.type === 'stat' || widget.type === 'timeseries' || widget.type === 'gauge' || widget.type === 'query-inspector'); const widgetItems = (field(widget.data ?? {}, 'items') ?? []) as ApiRecord[]; const rankedItems: RankedListItem[] = widgetItems.map((item) => ({ id: String(field(item, 'id') ?? ''), label: String(field(item, 'label') ?? field(item, 'name') ?? ''), value: String(field(item, 'value') ?? ''), detail: field(item, 'detail') } )).filter((item) => item.id !== '' && item.label !== ''); const statusItems: StatusGridItem[] = widgetItems.map((item) => ({ id: String(field(item, 'id') ?? ''), label: String(field(item, 'label') ?? field(item, 'name') ?? ''), status: String(field(item, 'status') ?? 'unknown'), reason: field(item, 'reason') } )).filter((item) => item.id !== '' && item.label !== ''); const storageNodes: StorageMapNode[] = widgetItems.map((item) => ({ id: String(field(item, 'id') ?? ''), label: String(field(item, 'label') ?? field(item, 'name') ?? ''), kind: String(field(item, 'kind') ?? 'storage'), state: String(field(item, 'status') ?? 'unknown'), detail: field(item, 'detail'), href: field(item, 'href') })).filter((item) => item.id !== '' && item.label !== ''); const topologyData = field(widget.data ?? {}, 'topology'); const networkData = field(widget.data ?? {}, 'network'); const heatmapPoints: HeatmapPoint[] = widgetItems.map((item) => { const value = Number(field(item, 'value')); return { id: String(field(item, 'id') ?? ''), label: String(field(item, 'label') ?? field(item, 'name') ?? ''), observedAt: String(field(item, 'observedAt') ?? new Date(0).toISOString()), value: Number.isFinite(value) ? value : null, status: String(field(item, 'status') ?? 'unknown'), href: field(item, 'href') }; }).filter((item) => item.id !== '' && item.label !== ''); return

    {typeLabel}

    {widget.title}

    {widget.description &&

    {widget.description}

    }{metricKind ? : widget.type === 'ranked-list' && rankedItems.length > 0 ? onFilter(widget)} /> : widget.type === 'status-grid' && statusItems.length > 0 ? onFilter(widget)} /> : widget.type === 'storage-map' && storageNodes.length > 0 ? : widget.type === 'topology' && topologyData ? {copy.topology.loading}

    }>
    : widget.type === 'network' && networkData ? : widget.type === 'heatmap' && heatmapPoints.length > 0 ? : }
    ; } function DashboardViewPage({ dashboardId, wallboard = false, wallboardSlide = 0, onWallboardSlideCount, onRuntimeState }: { dashboardId: string; wallboard?: boolean; wallboardSlide?: number; onWallboardSlideCount?: (count: number) => void; onRuntimeState?: (state: RuntimeState) => void }) { const [state, setState] = useState<'loading' | 'error' | 'unauthorized' | 'ready'>('loading'); const [summary, setSummary] = useState(null); const [document, setDocument] = useState({}); const [widgets, setWidgets] = useState([]); const [editing, setEditing] = useState(false); const [crossFilter, setCrossFilter] = useState(null); const [runtimeStates, setRuntimeStates] = useState>({}); const hasWallboardContent = useRef(false); const systemSnapshot = useSystemStatus(); const storageKey = 'pulse.dashboard.view.' + dashboardId; const [timeRange, setTimeRange] = useState(() => wallboard ? 'live' : window.localStorage.getItem(storageKey + '.range') ?? '1h'); const [filter, setFilter] = useState(() => window.localStorage.getItem(storageKey + '.filter') ?? ''); const updateRuntimeState = useCallback((id: string, next: RuntimeState) => setRuntimeStates((current) => current[id] === next ? current : { ...current, [id]: next }), []); useEffect(() => { const controller = new AbortController(); const replacingVisibleWallboard = wallboard && hasWallboardContent.current; if (!replacingVisibleWallboard) setState('loading'); getJSON<{ dashboard: ApiRecord; version: ApiRecord }>('/api/v1/dashboards/' + encodeURIComponent(dashboardId), controller.signal).then((data) => { const rawDocument = field(data.version, 'document') ?? {}; setDocument(rawDocument); setCrossFilter(filterFromURL(rawDocument)); setSummary(summaryFromApi(data.dashboard)); setWidgets((field(rawDocument, 'widgets') ?? []) as DashboardWidget[]); setRuntimeStates({}); if (wallboard) hasWallboardContent.current = true; setState('ready'); }).catch((error: unknown) => { if (error instanceof DOMException && error.name === 'AbortError') return; if (error instanceof ApiError && error.status === 401) { setState('unauthorized'); return; } // A wallboard is a continuous operational display. During a transient // failed rotation, keep the last verified document visible and let the // next bounded rotation/refresh retry. Never apply this to first load or // authentication failure. if (!replacingVisibleWallboard) setState('error'); }); return () => controller.abort(); }, [dashboardId, wallboard]); useEffect(() => { window.localStorage.setItem(storageKey + '.range', timeRange); }, [storageKey, timeRange]); useEffect(() => { window.localStorage.setItem(storageKey + '.filter', filter); }, [storageKey, filter]); useEffect(() => { const values = Object.values(runtimeStates); const aggregate: RuntimeState = values.includes('usable') ? 'usable' : values.includes('error') ? 'error' : values.length > 0 && values.every((value) => value === 'empty') ? 'empty' : 'loading'; onRuntimeState?.(aggregate); }, [onRuntimeState, runtimeStates]); useEffect(() => { if (wallboard) setRuntimeStates({}); }, [wallboard, wallboardSlide]); const wallboardWidgets = widgets.filter((widget) => { const layout = field(widget.layouts ?? {}, 'wallboard') ?? field(widget.layouts ?? {}, 'desktop') ?? {}; return field(widget.behavior ?? {}, 'hidden') !== true && field(layout, 'visible') !== false; }); const wallboardSlideCount = Math.max(1, ...wallboardWidgets.map((widget) => wallboardSlideFor(widget) + 1)); useEffect(() => { if (wallboard) onWallboardSlideCount?.(wallboardSlideCount); }, [onWallboardSlideCount, wallboard, wallboardSlideCount]); if (state === 'loading') return
    ; if (state === 'unauthorized') return ; if (state === 'error' || !summary) return ; if (editing) return }> setEditing(false)} onSaved={(revision, nextDocument) => { setDocument(nextDocument); setWidgets((field(nextDocument, 'widgets') ?? []) as DashboardWidget[]); setSummary({ ...summary, revision, currentVersion: summary.currentVersion + 1 }); setEditing(false); }} />; const viewport: DashboardViewport = wallboard ? 'wallboard' : responsiveViewport(); const normalized = filter.trim().toLowerCase(); const shown = widgets.filter((widget) => { const activeLayout = field(widget.layouts ?? {}, viewport) ?? field(widget.layouts ?? {}, 'desktop') ?? {}; return field(widget.behavior ?? {}, 'hidden') !== true && field(activeLayout, 'visible') !== false && (!wallboard || wallboardSlideFor(widget) === Math.min(wallboardSlide, wallboardSlideCount - 1)) && (!normalized || widget.title.toLowerCase().includes(normalized)) && compatibleWithFilter(widget, crossFilter); }); const systemStatus = aggregateStatus(systemSnapshot); const applyCrossFilter = (widget: DashboardWidget) => { const next = widgetFilter(widget); const filter = { ...next, sourceWidgetId: widget.id }; setCrossFilter(filter); const params = new URLSearchParams(window.location.search); params.set('filterKey', filter.key); params.set('filterValue', filter.value); window.history.replaceState({}, '', window.location.pathname + '?' + params.toString()); }; const clearCrossFilter = () => { setCrossFilter(null); const params = new URLSearchParams(window.location.search); params.delete('filterKey'); params.delete('filterValue'); const query = params.toString(); window.history.replaceState({}, '', window.location.pathname + (query ? '?' + query : '')); }; const usableCount = Object.values(runtimeStates).filter((value) => value === 'usable').length; return
    {!wallboard && }

    {copy.dashboards.viewMode}

    {wallboard ?

    {summary.name}

    :

    {summary.name}

    }

    {summary.description || copy.dashboards.noDescription}

    {wallboard && {copy.wallboard.readOnly}}{copy.dashboards.version} {summary.currentVersion}{!wallboard && }
    {!wallboard &&
    {crossFilter ? copy.dashboards.filterActive + ': ' + crossFilter.label : copy.dashboards.fixedView}{usableCount} van {shown.length} {copy.dashboards.widgetsWithData}{crossFilter && }
    }{shown.length === 0 ?

    {copy.dashboards.noMatchingWidgets}

    {copy.dashboards.clearFilterHint}

    : <>

    {copy.dashboards.widgetCollection}

    {shown.map((widget) => {['semantic-metric', 'inventory', 'events'].includes(String(field(widget.data, 'sourceType') ?? '')) ? applyCrossFilter(widget)} /> : })}
    }
    ; } type WallboardPriorityData = { serviceProblems: number; openIncidents: number; loading: boolean; unavailable: boolean }; function useWallboardPriorityData(): WallboardPriorityData { const [value, setValue] = useState({ serviceProblems: 0, openIncidents: 0, loading: true, unavailable: false }); useEffect(() => { let active = true; let controller: AbortController | null = null; const load = async () => { controller?.abort(); const current = new AbortController(); controller = current; try { const [servicesResponse, incidentsResponse] = await Promise.all([ fetch('/api/v1/services?limit=100', { signal: current.signal, cache: 'no-store' }), fetch('/api/v1/incidents?limit=100&status=open', { signal: current.signal, cache: 'no-store' }), ]); if (!servicesResponse.ok || !incidentsResponse.ok) throw new Error('priority'); const services = await servicesResponse.json() as { services?: Array<{ state?: string }> }; const incidents = await incidentsResponse.json() as { items?: unknown[] }; if (active) setValue({ serviceProblems: (services.services ?? []).filter((item) => item.state !== 'up').length, openIncidents: (incidents.items ?? []).length, loading: false, unavailable: false }); } catch (error: unknown) { if (error instanceof DOMException && error.name === 'AbortError') return; if (active) setValue((currentValue) => ({ ...currentValue, loading: false, unavailable: true })); } }; void load(); const timer = window.setInterval(load, 30000); return () => { active = false; controller?.abort(); window.clearInterval(timer); }; }, []); return value; } function WallboardPage() { const [state, setState] = useState<'loading' | 'ready' | 'error' | 'unauthorized' | 'empty'>('loading'); const [items, setItems] = useState([]); const [activeIndex, setActiveIndex] = useState(0); const [activeSlide, setActiveSlide] = useState(0); const [slideCount, setSlideCount] = useState(1); const [lastUpdated, setLastUpdated] = useState(); const [transport, setTransport] = useState<'connected' | 'reconnecting' | 'unavailable'>('reconnecting'); const [dataState, setDataState] = useState('loading'); const [fullscreen, setFullscreen] = useState(false); const [shift, setShift] = useState(0); const systemSnapshot = useSystemStatus(); const system = aggregateStatus(systemSnapshot); const storage = systemSnapshot.status?.components.find((component) => component.id === 'storage'); const priority = useWallboardPriorityData(); const params = new URLSearchParams(window.location.search); const intervalSeconds = Math.min(300, Math.max(10, Number(params.get('interval') ?? 30) || 30)); const refreshSeconds = Math.min(300, Math.max(10, Number(params.get('refresh') ?? 30) || 30)); useEffect(() => onUnauthenticated(() => { setTransport('unavailable'); setState('unauthorized'); }), []); useEffect(() => { let active = true; let inFlight: AbortController | null = null; const load = async () => { inFlight?.abort(); const controller = new AbortController(); inFlight = controller; try { const response = await fetch('/api/v1/dashboards?limit=100', { signal: controller.signal, cache: 'no-store' }); if (!response.ok) throw new Error('wallboard'); const data = await response.json() as { items?: ApiRecord[] }; const next = (data.items ?? []).map(summaryFromApi).filter((item) => item.id !== '').sort((a, b) => a.id.localeCompare(b.id)); if (!active) return; setItems(next); setActiveIndex((value) => next.length === 0 ? 0 : Math.min(value, next.length - 1)); setLastUpdated(new Date().toISOString()); setTransport('connected'); setState(next.length === 0 ? 'empty' : 'ready'); } catch (error: unknown) { if (error instanceof DOMException && error.name === 'AbortError') return; if (!active) return; setTransport('unavailable'); setState((value) => value === 'ready' || value === 'unauthorized' ? value : 'error'); } finally { if (inFlight === controller) inFlight = null; } }; void load(); const refresh = window.setInterval(() => { setTransport('reconnecting'); void load(); }, refreshSeconds * 1000); return () => { active = false; inFlight?.abort(); window.clearInterval(refresh); }; }, []); useEffect(() => { if (items.length === 0) return undefined; const rotation = window.setInterval(() => setActiveSlide((value) => { if (value + 1 < slideCount) return value + 1; if (items.length > 1) setActiveIndex((dashboard) => (dashboard + 1) % items.length); return 0; }), intervalSeconds * 1000); return () => window.clearInterval(rotation); }, [items.length, intervalSeconds, slideCount]); useEffect(() => { const timer = window.setInterval(() => setShift((value) => (value + 1) % 2), 60000); return () => window.clearInterval(timer); }, []); useEffect(() => { const update = () => setFullscreen(Boolean(document.fullscreenElement)); document.addEventListener('fullscreenchange', update); update(); return () => document.removeEventListener('fullscreenchange', update); }, []); const toggleFullscreen = async () => { try { if (document.fullscreenElement) await document.exitFullscreen(); else if (document.documentElement.requestFullscreen) await document.documentElement.requestFullscreen(); } catch { /* Fullscreen is optional; transport and data state remain truthful. */ } }; const handleRuntimeState = useCallback((runtime: RuntimeState) => setDataState(runtime), []); const handleSlideCount = useCallback((count: number) => { setSlideCount(Math.max(1, count)); setActiveSlide((value) => Math.min(value, Math.max(1, count) - 1)); }, []); if (state === 'loading') return

    {copy.wallboard.eyebrow}

    {copy.wallboard.loading}

    ; if (state === 'unauthorized') return

    {copy.wallboard.eyebrow}

    {copy.states.unauthorizedTitle}

    {copy.states.unauthorizedDetail}

    ; if (state === 'error') return

    {copy.wallboard.eyebrow}

    {copy.wallboard.errorTitle}

    {copy.wallboard.errorDetail}

    ; if (state === 'empty') return

    {copy.wallboard.eyebrow}

    {copy.wallboard.noDashboards}

    ; const current = items[activeIndex]; return

    {copy.wallboard.eyebrow}

    {copy.wallboard.title}

    {copy.wallboard.intro}

    {copy.wallboard.transport}: {transport === 'connected' ? copy.wallboard.connected : transport === 'reconnecting' ? copy.wallboard.reconnecting : copy.wallboard.unavailable}{copy.wallboard.data}: {dataState === 'usable' ? copy.wallboard.dataUsable : dataState === 'loading' ? copy.wallboard.dataLoading : dataState === 'empty' ? copy.wallboard.dataEmpty : copy.wallboard.unavailable}
    {copy.wallboard.overall}{system.label}{copy.wallboard.storage}{storage ? presentStatus(storage.state) : copy.wallboard.unknown}{copy.wallboard.services}{priority.loading ? copy.wallboard.dataLoading : priority.unavailable ? copy.wallboard.unknown : `${priority.serviceProblems} ${copy.wallboard.problems}`}{copy.wallboard.incidents}{priority.loading ? copy.wallboard.dataLoading : priority.unavailable ? copy.wallboard.unknown : `${priority.openIncidents} ${copy.wallboard.open}`}
    {copy.wallboard.lastUpdated}: {lastUpdated ? formatDateTime(lastUpdated) : copy.wallboard.reconnecting}{copy.wallboard.rotate} {copy.wallboard.every} {intervalSeconds} {copy.wallboard.seconds}{copy.wallboard.slide} {activeSlide + 1} / {slideCount}{copy.wallboard.dashboard} {activeIndex + 1} / {items.length}
    ; } const AlertsPage = AlertRulesPage; function SettingsPage() { const snapshot = useSystemStatus(); const status = aggregateStatus(snapshot); const connected = snapshot.status?.sourceLag.filter((source) => source.state === 'healthy').length ?? 0; const total = snapshot.status?.sourceLag.length ?? 0; const sourceDetail = snapshot.state === 'ready' && total > 0 ? `${connected} van ${total} ${copy.settings.sourcesCurrent}` : status.detail; const groups = [ { title: copy.settings.healthTitle, detail: copy.settings.healthDetail, links: [ { href: '/status', title: copy.settings.systemStatus, detail: copy.settings.systemStatusDetail, access: copy.settings.adminActions }, { href: '/onboarding', title: copy.settings.onboarding, detail: copy.settings.onboardingDetail, access: copy.settings.adminChanges }, ] }, { title: copy.settings.alertingTitle, detail: copy.settings.alertingDetail, links: [ { href: '/alerts?section=rules', title: copy.settings.alertRules, detail: copy.settings.alertRulesDetail, access: copy.settings.editorChanges }, { href: '/alerts?section=controls', title: copy.settings.alertControls, detail: copy.settings.alertControlsDetail, access: copy.settings.operatorChanges }, ] }, { title: copy.settings.presentationTitle, detail: copy.settings.presentationDetail, links: [ { href: '/dashboards', title: copy.settings.dashboards, detail: copy.settings.dashboardsDetail, access: copy.settings.editorChanges }, { href: '/inventory', title: copy.settings.inventory, detail: copy.settings.inventoryDetail, access: copy.settings.viewAccess }, ] }, ]; return <>

    {copy.settings.current}

    {copy.settings.environment}

    {copy.settings.source}{sourceDetail}{connected}/{total || '—'}
    {copy.settings.language}{copy.settings.languageDetail}{copy.settings.languageValue}

    {copy.settings.management}

    {groups.map((group) => )}
    ; } function StatePage({ kind }: { kind: 'loading' | 'error' | 'unauthorized' }) { if (kind === 'loading') return
    ; if (kind === 'unauthorized') { // The API answered 401, so the visitor is not signed in: offer the real // sign-in entry point and come back to the page they asked for. return

    {copy.states.unauthorizedTitle}

    {copy.states.unauthorizedDetail}

    {copy.auth.signInHint}

    ; } return

    {copy.states.errorTitle}

    {copy.states.errorDetail}

    ; } /** Wraps lazily loaded routes in the same loading state the rest of the app uses. */ function RouteSuspense({ children }: { children: ReactNode }) { return }>{children}; } function Page({ route }: { route: RoutePath }) { if (route.startsWith('/inventory/')) return ; if (route.startsWith('/dashboards/')) return ; if (route.startsWith('/services/')) return ; if (route.startsWith('/incidents/')) return ; if (route.startsWith('/containers/')) return ; if (route.startsWith('/disks/')) return ; if (route.startsWith('/pools/')) return ; if (route.startsWith('/shares/')) return ; if (route.startsWith('/applications/')) return ; switch (route) { case '/': return ; case '/processes': return ; case '/containers': return ; case '/services': return ; case '/topology': return ; case '/network': return ; case '/applications': return ; case '/host': return ; case '/array': return ; case '/disks': return ; case '/pools': return ; case '/shares': return ; case '/storage': return ; case '/capacity': return ; case '/inventory': return ; case '/dashboards': return ; case '/wallboard': return ; case '/alerts': return ; case '/events': return ; case '/incidents': return ; case '/settings': return ; case '/status': return ; case '/onboarding': return ; case '/loading': return ; case '/error': return ; case '/unauthorized': return ; case '/404': return ; default: return ; } } function App() { const [route, setRoute] = useState(() => routeFromLocation(window.location.pathname)); const shellStatus = aggregateStatus(useSystemStatus()); useEffect(() => { const handleNavigation = () => setRoute(routeFromLocation(window.location.pathname)); window.addEventListener('popstate', handleNavigation); return () => window.removeEventListener('popstate', handleNavigation); }, []); useEffect(() => { const mobileMenu = document.querySelector('.mobile-more'); if (mobileMenu?.open) mobileMenu.open = false; }, [route]); const currentNavigation = navigation.find((item) => item.path === route) ?? navigation.find((item) => item.path !== '/' && route.startsWith(item.path + '/')) ?? navigation[0]; if (route === '/wallboard') return ; return
    {copy.accessibility.skipToContent}
    ServerTower
    Live verbondenAlle bronnen · alleen-lezen
    ; } export default App;