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