import { useMemo } from 'react' import { useParams, useNavigate, Link } from 'react-router-dom' 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 import { GroupCard } from '../../design-system/composites/GroupCard/GroupCard' import { LineChart } from '../../design-system/composites/LineChart/LineChart' import { EventFeed } from '../../design-system/composites/EventFeed/EventFeed' // 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' import { StatCard } from '../../design-system/primitives/StatCard/StatCard' // Mock data import { agents, type AgentHealth as AgentHealthData } from '../../mocks/agents' import { SIDEBAR_APPS } from '../../mocks/sidebar' import { agentEvents } from '../../mocks/agentEvents' // ── URL scope parsing ──────────────────────────────────────────────────────── type Scope = | { level: 'all' } | { level: 'app'; appId: string } | { level: 'instance'; appId: string; instanceId: string } function useScope(): Scope { const { '*': rest } = useParams() const segments = rest?.split('/').filter(Boolean) ?? [] if (segments.length >= 2) return { level: 'instance', appId: segments[0], instanceId: segments[1] } if (segments.length === 1) return { level: 'app', appId: segments[0] } 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() for (const a of agentList) { const list = map.get(a.appId) ?? [] list.push(a) map.set(a.appId, list) } 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) ──────────────────────────────────────────────────────── function buildTrendData(agent: AgentHealthData) { const now = Date.now() const points = 20 const interval = (3 * 60 * 60 * 1000) / points 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), })) 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), })) return { throughput, errorRate } } // ── Breadcrumb ─────────────────────────────────────────────────────────────── function buildBreadcrumb(scope: Scope) { const crumbs: { label: string; href?: string }[] = [ { label: 'Applications', href: '/apps' }, { label: 'Agents', href: '/agents' }, ] if (scope.level === 'app' || scope.level === 'instance') { crumbs.push({ label: scope.appId, href: `/agents/${scope.appId}` }) } if (scope.level === 'instance') { crumbs.push({ label: scope.instanceId }) } return crumbs } // ── AgentHealth page ───────────────────────────────────────────────────────── export function AgentHealth() { const scope = useScope() const navigate = useNavigate() // Filter agents by scope const filteredAgents = useMemo(() => { if (scope.level === 'all') return agents if (scope.level === 'app') return agents.filter((a) => a.appId === scope.appId) return agents.filter((a) => a.appId === scope.appId && a.id === scope.instanceId) }, [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) // Events are a global timeline feed — show all regardless of scope const filteredEvents = agentEvents // Single instance for expanded charts const singleInstance = scope.level === 'instance' ? filteredAgents[0] : null const trendData = singleInstance ? buildTrendData(singleInstance) : null const isFullWidth = scope.level !== 'all' return ( }>
{/* Stat strip */}
0 ? 'warning' : undefined} /> 0 ? 'error' : undefined} />
{/* Scope breadcrumb trail */} {scope.level !== 'all' && (
All Agents {scope.level === 'instance' && ( <> {scope.appId} )} {scope.level === 'app' ? scope.appId : scope.instanceId}
)} {/* Section header */}
{scope.level === 'all' ? 'Agents' : scope.level === 'app' ? scope.appId : scope.instanceId} 0 ? 'error' : staleCount > 0 ? 'warning' : 'success'} variant="filled" />
{/* Group cards grid */}
{groups.map((group) => ( } meta={
{group.totalTps.toFixed(1)} msg/s {group.totalActiveRoutes}/{group.totalRoutes} routes
} footer={group.deadCount > 0 ? (
Single point of failure — {group.deadCount === group.instances.length ? 'no redundancy' : `${group.deadCount} dead instance${group.deadCount > 1 ? 's' : ''}`}
) : undefined} > {group.instances.map((inst) => ( <> navigate(`/agents/${inst.appId}/${inst.id}`)} > {/* Expanded charts for single instance */} {singleInstance?.id === inst.id && trendData && ( )} ))}
Instance State Uptime TPS Errors Heartbeat
{inst.name} {inst.uptime} {inst.tps.toFixed(1)}/s {inst.errorRate ?? '0 err/h'} {inst.lastSeen}
Throughput (msg/s)
Error Rate (err/h)
))}
{/* EventFeed */} {filteredEvents.length > 0 && (
Timeline {filteredEvents.length} events
)}
) }