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); }}>{item.icon} {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 {navigationGroups.map((group) => {
const items = navigation.filter((item) => item.group === group);
return { const open = event.currentTarget.open; setOpenGroups((current) => { if (current.has(group) === open) return current; const next = new Set(current); if (open) next.add(group); else next.delete(group); return next; }); }}>{group} ⌄ ;
})} ;
}
function navigate(path: string) {
window.history.pushState({}, '', path);
window.dispatchEvent(new PopStateEvent('popstate'));
}
function StatusBadge({ label, tone = 'unknown' }: { label: string; tone?: 'unknown' | 'ready' }) {
return {tone === 'ready' ? '✓' : '?'} {label} ;
}
function PageIntro({ eyebrow, title, intro }: { eyebrow: string; title: string; intro: string }) {
return ;
}
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
{sourceLags.slice(0, 6).map((source) => {source.state === 'healthy' ? '✓' : source.state === 'degraded' ? '!' : '?'} {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) => {index === 0 ? '!' : '?'} {problem.label} {problem.reason} )} : {resourcesLoading ? copy.overview.loadingResources : copy.overview.noProblems}
}{overviewNeedsAuthentication && } { refreshSystemStatus(); refreshOverview(); }}>{copy.overview.retry} navigate('/status')}>{copy.overview.openStatus}
{copy.overview.storagePools}
{copy.overview.capacity} navigate('/pools')}>{copy.overview.viewAll} {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} navigate('/containers')}>{copy.overview.viewAll} {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} navigate('/incidents')}>{copy.overview.viewAll} {overview.resources.incidents !== 'ready' ? {resourceStateDetail(overview.resources.incidents)}
: orderedIncidents.length ? {orderedIncidents.slice(0, 5).map((incident) => {incident.title} {formatDateTime(incident.startedAt)} )} : {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}
setReload((value) => value + 1)}>{copy.dashboards.retry} :
state === 'empty' ? ▦ {copy.dashboards.empty} {copy.dashboards.emptyDetail}
:
{items.map((item) => navigate('/dashboards/' + encodeURIComponent(item.id))}>{item.name} {item.description || item.slug} {copy.dashboards.version} {item.currentVersion} )} ;
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 {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 ? : onFilter(widget)}>{widget.type === 'text' ? 'T' : '◌'} {copy.dashboards.noData} {source} · {copy.dashboards.dataPending} };
}
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 {copy.dashboards.loading} ;
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 && navigate('/dashboards')}>← {copy.dashboards.back} }{copy.dashboards.viewMode}
{wallboard ?
{summary.name} :
{summary.name} }
{summary.description || copy.dashboards.noDescription}
{wallboard && {copy.wallboard.readOnly} }{copy.dashboards.version} {summary.currentVersion} {!wallboard && setEditing(true)}>{copy.dashboards.edit} }
{!wallboard && {copy.dashboards.timeRange} setTimeRange(event.target.value)}>{copy.dashboards.live} 15 {copy.dashboards.minutes} 1 {copy.dashboards.hour} 6 {copy.dashboards.hours} 24 {copy.dashboards.hours} 7 {copy.dashboards.days} {copy.dashboards.filter} setFilter(event.target.value)} placeholder={copy.dashboards.filterPlaceholder} /> {crossFilter ? copy.dashboards.filterActive + ': ' + crossFilter.label : copy.dashboards.fixedView} {usableCount} van {shown.length} {copy.dashboards.widgetsWithData} {crossFilter && {copy.dashboards.clearFilter} }
}{shown.length === 0 ? {copy.dashboards.noMatchingWidgets} {copy.dashboards.clearFilterHint}
: <>{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} {fullscreen ? copy.wallboard.exitFullscreen : copy.wallboard.enterFullscreen}
{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) => {copy.settings.management}
{group.title} {group.detail}
)}
>;
}
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}
navigate('/')}>{copy.states.returnHome}
;
}
return × {copy.states.errorTitle} {copy.states.errorDetail}
navigate(routeFromLocation(window.location.pathname))}>{copy.states.retry} ;
}
/** 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 ;
}
export default App;