11'use client' ;
22
3- import { useMemo , useState } from 'react' ;
3+ import { useEffect , useMemo , useState } from 'react' ;
44import { useWebSocket } from './providers/WebSocketProvider' ;
55import { useFleetSnapshots , AgentSnapshot } from './providers/FleetSnapshotsProvider' ;
6+ import type { HealthSnapshotRow } from '@/lib/types' ;
67import {
78 ServerIcon ,
89 CpuIcon ,
@@ -13,6 +14,7 @@ import {
1314 RefreshCwIcon ,
1415 SearchIcon ,
1516 XIcon ,
17+ ActivitySquareIcon ,
1618} from 'lucide-react' ;
1719
1820function formatBytes ( kib : number ) : string {
@@ -45,6 +47,30 @@ export default function FleetOverview({
4547 const { agents } = useWebSocket ( ) ;
4648 const { snapshots, refresh } = useFleetSnapshots ( ) ;
4749 const [ search , setSearch ] = useState ( '' ) ;
50+ const [ healthByAgent , setHealthByAgent ] = useState < Record < string , HealthSnapshotRow > > ( { } ) ;
51+
52+ useEffect ( ( ) => {
53+ let cancelled = false ;
54+ const load = async ( ) => {
55+ try {
56+ const res = await fetch ( '/api/health-probes/snapshot' , { credentials : 'include' } ) ;
57+ if ( ! res . ok ) return ;
58+ const rows : HealthSnapshotRow [ ] = await res . json ( ) ;
59+ if ( cancelled ) return ;
60+ const map : Record < string , HealthSnapshotRow > = { } ;
61+ for ( const r of rows ) map [ r . agent_id ] = r ;
62+ setHealthByAgent ( map ) ;
63+ } catch {
64+ /* ignore — chip just won't render */
65+ }
66+ } ;
67+ void load ( ) ;
68+ const t = setInterval ( load , 10_000 ) ;
69+ return ( ) => {
70+ cancelled = true ;
71+ clearInterval ( t ) ;
72+ } ;
73+ } , [ ] ) ;
4874
4975 const totals = useMemo ( ( ) => {
5076 let cpu = 0 ;
@@ -231,7 +257,10 @@ export default function FleetOverview({
231257 onClick = { ( ) => onSelectAgent ?.( agentId ) }
232258 className = "w-full text-left bg-slate-900 border border-slate-800 hover:border-slate-700 rounded-md px-4 py-3 transition-colors"
233259 >
234- < HostRow snapshot = { snap ?? { agentId, hostname : agentId . replace ( / - i d $ / , '' ) } } />
260+ < HostRow
261+ snapshot = { snap ?? { agentId, hostname : agentId . replace ( / - i d $ / , '' ) } }
262+ health = { healthByAgent [ agentId ] }
263+ />
235264 </ button >
236265 </ li >
237266 ) ;
@@ -359,7 +388,13 @@ function SmallStat({
359388 ) ;
360389}
361390
362- function HostRow ( { snapshot } : { snapshot : AgentSnapshot } ) {
391+ function HostRow ( {
392+ snapshot,
393+ health,
394+ } : {
395+ snapshot : AgentSnapshot ;
396+ health ?: HealthSnapshotRow ;
397+ } ) {
363398 const stats = snapshot . stats ;
364399 const services = snapshot . services ;
365400 const docker = snapshot . docker ;
@@ -414,6 +449,21 @@ function HostRow({ snapshot }: { snapshot: AgentSnapshot }) {
414449 { failed } failed
415450 </ span >
416451 ) }
452+ { health && health . total > 0 && (
453+ < span
454+ className = { `inline-flex items-center gap-1 text-xs rounded-full px-2 py-0.5 border ${
455+ health . red > 0
456+ ? 'text-red-300 bg-red-500/10 border-red-500/30'
457+ : health . unknown > 0
458+ ? 'text-slate-300 bg-slate-700/30 border-slate-600/40'
459+ : 'text-emerald-300 bg-emerald-500/10 border-emerald-500/30'
460+ } `}
461+ title = { `${ health . green } green / ${ health . red } red / ${ health . unknown } pending` }
462+ >
463+ < ActivitySquareIcon className = "w-3 h-3" />
464+ { health . green } /{ health . total }
465+ </ span >
466+ ) }
417467 < Field
418468 label = "Containers"
419469 value = {
0 commit comments