import { formatDateTime } from './locale'; import { useEffect, useState } from 'react'; import { copy } from './copy'; import { presentReason, presentStatus } from './presentation'; import { SourceStatusDetails } from './SourceStatusDetails'; type HostSnapshot = { source: { id: string; type: string; observedAt?: string; freshness: string; state: string; reason?: string }; identity: { name: string; version?: string; kernel?: string; architecture?: string }; uptimeSeconds: number; bootTime?: string; cpu: { totalPercent?: number; perCore?: number[]; iowaitPercent?: number }; load: { one: number; five: number; fifteen: number }; memory: { totalBytes: number; availableBytes: number; usedBytes: number; utilizationPercent: number; swapTotalBytes: number; swapUsedBytes: number; swapUtilizationPercent: number }; filesystems: Array<{ mount: string; filesystem?: string; capacityBytes: number; usedBytes: number; utilizationPercent: number; inodes?: { total: number; used: number } }>; network: Array<{ name: string; state?: string; rxBytes: number; txBytes: number; rxErrors: number; txErrors: number; rxDrops: number; txDrops: number }>; time: { synchronized: boolean; offsetSeconds: number; stratum?: number; state: string }; hardware?: { capabilities: Array<{ id: string; version: string; state: string; reason?: string }>; temperatures: Array<{ id: string; name: string; celsius: number }>; fans: Array<{ id: string; name: string; rpm: number }>; gpus: Array<{ id: string; name: string; vendor?: string; utilizationPercent?: number; memoryUsedBytes: number; memoryTotalBytes: number; memoryUtilizationPercent?: number; temperatureCelsius?: number }>; status: { state: string; reasons?: Array<{ code: string; message: string }> } }; status: { state: string; reasons?: Array<{ code: string; message: string }> }; observedAt?: string; receivedAt: string; warnings?: string[]; }; function getStatusLabel(state: string): string { return state === 'healthy' ? 'Gezond' : state === 'degraded' ? 'Aandacht' : 'Onbekend'; } function Badge({ state }: { state: string }) { const ready = state === 'healthy'; return {getStatusLabel(state)}; } function bytes(value: number): string { if (!Number.isFinite(value) || value < 0) return '—'; const units = ['B', 'KB', 'MB', 'GB', 'TB']; let scaled = value; let index = 0; while (scaled >= 1024 && index < units.length - 1) { scaled /= 1024; index += 1; } return scaled.toLocaleString('nl-BE', { maximumFractionDigits: 1 }) + ' ' + units[index]; } function duration(seconds: number): string { if (!Number.isFinite(seconds) || seconds < 0) return '—'; const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); const minutes = Math.floor((seconds % 3600) / 60); return days > 0 ? days + ' d ' + hours + ' u' : hours + ' u ' + minutes + ' min'; } function when(value?: string): string { return formatDateTime(value); } export function HostPage() { const [state, setState] = useState<'loading' | 'ready' | 'error'>('loading'); const [snapshot, setSnapshot] = useState(null); useEffect(() => { const controller = new AbortController(); fetch('/api/v1/host', { signal: controller.signal }).then((response) => { if (!response.ok) throw new Error('host'); return response.json() as Promise; }).then((data) => { setSnapshot(data); setState('ready'); }).catch((error: unknown) => { if (error instanceof DOMException && error.name === 'AbortError') return; setState('error'); }); return () => controller.abort(); }, []); if (state === 'loading') return
; if (state === 'error' || !snapshot) return

{copy.host.errorTitle}

{copy.host.errorDetail}

; const status = snapshot.status?.state ?? 'unknown'; const observed = snapshot.observedAt ?? snapshot.source?.observedAt; return <>

{copy.host.eyebrow}

{copy.host.title}

{copy.host.intro}

{copy.host.identity}

{snapshot.identity?.name || copy.host.unknown}

{[snapshot.identity?.version, snapshot.identity?.kernel, snapshot.identity?.architecture].filter(Boolean).join(' · ') || copy.host.noIdentityDetails}

{snapshot.status?.reasons?.map((reason) =>

{presentReason(reason.code)}

)}

{copy.host.uptime}

{duration(snapshot.uptimeSeconds)}

{snapshot.bootTime ? copy.host.boot + ' ' + when(snapshot.bootTime) : copy.host.noBoot}

{copy.host.cpu}

{snapshot.cpu?.totalPercent == null ? '—' : snapshot.cpu.totalPercent.toLocaleString('nl-BE', { maximumFractionDigits: 1 }) + '%'}

{snapshot.cpu?.perCore?.length ?? 0} {copy.host.cores} · iowait {snapshot.cpu?.iowaitPercent == null ? '—' : snapshot.cpu.iowaitPercent.toLocaleString('nl-BE', { maximumFractionDigits: 1 }) + '%'}

{copy.host.load}

{snapshot.load?.one?.toLocaleString('nl-BE', { maximumFractionDigits: 2 }) ?? '—'}

1 / 5 / 15 min: {snapshot.load?.one ?? '—'} · {snapshot.load?.five ?? '—'} · {snapshot.load?.fifteen ?? '—'}

{copy.host.memory}

{snapshot.memory?.utilizationPercent?.toLocaleString('nl-BE', { maximumFractionDigits: 1 }) ?? '—'}%

{bytes(snapshot.memory?.usedBytes ?? 0)} / {bytes(snapshot.memory?.totalBytes ?? 0)} · {copy.host.available}: {bytes(snapshot.memory?.availableBytes ?? 0)}

{copy.host.time}

{snapshot.time?.synchronized ? copy.host.synchronized : copy.host.notSynchronized}

Offset {snapshot.time?.offsetSeconds?.toLocaleString('nl-BE', { maximumFractionDigits: 3 }) ?? '—'} s · stratum {snapshot.time?.stratum ?? '—'}

{copy.host.filesystems}

{snapshot.filesystems?.length ?? 0}

{snapshot.filesystems?.length ?
{snapshot.filesystems.map((item) => )}
{copy.host.mount}{copy.host.used}{copy.host.inodes}
{item.mount}{item.utilizationPercent.toLocaleString('nl-BE', { maximumFractionDigits: 1 })}%{bytes(item.usedBytes)} / {bytes(item.capacityBytes)}{item.inodes ? ((item.inodes.used / item.inodes.total) * 100).toLocaleString('nl-BE', { maximumFractionDigits: 1 }) + '%' : '—'}
:

{copy.host.noData}

}

{copy.host.network}

{snapshot.network?.length ?? 0}

{snapshot.network?.length ?
{snapshot.network.map((item) => )}
{copy.host.interface}RX / TX{copy.host.errors}
{item.name}{item.state || copy.host.unknown}{bytes(item.rxBytes)} / {bytes(item.txBytes)}{item.rxErrors + item.txErrors + item.rxDrops + item.txDrops}
:

{copy.host.noData}

}

{copy.host.hardware}

{snapshot.hardware?.temperatures?.length ?? 0} · {snapshot.hardware?.fans?.length ?? 0} {copy.host.sensors}

{snapshot.hardware?.status?.reasons?.map((reason) =>

{presentReason(reason.code)}

)}{snapshot.hardware?.capabilities?.length ?
    {snapshot.hardware.capabilities.map((capability) =>
  • {capability.id}{presentStatus(capability.state)} · {presentReason(capability.reason)}
  • )}
: null}{snapshot.hardware?.temperatures?.length ?
{snapshot.hardware.temperatures.map((item) => )}
{copy.host.sensor}{copy.host.temperature}
{item.name}{item.id}{item.celsius.toLocaleString('nl-BE', { maximumFractionDigits: 1 })} °C
:

{copy.host.noSensors}

}

{copy.host.gpu}

{snapshot.hardware?.gpus?.length ?? 0}

{snapshot.hardware?.gpus?.length ?
{snapshot.hardware.gpus.map((item) => )}
{copy.host.device}{copy.host.gpuUsage}{copy.host.gpuMemory}
{item.name}{item.vendor || copy.host.unknown}{item.utilizationPercent == null ? '—' : item.utilizationPercent.toLocaleString('nl-BE', { maximumFractionDigits: 1 }) + '%'}{item.memoryTotalBytes ? item.memoryUtilizationPercent?.toLocaleString('nl-BE', { maximumFractionDigits: 1 }) + '%' : '—'}
:

{copy.host.noGpu}

}
{snapshot.warnings?.length ?

{copy.host.warnings}

{snapshot.warnings.map((warning) =>

{presentReason(warning)}

)}
: null} ; }