import { useMemo, useState } from 'react'; import { useParams, Link } from 'react-router'; import { StatCard, StatusDot, Badge, LineChart, AreaChart, BarChart, EventFeed, Spinner, EmptyState, SectionHeader, MonoText, LogViewer, Tabs, useGlobalFilters, } from '@cameleer/design-system'; import type { FeedEvent, LogEntry } from '@cameleer/design-system'; import styles from './AgentInstance.module.css'; import { useAgents, useAgentEvents } from '../../api/queries/agents'; import { useStatsTimeseries } from '../../api/queries/executions'; import { useAgentMetrics } from '../../api/queries/agent-metrics'; const LOG_TABS = [ { label: 'All', value: 'all' }, { label: 'Warnings', value: 'warn' }, { label: 'Errors', value: 'error' }, ]; export default function AgentInstance() { const { appId, instanceId } = useParams(); const { timeRange } = useGlobalFilters(); const [logFilter, setLogFilter] = useState('all'); const timeFrom = timeRange.start.toISOString(); const timeTo = timeRange.end.toISOString(); const { data: agents, isLoading } = useAgents(undefined, appId); const { data: events } = useAgentEvents(appId, instanceId); const { data: timeseries } = useStatsTimeseries(timeFrom, timeTo, undefined, appId); const agent = useMemo( () => (agents || []).find((a: any) => a.id === instanceId) as any, [agents, instanceId], ); // Stat card metrics (latest 1 bucket) const { data: latestMetrics } = useAgentMetrics( agent?.id || null, ['jvm.cpu.process', 'jvm.memory.heap.used', 'jvm.memory.heap.max'], 1, ); const cpuPct = latestMetrics?.metrics?.['jvm.cpu.process']?.[0]?.value; const heapUsed = latestMetrics?.metrics?.['jvm.memory.heap.used']?.[0]?.value; const heapMax = latestMetrics?.metrics?.['jvm.memory.heap.max']?.[0]?.value; const memPct = heapMax ? (heapUsed! / heapMax) * 100 : undefined; // Chart metrics (60 buckets) const { data: jvmMetrics } = useAgentMetrics( agent?.id || null, ['jvm.cpu.process', 'jvm.memory.heap.used', 'jvm.memory.heap.max', 'jvm.threads.count', 'jvm.gc.time'], 60, ); const chartData = useMemo( () => (timeseries?.buckets || []).map((b: any) => ({ time: new Date(b.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), throughput: b.totalCount, latency: b.avgDurationMs, errors: b.failedCount, })), [timeseries], ); const feedEvents = useMemo( () => (events || []) .filter((e: any) => !instanceId || e.agentId === instanceId) .map((e: any) => ({ id: String(e.id), severity: e.eventType === 'WENT_DEAD' ? ('error' as const) : e.eventType === 'WENT_STALE' ? ('warning' as const) : e.eventType === 'RECOVERED' ? ('success' as const) : ('running' as const), message: `${e.eventType}${e.detail ? ' \u2014 ' + e.detail : ''}`, timestamp: new Date(e.timestamp), })), [events, instanceId], ); // JVM chart series helpers const cpuSeries = useMemo(() => { const pts = jvmMetrics?.metrics?.['jvm.cpu.process']; if (!pts?.length) return null; return [{ label: 'CPU %', data: pts.map((p: any, i: number) => ({ x: i, y: p.value * 100 })) }]; }, [jvmMetrics]); const heapSeries = useMemo(() => { const pts = jvmMetrics?.metrics?.['jvm.memory.heap.used']; if (!pts?.length) return null; return [{ label: 'Heap MB', data: pts.map((p: any, i: number) => ({ x: i, y: p.value / (1024 * 1024) })) }]; }, [jvmMetrics]); const threadSeries = useMemo(() => { const pts = jvmMetrics?.metrics?.['jvm.threads.count']; if (!pts?.length) return null; return [{ label: 'Threads', data: pts.map((p: any, i: number) => ({ x: i, y: p.value })) }]; }, [jvmMetrics]); const gcSeries = useMemo(() => { const pts = jvmMetrics?.metrics?.['jvm.gc.time']; if (!pts?.length) return null; return [{ label: 'GC ms', data: pts.map((p: any) => ({ x: String(p.time ?? ''), y: p.value })) }]; }, [jvmMetrics]); const throughputSeries = useMemo( () => chartData.length ? [{ label: 'Throughput', data: chartData.map((d: any, i: number) => ({ x: i, y: d.throughput })) }] : null, [chartData], ); const errorSeries = useMemo( () => chartData.length ? [{ label: 'Errors', data: chartData.map((d: any, i: number) => ({ x: i, y: d.errors })) }] : null, [chartData], ); // Placeholder log entries (backend does not stream logs yet) const logEntries = useMemo(() => [], []); const filteredLogs = logFilter === 'all' ? logEntries : logEntries.filter((l) => l.level === logFilter); if (isLoading) return ; const statusVariant = agent?.status === 'LIVE' ? 'live' : agent?.status === 'STALE' ? 'stale' : 'dead'; const statusColor: 'success' | 'warning' | 'error' = agent?.status === 'LIVE' ? 'success' : agent?.status === 'STALE' ? 'warning' : 'error'; const cpuDisplay = cpuPct != null ? (cpuPct * 100).toFixed(0) : null; const heapUsedMB = heapUsed != null ? (heapUsed / (1024 * 1024)).toFixed(0) : null; const heapMaxMB = heapMax != null ? (heapMax / (1024 * 1024)).toFixed(0) : null; return (
{/* Stat strip — 5 columns */}
85 ? 'error' : Number(cpuDisplay) > 70 ? 'warning' : 'success' : undefined } /> 85 ? 'error' : memPct > 70 ? 'warning' : 'success' : undefined } detail={ heapUsedMB != null && heapMaxMB != null ? `${heapUsedMB} MB / ${heapMaxMB} MB` : undefined } /> 0 ? 'error' : 'success'} />
{/* Scope trail + badges */} {agent && ( <>
All Agents {appId} {agent.name} {agent.version && }
{/* Process info card */}
Process Information
{agent.capabilities?.jvmVersion && ( <> JVM {agent.capabilities.jvmVersion} )} {agent.capabilities?.camelVersion && ( <> Camel {agent.capabilities.camelVersion} )} {agent.capabilities?.springBootVersion && ( <> Spring Boot {agent.capabilities.springBootVersion} )} Started {agent.registeredAt ? new Date(agent.registeredAt).toLocaleString() : '\u2014'} {agent.capabilities && ( <> Capabilities {Object.entries(agent.capabilities) .filter(([, v]) => typeof v === 'boolean' && v) .map(([k]) => ( ))} )}
{/* Routes */} {(agent.routeIds?.length ?? 0) > 0 && ( <> Routes
{(agent.routeIds || []).map((r: string) => ( ))}
)} )} {/* Charts grid — 3x2 */}
CPU Usage {cpuDisplay != null ? `${cpuDisplay}% current` : ''}
{cpuSeries ? ( ) : ( )}
Memory (Heap) {heapUsedMB != null && heapMaxMB != null ? `${heapUsedMB} MB / ${heapMaxMB} MB` : ''}
{heapSeries ? ( ) : ( )}
Throughput {agent?.tps != null ? `${agent.tps.toFixed(1)} msg/s` : ''}
{throughputSeries ? ( ) : ( )}
Error Rate {agent?.errorRate != null ? `${(agent.errorRate * 100).toFixed(1)}%` : ''}
{errorSeries ? ( ) : ( )}
Thread Count {threadSeries ? `${threadSeries[0].data[threadSeries[0].data.length - 1]?.y.toFixed(0)} active` : ''}
{threadSeries ? ( ) : ( )}
GC Pauses
{gcSeries ? ( ) : ( )}
{/* Log + Timeline side by side */}
Application Log
{filteredLogs.length > 0 ? ( ) : (
Application log streaming is not yet available.
)}
Timeline {feedEvents.length} events
{feedEvents.length > 0 ? ( ) : (
No events in the selected time range.
)}
); } function formatUptime(seconds?: number): string { if (!seconds) return '\u2014'; const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); const mins = Math.floor((seconds % 3600) / 60); if (days > 0) return `${days}d ${hours}h`; if (hours > 0) return `${hours}h ${mins}m`; return `${mins}m`; }