2026-03-19 10:23:13 +01:00
import { useState , useMemo } from 'react'
import { useParams , Link } from 'react-router-dom'
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
import styles from './AgentHealth.module.css'
// Layout
import { AppShell } from '../../design-system/layout/AppShell/AppShell'
import { Sidebar } from '../../design-system/layout/Sidebar/Sidebar'
import { TopBar } from '../../design-system/layout/TopBar/TopBar'
// Composites
2026-03-18 18:22:14 +01:00
import { GroupCard } from '../../design-system/composites/GroupCard/GroupCard'
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
import { LineChart } from '../../design-system/composites/LineChart/LineChart'
2026-03-18 18:22:14 +01:00
import { EventFeed } from '../../design-system/composites/EventFeed/EventFeed'
2026-03-19 10:23:13 +01:00
import { DetailPanel } from '../../design-system/composites/DetailPanel/DetailPanel'
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
// Primitives
import { StatusDot } from '../../design-system/primitives/StatusDot/StatusDot'
import { MonoText } from '../../design-system/primitives/MonoText/MonoText'
import { Badge } from '../../design-system/primitives/Badge/Badge'
2026-03-18 18:22:14 +01:00
import { StatCard } from '../../design-system/primitives/StatCard/StatCard'
2026-03-19 10:23:13 +01:00
import { ProgressBar } from '../../design-system/primitives/ProgressBar/ProgressBar'
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
2026-03-18 20:06:25 +01:00
// Global filters
import { useGlobalFilters } from '../../design-system/providers/GlobalFilterProvider'
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
// Mock data
2026-03-18 18:22:14 +01:00
import { agents , type AgentHealth as AgentHealthData } from '../../mocks/agents'
2026-03-18 17:50:41 +01:00
import { SIDEBAR_APPS } from '../../mocks/sidebar'
2026-03-18 18:22:14 +01:00
import { agentEvents } from '../../mocks/agentEvents'
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
2026-03-18 18:22:14 +01:00
// ── URL scope parsing ────────────────────────────────────────────────────────
type Scope =
| { level : 'all' }
| { level : 'app' ; appId : string }
function useScope ( ) : Scope {
const { '*' : rest } = useParams ( )
const segments = rest ? . split ( '/' ) . filter ( Boolean ) ? ? [ ]
2026-03-19 10:23:13 +01:00
if ( segments . length >= 1 ) return { level : 'app' , appId : segments [ 0 ] }
2026-03-18 18:22:14 +01:00
return { level : 'all' }
}
// ── Data grouping ────────────────────────────────────────────────────────────
interface AppGroup {
appId : string
instances : AgentHealthData [ ]
liveCount : number
staleCount : number
deadCount : number
totalTps : number
totalActiveRoutes : number
totalRoutes : number
}
function groupByApp ( agentList : AgentHealthData [ ] ) : AppGroup [ ] {
const map = new Map < string , AgentHealthData [ ] > ( )
for ( const a of agentList ) {
const list = map . get ( a . appId ) ? ? [ ]
list . push ( a )
map . set ( a . appId , list )
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
}
2026-03-18 18:22:14 +01:00
return Array . from ( map . entries ( ) ) . map ( ( [ appId , instances ] ) = > ( {
appId ,
instances ,
liveCount : instances.filter ( ( i ) = > i . status === 'live' ) . length ,
staleCount : instances.filter ( ( i ) = > i . status === 'stale' ) . length ,
deadCount : instances.filter ( ( i ) = > i . status === 'dead' ) . length ,
totalTps : instances.reduce ( ( s , i ) = > s + i . tps , 0 ) ,
totalActiveRoutes : instances.reduce ( ( s , i ) = > s + i . activeRoutes , 0 ) ,
totalRoutes : instances.reduce ( ( s , i ) = > s + i . totalRoutes , 0 ) ,
} ) )
}
function appHealth ( group : AppGroup ) : 'success' | 'warning' | 'error' {
if ( group . deadCount > 0 ) return 'error'
if ( group . staleCount > 0 ) return 'warning'
return 'success'
}
// ── Trend data (mock) ────────────────────────────────────────────────────────
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
2026-03-18 18:22:14 +01:00
function buildTrendData ( agent : AgentHealthData ) {
const now = Date . now ( )
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
const points = 20
2026-03-18 18:22:14 +01:00
const interval = ( 3 * 60 * 60 * 1000 ) / points
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
2026-03-18 18:22:14 +01:00
const throughput = Array . from ( { length : points } , ( _ , i ) = > ( {
x : new Date ( now - ( points - i ) * interval ) ,
y : Math.max ( 0 , agent . tps + ( Math . random ( ) - 0.5 ) * 4 ) ,
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
} ) )
2026-03-18 18:22:14 +01:00
const errorRate = Array . from ( { length : points } , ( _ , i ) = > ( {
x : new Date ( now - ( points - i ) * interval ) ,
y : Math.max ( 0 , ( agent . errorRate ? parseFloat ( agent . errorRate ) : 0.5 ) + ( Math . random ( ) - 0.5 ) * 2 ) ,
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
} ) )
2026-03-18 18:22:14 +01:00
return { throughput , errorRate }
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
}
2026-03-18 18:22:14 +01:00
// ── Breadcrumb ───────────────────────────────────────────────────────────────
function buildBreadcrumb ( scope : Scope ) {
const crumbs : { label : string ; href? : string } [ ] = [
2026-03-18 19:26:27 +01:00
{ label : 'Applications' , href : '/apps' } ,
2026-03-18 18:22:14 +01:00
{ label : 'Agents' , href : '/agents' } ,
]
2026-03-19 10:23:13 +01:00
if ( scope . level === 'app' ) {
crumbs . push ( { label : scope.appId } )
2026-03-18 18:22:14 +01:00
}
return crumbs
}
// ── AgentHealth page ─────────────────────────────────────────────────────────
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
export function AgentHealth() {
2026-03-18 18:22:14 +01:00
const scope = useScope ( )
2026-03-18 20:06:25 +01:00
const { isInTimeRange } = useGlobalFilters ( )
2026-03-19 10:23:13 +01:00
const [ selectedInstance , setSelectedInstance ] = useState < AgentHealthData | null > ( null )
const [ panelOpen , setPanelOpen ] = useState ( false )
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
2026-03-18 18:22:14 +01:00
// Filter agents by scope
const filteredAgents = useMemo ( ( ) = > {
if ( scope . level === 'all' ) return agents
2026-03-19 10:23:13 +01:00
return agents . filter ( ( a ) = > a . appId === scope . appId )
2026-03-18 18:22:14 +01:00
} , [ scope ] )
const groups = useMemo ( ( ) = > groupByApp ( filteredAgents ) , [ filteredAgents ] )
// Aggregate stats
const totalInstances = filteredAgents . length
const liveCount = filteredAgents . filter ( ( a ) = > a . status === 'live' ) . length
const staleCount = filteredAgents . filter ( ( a ) = > a . status === 'stale' ) . length
const deadCount = filteredAgents . filter ( ( a ) = > a . status === 'dead' ) . length
const totalTps = filteredAgents . reduce ( ( s , a ) = > s + a . tps , 0 )
const totalActiveRoutes = filteredAgents . reduce ( ( s , a ) = > s + a . activeRoutes , 0 )
2026-03-19 10:23:13 +01:00
const totalRoutes = filteredAgents . reduce ( ( s , a ) = > s + a . totalRoutes , 0 )
2026-03-18 18:22:14 +01:00
2026-03-18 20:06:25 +01:00
// Filter events by global time range
const filteredEvents = agentEvents . filter ( ( e ) = > isInTimeRange ( e . timestamp ) )
2026-03-18 18:22:14 +01:00
2026-03-19 10:23:13 +01:00
// Build trend data for selected instance
const trendData = selectedInstance ? buildTrendData ( selectedInstance ) : null
function handleInstanceClick ( inst : AgentHealthData ) {
setSelectedInstance ( inst )
setPanelOpen ( true )
}
// Detail panel tabs
const detailTabs = selectedInstance
? [
{
label : 'Overview' ,
value : 'overview' ,
content : (
< div className = { styles . detailContent } >
< div className = { styles . detailRow } >
< span className = { styles . detailLabel } > Status < / span >
< Badge
label = { selectedInstance . status . toUpperCase ( ) }
color = { selectedInstance . status === 'live' ? 'success' : selectedInstance . status === 'stale' ? 'warning' : 'error' }
/ >
< / div >
< div className = { styles . detailRow } >
< span className = { styles . detailLabel } > Application < / span >
< MonoText size = "xs" > { selectedInstance . appId } < / MonoText >
< / div >
< div className = { styles . detailRow } >
< span className = { styles . detailLabel } > Version < / span >
< MonoText size = "xs" > { selectedInstance . version } < / MonoText >
< / div >
< div className = { styles . detailRow } >
< span className = { styles . detailLabel } > Uptime < / span >
< MonoText size = "xs" > { selectedInstance . uptime } < / MonoText >
< / div >
< div className = { styles . detailRow } >
< span className = { styles . detailLabel } > Last Seen < / span >
< MonoText size = "xs" > { selectedInstance . lastSeen } < / MonoText >
< / div >
< div className = { styles . detailRow } >
< span className = { styles . detailLabel } > Throughput < / span >
< MonoText size = "xs" > { selectedInstance . tps . toFixed ( 1 ) } / s < / MonoText >
< / div >
< div className = { styles . detailRow } >
< span className = { styles . detailLabel } > Errors < / span >
< MonoText size = "xs" className = { selectedInstance . errorRate ? styles.instanceError : undefined } >
{ selectedInstance . errorRate ? ? '0 err/h' }
< / MonoText >
< / div >
< div className = { styles . detailRow } >
< span className = { styles . detailLabel } > Routes < / span >
< span > { selectedInstance . activeRoutes } / { selectedInstance . totalRoutes } active < / span >
< / div >
< div className = { styles . detailRow } >
< span className = { styles . detailLabel } > Memory < / span >
< div className = { styles . detailProgress } >
< ProgressBar
value = { selectedInstance . memoryUsagePct }
variant = { selectedInstance . memoryUsagePct > 85 ? 'error' : selectedInstance . memoryUsagePct > 70 ? 'warning' : 'success' }
/ >
< MonoText size = "xs" > { selectedInstance . memoryUsagePct } % < / MonoText >
< / div >
< / div >
< div className = { styles . detailRow } >
< span className = { styles . detailLabel } > CPU < / span >
< div className = { styles . detailProgress } >
< ProgressBar
value = { selectedInstance . cpuUsagePct }
variant = { selectedInstance . cpuUsagePct > 85 ? 'error' : selectedInstance . cpuUsagePct > 70 ? 'warning' : 'success' }
/ >
< MonoText size = "xs" > { selectedInstance . cpuUsagePct } % < / MonoText >
< / div >
< / div >
< / div >
) ,
} ,
{
label : 'Performance' ,
value : 'performance' ,
content : trendData ? (
< div className = { styles . detailContent } >
< div className = { styles . chartPanel } >
< div className = { styles . chartTitle } > Throughput ( msg / s ) < / div >
< LineChart
series = { [ { label : 'tps' , data : trendData.throughput } ] }
height = { 160 }
width = { 360 }
yLabel = "msg/s"
/ >
< / div >
< div className = { styles . chartPanel } >
< div className = { styles . chartTitle } > Error Rate ( err / h ) < / div >
< LineChart
series = { [ { label : 'errors' , data : trendData.errorRate , color : 'var(--error)' } ] }
height = { 160 }
width = { 360 }
yLabel = "err/h"
/ >
< / div >
< / div >
) : null ,
} ,
]
: [ ]
2026-03-18 18:22:14 +01:00
const isFullWidth = scope . level !== 'all'
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
return (
2026-03-19 10:23:13 +01:00
< AppShell
sidebar = { < Sidebar apps = { SIDEBAR_APPS } / > }
detail = {
selectedInstance ? (
< DetailPanel
open = { panelOpen }
onClose = { ( ) = > setPanelOpen ( false ) }
title = { selectedInstance . name }
tabs = { detailTabs }
/ >
) : undefined
}
>
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
< TopBar
2026-03-18 18:22:14 +01:00
breadcrumb = { buildBreadcrumb ( scope ) }
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
environment = "PRODUCTION"
user = { { name : 'hendrik' } }
/ >
< div className = { styles . content } >
2026-03-18 18:22:14 +01:00
{ /* Stat strip */ }
< div className = { styles . statStrip } >
2026-03-19 10:23:13 +01:00
< StatCard
label = "Total Agents"
value = { String ( totalInstances ) }
accent = { deadCount > 0 ? 'warning' : 'amber' }
detail = {
< span className = { styles . breakdown } >
< span className = { styles . bpLive } > < StatusDot variant = "live" / > { liveCount } live < / span >
< span className = { styles . bpStale } > < StatusDot variant = "stale" / > { staleCount } stale < / span >
< span className = { styles . bpDead } > < StatusDot variant = "dead" / > { deadCount } dead < / span >
< / span >
}
/ >
< StatCard
label = "Applications"
value = { String ( groups . length ) }
accent = "running"
detail = {
< span className = { styles . breakdown } >
< span className = { styles . bpLive } > < StatusDot variant = "live" / > { groups . filter ( ( g ) = > g . deadCount === 0 && g . staleCount === 0 ) . length } healthy < / span >
< span className = { styles . bpStale } > < StatusDot variant = "stale" / > { groups . filter ( ( g ) = > g . staleCount > 0 && g . deadCount === 0 ) . length } degraded < / span >
< span className = { styles . bpDead } > < StatusDot variant = "dead" / > { groups . filter ( ( g ) = > g . deadCount > 0 ) . length } critical < / span >
< / span >
}
/ >
< StatCard
label = "Active Routes"
value = { < span className = { styles [ totalActiveRoutes === 0 ? 'routesError' : totalActiveRoutes < totalRoutes ? 'routesWarning' : 'routesSuccess' ] } > { totalActiveRoutes } / { totalRoutes } < / span > }
accent = { totalActiveRoutes === 0 ? 'error' : totalActiveRoutes < totalRoutes ? 'warning' : 'success' }
detail = { totalActiveRoutes < totalRoutes ? ` ${ totalRoutes - totalActiveRoutes } suspended ` : 'all routes active' }
/ >
< StatCard
label = "Total TPS"
value = { totalTps . toFixed ( 1 ) }
accent = "amber"
detail = "msg/s"
trend = "up"
trendValue = "4.2%"
/ >
< StatCard
label = "Dead"
value = { String ( deadCount ) }
accent = { deadCount > 0 ? 'error' : 'success' }
detail = { deadCount > 0 ? 'requires attention' : 'all healthy' }
/ >
2026-03-18 18:22:14 +01:00
< / div >
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
2026-03-18 18:22:14 +01:00
{ /* Scope breadcrumb trail */ }
{ scope . level !== 'all' && (
< div className = { styles . scopeTrail } >
< Link to = "/agents" className = { styles . scopeLink } > All Agents < / Link >
< span className = { styles . scopeSep } > & # 9656 ; < / span >
2026-03-19 10:23:13 +01:00
< span className = { styles . scopeCurrent } > { scope . appId } < / span >
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
< / div >
2026-03-18 18:22:14 +01:00
) }
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
{ /* Section header */ }
< div className = { styles . sectionHeaderRow } >
2026-03-18 18:22:14 +01:00
< span className = { styles . sectionTitle } >
2026-03-19 10:23:13 +01:00
{ scope . level === 'all' ? 'Agents' : scope . appId }
2026-03-18 18:22:14 +01:00
< / span >
2026-03-18 19:11:58 +01:00
< Badge
label = { ` ${ liveCount } / ${ totalInstances } live ` }
color = { deadCount > 0 ? 'error' : staleCount > 0 ? 'warning' : 'success' }
variant = "filled"
/ >
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
< / div >
2026-03-18 18:22:14 +01:00
{ /* Group cards grid */ }
< div className = { isFullWidth ? styles.groupGridSingle : styles.groupGrid } >
{ groups . map ( ( group ) = > (
< GroupCard
key = { group . appId }
title = { group . appId }
accent = { appHealth ( group ) }
headerRight = {
2026-03-18 19:11:58 +01:00
< Badge
label = { ` ${ group . liveCount } / ${ group . instances . length } LIVE ` }
color = { appHealth ( group ) }
variant = "filled"
/ >
2026-03-18 18:22:14 +01:00
}
meta = {
< div className = { styles . groupMeta } >
< span > < strong > { group . totalTps . toFixed ( 1 ) } < / strong > msg / s < / span >
< span > < strong > { group . totalActiveRoutes } < / strong > / { group . totalRoutes } routes < / span >
< span >
< StatusDot variant = { appHealth ( group ) === 'success' ? 'live' : appHealth ( group ) === 'warning' ? 'stale' : 'dead' } / >
< / span >
< / div >
}
footer = { group . deadCount > 0 ? (
< div className = { styles . alertBanner } >
< span className = { styles . alertIcon } > & # 9888 ; < / span >
< span > Single point of failure — { group . deadCount === group . instances . length ? 'no redundancy' : ` ${ group . deadCount } dead instance ${ group . deadCount > 1 ? 's' : '' } ` } < / span >
< / div >
) : undefined }
>
2026-03-18 19:11:58 +01:00
< table className = { styles . instanceTable } >
< thead >
< tr >
< th className = { styles . thStatus } / >
< th > Instance < / th >
< th > State < / th >
< th > Uptime < / th >
< th > TPS < / th >
< th > Errors < / th >
< th > Heartbeat < / th >
< / tr >
< / thead >
< tbody >
{ group . instances . map ( ( inst ) = > (
2026-03-19 10:23:13 +01:00
< tr
key = { inst . id }
className = { [
styles . instanceRow ,
selectedInstance ? . id === inst . id && panelOpen ? styles . instanceRowActive : '' ,
] . filter ( Boolean ) . join ( ' ' ) }
onClick = { ( ) = > handleInstanceClick ( inst ) }
>
< td className = { styles . tdStatus } >
< StatusDot variant = { inst . status === 'live' ? 'live' : inst . status === 'stale' ? 'stale' : 'dead' } / >
< / td >
< td >
< MonoText size = "sm" className = { styles . instanceName } > { inst . name } < / MonoText >
< / td >
< td >
< Badge
label = { inst . status . toUpperCase ( ) }
color = { inst . status === 'live' ? 'success' : inst . status === 'stale' ? 'warning' : 'error' }
variant = "filled"
/ >
< / td >
< td >
< MonoText size = "xs" className = { styles . instanceMeta } > { inst . uptime } < / MonoText >
< / td >
< td >
< MonoText size = "xs" className = { styles . instanceMeta } > { inst . tps . toFixed ( 1 ) } / s < / MonoText >
< / td >
< td >
< MonoText size = "xs" className = { inst . errorRate ? styles.instanceError : styles.instanceMeta } >
{ inst . errorRate ? ? '0 err/h' }
< / MonoText >
< / td >
< td >
< MonoText size = "xs" className = {
inst . status === 'dead' ? styles . instanceHeartbeatDead :
inst . status === 'stale' ? styles . instanceHeartbeatStale :
styles . instanceMeta
} >
{ inst . lastSeen }
< / MonoText >
< / td >
< / tr >
2026-03-18 19:11:58 +01:00
) ) }
< / tbody >
< / table >
2026-03-18 18:22:14 +01:00
< / GroupCard >
) ) }
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
< / div >
2026-03-18 18:22:14 +01:00
{ /* EventFeed */ }
{ filteredEvents . length > 0 && (
2026-03-18 19:11:58 +01:00
< div className = { styles . eventCard } >
< div className = { styles . eventCardHeader } >
2026-03-18 18:22:14 +01:00
< span className = { styles . sectionTitle } > Timeline < / span >
2026-03-18 19:11:58 +01:00
< span className = { styles . sectionMeta } > { filteredEvents . length } events < / span >
2026-03-18 18:22:14 +01:00
< / div >
< EventFeed events = { filteredEvents } / >
< / div >
) }
feat: ExchangeDetail and AgentHealth pages
ExchangeDetail (/exchanges/:id): exchange header card with ID/route/
status/duration, ProcessorTimeline for the specific exchange, step-by-
step exchange inspector using Collapsible+CodeBlock for headers/body at
each processor step, and error details block for failed exchanges.
AgentHealth (/agents): 6-card system overview strip, 2-column grid of
agent cards (StatusDot, name, version, tps, uptime, last-seen, CPU/mem
usage, active routes), expandable per-agent LineCharts for throughput
and error rate trends. Both pages use AppShell + shared Sidebar layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:22:11 +01:00
< / div >
< / AppShell >
)
}