From 83caf4be5b9abe5d4d7f30561c6aa54fbc0043be Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Mon, 23 Mar 2026 18:29:25 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20align=20Agent=20Instance=20with=20mock?= =?UTF-8?q?=20=E2=80=94=20JVM=20charts,=20process=20info,=20stat=20cards,?= =?UTF-8?q?=20log=20placeholder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- .../AgentInstance/AgentInstance.module.css | 40 +++- ui/src/pages/AgentInstance/AgentInstance.tsx | 192 ++++++++++++++---- 2 files changed, 182 insertions(+), 50 deletions(-) diff --git a/ui/src/pages/AgentInstance/AgentInstance.module.css b/ui/src/pages/AgentInstance/AgentInstance.module.css index 3008d533..bcf68065 100644 --- a/ui/src/pages/AgentInstance/AgentInstance.module.css +++ b/ui/src/pages/AgentInstance/AgentInstance.module.css @@ -1,6 +1,6 @@ .statStrip { display: grid; - grid-template-columns: repeat(4, 1fr); + grid-template-columns: repeat(5, 1fr); gap: 10px; margin-bottom: 16px; } @@ -26,7 +26,7 @@ .chartsGrid { display: grid; - grid-template-columns: 1fr 1fr; + grid-template-columns: repeat(3, 1fr); gap: 14px; margin-bottom: 20px; } @@ -84,9 +84,35 @@ } .infoCard { - background: var(--bg-surface); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-card); - padding: 16px; + margin-bottom: 20px; +} + +.infoGrid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px 16px; + font-size: 13px; +} + +.infoLabel { + font-weight: 700; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.6px; + color: var(--text-muted); + display: block; + margin-bottom: 2px; +} + +.capTags { + display: flex; + gap: 4px; + flex-wrap: wrap; +} + +.paneTitle { + font-size: 13px; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 12px; } diff --git a/ui/src/pages/AgentInstance/AgentInstance.tsx b/ui/src/pages/AgentInstance/AgentInstance.tsx index 4511d2a5..d20ea927 100644 --- a/ui/src/pages/AgentInstance/AgentInstance.tsx +++ b/ui/src/pages/AgentInstance/AgentInstance.tsx @@ -1,13 +1,13 @@ import { useMemo } from 'react'; import { useParams } from 'react-router'; import { - StatCard, StatusDot, Badge, - LineChart, AreaChart, EventFeed, Breadcrumb, Spinner, - CodeBlock, + StatCard, StatusDot, Badge, Card, + LineChart, AreaChart, BarChart, EventFeed, Breadcrumb, Spinner, EmptyState, } 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'; import { useGlobalFilters } from '@cameleer/design-system'; export default function AgentInstance() { @@ -21,10 +21,28 @@ export default function AgentInstance() { const { data: timeseries } = useStatsTimeseries(timeFrom, timeTo, undefined, appId); const agent = useMemo(() => - (agents || []).find((a: any) => a.id === instanceId), + (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' }), @@ -48,6 +66,41 @@ export default function AgentInstance() { [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, i: number) => ({ x: String(i), 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], + ); + if (isLoading) return ; return ( @@ -64,15 +117,55 @@ export default function AgentInstance() {

{agent.name}

+ {agent.version && }
- - 0.05 ? 'error' : undefined} /> - - + + + + 0 ? 'error' : undefined} /> +
+ +
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() : '—'} +
+
+ Capabilities + + {Object.entries(agent?.capabilities || {}) + .filter(([, v]) => typeof v === 'boolean' && v) + .map(([k]) => ( + + ))} + +
+
+
+
Routes
{(agent.routeIds || []).map((r: string) => ( @@ -82,21 +175,45 @@ export default function AgentInstance() { )} - {chartData.length > 0 && ( - <> -
Performance
-
-
-
Throughput
- ({ x: i, y: d.throughput })) }]} height={200} /> -
-
-
Latency
- ({ x: i, y: d.latency })) }]} height={200} /> -
-
- - )} +
Performance
+
+
+
CPU Usage
+ {cpuSeries + ? + : } +
+
+
Memory Heap
+ {heapSeries + ? + : } +
+
+
Throughput
+ {throughputSeries + ? + : } +
+
+
Error Rate
+ {errorSeries + ? + : } +
+
+
Thread Count
+ {threadSeries + ? + : } +
+
+
GC Pauses
+ {gcSeries + ? + : } +
+
{feedEvents.length > 0 && (
@@ -105,28 +222,17 @@ export default function AgentInstance() {
)} - {agent && ( - <> -
Agent Info
-
- -
- - )} +
); } -function formatUptime(seconds: number): string { - if (seconds < 60) return `${seconds}s`; - if (seconds < 3600) return `${Math.floor(seconds / 60)}m`; - if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`; - return `${Math.floor(seconds / 86400)}d ${Math.floor((seconds % 86400) / 3600)}h`; +function formatUptime(seconds?: number): string { + if (!seconds) return '—'; + 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`; }