import { useMemo } from 'react' import { useParams, Link } from 'react-router-dom' import { ChevronRight } from 'lucide-react' import styles from './AgentInstance.module.css' // Layout import { TopBar } from '../../design-system/layout/TopBar/TopBar' // Composites import { LineChart } from '../../design-system/composites/LineChart/LineChart' import { AreaChart } from '../../design-system/composites/AreaChart/AreaChart' import { EventFeed } from '../../design-system/composites/EventFeed/EventFeed' import { Tabs } from '../../design-system/composites/Tabs/Tabs' import { LogViewer } from '../../design-system/composites/LogViewer/LogViewer' import type { LogEntry } from '../../design-system/composites/LogViewer/LogViewer' // 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' import { SectionHeader } from '../../design-system/primitives/SectionHeader/SectionHeader' // Global filters import { useGlobalFilters } from '../../design-system/providers/GlobalFilterProvider' // Data import { agents } from '../../mocks/agents' import { agentEvents } from '../../mocks/agentEvents' import { useState } from 'react' // ── Mock trend data ────────────────────────────────────────────────────────── function buildTimeSeries(baseValue: number, variance: number, points = 30) { const now = Date.now() const interval = (6 * 60 * 60 * 1000) / points return Array.from({ length: points }, (_, i) => ({ x: new Date(now - (points - i) * interval), y: Math.max(0, baseValue + (Math.random() - 0.5) * variance * 2), })) } function buildMemoryHistory(currentPct: number) { return [ { label: 'Heap Used', data: buildTimeSeries(currentPct * 0.7, 10) }, { label: 'Heap Total', data: buildTimeSeries(currentPct * 0.9, 5) }, ] } // ── Mock log entries ───────────────────────────────────────────────────────── function buildLogEntries(agentName: string): LogEntry[] { const now = Date.now() const MIN = 60_000 return [ { timestamp: new Date(now - 1 * MIN).toISOString(), level: 'info', message: `[o.a.c.impl.DefaultCamelContext] Route order-validation started and consuming from: direct:validate` }, { timestamp: new Date(now - 2 * MIN).toISOString(), level: 'info', message: `[o.a.c.impl.DefaultCamelContext] Total 3 routes, of which 3 are started` }, { timestamp: new Date(now - 5 * MIN).toISOString(), level: 'warn', message: `[o.a.c.processor.errorhandler] Failed delivery for exchangeId: ID-${agentName}-1710847200000-0-1. Exhausted after 3 attempts.` }, { timestamp: new Date(now - 8 * MIN).toISOString(), level: 'info', message: `[o.a.c.health.HealthCheckHelper] Health check [routes] is UP` }, { timestamp: new Date(now - 12 * MIN).toISOString(), level: 'info', message: `[o.a.c.health.HealthCheckHelper] Health check [consumers] is UP` }, { timestamp: new Date(now - 15 * MIN).toISOString(), level: 'debug', message: `[o.a.c.component.kafka] KafkaConsumer[order-events] poll returned 42 records in 18ms` }, { timestamp: new Date(now - 18 * MIN).toISOString(), level: 'info', message: `[o.a.c.impl.engine.InternalRouteStartup] Route order-enrichment started and consuming from: kafka:order-events` }, { timestamp: new Date(now - 25 * MIN).toISOString(), level: 'warn', message: `[o.a.c.component.http] HTTP endpoint https://payment-api.internal/verify returned 503 — will retry` }, { timestamp: new Date(now - 30 * MIN).toISOString(), level: 'info', message: `[o.a.c.impl.DefaultCamelContext] Apache Camel ${agentName} (CamelContext) is starting` }, { timestamp: new Date(now - 32 * MIN).toISOString(), level: 'info', message: `[org.springframework.boot] Started ${agentName} in 4.231 seconds (process running for 4.892)` }, ] } // ── Mock JVM / process info ────────────────────────────────────────────────── function buildProcessInfo(agent: typeof agents[0]) { return { jvmVersion: 'OpenJDK 21.0.2+13', camelVersion: '4.4.0', springBootVersion: '3.2.4', pid: Math.floor(1000 + Math.random() * 90000), startTime: new Date(Date.now() - parseDuration(agent.uptime)).toISOString(), heapMax: '512 MB', heapUsed: `${Math.round(512 * agent.memoryUsagePct / 100)} MB`, nonHeapUsed: `${Math.round(80 + Math.random() * 40)} MB`, threadCount: Math.floor(20 + Math.random() * 30), peakThreads: Math.floor(45 + Math.random() * 20), gcCollections: Math.floor(Math.random() * 500), gcPauseTotal: `${(Math.random() * 2).toFixed(2)}s`, classesLoaded: Math.floor(8000 + Math.random() * 4000), openFileDescriptors: Math.floor(50 + Math.random() * 200), maxFileDescriptors: 65536, } } function parseDuration(s: string): number { let ms = 0 const dMatch = s.match(/(\d+)d/) const hMatch = s.match(/(\d+)h/) const mMatch = s.match(/(\d+)m/) if (dMatch) ms += parseInt(dMatch[1]) * 86400000 if (hMatch) ms += parseInt(hMatch[1]) * 3600000 if (mMatch) ms += parseInt(mMatch[1]) * 60000 return ms || 60000 } // ── Component ──────────────────────────────────────────────────────────────── const LOG_TABS = [ { label: 'All', value: 'all' }, { label: 'Warnings', value: 'warn' }, { label: 'Errors', value: 'error' }, ] export function AgentInstance() { const { appId, instanceId } = useParams<{ appId: string; instanceId: string }>() const { isInTimeRange } = useGlobalFilters() const [logFilter, setLogFilter] = useState('all') const agent = agents.find((a) => a.appId === appId && a.id === instanceId) const instanceEvents = useMemo(() => { if (!agent) return [] return agentEvents .filter((e) => e.searchText?.toLowerCase().includes(agent.name.toLowerCase())) .filter((e) => isInTimeRange(e.timestamp)) }, [agent, isInTimeRange]) if (!agent) { return ( <>
Agent instance not found.
) } const processInfo = buildProcessInfo(agent) const logEntries = buildLogEntries(agent.name) const filteredLogs = logFilter === 'all' ? logEntries : logEntries.filter((l) => l.level === logFilter) const cpuData = buildTimeSeries(agent.cpuUsagePct, 15) const memSeries = buildMemoryHistory(agent.memoryUsagePct) const tpsSeries = [{ label: 'Throughput', data: buildTimeSeries(agent.tps, 5) }] const errorSeries = [{ label: 'Errors', data: buildTimeSeries(agent.errorRate ? parseFloat(agent.errorRate) : 0.2, 2), color: 'var(--error)' }] const threadSeries = [{ label: 'Threads', data: buildTimeSeries(processInfo.threadCount, 8) }] const gcSeries = [{ label: 'GC Pause', data: buildTimeSeries(4, 6) }] const statusVariant = agent.status === 'live' ? 'live' : agent.status === 'stale' ? 'stale' : 'dead' const statusColor = agent.status === 'live' ? 'success' : agent.status === 'stale' ? 'warning' : 'error' return ( <>
{/* Stat strip — 5 columns matching /agents */}
85 ? 'error' : agent.cpuUsagePct > 70 ? 'warning' : 'success'} /> 85 ? 'error' : agent.memoryUsagePct > 70 ? 'warning' : 'success'} detail={`${processInfo.heapUsed} / ${processInfo.heapMax}`} />
{/* Scope trail + badges */}
All Agents {appId} {agent.name}
{/* Process info card — right below stat strip */}
Process Information
JVM {processInfo.jvmVersion} Camel {processInfo.camelVersion} Spring Boot {processInfo.springBootVersion} Started {new Date(processInfo.startTime).toLocaleString()} File Descriptors {processInfo.openFileDescriptors} / {processInfo.maxFileDescriptors.toLocaleString()}
{/* Charts grid — 3x2 (CPU, Memory, Throughput, Errors, Threads, GC) */}
CPU Usage {agent.cpuUsagePct}% current
Memory (Heap) {processInfo.heapUsed} / {processInfo.heapMax}
Throughput {agent.tps.toFixed(1)} msg/s
Error Rate {agent.errorRate ?? '0 err/h'}
Thread Count {processInfo.threadCount} active
GC Pauses {processInfo.gcPauseTotal} total
{/* Log + Timeline side by side */}
{/* Log viewer */}
Application Log
{/* Timeline */}
Timeline {instanceEvents.length} events
{instanceEvents.length > 0 ? ( ) : (
No events in the selected time range.
)}
) }