diff --git a/src/pages/Routes/Routes.module.css b/src/pages/Routes/Routes.module.css
index db6ca45..ff20a4d 100644
--- a/src/pages/Routes/Routes.module.css
+++ b/src/pages/Routes/Routes.module.css
@@ -35,176 +35,6 @@
font-family: var(--font-mono);
}
-/* KPI strip */
-.kpiStrip {
- display: grid;
- grid-template-columns: repeat(5, 1fr);
- gap: 12px;
- margin-bottom: 20px;
-}
-
-/* KPI card */
-.kpiCard {
- background: var(--bg-surface);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-lg);
- padding: 16px 18px 12px;
- box-shadow: var(--shadow-card);
- position: relative;
- overflow: hidden;
- transition: box-shadow 0.15s;
-}
-
-.kpiCard:hover {
- box-shadow: var(--shadow-md);
-}
-
-.kpiCard::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 3px;
-}
-
-.kpiCardAmber::before { background: linear-gradient(90deg, var(--amber), transparent); }
-.kpiCardGreen::before { background: linear-gradient(90deg, var(--success), transparent); }
-.kpiCardError::before { background: linear-gradient(90deg, var(--error), transparent); }
-.kpiCardTeal::before { background: linear-gradient(90deg, var(--running), transparent); }
-.kpiCardWarn::before { background: linear-gradient(90deg, var(--warning), transparent); }
-
-.kpiLabel {
- font-size: 10px;
- font-weight: 600;
- text-transform: uppercase;
- letter-spacing: 0.6px;
- color: var(--text-muted);
- margin-bottom: 6px;
-}
-
-.kpiValueRow {
- display: flex;
- align-items: baseline;
- gap: 6px;
- margin-bottom: 4px;
-}
-
-.kpiValue {
- font-family: var(--font-mono);
- font-size: 26px;
- font-weight: 600;
- line-height: 1.2;
-}
-
-.kpiValueAmber { color: var(--amber); }
-.kpiValueGreen { color: var(--success); }
-.kpiValueError { color: var(--error); }
-.kpiValueTeal { color: var(--running); }
-.kpiValueWarn { color: var(--warning); }
-
-.kpiUnit {
- font-size: 12px;
- color: var(--text-muted);
-}
-
-.kpiTrend {
- font-family: var(--font-mono);
- font-size: 11px;
- display: inline-flex;
- align-items: center;
- gap: 2px;
- margin-left: auto;
-}
-
-.trendUpGood { color: var(--success); }
-.trendUpBad { color: var(--error); }
-.trendDownGood { color: var(--success); }
-.trendDownBad { color: var(--error); }
-.trendFlat { color: var(--text-muted); }
-
-.kpiDetail {
- font-size: 11px;
- color: var(--text-muted);
- margin-top: 2px;
-}
-
-.kpiDetailStrong {
- color: var(--text-secondary);
- font-weight: 600;
-}
-
-.kpiSparkline {
- margin-top: 8px;
- height: 32px;
-}
-
-/* Latency percentiles card */
-.latencyValues {
- display: flex;
- gap: 12px;
- margin-bottom: 4px;
-}
-
-.latencyItem {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 2px;
-}
-
-.latencyLabel {
- font-size: 9px;
- font-weight: 600;
- text-transform: uppercase;
- letter-spacing: 0.5px;
- color: var(--text-muted);
-}
-
-.latencyVal {
- font-family: var(--font-mono);
- font-size: 18px;
- font-weight: 600;
- line-height: 1.2;
-}
-
-.latValGreen { color: var(--success); }
-.latValAmber { color: var(--amber); }
-.latValRed { color: var(--error); }
-
-.latencyTrend {
- font-family: var(--font-mono);
- font-size: 9px;
-}
-
-/* Active routes donut */
-.donutWrap {
- display: flex;
- align-items: center;
- gap: 10px;
- margin-top: 4px;
-}
-
-.donutLabel {
- font-family: var(--font-mono);
- font-size: 10px;
- font-weight: 600;
- color: var(--text-secondary);
-}
-
-.donutLegend {
- display: flex;
- flex-direction: column;
- gap: 2px;
- font-size: 10px;
- color: var(--text-muted);
-}
-
-.donutLegendActive {
- color: var(--running);
- font-weight: 600;
-}
-
/* Route performance table */
.tableSection {
background: var(--bg-surface);
@@ -273,24 +103,6 @@
gap: 16px;
}
-.chartCard {
- background: var(--bg-surface);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-lg);
- box-shadow: var(--shadow-card);
- padding: 16px;
- overflow: hidden;
-}
-
-.chartTitle {
- font-size: 12px;
- font-weight: 600;
- color: var(--text-secondary);
- text-transform: uppercase;
- letter-spacing: 0.5px;
- margin-bottom: 12px;
-}
-
.chart {
width: 100%;
}
diff --git a/src/pages/Routes/Routes.tsx b/src/pages/Routes/Routes.tsx
index aa08d27..b49165b 100644
--- a/src/pages/Routes/Routes.tsx
+++ b/src/pages/Routes/Routes.tsx
@@ -15,11 +15,14 @@ import { DataTable } from '../../design-system/composites/DataTable/DataTable'
import type { Column } from '../../design-system/composites/DataTable/types'
import { RouteFlow } from '../../design-system/composites/RouteFlow/RouteFlow'
import type { RouteNode } from '../../design-system/composites/RouteFlow/RouteFlow'
+import { KpiStrip } from '../../design-system/composites'
+import type { KpiItem } from '../../design-system/composites'
// Primitives
import { Sparkline } from '../../design-system/primitives/Sparkline/Sparkline'
import { MonoText } from '../../design-system/primitives/MonoText/MonoText'
import { Badge } from '../../design-system/primitives/Badge/Badge'
+import { Card } from '../../design-system/primitives'
// Mock data
import {
@@ -34,8 +37,8 @@ import { SIDEBAR_APPS, buildRouteToAppMap } from '../../mocks/sidebar'
const ROUTE_TO_APP = buildRouteToAppMap()
-// ─── KPI Header Strip (matches mock-v3-metrics-dashboard) ────────────────────
-function KpiHeader({ scopedMetrics }: { scopedMetrics: RouteMetricRow[] }) {
+// ─── Build KPI items from scoped route metrics ──────────────────────────────
+function buildKpiItems(scopedMetrics: RouteMetricRow[]): KpiItem[] {
const totalExchanges = scopedMetrics.reduce((sum, r) => sum + r.exchangeCount, 0)
const totalErrors = scopedMetrics.reduce((sum, r) => sum + r.errorCount, 0)
const errorRate = totalExchanges > 0 ? ((totalErrors / totalExchanges) * 100) : 0
@@ -45,113 +48,57 @@ function KpiHeader({ scopedMetrics }: { scopedMetrics: RouteMetricRow[] }) {
const p99Latency = scopedMetrics.length > 0
? Math.max(...scopedMetrics.map((r) => r.p99DurationMs))
: 0
- const avgSuccessRate = scopedMetrics.length > 0
- ? Number((scopedMetrics.reduce((sum, r) => sum + r.successRate, 0) / scopedMetrics.length).toFixed(1))
- : 0
const throughputPerSec = totalExchanges > 0 ? (totalExchanges / 360).toFixed(1) : '0'
const activeRoutes = scopedMetrics.length
const totalRoutes = routeMetrics.length
- return (
-
- {/* Card 1: Total Throughput */}
-
-
Total Throughput
-
- {totalExchanges.toLocaleString()}
- exchanges
- ▲ +8%
-
-
- {throughputPerSec} msg/s · Capacity 39%
-
-
-
-
-
+ const p50 = Math.round(avgLatency * 0.5)
+ const p95 = Math.round(avgLatency * 1.4)
+ const slaStatus = p99Latency > 300 ? 'BREACH' : 'OK'
- {/* Card 2: System Error Rate */}
-
-
System Error Rate
-
- {errorRate.toFixed(2)}%
-
- {errorRate < 1 ? '\u25BC -0.1%' : '\u25B2 +0.4%'}
-
-
-
- {totalErrors} errors / {totalExchanges.toLocaleString()} total (6h)
-
-
-
-
-
-
- {/* Card 3: Latency Percentiles */}
-
300 ? styles.kpiCardWarn : styles.kpiCardGreen}`}>
-
Latency Percentiles
-
-
- P50
- {Math.round(avgLatency * 0.5)}ms
- ▼3
-
-
- P95
- 150 ? styles.latValAmber : styles.latValGreen}`}>{Math.round(avgLatency * 1.4)}ms
- ▲12
-
-
- P99
- 300 ? styles.latValRed : styles.latValAmber}`}>{p99Latency}ms
- ▲28
-
-
-
- SLA: <300ms P99 · {p99Latency > 300
- ? BREACH
- : OK }
-
-
-
- {/* Card 4: Active Routes */}
-
-
Active Routes
-
- {activeRoutes}
- of {totalRoutes}
- ↔ stable
-
-
-
-
-
-
-
- {activeRoutes} active
- {totalRoutes - activeRoutes} stopped
-
-
-
-
- {/* Card 5: In-Flight Exchanges */}
-
-
In-Flight Exchanges
-
- 23
- ↔
-
-
- High-water: 67 (2h ago)
-
-
-
-
-
-
- )
+ return [
+ {
+ label: 'Total Throughput',
+ value: totalExchanges.toLocaleString(),
+ trend: { label: '\u25B2 +8%', variant: 'success' as const },
+ subtitle: `${throughputPerSec} msg/s \u00B7 Capacity 39%`,
+ sparkline: [44, 46, 45, 47, 48, 46, 47, 48, 46, 47, 48, 47, 46, 47],
+ borderColor: 'var(--amber)',
+ },
+ {
+ label: 'System Error Rate',
+ value: `${errorRate.toFixed(2)}%`,
+ trend: {
+ label: errorRate < 1 ? '\u25BC -0.1%' : '\u25B2 +0.4%',
+ variant: errorRate < 1 ? 'success' as const : 'error' as const,
+ },
+ subtitle: `${totalErrors} errors / ${totalExchanges.toLocaleString()} total (6h)`,
+ sparkline: [1.2, 1.8, 1.5, 2.1, 2.4, 2.2, 2.5, 2.6, 2.7, 2.8, 2.7, 2.9, 2.8, errorRate],
+ borderColor: errorRate < 1 ? 'var(--success)' : 'var(--error)',
+ },
+ {
+ label: 'Latency Percentiles',
+ value: `${p99Latency}ms`,
+ trend: { label: '\u25B2 +28', variant: p99Latency > 300 ? 'error' as const : 'warning' as const },
+ subtitle: `P50 ${p50}ms \u00B7 P95 ${p95}ms \u00B7 SLA <300ms P99: ${slaStatus}`,
+ borderColor: p99Latency > 300 ? 'var(--warning)' : 'var(--success)',
+ },
+ {
+ label: 'Active Routes',
+ value: `${activeRoutes} / ${totalRoutes}`,
+ trend: { label: '\u2194 stable', variant: 'muted' as const },
+ subtitle: `${activeRoutes} active \u00B7 ${totalRoutes - activeRoutes} stopped`,
+ borderColor: 'var(--running)',
+ },
+ {
+ label: 'In-Flight Exchanges',
+ value: '23',
+ trend: { label: '\u2194', variant: 'muted' as const },
+ subtitle: 'High-water: 67 (2h ago)',
+ sparkline: [16, 14, 18, 12, 10, 15, 8, 6, 4, 3, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 18, 16, 18, 20, 18, 23],
+ borderColor: 'var(--amber)',
+ },
+ ]
}
// ─── Route metric row with id field (required by DataTable) ──────────────────
@@ -475,7 +422,7 @@ export function Routes() {
Auto-refresh: 30s