From dd454547c7939ea8f72ed46a122a01e3919bf63c Mon Sep 17 00:00:00 2001 From: Sergio Arroutbi Date: Wed, 13 May 2026 18:35:20 +0200 Subject: [PATCH] Redesign Performance KPI cards to use backend summary endpoint Replace CPU/Memory/Queue/Attestation cards with actionable metrics from GET /api/performance/summary: Verifier Status (reachable with latency), Circuit Breaker state (color-coded), Attestation Rate, and Capacity utilization with warning/danger thresholds. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Sergio Arroutbi --- src/api/__tests__/performance.test.ts | 6 +- src/api/performance.ts | 6 +- src/pages/Performance/Performance.tsx | 47 +++++++++++---- .../__tests__/Performance.test.tsx | 60 +++++++++++-------- src/types/index.ts | 15 ++--- 5 files changed, 84 insertions(+), 50 deletions(-) diff --git a/src/api/__tests__/performance.test.ts b/src/api/__tests__/performance.test.ts index bdffda3..76971c2 100644 --- a/src/api/__tests__/performance.test.ts +++ b/src/api/__tests__/performance.test.ts @@ -13,9 +13,9 @@ import { performanceApi } from '../performance'; beforeEach(() => vi.clearAllMocks()); describe('performanceApi', () => { - it('system calls GET /system/performance', async () => { - await performanceApi.system(); - expect(mockGet).toHaveBeenCalledWith('/system/performance'); + it('summary calls GET /performance/summary', async () => { + await performanceApi.summary(); + expect(mockGet).toHaveBeenCalledWith('/performance/summary'); }); it('database calls GET /system/database', async () => { diff --git a/src/api/performance.ts b/src/api/performance.ts index 2708157..e05feab 100644 --- a/src/api/performance.ts +++ b/src/api/performance.ts @@ -1,9 +1,9 @@ import apiClient from './client'; -import type { SystemPerformance, IntegrationService } from '@/types'; +import type { PerformanceSummary, IntegrationService } from '@/types'; export const performanceApi = { - system() { - return apiClient.get('/system/performance'); + summary() { + return apiClient.get('/performance/summary'); }, database() { diff --git a/src/pages/Performance/Performance.tsx b/src/pages/Performance/Performance.tsx index 3f8a15e..84171b4 100644 --- a/src/pages/Performance/Performance.tsx +++ b/src/pages/Performance/Performance.tsx @@ -2,10 +2,30 @@ import { useQuery } from '@tanstack/react-query'; import { KpiCard } from '@/components/common/KpiCard'; import { performanceApi } from '@/api/performance'; +const CIRCUIT_BREAKER_LABELS: Record = { + closed: 'Closed', + open: 'Open', + half_open: 'Half-Open', +}; + +type CbVariant = 'success' | 'danger' | 'warning'; + +const CIRCUIT_BREAKER_VARIANTS: Record = { + closed: 'success', + open: 'danger', + half_open: 'warning', +}; + +function capacityVariant(pct: number): 'default' | 'warning' | 'danger' { + if (pct > 90) return 'danger'; + if (pct > 70) return 'warning'; + return 'default'; +} + export function Performance() { const { data: perf } = useQuery({ - queryKey: ['system', 'performance'], - queryFn: () => performanceApi.system(), + queryKey: ['performance', 'summary'], + queryFn: () => performanceApi.summary(), select: (res) => res.data, }); @@ -18,24 +38,25 @@ export function Performance() {
80 ? 'danger' : 'default'} + title="Verifier Status" + value={perf ? (perf.verifier_reachable ? 'Reachable' : 'Unreachable') : '--'} + subtitle={perf?.verifier_latency_ms != null ? `${perf.verifier_latency_ms} ms` : undefined} + variant={perf ? (perf.verifier_reachable ? 'success' : 'danger') : 'default'} /> 80 ? 'danger' : 'default'} + title="Circuit Breaker" + value={perf ? (CIRCUIT_BREAKER_LABELS[perf.circuit_breaker_state] ?? perf.circuit_breaker_state) : '--'} + variant={perf ? (CIRCUIT_BREAKER_VARIANTS[perf.circuit_breaker_state] ?? 'default') : 'default'} /> 100 ? 'warning' : 'default'} + title="Capacity" + value={perf?.capacity_utilization_pct != null ? `${perf.capacity_utilization_pct.toFixed(1)}%` : '--'} + variant={perf?.capacity_utilization_pct != null ? capacityVariant(perf.capacity_utilization_pct) : 'default'} />
diff --git a/src/pages/Performance/__tests__/Performance.test.tsx b/src/pages/Performance/__tests__/Performance.test.tsx index 4f3adab..2ada288 100644 --- a/src/pages/Performance/__tests__/Performance.test.tsx +++ b/src/pages/Performance/__tests__/Performance.test.tsx @@ -6,12 +6,15 @@ import { Performance } from '../Performance'; vi.mock('@/api/performance', () => ({ performanceApi: { - system: vi.fn().mockResolvedValue({ + summary: vi.fn().mockResolvedValue({ data: { - cpu_percent: 45.2, - memory_percent: 62.8, - attestations_per_sec: 120, - queue_depth: 15, + verifier_reachable: true, + verifier_latency_ms: 42, + circuit_breaker_state: 'closed', + agent_count: 8, + estimated_attestation_rate: 120, + capacity_utilization_pct: 55.3, + database_status: 'ok', }, }), }, @@ -37,10 +40,11 @@ describe('Performance', () => { it('renders KPI cards with performance data', async () => { renderWithProviders(); - expect(await screen.findByText('45.2%')).toBeInTheDocument(); - expect(await screen.findByText('62.8%')).toBeInTheDocument(); - expect(await screen.findByText('120')).toBeInTheDocument(); - expect(await screen.findByText('15')).toBeInTheDocument(); + expect(await screen.findByText('Reachable')).toBeInTheDocument(); + expect(await screen.findByText('42 ms')).toBeInTheDocument(); + expect(await screen.findByText('Closed')).toBeInTheDocument(); + expect(await screen.findByText('120/s')).toBeInTheDocument(); + expect(await screen.findByText('55.3%')).toBeInTheDocument(); }); it('renders placeholder sections', () => { @@ -50,37 +54,45 @@ describe('Performance', () => { expect(screen.getByText('Circuit Breaker Status')).toBeInTheDocument(); }); - it('shows danger variant for high CPU usage', async () => { + it('shows danger variant for unreachable verifier', async () => { const { performanceApi } = await import('@/api/performance'); - vi.mocked(performanceApi.system).mockResolvedValueOnce({ + vi.mocked(performanceApi.summary).mockResolvedValueOnce({ data: { - cpu_percent: 92.5, - memory_percent: 30.0, - attestations_per_sec: 50, - queue_depth: 5, + verifier_reachable: false, + verifier_latency_ms: null, + circuit_breaker_state: 'open', + agent_count: 0, + estimated_attestation_rate: null, + capacity_utilization_pct: null, + database_status: 'ok', }, } as never); renderWithProviders(); - expect(await screen.findByText('92.5%')).toBeInTheDocument(); + expect(await screen.findByText('Unreachable')).toBeInTheDocument(); + expect(await screen.findByText('Open')).toBeInTheDocument(); }); - it('shows warning variant for high queue depth', async () => { + it('shows warning variant for high capacity', async () => { const { performanceApi } = await import('@/api/performance'); - vi.mocked(performanceApi.system).mockResolvedValueOnce({ + vi.mocked(performanceApi.summary).mockResolvedValueOnce({ data: { - cpu_percent: 10.0, - memory_percent: 20.0, - attestations_per_sec: 80, - queue_depth: 150, + verifier_reachable: true, + verifier_latency_ms: 15, + circuit_breaker_state: 'half_open', + agent_count: 50, + estimated_attestation_rate: 200, + capacity_utilization_pct: 78.5, + database_status: 'ok', }, } as never); renderWithProviders(); - expect(await screen.findByText('150')).toBeInTheDocument(); + expect(await screen.findByText('Half-Open')).toBeInTheDocument(); + expect(await screen.findByText('78.5%')).toBeInTheDocument(); }); it('renders dashes when no data', async () => { const { performanceApi } = await import('@/api/performance'); - vi.mocked(performanceApi.system).mockResolvedValueOnce({ data: null } as never); + vi.mocked(performanceApi.summary).mockResolvedValueOnce({ data: null } as never); renderWithProviders(); const dashes = screen.getAllByText('--'); expect(dashes.length).toBeGreaterThanOrEqual(4); diff --git a/src/types/index.ts b/src/types/index.ts index cd3997c..3f56c7c 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -33,13 +33,14 @@ export interface IntegrationService { latency_ms?: number; } -export interface SystemPerformance { - cpu_percent: number; - memory_percent: number; - open_fds: number; - thread_count: number; - attestations_per_sec: number; - queue_depth: number; +export interface PerformanceSummary { + verifier_reachable: boolean; + verifier_latency_ms: number | null; + circuit_breaker_state: 'closed' | 'open' | 'half_open'; + agent_count: number; + estimated_attestation_rate: number | null; + capacity_utilization_pct: number | null; + database_status: string; } export interface TimeRange {