refactor: rename group/groupName to application/applicationName
The execution-related "group" concept actually represents the
application name. Rename all Java fields, API parameters, and frontend
types from groupName→applicationName and group→application for clarity.
- Java records: ExecutionSummary, ExecutionDetail, ExecutionDocument,
ExecutionRecord, ProcessorRecord
- API params: SearchRequest.group→application, SearchController
@RequestParam group→application
- Services: IngestionService, DetailService, SearchIndexer, StatsStore
- Frontend: schema.d.ts, Dashboard, ExchangeDetail, RouteDetail,
executions query hooks
Database column names (group_name) and OpenSearch field names are
unchanged — only the API-facing Java/TS field names are renamed.
RBAC group references (groups table, GroupRepository, GroupsTab) are
a separate domain concept and are NOT affected by this change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:21:38 +01:00
<!--
============================================================================
2026-04-15 15:28:42 +02:00
CAMELEER v3 — Metrics & KPI Dashboard (Light Theme)
refactor: rename group/groupName to application/applicationName
The execution-related "group" concept actually represents the
application name. Rename all Java fields, API parameters, and frontend
types from groupName→applicationName and group→application for clarity.
- Java records: ExecutionSummary, ExecutionDetail, ExecutionDocument,
ExecutionRecord, ProcessorRecord
- API params: SearchRequest.group→application, SearchController
@RequestParam group→application
- Services: IngestionService, DetailService, SearchIndexer, StatsStore
- Frontend: schema.d.ts, Dashboard, ExchangeDetail, RouteDetail,
executions query hooks
Database column names (group_name) and OpenSearch field names are
unchanged — only the API-facing Java/TS field names are renamed.
RBAC group references (groups table, GroupRepository, GroupsTab) are
a separate domain concept and are NOT affected by this change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:21:38 +01:00
============================================================================
DESIGN NOTES
============
PURPOSE:
A dedicated KPI/Metrics page focused on system-wide and per-route performance
metrics. Separate from the transaction list (v2), this page provides at-a-glance
system health, per-route performance comparison, and time-series trend analysis.
LAYOUT:
- Same 220px warm charcoal sidebar as v2 (with "Metrics" active)
- Same 48px topbar with breadcrumb: Dashboard > Metrics
- Scrollable main content with:
1. Time range selector bar + auto-refresh indicator
2. System Health KPI cards (5 large cards)
3. Per-Route Performance Table with sparklines
4. 2x2 Time-Series Chart grid (SVG, no libraries)
5. Live Event Feed panel (right rail, optional)
DESIGN SYSTEM (identical to v2-light):
- Surface: warm parchment #F5F2ED, cards #FFFFFF
- Sidebar: warm charcoal #2C2520
- Brand accent: amber-gold #C6820E
- Typography: DM Sans body + JetBrains Mono data
- Status: olive green #3D7C47, burnt orange #C27516, terracotta #C0392B, teal #1A7F8E
- Shadows, radii, border styles all from v2
DATA STORY:
- order-processing: high throughput, low latency, healthy
- payment-flow: moderate throughput, HIGHER latency (payment gateway), occasional errors
- shipment-notify: steady, fast
- inventory-check: fast, low volume
- customer-validation: moderate, stable
- email-notification: low volume, very fast
- retry-handler: LOW throughput but HIGH error rate (by design, retries failed messages)
- dead-letter-processor: very low volume, 100% "errors" (receives poison pills)
CHARTS:
All SVG inline, no external libraries. Sparklines in KPI cards and table.
Four large charts: throughput area, latency lines, error bars, volume heatmap.
============================================================================
-->
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=1920" >
2026-04-15 15:28:42 +02:00
< title > Cameleer — Metrics Dashboard< / title >
refactor: rename group/groupName to application/applicationName
The execution-related "group" concept actually represents the
application name. Rename all Java fields, API parameters, and frontend
types from groupName→applicationName and group→application for clarity.
- Java records: ExecutionSummary, ExecutionDetail, ExecutionDocument,
ExecutionRecord, ProcessorRecord
- API params: SearchRequest.group→application, SearchController
@RequestParam group→application
- Services: IngestionService, DetailService, SearchIndexer, StatsStore
- Frontend: schema.d.ts, Dashboard, ExchangeDetail, RouteDetail,
executions query hooks
Database column names (group_name) and OpenSearch field names are
unchanged — only the API-facing Java/TS field names are renamed.
RBAC group references (groups table, GroupRepository, GroupsTab) are
a separate domain concept and are NOT affected by this change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:21:38 +01:00
< link href = "https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel = "stylesheet" >
< style >
/* ==========================================================================
RESET & FOUNDATIONS (identical to v2)
========================================================================== */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
/* Surface palette (warm parchment) */
--bg-body: #F5F2ED;
--bg-surface: #FFFFFF;
--bg-raised: #FAF8F5;
--bg-inset: #F0EDE8;
--bg-hover: #F5F0EA;
/* Sidebar (warm charcoal) */
--sidebar-bg: #2C2520;
--sidebar-hover: #3A322C;
--sidebar-active: #4A3F38;
--sidebar-text: #BFB5A8;
--sidebar-muted: #7A6F63;
/* Text */
--text-primary: #1A1612;
--text-secondary: #5C5347;
--text-muted: #9C9184;
--text-faint: #C4BAB0;
/* Borders */
--border: #E4DFD8;
--border-subtle: #EDE9E3;
/* Brand accent (amber-gold) */
--amber: #C6820E;
--amber-light: #F0D9A8;
--amber-bg: #FDF6E9;
--amber-deep: #8B5A06;
/* Status colors (warm) */
--success: #3D7C47;
--success-bg: #EFF7F0;
--success-border: #C2DFC6;
--warning: #C27516;
--warning-bg: #FEF5E7;
--warning-border: #F0D9A8;
--error: #C0392B;
--error-bg: #FDF0EE;
--error-border: #F0C4BE;
--running: #1A7F8E;
--running-bg: #E8F5F7;
--running-border: #B0DDE4;
/* Typography */
--font-body: 'DM Sans', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
/* Spacing & Radii */
--radius-sm: 5px;
--radius-md: 8px;
--radius-lg: 12px;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(44, 37, 32, 0.06);
--shadow-md: 0 2px 8px rgba(44, 37, 32, 0.08);
--shadow-lg: 0 4px 16px rgba(44, 37, 32, 0.10);
--shadow-card: 0 1px 3px rgba(44, 37, 32, 0.04), 0 0 0 1px rgba(44, 37, 32, 0.04);
/* Chart palette (warm spectrum) */
--chart-1: #C6820E; /* amber */
--chart-2: #3D7C47; /* olive green */
--chart-3: #1A7F8E; /* teal */
--chart-4: #C27516; /* burnt orange */
--chart-5: #8B5A06; /* deep amber */
--chart-6: #6B8E4E; /* sage */
--chart-7: #C0392B; /* terracotta */
--chart-8: #9C7A3C; /* gold */
}
html { font-size: 14px; }
body {
font-family: var(--font-body);
background: var(--bg-body);
color: var(--text-primary);
line-height: 1.5;
min-height: 100vh;
-webkit-font-smoothing: antialiased;
}
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-faint); }
/* ==========================================================================
ANIMATIONS
========================================================================== */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(61, 124, 71, 0.35); }
50% { box-shadow: 0 0 0 5px rgba(61, 124, 71, 0); }
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.animate-in { animation: fadeIn 0.3s ease-out both; }
.delay-1 { animation-delay: 0.04s; }
.delay-2 { animation-delay: 0.08s; }
.delay-3 { animation-delay: 0.12s; }
.delay-4 { animation-delay: 0.16s; }
.delay-5 { animation-delay: 0.20s; }
/* ==========================================================================
LAYOUT
========================================================================== */
.app {
display: flex;
height: 100vh;
overflow: hidden;
}
/* ==========================================================================
SIDEBAR (220px, warm charcoal — identical structure to v2)
========================================================================== */
.sidebar {
width: 220px;
flex-shrink: 0;
background: var(--sidebar-bg);
display: flex;
flex-direction: column;
overflow: hidden;
}
.sidebar-logo {
padding: 16px 18px;
display: flex;
align-items: center;
gap: 10px;
border-bottom: 1px solid rgba(255,255,255,0.06);
}
.sidebar-logo .brand {
font-family: var(--font-mono);
font-weight: 600;
font-size: 15px;
color: var(--amber-light);
letter-spacing: -0.3px;
}
.sidebar-logo .version {
font-family: var(--font-mono);
font-size: 10px;
color: var(--sidebar-muted);
margin-left: 2px;
}
.sidebar-search { padding: 10px 12px; }
.sidebar-search input {
width: 100%;
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.08);
border-radius: var(--radius-sm);
padding: 6px 10px 6px 28px;
color: var(--sidebar-text);
font-family: var(--font-body);
font-size: 12px;
outline: none;
transition: border-color 0.15s;
}
.sidebar-search input::placeholder { color: var(--sidebar-muted); }
.sidebar-search input:focus { border-color: rgba(198, 130, 14, 0.4); }
.sidebar-search-wrap { position: relative; }
.sidebar-search-wrap .search-icon {
position: absolute;
left: 9px;
top: 50%;
transform: translateY(-50%);
color: var(--sidebar-muted);
font-size: 12px;
}
.sidebar-section {
padding: 14px 12px 5px;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1.2px;
color: var(--sidebar-muted);
}
.sidebar-items {
flex: 1;
overflow-y: auto;
padding: 0 6px;
}
.sidebar-item {
display: flex;
align-items: center;
gap: 10px;
padding: 7px 12px;
border-radius: var(--radius-sm);
color: var(--sidebar-text);
font-size: 13px;
cursor: pointer;
transition: all 0.12s;
text-decoration: none;
border-left: 3px solid transparent;
margin-bottom: 1px;
}
.sidebar-item:hover { background: var(--sidebar-hover); color: #E8DFD4; }
.sidebar-item.active { background: var(--sidebar-active); color: var(--amber-light); border-left-color: var(--amber); }
.sidebar-item .health {
width: 7px;
height: 7px;
border-radius: 50%;
flex-shrink: 0;
}
.health-live { background: #5DB866; box-shadow: 0 0 6px rgba(93, 184, 102, 0.4); }
.health-stale { background: var(--warning); }
.health-dead { background: var(--sidebar-muted); }
.sidebar-item .item-info { flex: 1; min-width: 0; }
.sidebar-item .item-name { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sidebar-item .item-meta { font-size: 11px; color: var(--sidebar-muted); font-family: var(--font-mono); }
.sidebar-item .item-count {
font-family: var(--font-mono);
font-size: 11px;
color: var(--sidebar-muted);
background: rgba(255,255,255,0.06);
padding: 1px 6px;
border-radius: 10px;
}
.sidebar-item.active .item-count { background: rgba(198, 130, 14, 0.2); color: var(--amber-light); }
.sidebar-divider {
height: 1px;
background: rgba(255,255,255,0.06);
margin: 6px 12px;
}
/* Agent health in sidebar */
.sidebar-agents-header {
padding: 14px 12px 6px;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1.2px;
color: var(--sidebar-muted);
display: flex;
align-items: center;
justify-content: space-between;
}
.sidebar-agent-badge {
font-family: var(--font-mono);
font-size: 10px;
padding: 1px 6px;
border-radius: 10px;
background: rgba(93, 184, 102, 0.15);
color: #5DB866;
}
.agent-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
margin: 0 6px 2px;
border-radius: var(--radius-sm);
font-size: 11px;
color: var(--sidebar-text);
transition: background 0.1s;
}
.agent-item:hover { background: var(--sidebar-hover); }
.agent-dot {
width: 6px;
height: 6px;
border-radius: 50%;
flex-shrink: 0;
}
.agent-info { flex: 1; min-width: 0; }
.agent-name { font-family: var(--font-mono); font-size: 11px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.agent-detail { font-size: 10px; color: var(--sidebar-muted); }
.agent-stats-col {
text-align: right;
font-family: var(--font-mono);
font-size: 10px;
color: var(--sidebar-muted);
}
.agent-tps { color: var(--sidebar-text); }
.sidebar-bottom {
border-top: 1px solid rgba(255,255,255,0.06);
padding: 6px;
}
.sidebar-bottom .sidebar-item {
font-size: 12px;
color: var(--sidebar-muted);
border-left: 3px solid transparent;
}
.sidebar-bottom .sidebar-item:hover { color: var(--sidebar-text); }
/* ==========================================================================
MAIN CONTENT AREA
========================================================================== */
.main {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-width: 0;
}
/* Top bar */
.topbar {
display: flex;
align-items: center;
gap: 12px;
padding: 0 24px;
height: 48px;
flex-shrink: 0;
background: var(--bg-surface);
border-bottom: 1px solid var(--border);
}
.topbar-breadcrumb {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: var(--text-muted);
}
.topbar-breadcrumb .crumb-active { color: var(--text-primary); font-weight: 600; }
.topbar-breadcrumb .crumb-sep { color: var(--text-faint); font-size: 11px; }
.topbar-right {
margin-left: auto;
display: flex;
align-items: center;
gap: 12px;
}
.topbar-env {
font-family: var(--font-mono);
font-size: 10px;
font-weight: 600;
padding: 3px 10px;
border-radius: 10px;
background: var(--success-bg);
color: var(--success);
border: 1px solid var(--success-border);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.topbar-shift {
font-family: var(--font-mono);
font-size: 10px;
padding: 3px 10px;
border-radius: 10px;
background: var(--running-bg);
color: var(--running);
border: 1px solid var(--running-border);
}
.topbar-user {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--text-secondary);
}
.topbar-avatar {
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--amber-bg);
color: var(--amber);
font-weight: 600;
font-size: 11px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--amber-light);
}
/* Content scroll container */
.content {
flex: 1;
overflow-y: auto;
padding: 20px 24px 40px;
min-width: 0;
}
/* ==========================================================================
TIME RANGE SELECTOR BAR
========================================================================== */
.time-range-bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
.time-range-pills {
display: flex;
gap: 4px;
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
padding: 3px;
box-shadow: var(--shadow-card);
}
.time-pill {
padding: 5px 14px;
border-radius: var(--radius-sm);
font-size: 12px;
font-weight: 500;
color: var(--text-secondary);
cursor: pointer;
transition: all 0.12s;
background: none;
border: none;
font-family: var(--font-body);
}
.time-pill:hover { background: var(--bg-hover); }
.time-pill.active {
background: var(--amber);
color: white;
font-weight: 600;
}
.refresh-group {
display: flex;
align-items: center;
gap: 10px;
}
.auto-refresh {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
color: var(--text-muted);
font-family: var(--font-mono);
}
.auto-refresh-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--success);
animation: pulse 2s ease-in-out infinite;
}
.refresh-btn {
display: flex;
align-items: center;
gap: 5px;
padding: 5px 12px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--bg-surface);
color: var(--text-secondary);
font-size: 11px;
font-family: var(--font-body);
cursor: pointer;
transition: all 0.15s;
box-shadow: var(--shadow-sm);
}
.refresh-btn:hover { border-color: var(--text-faint); background: var(--bg-raised); }
.refresh-icon {
display: inline-block;
font-size: 12px;
}
.last-updated {
font-size: 10px;
color: var(--text-faint);
font-family: var(--font-mono);
}
/* ==========================================================================
KPI CARDS (System Health Overview)
========================================================================== */
.kpi-strip {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 12px;
margin-bottom: 20px;
}
.kpi-card {
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;
cursor: pointer;
}
.kpi-card:hover { box-shadow: var(--shadow-md); }
.kpi-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
}
.kpi-card.card-amber::before { background: linear-gradient(90deg, var(--amber), transparent); }
.kpi-card.card-green::before { background: linear-gradient(90deg, var(--success), transparent); }
.kpi-card.card-error::before { background: linear-gradient(90deg, var(--error), transparent); }
.kpi-card.card-teal::before { background: linear-gradient(90deg, var(--running), transparent); }
.kpi-card.card-warn::before { background: linear-gradient(90deg, var(--warning), transparent); }
.kpi-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.6px;
color: var(--text-muted);
margin-bottom: 6px;
}
.kpi-value-row {
display: flex;
align-items: baseline;
gap: 6px;
margin-bottom: 4px;
}
.kpi-value {
font-family: var(--font-mono);
font-size: 26px;
font-weight: 600;
line-height: 1.2;
}
.kpi-value.val-amber { color: var(--amber); }
.kpi-value.val-green { color: var(--success); }
.kpi-value.val-error { color: var(--error); }
.kpi-value.val-teal { color: var(--running); }
.kpi-value.val-warn { color: var(--warning); }
.kpi-unit {
font-size: 12px;
color: var(--text-muted);
}
.kpi-trend {
font-family: var(--font-mono);
font-size: 11px;
display: inline-flex;
align-items: center;
gap: 2px;
margin-left: auto;
}
.trend-up-good { color: var(--success); }
.trend-up-bad { color: var(--error); }
.trend-down-good { color: var(--success); }
.trend-down-bad { color: var(--error); }
.trend-flat { color: var(--text-muted); }
.kpi-detail {
font-size: 11px;
color: var(--text-muted);
margin-top: 2px;
}
.kpi-detail strong { color: var(--text-secondary); }
.kpi-sparkline {
margin-top: 8px;
height: 32px;
}
.kpi-sparkline svg { width: 100%; height: 100%; }
/* Latency sub-values */
.latency-values {
display: flex;
gap: 12px;
margin-top: 4px;
}
.latency-item {
display: flex;
flex-direction: column;
align-items: center;
}
.latency-label {
font-size: 9px;
font-weight: 600;
text-transform: uppercase;
color: var(--text-faint);
letter-spacing: 0.5px;
}
.latency-val {
font-family: var(--font-mono);
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
}
.latency-val.lat-green { color: var(--success); }
.latency-val.lat-amber { color: var(--warning); }
.latency-val.lat-red { color: var(--error); }
.latency-trend {
font-family: var(--font-mono);
font-size: 9px;
}
/* Mini donut */
.mini-donut-wrap {
display: flex;
align-items: center;
gap: 12px;
margin-top: 6px;
}
.mini-donut { position: relative; width: 40px; height: 40px; }
.donut-label {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-family: var(--font-mono);
font-size: 9px;
font-weight: 600;
color: var(--text-secondary);
}
.donut-legend {
display: flex;
flex-direction: column;
gap: 2px;
font-size: 10px;
color: var(--text-muted);
font-family: var(--font-mono);
}
.donut-legend-active { color: var(--success); }
/* ==========================================================================
PER-ROUTE PERFORMANCE TABLE
========================================================================== */
.table-section {
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-card);
overflow: hidden;
margin-bottom: 20px;
}
.table-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 16px;
border-bottom: 1px solid var(--border-subtle);
}
.table-title { font-size: 13px; font-weight: 600; color: var(--text-primary); }
.table-right {
display: flex;
align-items: center;
gap: 12px;
}
.table-meta { font-family: var(--font-mono); font-size: 11px; color: var(--text-muted); }
.table-scroll { overflow-x: auto; }
table {
width: 100%;
border-collapse: collapse;
}
thead {
background: var(--bg-raised);
border-bottom: 1px solid var(--border);
}
th {
padding: 9px 14px;
text-align: left;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.8px;
color: var(--text-muted);
white-space: nowrap;
user-select: none;
cursor: pointer;
transition: color 0.12s;
}
th:hover { color: var(--text-secondary); }
th.sorted { color: var(--amber); }
.sort-arrow { font-size: 8px; margin-left: 3px; opacity: 0.4; }
th.sorted .sort-arrow { opacity: 1; }
tbody tr {
border-bottom: 1px solid var(--border-subtle);
transition: background 0.08s;
cursor: pointer;
}
tbody tr:last-child { border-bottom: none; }
tbody tr:hover { background: var(--bg-hover); }
td {
padding: 9px 14px;
font-size: 13px;
vertical-align: middle;
white-space: nowrap;
}
.route-name-cell {
font-weight: 500;
color: var(--text-primary);
}
.route-name-cell:hover { color: var(--amber); }
.route-group-cell {
font-size: 11px;
color: var(--text-muted);
}
.mono { font-family: var(--font-mono); }
.cell-sparkline { display: inline-block; vertical-align: middle; }
/* Latency cell color coding */
.lat-cell { font-family: var(--font-mono); font-size: 12px; font-weight: 500; }
.lat-fast { color: var(--success); }
.lat-normal { color: var(--text-primary); }
.lat-slow { color: var(--warning); }
.lat-breach { color: var(--error); font-weight: 600; }
/* Error rate cell */
.err-rate { font-family: var(--font-mono); font-size: 12px; }
.err-low { color: var(--success); }
.err-mid { color: var(--warning); }
.err-high { color: var(--error); font-weight: 600; }
/* Status badge */
.status-badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 2px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.2px;
}
.status-success { background: var(--success-bg); color: var(--success); border: 1px solid var(--success-border); }
.status-warning { background: var(--warning-bg); color: var(--warning); border: 1px solid var(--warning-border); }
.status-error { background: var(--error-bg); color: var(--error); border: 1px solid var(--error-border); }
.status-dot {
width: 5px;
height: 5px;
border-radius: 50%;
background: currentColor;
}
.drill-arrow {
color: var(--text-faint);
font-size: 11px;
transition: color 0.12s;
}
tbody tr:hover .drill-arrow { color: var(--amber); }
/* ==========================================================================
CHART GRID (2x2)
========================================================================== */
.chart-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
margin-bottom: 20px;
}
.chart-card {
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-card);
overflow: hidden;
}
.chart-card-header {
padding: 12px 16px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--border-subtle);
}
.chart-card-title {
font-size: 12px;
font-weight: 600;
color: var(--text-primary);
}
.chart-card-subtitle {
font-size: 10px;
color: var(--text-muted);
font-family: var(--font-mono);
}
.chart-body {
padding: 16px;
height: 220px;
position: relative;
}
.chart-body svg { width: 100%; height: 100%; }
/* Chart elements */
.chart-grid-line { stroke: var(--border-subtle); stroke-width: 1; stroke-dasharray: 3 3; }
.chart-axis-line { stroke: var(--border); stroke-width: 1; }
.chart-label { font-family: var(--font-mono); font-size: 9px; fill: var(--text-faint); }
.chart-value-label { font-family: var(--font-mono); font-size: 8px; fill: var(--text-muted); }
.sla-line { stroke: var(--error); stroke-width: 1; stroke-dasharray: 4 3; opacity: 0.5; }
.sla-label-text { fill: var(--error); font-size: 8px; font-family: var(--font-mono); opacity: 0.6; }
.sla-zone { fill: var(--error); opacity: 0.04; }
/* Chart legend */
.chart-legend {
display: flex;
flex-wrap: wrap;
gap: 10px;
padding: 8px 16px 12px;
border-top: 1px solid var(--border-subtle);
}
.legend-item {
display: flex;
align-items: center;
gap: 5px;
font-size: 10px;
color: var(--text-muted);
font-family: var(--font-mono);
}
.legend-dot {
width: 8px;
height: 3px;
border-radius: 1px;
}
/* Heatmap */
.heatmap-grid {
display: flex;
flex-direction: column;
gap: 2px;
height: 100%;
}
.heatmap-row {
display: flex;
align-items: center;
gap: 2px;
flex: 1;
}
.heatmap-label {
width: 90px;
font-family: var(--font-mono);
font-size: 9px;
color: var(--text-muted);
text-align: right;
padding-right: 6px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.heatmap-cells {
display: flex;
gap: 2px;
flex: 1;
}
.heatmap-cell {
flex: 1;
border-radius: 2px;
position: relative;
cursor: pointer;
transition: transform 0.1s;
min-height: 16px;
}
.heatmap-cell:hover { transform: scale(1.15); z-index: 1; }
.heatmap-hour-labels {
display: flex;
gap: 2px;
margin-left: 92px;
margin-top: 2px;
}
.heatmap-hour-label {
flex: 1;
text-align: center;
font-family: var(--font-mono);
font-size: 8px;
color: var(--text-faint);
}
/* Heatmap intensity scale */
.heat-0 { background: var(--bg-inset); }
.heat-1 { background: #FDF6E9; }
.heat-2 { background: #F8E6C1; }
.heat-3 { background: #F0D9A8; }
.heat-4 { background: #E5C07A; }
.heat-5 { background: #D4A24E; }
.heat-6 { background: #C6820E; }
.heat-7 { background: #A66A0A; }
.heat-8 { background: #8B5A06; }
.heatmap-scale {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 16px 10px;
justify-content: flex-end;
}
.heatmap-scale-label {
font-size: 9px;
color: var(--text-faint);
font-family: var(--font-mono);
}
.heatmap-scale-cells {
display: flex;
gap: 2px;
}
.heatmap-scale-cell {
width: 12px;
height: 10px;
border-radius: 1px;
}
/* ==========================================================================
LIVE EVENT FEED
========================================================================== */
.live-feed-section {
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-card);
overflow: hidden;
}
.live-feed-header {
padding: 10px 16px;
border-bottom: 1px solid var(--border-subtle);
display: flex;
align-items: center;
justify-content: space-between;
}
.live-feed-title {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 8px;
}
.live-feed-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--success);
animation: pulse 2s ease-in-out infinite;
}
.live-feed-list {
max-height: 240px;
overflow-y: auto;
}
.feed-item {
display: flex;
gap: 10px;
padding: 8px 16px;
border-bottom: 1px solid var(--border-subtle);
transition: background 0.08s;
cursor: pointer;
}
.feed-item:hover { background: var(--bg-hover); }
.feed-item:last-child { border-bottom: none; }
.feed-icon {
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
flex-shrink: 0;
margin-top: 1px;
}
.feed-icon.feed-error { background: var(--error-bg); color: var(--error); border: 1px solid var(--error-border); }
.feed-icon.feed-warn { background: var(--warning-bg); color: var(--warning); border: 1px solid var(--warning-border); }
.feed-icon.feed-info { background: var(--running-bg); color: var(--running); border: 1px solid var(--running-border); }
.feed-icon.feed-ok { background: var(--success-bg); color: var(--success); border: 1px solid var(--success-border); }
.feed-content { flex: 1; min-width: 0; }
.feed-text {
font-size: 12px;
color: var(--text-primary);
line-height: 1.4;
}
.feed-text strong { font-weight: 600; }
.feed-text .feed-route { color: var(--amber); font-family: var(--font-mono); font-size: 11px; }
.feed-meta {
font-size: 10px;
color: var(--text-faint);
font-family: var(--font-mono);
margin-top: 1px;
}
/* Utility */
.mono { font-family: var(--font-mono); }
< / style >
< / head >
< body >
< div class = "app" >
<!-- ====================================================================
SIDEBAR (warm charcoal — identical to v2, "Metrics" highlighted)
==================================================================== -->
< aside class = "sidebar" >
< div class = "sidebar-logo" >
< svg width = "24" height = "26" viewBox = "940 300 900 1000" fill = "none" xmlns = "http://www.w3.org/2000/svg" >
< path style = "fill:#C6820E" d = "M 1385 . 2768 , 1205 . 0273 c -1 . 7727 , -1 . 0809 -3 . 4419 , -2 . 9917 -3 . 7093 , -4 . 2463 -0 . 2674 , -1 . 2545 -0 . 6851 , -23 . 881 -0 . 9282 , -50 . 281 -0 . 4544 , -49 . 3389 -1 . 4003 , -65 . 7355 -5 . 6645 , -98 . 1921 -10 . 4552 , -79 . 57719 -28 . 3441 , -125 . 96796 -62 . 5438 , -162 . 19311 -9 . 2529 , -9 . 80096 -19 . 9748 , -18 . 38696 -33 . 931 , -27 . 17162 -12 . 6119 , -7 . 93853 -24 . 8581 , -13 . 73088 -49 . 5 , -23 . 41316 -41 . 7081 , -16 . 38789 -67 . 8677 , -32 . 29827 -89 . 0588 , -54 . 16599 -34 . 7586 , -35 . 86827 -54 . 3653 , -83 . 30392 -59 . 0504 , -142 . 86402 -0 . 8357 , -10 . 6245 -0 . 92 , -22 . 5842 -0 . 2891 , -41 0 . 4899 , -14 . 3 0 . 8164 , -35 . 225 0 . 7255 , -46 . 5 -0 . 1983 , -24 . 59199 -2 . 0251 , -34 . 76973 -8 . 4617 , -47 . 14159 -6 . 2021 , -11 . 92135 -10 . 364 , -15 . 10789 -36 . 2345 , -27 . 74305 -25 . 4032 , -12 . 40694 -27 . 7701 , -13 . 30168 -35 . 4717 , -13 . 40961 -4 . 63013 , -0 . 0649 -7 . 47783 , 0 . 72754 -15 . 6593 , 4 . 35747 -9 . 86218 , 4 . 37563 -10 . 15161 , 4 . 43672 -21 , 4 . 43267 -10 . 86566 , -0 . 004 -11 . 08917 , -0 . 0517 -18 . 30169 , -3 . 90103 -17 . 43524 , -9 . 3052 -21 . 50075 , -23 . 5772 -13 . 55784 , -47 . 59486 2 . 73631 , -8 . 27402 10 . 00732 , -22 . 43593 14 . 69263 , -28 . 61719 5 . 4984 , -7 . 25394 10 . 89014 , -11 . 83885 20 . 83968 , -17 . 72117 15 . 06768 , -8 . 90827 29 . 60254 , -11 . 98884 63 . 90372 , -13 . 54403 l 16 . 5765 , -0 . 75156 14 . 9235 , -7 . 05735 c 8 . 2079 , -3 . 88154 17 . 1735 , -7 . 76831 19 . 9235 , -8 . 63727 7 . 7052 , -2 . 43474 21 . 059 , -4 . 67186 27 . 8605 , -4 . 66741 8 . 0518 , 0 . 005 22 . 643 , 2 . 41202 30 . 0139 , 4 . 95066 l 5 . 8744 , 2 . 02321 4 . 8856 , -4 . 09041 c 10 . 341 , -8 . 65797 18 . 6496 , -12 . 95738 28 . 96 , -14 . 98583 6 . 9966 , -1 . 37649 26 . 3532 , -0 . 64631 32 . 1116 , 1 . 21134 4 . 5531 , 1 . 46885 5 . 4951 , 3 . 7902 6 . 3689 , 15 . 69592 1 . 5167 , 20 . 66426 -5 . 0112 , 40 . 44987 -18 . 1772 , 55 . 09363 -4 . 3065 , 4 . 78983 -4 . 5016 , 5 . 25488 -3 . 4977 , 8 . 33516 4 . 5184 , 13 . 86447 5 . 3154 , 38 . 07517 2 . 1537 , 65 . 42564 -5 . 169 , 44 . 71349 -5 . 0411 , 70 . 7797 0 . 5404 , 110 . 15637 6 . 8135 , 48 . 06863 22 . 3335 , 73 . 51874 48 . 2051 , 79 . 04779 24 . 1748 , 5 . 16643 45 . 2921 , -5 . 78181 66 . 8353 , -34 . 65077 4 . 809 , -6 . 4442 11 . 1363 , -14 . 93006 14 . 0608 , -18 . 85747 7 . 3865 , -9 . 91943 25 . 6102 , -27 . 11708 35 . 952 , -33 . 92766 12 . 5235 , -8 . 24739 26 . 8808 , -14 . 74833 42 . 8527 , -19 . 40354 15 . 4108 , -4 . 49168 26 . 7091 , -9 . 74984 36 . 2432 , -16 . 86731 3 . 4338 , -2 . 56341 13 . 3338 , -12 . 03104 22 , -21 . 03916 26 . 2356 , -27 . 27067 44 . 5755 , -40 . 32368 66 . 9928 , -47 . 68062 19 . 1052 , -6 . 26998 35 . 4927 , -7 . 73718 50 . 8681 , -4 . 55427 37 . 0856 , 7 . 67726 63 . 5507 , 26 . 77589 100 . 2888 , 72 . 37362 16 . 6011 , 20 . 60463 29 . 9711 , 32 . 07977 51 . 6071 , 44 . 29313 39 . 3131 , 22 . 19195 50 . 1228 , 30 . 50985 68 . 8076 , 52 . 94655 5 . 4963 , 6 . 6 12 . 9187 , 14 . 91604 16 . 4941 , 18 . 4801 33 . 6796 , 33 . 57259 54 . 1965 , 72 . 51092 61 . 6587 , 117 . 0199 2 . 7315 , 16 . 29242 3 . 3374 , 26 . 62538 3 . 1861 , 54 . 33713 l -0 . 1465 , 26 . 83714 -2 . 7975 , 2 . 40572 c -3 . 9132 , 3 . 36522 -7 . 2806 , 3 . 99163 -11 . 2591 , 2 . 09442 -6 . 4731 , -3 . 08682 -6 . 5715 , -3 . 57833 -6 . 878 , -34 . 36929 -0 . 2908 , -29 . 2175 -2 . 0265 , -46 . 13705 -6 . 6479 , -64 . 80512 -3 . 2997 , -13 . 32891 -12 . 2529 , -34 . 61943 -17 . 0313 , -40 . 5 l -2 . 0314 , -2 . 5 0 . 5698 , 3 . 5 c 5 . 8574 , 35 . 97875 4 . 2855 , 72 . 40287 -5 . 3946 , 125 -8 . 4016 , 45 . 65054 -4 . 3665 , 69 . 39588 20 . 7318 , 122 11 . 4175 , 23 . 93009 12 . 5452 , 27 . 25907 12 . 4212 , 36 . 6684 -0 . 1018 , 7 . 7227 -2 . 5346 , 19 . 5162 -13 . 1674 , 63 . 8316 -1 . 0558 , 4 . 4 -3 . 5336 , 14 . 975 -5 . 5063 , 23 . 5 -1 . 9727 , 8 . 525 -4 . 2259 , 18 . 2 -5 . 0072 , 21 . 5 -0 . 7812 , 3 . 3 -3 . 4495 , 15 . 225 -5 . 9296 , 26 . 5 -9 . 1636 , 41 . 6596 -13 . 4372 , 59 . 8787 -14 . 5204 , 61 . 9027 -2 . 4994 , 4 . 6702 -5 . 2312 , 5 . 0973 -32 . 6024 , 5 . 0973 H 1765 . 8 l -3 . 4 , -3 . 4 c -2 . 3518 , -2 . 3518 -3 . 4 , -4 . 3226 -3 . 4 , -6 . 3925 0 , -1 . 6458 2 . 2347 , -12 . 1533 4 . 966 , -23 . 35 8 . 6906 , -35 . 6259 11 . 6969 , -54 . 778 12 . 6921 , -80 . 8575 1 . 3475 , -35 . 3073 -4 . 6406 , -62 . 7687 -18 . 7825 , -86 . 137 -7 . 6672 , -12 . 66954 -11 . 9163 , -17 . 84148 -29 . 8756 , -36 . 36415 -22 . 9362 , -23 . 65574 -34 . 6222 , -39 . 72583 -47 . 9268 , -65 . 90697 -5 . 7294 , -11 . 27448 -13 . 6061 , -31 . 99995 -15 . 5051 , -40 . 79778 -0 . 3212 , -1 . 48824 -1 . 0176 , -3 . 84619 -1 . 5475 , -5 . 2399 l -0 . 9634 , -2 . 534 -11 . 7786 , 7 . 15145 c -38 . 634 , 23 . 45687 -74 . 5513 , 34 . 71091 -124 . 1895 , 38 . 91257 -14 . 5402 , 1 . 23075 -58 . 2359 , 0 . 66344 -71 . 9142 , -0 . 93369 -5 . 4419 , -0 . 63542 -6 . 4542 , -0 . 49723 -7 . 25 , 0 . 98972 -0 . 5545 , 1 . 03621 -0 . 9249 , 60 . 63835 -0 . 9249 , 148 . 84365 0 , 161 . 6262 0 . 4025 , 151 . 2052 -5 . 9673 , 154 . 4992 -2 . 3865 , 1 . 2341 -7 . 4633 , 1 . 5162 -27 . 233 , 1 . 5132 -22 . 3926 , -0 -24 . 5527 , -0 . 158 -27 . 5229 , -1 . 969 z M 1424 , 1045 . 6452 c 0 , -87 . 21053 0 . 3878 , -144 . 17994 1 . 0365 , -152 . 24998 1 . 8115 , -22 . 53829 7 . 2373 , -44 . 8067 16 . 1142 , -66 . 13567 20 . 7842 , -49 . 93942 66 . 8961 , -95 . 01414 129 . 8493 , -126 . 92865 14 . 9469 , -7 . 57742 29 . 283
< / svg >
< div >
< span class = "brand" > cameleer< / span > < span class = "version" > v3< / span >
< / div >
< / div >
< div class = "sidebar-search" >
< div class = "sidebar-search-wrap" >
< span class = "search-icon" > ◇ < / span >
< input type = "text" placeholder = "Filter applications..." >
< / div >
< / div >
< div class = "sidebar-section" > Navigation< / div >
< div class = "sidebar-items" >
< div class = "sidebar-item" >
< span style = "font-size: 13px; width: 18px; text-align: center;" > ▢ < / span >
< div class = "item-info" > < div class = "item-name" > Transactions< / div > < / div >
< / div >
< div class = "sidebar-item active" >
< span style = "font-size: 13px; width: 18px; text-align: center;" > ◆ < / span >
< div class = "item-info" > < div class = "item-name" > Metrics< / div > < / div >
< / div >
< div class = "sidebar-item" >
< span style = "font-size: 13px; width: 18px; text-align: center;" > ☌ < / span >
< div class = "item-info" > < div class = "item-name" > Route Diagrams< / div > < / div >
< / div >
< div class = "sidebar-divider" > < / div >
< div class = "sidebar-section" > Applications< / div >
< div class = "sidebar-item" >
< span class = "health health-live" > < / span >
< div class = "item-info" >
< div class = "item-name" > order-service< / div >
< div class = "item-meta" > 2 agents< / div >
< / div >
< span class = "item-count" > 1,847< / span >
< / div >
< div class = "sidebar-item" >
< span class = "health health-live" > < / span >
< div class = "item-info" >
< div class = "item-name" > payment-gateway< / div >
< div class = "item-meta" > 2 agents< / div >
< / div >
< span class = "item-count" > 923< / span >
< / div >
< div class = "sidebar-item" >
< span class = "health health-live" > < / span >
< div class = "item-info" >
< div class = "item-name" > shipment-tracker< / div >
< div class = "item-meta" > 2 agents< / div >
< / div >
< span class = "item-count" > 471< / span >
< / div >
< div class = "sidebar-item" >
< span class = "health health-stale" > < / span >
< div class = "item-info" >
< div class = "item-name" > notification-hub< / div >
< div class = "item-meta" > 1 agent< / div >
< / div >
< span class = "item-count" > 128< / span >
< / div >
< / div >
<!-- Agent health section -->
< div class = "sidebar-agents-header" >
< span > Agents< / span >
< span class = "sidebar-agent-badge" > 4/4 live< / span >
< / div >
< div style = "padding: 0 0 6px; overflow-y: auto; max-height: 180px;" >
< div class = "agent-item" >
< span class = "agent-dot health-live" > < / span >
< div class = "agent-info" >
< div class = "agent-name" > prod-1< / div >
< div class = "agent-detail" > order-service v3.2.1< / div >
< / div >
< div class = "agent-stats-col" >
< div class = "agent-tps" > 14.2/s< / div >
< div > 12s ago< / div >
< / div >
< / div >
< div class = "agent-item" >
< span class = "agent-dot health-live" > < / span >
< div class = "agent-info" >
< div class = "agent-name" > prod-2< / div >
< div class = "agent-detail" > payment-svc v3.2.1< / div >
< / div >
< div class = "agent-stats-col" >
< div class = "agent-tps" > 11.8/s< / div >
< div style = "color: var(--error);" > 3 err/h< / div >
< / div >
< / div >
< div class = "agent-item" >
< span class = "agent-dot health-live" > < / span >
< div class = "agent-info" >
< div class = "agent-name" > prod-3< / div >
< div class = "agent-detail" > shipment-svc v3.2.0< / div >
< / div >
< div class = "agent-stats-col" >
< div class = "agent-tps" > 12.1/s< / div >
< div > 5s ago< / div >
< / div >
< / div >
< div class = "agent-item" >
< span class = "agent-dot health-live" > < / span >
< div class = "agent-info" >
< div class = "agent-name" > prod-4< / div >
< div class = "agent-detail" > notif-hub v3.1.8< / div >
< / div >
< div class = "agent-stats-col" >
< div class = "agent-tps" > 9.1/s< / div >
< div > 3s ago< / div >
< / div >
< / div >
< / div >
< div class = "sidebar-bottom" >
< div class = "sidebar-item" > < span style = "font-size: 13px; width: 18px; text-align: center;" > ⚙ < / span > < div class = "item-info" > < div class = "item-name" > Admin< / div > < / div > < / div >
< div class = "sidebar-item" > < span style = "font-size: 13px; width: 18px; text-align: center;" > ☰ < / span > < div class = "item-info" > < div class = "item-name" > API Docs< / div > < / div > < / div >
< / div >
< / aside >
<!-- ====================================================================
MAIN CONTENT
==================================================================== -->
< div class = "main" >
<!-- Top bar -->
< div class = "topbar" >
< div class = "topbar-breadcrumb" >
< span > Dashboard< / span >
< span class = "crumb-sep" > /< / span >
< span class = "crumb-active" > Metrics< / span >
< / div >
< div class = "topbar-right" >
< span class = "topbar-env" > PRODUCTION< / span >
< span class = "topbar-shift" > Shift: Day (06:00-18:00)< / span >
< div class = "topbar-user" >
< span > hendrik< / span >
< div class = "topbar-avatar" > H< / div >
< / div >
< / div >
< / div >
<!-- Scrollable content area -->
< div class = "content" >
<!-- Time range selector bar -->
< div class = "time-range-bar animate-in" >
< div class = "time-range-pills" >
< button class = "time-pill" > 1h< / button >
< button class = "time-pill active" > 6h< / button >
< button class = "time-pill" > 24h< / button >
< button class = "time-pill" > 7d< / button >
< button class = "time-pill" > 30d< / button >
< button class = "time-pill" > Custom< / button >
< / div >
< div class = "refresh-group" >
< div class = "auto-refresh" >
< span class = "auto-refresh-dot" > < / span >
Auto-refresh: 30s
< / div >
< button class = "refresh-btn" >
< span class = "refresh-icon" > ↻ < / span >
Refresh
< / button >
< span class = "last-updated" > Updated 4s ago< / span >
< / div >
< / div >
<!-- ================================================================
KPI CARDS — System Health Overview
================================================================ -->
< div class = "kpi-strip animate-in delay-1" >
<!-- Card 1: Total Throughput -->
< div class = "kpi-card card-amber" >
< div class = "kpi-label" > Total Throughput< / div >
< div class = "kpi-value-row" >
< span class = "kpi-value val-amber" > 2,847< / span >
< span class = "kpi-unit" > msg/min< / span >
< span class = "kpi-trend trend-up-good" > ▲ +8%< / span >
< / div >
< div class = "kpi-detail" > < strong > 47.5< / strong > msg/s · Capacity 39%< / div >
< div class = "kpi-sparkline" >
< svg viewBox = "0 0 200 32" preserveAspectRatio = "none" >
< polyline points = "0,28 8,26 16,24 24,25 32,22 40,20 48,21 56,18 64,16 72,17 80,15 88,13 96,14 104,12 112,10 120,11 128,9 136,8 144,10 152,7 160,6 168,8 176,5 184,4 192,6 200,3" fill = "none" stroke = "#C6820E" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" / >
< linearGradient id = "sparkGrad1" x1 = "0" y1 = "0" x2 = "0" y2 = "1" >
< stop offset = "0%" stop-color = "#C6820E" stop-opacity = "0.2" / >
< stop offset = "100%" stop-color = "#C6820E" stop-opacity = "0" / >
< / linearGradient >
< polyline points = "0,28 8,26 16,24 24,25 32,22 40,20 48,21 56,18 64,16 72,17 80,15 88,13 96,14 104,12 112,10 120,11 128,9 136,8 144,10 152,7 160,6 168,8 176,5 184,4 192,6 200,3 200,32 0,32" fill = "url(#sparkGrad1)" / >
< / svg >
< / div >
< / div >
<!-- Card 2: System Error Rate -->
< div class = "kpi-card card-green" >
< div class = "kpi-label" > System Error Rate< / div >
< div class = "kpi-value-row" >
< span class = "kpi-value val-green" > 0.82%< / span >
< span class = "kpi-trend trend-down-good" > ▼ -0.1%< / span >
< / div >
< div class = "kpi-detail" > < strong > 94< / strong > errors / < strong > 11,482< / strong > total (6h)< / div >
< div class = "kpi-sparkline" >
< svg viewBox = "0 0 200 32" preserveAspectRatio = "none" >
< polyline points = "0,18 8,17 16,19 24,16 32,15 40,17 48,14 56,16 64,13 72,15 80,12 88,14 96,11 104,13 112,10 120,12 128,14 136,11 144,13 152,10 160,12 168,10 176,11 184,9 192,10 200,8" fill = "none" stroke = "#3D7C47" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" / >
< linearGradient id = "sparkGrad2" x1 = "0" y1 = "0" x2 = "0" y2 = "1" >
< stop offset = "0%" stop-color = "#3D7C47" stop-opacity = "0.15" / >
< stop offset = "100%" stop-color = "#3D7C47" stop-opacity = "0" / >
< / linearGradient >
< polyline points = "0,18 8,17 16,19 24,16 32,15 40,17 48,14 56,16 64,13 72,15 80,12 88,14 96,11 104,13 112,10 120,12 128,14 136,11 144,13 152,10 160,12 168,10 176,11 184,9 192,10 200,8 200,32 0,32" fill = "url(#sparkGrad2)" / >
< / svg >
< / div >
< / div >
<!-- Card 3: Avg Latency (P50/P95/P99) -->
< div class = "kpi-card card-warn" >
< div class = "kpi-label" > Latency Percentiles< / div >
< div class = "latency-values" >
< div class = "latency-item" >
< span class = "latency-label" > P50< / span >
< span class = "latency-val lat-green" > 42ms< / span >
< span class = "latency-trend trend-down-good" > ▼ 3< / span >
< / div >
< div class = "latency-item" >
< span class = "latency-label" > P95< / span >
< span class = "latency-val lat-amber" > 187ms< / span >
< span class = "latency-trend trend-up-bad" > ▲ 12< / span >
< / div >
< div class = "latency-item" >
< span class = "latency-label" > P99< / span >
< span class = "latency-val lat-red" > 312ms< / span >
< span class = "latency-trend trend-up-bad" > ▲ 28< / span >
< / div >
< / div >
< div class = "kpi-detail" style = "margin-top:6px;" > SLA: < 300ms P99 · < strong style = "color:var(--error);" > BREACH< / strong > < / div >
< / div >
<!-- Card 4: Active Routes (mini donut) -->
< div class = "kpi-card card-teal" >
< div class = "kpi-label" > Active Routes< / div >
< div class = "kpi-value-row" >
< span class = "kpi-value val-teal" > 7< / span >
< span class = "kpi-unit" > of 8< / span >
< span class = "kpi-trend trend-flat" > ↔ stable< / span >
< / div >
< div class = "mini-donut-wrap" >
< div class = "mini-donut" >
< svg viewBox = "0 0 36 36" width = "40" height = "40" >
< circle cx = "18" cy = "18" r = "15.9" fill = "none" stroke = "var(--bg-inset)" stroke-width = "3" / >
< circle cx = "18" cy = "18" r = "15.9" fill = "none" stroke = "var(--running)" stroke-width = "3"
stroke-dasharray="87.5 12.5" stroke-dashoffset="25" stroke-linecap="round"/>
< / svg >
< span class = "donut-label" > 88%< / span >
< / div >
< div class = "donut-legend" >
< span class = "donut-legend-active" > 7 active< / span >
< span > 1 stopped< / span >
< / div >
< / div >
< / div >
<!-- Card 5: In - Flight Exchanges -->
< div class = "kpi-card card-amber" >
< div class = "kpi-label" > In-Flight Exchanges< / div >
< div class = "kpi-value-row" >
< span class = "kpi-value" > 23< / span >
< span class = "kpi-trend trend-flat" > ↔ < / span >
< / div >
< div class = "kpi-detail" > High-water: < strong > 67< / strong > (2h ago)< / div >
< div class = "kpi-sparkline" >
< svg viewBox = "0 0 200 32" preserveAspectRatio = "none" >
< polyline points = "0,16 8,14 16,18 24,12 32,10 40,15 48,8 56,6 64,4 72,3 80,2 88,4 96,6 104,8 112,10 120,12 128,14 136,16 144,18 152,20 160,18 168,16 176,18 184,20 192,18 200,17" fill = "none" stroke = "#C6820E" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" / >
< linearGradient id = "sparkGrad5" x1 = "0" y1 = "0" x2 = "0" y2 = "1" >
< stop offset = "0%" stop-color = "#C6820E" stop-opacity = "0.15" / >
< stop offset = "100%" stop-color = "#C6820E" stop-opacity = "0" / >
< / linearGradient >
< polyline points = "0,16 8,14 16,18 24,12 32,10 40,15 48,8 56,6 64,4 72,3 80,2 88,4 96,6 104,8 112,10 120,12 128,14 136,16 144,18 152,20 160,18 168,16 176,18 184,20 192,18 200,17 200,32 0,32" fill = "url(#sparkGrad5)" / >
<!-- High - water mark line -->
< line x1 = "78" y1 = "0" x2 = "78" y2 = "32" stroke = "var(--error)" stroke-width = "0.5" stroke-dasharray = "2 2" opacity = "0.5" / >
< text x = "80" y = "7" font-family = "var(--font-mono)" font-size = "6" fill = "var(--error)" opacity = "0.6" > 67< / text >
< / svg >
< / div >
< / div >
< / div >
<!-- ================================================================
PER-ROUTE PERFORMANCE TABLE
================================================================ -->
< div class = "table-section animate-in delay-2" >
< div class = "table-header" >
< span class = "table-title" > Per-Route Performance< / span >
< div class = "table-right" >
< span class = "table-meta" > 8 routes · last 6h< / span >
< / div >
< / div >
< div class = "table-scroll" >
< table >
< thead >
< tr >
< th style = "width:180px" > Route< / th >
< th > Trend (1h)< / th >
< th class = "sorted" > Throughput < span class = "sort-arrow" > ▼ < / span > < / th >
< th > Error Rate< / th >
< th > P50< / th >
< th > P95< / th >
< th > P99< / th >
< th style = "text-align:right" > Success< / th >
< th style = "text-align:right" > Failed< / th >
< th style = "width:80px" > Status< / th >
< th style = "width:30px" > < / th >
< / tr >
< / thead >
< tbody >
<!-- Row 1: order - processing — high throughput, healthy -->
< tr >
< td >
< div class = "route-name-cell" > order-processing< / div >
< div class = "route-group-cell" > order-service< / div >
< / td >
< td >
< span class = "cell-sparkline" >
< svg width = "80" height = "20" viewBox = "0 0 80 20" >
< polyline points = "0,14 8,12 16,10 24,11 32,8 40,9 48,7 56,6 64,7 72,5 80,4" fill = "none" stroke = "var(--chart-1)" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" / >
< / svg >
< / span >
< / td >
< td > < span class = "mono" style = "font-size:12px;font-weight:600;" > 842< / span > < span style = "font-size:10px;color:var(--text-muted);" > msg/min< / span > < / td >
< td > < span class = "err-rate err-low" > 0.2%< / span > < / td >
< td > < span class = "lat-cell lat-fast" > 28ms< / span > < / td >
< td > < span class = "lat-cell lat-fast" > 94ms< / span > < / td >
< td > < span class = "lat-cell lat-normal" > 156ms< / span > < / td >
< td style = "text-align:right" > < span class = "mono" style = "font-size:12px;color:var(--success);" > 4,987< / span > < / td >
< td style = "text-align:right" > < span class = "mono" style = "font-size:12px;color:var(--error);" > 10< / span > < / td >
< td > < span class = "status-badge status-success" > < span class = "status-dot" > < / span > OK< / span > < / td >
< td > < span class = "drill-arrow" > ▸ < / span > < / td >
< / tr >
<!-- Row 2: payment - flow — higher latency, some errors -->
< tr >
< td >
< div class = "route-name-cell" > payment-flow< / div >
< div class = "route-group-cell" > payment-gateway< / div >
< / td >
< td >
< span class = "cell-sparkline" >
< svg width = "80" height = "20" viewBox = "0 0 80 20" >
< polyline points = "0,10 8,11 16,9 24,12 32,10 40,8 48,10 56,9 64,11 72,8 80,7" fill = "none" stroke = "var(--chart-2)" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" / >
< / svg >
< / span >
< / td >
< td > < span class = "mono" style = "font-size:12px;font-weight:600;" > 524< / span > < span style = "font-size:10px;color:var(--text-muted);" > msg/min< / span > < / td >
< td > < span class = "err-rate err-mid" > 2.1%< / span > < / td >
< td > < span class = "lat-cell lat-normal" > 89ms< / span > < / td >
< td > < span class = "lat-cell lat-slow" > 245ms< / span > < / td >
< td > < span class = "lat-cell lat-breach" > 412ms< / span > < / td >
< td style = "text-align:right" > < span class = "mono" style = "font-size:12px;color:var(--success);" > 3,078< / span > < / td >
< td style = "text-align:right" > < span class = "mono" style = "font-size:12px;color:var(--error);" > 66< / span > < / td >
< td > < span class = "status-badge status-warning" > < span class = "status-dot" > < / span > SLA< / span > < / td >
< td > < span class = "drill-arrow" > ▸ < / span > < / td >
< / tr >
<!-- Row 3: shipment - notify — steady, fast -->
< tr >
< td >
< div class = "route-name-cell" > shipment-notify< / div >
< div class = "route-group-cell" > shipment-tracker< / div >
< / td >
< td >
< span class = "cell-sparkline" >
< svg width = "80" height = "20" viewBox = "0 0 80 20" >
< polyline points = "0,10 8,10 16,9 24,10 32,10 40,9 48,10 56,9 64,10 72,9 80,10" fill = "none" stroke = "var(--chart-3)" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" / >
< / svg >
< / span >
< / td >
< td > < span class = "mono" style = "font-size:12px;font-weight:600;" > 471< / span > < span style = "font-size:10px;color:var(--text-muted);" > msg/min< / span > < / td >
< td > < span class = "err-rate err-low" > 0.1%< / span > < / td >
< td > < span class = "lat-cell lat-fast" > 18ms< / span > < / td >
< td > < span class = "lat-cell lat-fast" > 52ms< / span > < / td >
< td > < span class = "lat-cell lat-fast" > 87ms< / span > < / td >
< td style = "text-align:right" > < span class = "mono" style = "font-size:12px;color:var(--success);" > 2,823< / span > < / td >
< td style = "text-align:right" > < span class = "mono" style = "font-size:12px;color:var(--error);" > 3< / span > < / td >
< td > < span class = "status-badge status-success" > < span class = "status-dot" > < / span > OK< / span > < / td >
< td > < span class = "drill-arrow" > ▸ < / span > < / td >
< / tr >
<!-- Row 4: inventory - check — fast, low volume -->
< tr >
< td >
< div class = "route-name-cell" > inventory-check< / div >
< div class = "route-group-cell" > order-service< / div >
< / td >
< td >
< span class = "cell-sparkline" >
< svg width = "80" height = "20" viewBox = "0 0 80 20" >
< polyline points = "0,12 8,13 16,11 24,14 32,12 40,13 48,11 56,12 64,13 72,11 80,12" fill = "none" stroke = "var(--chart-4)" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" / >
< / svg >
< / span >
< / td >
< td > < span class = "mono" style = "font-size:12px;font-weight:600;" > 389< / span > < span style = "font-size:10px;color:var(--text-muted);" > msg/min< / span > < / td >
< td > < span class = "err-rate err-low" > 0.3%< / span > < / td >
< td > < span class = "lat-cell lat-fast" > 12ms< / span > < / td >
< td > < span class = "lat-cell lat-fast" > 34ms< / span > < / td >
< td > < span class = "lat-cell lat-fast" > 61ms< / span > < / td >
< td style = "text-align:right" > < span class = "mono" style = "font-size:12px;color:var(--success);" > 2,327< / span > < / td >
< td style = "text-align:right" > < span class = "mono" style = "font-size:12px;color:var(--error);" > 7< / span > < / td >
< td > < span class = "status-badge status-success" > < span class = "status-dot" > < / span > OK< / span > < / td >
< td > < span class = "drill-arrow" > ▸ < / span > < / td >
< / tr >
<!-- Row 5: customer - validation — moderate, stable -->
< tr >
< td >
< div class = "route-name-cell" > customer-validation< / div >
< div class = "route-group-cell" > order-service< / div >
< / td >
< td >
< span class = "cell-sparkline" >
< svg width = "80" height = "20" viewBox = "0 0 80 20" >
< polyline points = "0,8 8,10 16,9 24,8 32,10 40,9 48,10 56,8 64,9 72,10 80,9" fill = "none" stroke = "var(--chart-5)" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" / >
< / svg >
< / span >
< / td >
< td > < span class = "mono" style = "font-size:12px;font-weight:600;" > 312< / span > < span style = "font-size:10px;color:var(--text-muted);" > msg/min< / span > < / td >
< td > < span class = "err-rate err-low" > 0.5%< / span > < / td >
< td > < span class = "lat-cell lat-fast" > 35ms< / span > < / td >
< td > < span class = "lat-cell lat-normal" > 112ms< / span > < / td >
< td > < span class = "lat-cell lat-normal" > 178ms< / span > < / td >
< td style = "text-align:right" > < span class = "mono" style = "font-size:12px;color:var(--success);" > 1,863< / span > < / td >
< td style = "text-align:right" > < span class = "mono" style = "font-size:12px;color:var(--error);" > 9< / span > < / td >
< td > < span class = "status-badge status-success" > < span class = "status-dot" > < / span > OK< / span > < / td >
< td > < span class = "drill-arrow" > ▸ < / span > < / td >
< / tr >
<!-- Row 6: email - notification — low volume, very fast -->
< tr >
< td >
< div class = "route-name-cell" > email-notification< / div >
< div class = "route-group-cell" > notification-hub< / div >
< / td >
< td >
< span class = "cell-sparkline" >
< svg width = "80" height = "20" viewBox = "0 0 80 20" >
< polyline points = "0,14 8,15 16,13 24,14 32,16 40,14 48,15 56,13 64,14 72,15 80,14" fill = "none" stroke = "var(--chart-6)" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" / >
< / svg >
< / span >
< / td >
< td > < span class = "mono" style = "font-size:12px;font-weight:600;" > 201< / span > < span style = "font-size:10px;color:var(--text-muted);" > msg/min< / span > < / td >
< td > < span class = "err-rate err-low" > 0.0%< / span > < / td >
< td > < span class = "lat-cell lat-fast" > 8ms< / span > < / td >
< td > < span class = "lat-cell lat-fast" > 21ms< / span > < / td >
< td > < span class = "lat-cell lat-fast" > 38ms< / span > < / td >
< td style = "text-align:right" > < span class = "mono" style = "font-size:12px;color:var(--success);" > 1,206< / span > < / td >
< td style = "text-align:right" > < span class = "mono" style = "font-size:12px;color:var(--text-faint);" > 0< / span > < / td >
< td > < span class = "status-badge status-success" > < span class = "status-dot" > < / span > OK< / span > < / td >
< td > < span class = "drill-arrow" > ▸ < / span > < / td >
< / tr >
<!-- Row 7: retry - handler — low throughput, HIGH error rate -->
< tr >
< td >
< div class = "route-name-cell" > retry-handler< / div >
< div class = "route-group-cell" > order-service< / div >
< / td >
< td >
< span class = "cell-sparkline" >
< svg width = "80" height = "20" viewBox = "0 0 80 20" >
< polyline points = "0,10 8,8 16,12 24,6 32,10 40,4 48,8 56,6 64,10 72,4 80,8" fill = "none" stroke = "var(--chart-7)" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" / >
< / svg >
< / span >
< / td >
< td > < span class = "mono" style = "font-size:12px;font-weight:600;" > 78< / span > < span style = "font-size:10px;color:var(--text-muted);" > msg/min< / span > < / td >
< td > < span class = "err-rate err-high" > 12.8%< / span > < / td >
< td > < span class = "lat-cell lat-slow" > 210ms< / span > < / td >
< td > < span class = "lat-cell lat-breach" > 890ms< / span > < / td >
< td > < span class = "lat-cell lat-breach" > 2,140ms< / span > < / td >
< td style = "text-align:right" > < span class = "mono" style = "font-size:12px;color:var(--success);" > 408< / span > < / td >
< td style = "text-align:right" > < span class = "mono" style = "font-size:12px;color:var(--error);" > 60< / span > < / td >
< td > < span class = "status-badge status-warning" > < span class = "status-dot" > < / span > WARN< / span > < / td >
< td > < span class = "drill-arrow" > ▸ < / span > < / td >
< / tr >
<!-- Row 8: dead - letter - processor — very low, all "errors" -->
< tr >
< td >
< div class = "route-name-cell" style = "color:var(--text-muted);" > dead-letter-processor< / div >
< div class = "route-group-cell" > order-service< / div >
< / td >
< td >
< span class = "cell-sparkline" >
< svg width = "80" height = "20" viewBox = "0 0 80 20" >
< polyline points = "0,16 8,16 16,14 24,16 32,15 40,16 48,14 56,16 64,15 72,16 80,16" fill = "none" stroke = "var(--text-faint)" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" stroke-dasharray = "3 2" / >
< / svg >
< / span >
< / td >
< td > < span class = "mono" style = "font-size:12px;color:var(--text-muted);" > 0< / span > < span style = "font-size:10px;color:var(--text-faint);" > msg/min< / span > < / td >
< td > < span class = "err-rate" style = "color:var(--text-faint);" > -- %< / span > < / td >
< td > < span class = "lat-cell" style = "color:var(--text-faint);" > --< / span > < / td >
< td > < span class = "lat-cell" style = "color:var(--text-faint);" > --< / span > < / td >
< td > < span class = "lat-cell" style = "color:var(--text-faint);" > --< / span > < / td >
< td style = "text-align:right" > < span class = "mono" style = "font-size:12px;color:var(--text-faint);" > 0< / span > < / td >
< td style = "text-align:right" > < span class = "mono" style = "font-size:12px;color:var(--text-faint);" > 0< / span > < / td >
< td > < span class = "status-badge" style = "background:var(--bg-inset);color:var(--text-faint);border:1px solid var(--border);" > < span class = "status-dot" > < / span > STOP< / span > < / td >
< td > < span class = "drill-arrow" > ▸ < / span > < / td >
< / tr >
< / tbody >
< / table >
< / div >
< / div >
<!-- ================================================================
TIME-SERIES CHARTS (2x2 grid)
================================================================ -->
< div class = "chart-grid animate-in delay-3" >
<!-- Chart 1: Throughput Over Time (stacked area) -->
< div class = "chart-card" >
< div class = "chart-card-header" >
< div >
< div class = "chart-card-title" > Throughput Over Time< / div >
< div class = "chart-card-subtitle" > Messages/min by route · last 6h< / div >
< / div >
< / div >
< div class = "chart-body" >
< svg viewBox = "0 0 600 180" preserveAspectRatio = "none" >
<!-- Grid lines -->
< line x1 = "40" y1 = "10" x2 = "580" y2 = "10" class = "chart-grid-line" / >
< line x1 = "40" y1 = "52" x2 = "580" y2 = "52" class = "chart-grid-line" / >
< line x1 = "40" y1 = "94" x2 = "580" y2 = "94" class = "chart-grid-line" / >
< line x1 = "40" y1 = "136" x2 = "580" y2 = "136" class = "chart-grid-line" / >
< line x1 = "40" y1 = "170" x2 = "580" y2 = "170" class = "chart-axis-line" / >
<!-- Y - axis labels -->
< text x = "36" y = "14" class = "chart-label" text-anchor = "end" > 3k< / text >
< text x = "36" y = "56" class = "chart-label" text-anchor = "end" > 2k< / text >
< text x = "36" y = "98" class = "chart-label" text-anchor = "end" > 1k< / text >
< text x = "36" y = "140" class = "chart-label" text-anchor = "end" > 500< / text >
< text x = "36" y = "174" class = "chart-label" text-anchor = "end" > 0< / text >
<!-- X - axis labels -->
< text x = "40" y = "180" class = "chart-label" > 03:00< / text >
< text x = "130" y = "180" class = "chart-label" > 04:00< / text >
< text x = "220" y = "180" class = "chart-label" > 05:00< / text >
< text x = "310" y = "180" class = "chart-label" > 06:00< / text >
< text x = "400" y = "180" class = "chart-label" > 07:00< / text >
< text x = "490" y = "180" class = "chart-label" > 08:00< / text >
< text x = "565" y = "180" class = "chart-label" > 09:14< / text >
<!-- Stacked areas (bottom to top: email, inventory, shipment, customer, payment, order) -->
<!-- Layer 1: order - processing (amber) -->
< polygon points = "40,170 85,165 130,160 175,155 220,145 265,130 310,105 355,90 400,75 445,65 490,55 535,50 580,45 580,170" fill = "#C6820E" opacity = "0.3" / >
< polyline points = "40,170 85,165 130,160 175,155 220,145 265,130 310,105 355,90 400,75 445,65 490,55 535,50 580,45" fill = "none" stroke = "#C6820E" stroke-width = "1.5" / >
<!-- Layer 2: payment - flow (olive green) -->
< polygon points = "40,170 85,168 130,166 175,163 220,158 265,150 310,135 355,125 400,112 445,104 490,96 535,92 580,88 580,170" fill = "#3D7C47" opacity = "0.25" / >
< polyline points = "40,170 85,168 130,166 175,163 220,158 265,150 310,135 355,125 400,112 445,104 490,96 535,92 580,88" fill = "none" stroke = "#3D7C47" stroke-width = "1.5" / >
<!-- Layer 3: shipment - notify (teal) -->
< polygon points = "40,170 85,169 130,168 175,167 220,164 265,158 310,148 355,140 400,132 445,126 490,120 535,117 580,114 580,170" fill = "#1A7F8E" opacity = "0.25" / >
< polyline points = "40,170 85,169 130,168 175,167 220,164 265,158 310,148 355,140 400,132 445,126 490,120 535,117 580,114" fill = "none" stroke = "#1A7F8E" stroke-width = "1.5" / >
<!-- Layer 4: other routes combined (burnt orange, thin) -->
< polygon points = "40,170 85,169 130,169 175,168 220,167 265,164 310,158 355,152 400,146 445,141 490,136 535,134 580,132 580,170" fill = "#C27516" opacity = "0.15" / >
< polyline points = "40,170 85,169 130,169 175,168 220,167 265,164 310,158 355,152 400,146 445,141 490,136 535,134 580,132" fill = "none" stroke = "#C27516" stroke-width = "1" stroke-dasharray = "3 2" / >
< / svg >
< / div >
< div class = "chart-legend" >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--chart-1);" > < / div > order-processing< / div >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--chart-2);" > < / div > payment-flow< / div >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--chart-3);" > < / div > shipment-notify< / div >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--chart-4);" > < / div > others (combined)< / div >
< / div >
< / div >
<!-- Chart 2: Latency Percentiles (line chart) -->
< div class = "chart-card" >
< div class = "chart-card-header" >
< div >
< div class = "chart-card-title" > Latency Percentiles< / div >
< div class = "chart-card-subtitle" > P50 / P95 / P99 · last 6h< / div >
< / div >
< / div >
< div class = "chart-body" >
< svg viewBox = "0 0 600 180" preserveAspectRatio = "none" >
<!-- Grid -->
< line x1 = "40" y1 = "10" x2 = "580" y2 = "10" class = "chart-grid-line" / >
< line x1 = "40" y1 = "52" x2 = "580" y2 = "52" class = "chart-grid-line" / >
< line x1 = "40" y1 = "94" x2 = "580" y2 = "94" class = "chart-grid-line" / >
< line x1 = "40" y1 = "136" x2 = "580" y2 = "136" class = "chart-grid-line" / >
< line x1 = "40" y1 = "170" x2 = "580" y2 = "170" class = "chart-axis-line" / >
<!-- Y - axis (ms) -->
< text x = "36" y = "14" class = "chart-label" text-anchor = "end" > 500< / text >
< text x = "36" y = "56" class = "chart-label" text-anchor = "end" > 375< / text >
< text x = "36" y = "98" class = "chart-label" text-anchor = "end" > 250< / text >
< text x = "36" y = "140" class = "chart-label" text-anchor = "end" > 125< / text >
< text x = "36" y = "174" class = "chart-label" text-anchor = "end" > 0< / text >
<!-- X - axis -->
< text x = "40" y = "180" class = "chart-label" > 03:00< / text >
< text x = "130" y = "180" class = "chart-label" > 04:00< / text >
< text x = "220" y = "180" class = "chart-label" > 05:00< / text >
< text x = "310" y = "180" class = "chart-label" > 06:00< / text >
< text x = "400" y = "180" class = "chart-label" > 07:00< / text >
< text x = "490" y = "180" class = "chart-label" > 08:00< / text >
< text x = "565" y = "180" class = "chart-label" > 09:14< / text >
<!-- SLA threshold at 300ms = y ~74 -->
< rect x = "40" y = "10" width = "540" height = "64" class = "sla-zone" / >
< line x1 = "40" y1 = "74" x2 = "580" y2 = "74" class = "sla-line" / >
< text x = "582" y = "72" class = "sla-label-text" > SLA 300ms< / text >
<!-- P99 line (red, most volatile) -->
< polyline points = "40,100 85,95 130,88 175,82 220,78 265,72 310,68 355,62 400,58 445,55 490,52 535,48 580,42" fill = "none" stroke = "var(--error)" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" / >
<!-- P95 line (amber) -->
< polyline points = "40,128 85,126 130,122 175,118 220,114 265,110 310,106 355,102 400,98 445,96 490,94 535,92 580,90" fill = "none" stroke = "var(--warning)" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" / >
<!-- P50 line (green, stable at bottom) -->
< polyline points = "40,158 85,157 130,156 175,155 220,154 265,153 310,152 355,152 400,151 445,150 490,150 535,149 580,148" fill = "none" stroke = "var(--success)" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" / >
<!-- Current value labels -->
< text x = "582" y = "44" class = "chart-value-label" fill = "var(--error)" > 312ms< / text >
< text x = "582" y = "92" class = "chart-value-label" fill = "var(--warning)" > 187ms< / text >
< text x = "582" y = "150" class = "chart-value-label" fill = "var(--success)" > 42ms< / text >
< / svg >
< / div >
< div class = "chart-legend" >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--success);" > < / div > P50< / div >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--warning);" > < / div > P95< / div >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--error);" > < / div > P99< / div >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--error); opacity:0.3;" > < / div > SLA threshold (300ms)< / div >
< / div >
< / div >
<!-- Chart 3: Error Rate by Route (stacked bar chart) -->
< div class = "chart-card" >
< div class = "chart-card-header" >
< div >
< div class = "chart-card-title" > Errors by Route< / div >
< div class = "chart-card-subtitle" > 15-min buckets · last 6h< / div >
< / div >
< / div >
< div class = "chart-body" >
< svg viewBox = "0 0 600 180" preserveAspectRatio = "none" >
<!-- Grid -->
< line x1 = "40" y1 = "10" x2 = "580" y2 = "10" class = "chart-grid-line" / >
< line x1 = "40" y1 = "52" x2 = "580" y2 = "52" class = "chart-grid-line" / >
< line x1 = "40" y1 = "94" x2 = "580" y2 = "94" class = "chart-grid-line" / >
< line x1 = "40" y1 = "136" x2 = "580" y2 = "136" class = "chart-grid-line" / >
< line x1 = "40" y1 = "170" x2 = "580" y2 = "170" class = "chart-axis-line" / >
<!-- Y - axis -->
< text x = "36" y = "14" class = "chart-label" text-anchor = "end" > 20< / text >
< text x = "36" y = "56" class = "chart-label" text-anchor = "end" > 15< / text >
< text x = "36" y = "98" class = "chart-label" text-anchor = "end" > 10< / text >
< text x = "36" y = "140" class = "chart-label" text-anchor = "end" > 5< / text >
< text x = "36" y = "174" class = "chart-label" text-anchor = "end" > 0< / text >
<!-- X - axis -->
< text x = "40" y = "180" class = "chart-label" > 03:00< / text >
< text x = "130" y = "180" class = "chart-label" > 04:00< / text >
< text x = "220" y = "180" class = "chart-label" > 05:00< / text >
< text x = "310" y = "180" class = "chart-label" > 06:00< / text >
< text x = "400" y = "180" class = "chart-label" > 07:00< / text >
< text x = "490" y = "180" class = "chart-label" > 08:00< / text >
< text x = "565" y = "180" class = "chart-label" > 09:14< / text >
<!-- Stacked bars (24 bars over 6 hours, 15 - min each) -->
<!-- Low activity early morning (03:00 - 06:00) -->
<!-- Bar group 1: 03:00 -->
< rect x = "44" y = "162" width = "16" height = "8" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
< rect x = "44" y = "166" width = "16" height = "4" rx = "1" fill = "var(--chart-2)" opacity = "0.6" / >
<!-- 03:15 -->
< rect x = "66" y = "166" width = "16" height = "4" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
<!-- 03:30 -->
< rect x = "88" y = "162" width = "16" height = "8" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
<!-- 03:45 -->
< rect x = "110" y = "164" width = "16" height = "6" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
<!-- 04:00 -->
< rect x = "132" y = "158" width = "16" height = "12" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
< rect x = "132" y = "162" width = "16" height = "8" rx = "1" fill = "var(--chart-1)" opacity = "0.5" / >
<!-- 04:15 -->
< rect x = "154" y = "164" width = "16" height = "6" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
<!-- 04:30 -->
< rect x = "176" y = "160" width = "16" height = "10" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
<!-- 04:45 -->
< rect x = "198" y = "162" width = "16" height = "8" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
<!-- 05:00 -->
< rect x = "220" y = "158" width = "16" height = "12" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
< rect x = "220" y = "164" width = "16" height = "6" rx = "1" fill = "var(--chart-2)" opacity = "0.6" / >
<!-- 05:15 -->
< rect x = "242" y = "164" width = "16" height = "6" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
<!-- 05:30 -->
< rect x = "264" y = "162" width = "16" height = "8" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
<!-- 05:45 -->
< rect x = "286" y = "160" width = "16" height = "10" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
<!-- 06:00 — shift start, traffic ramp up -->
< rect x = "308" y = "148" width = "16" height = "22" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
< rect x = "308" y = "154" width = "16" height = "16" rx = "1" fill = "var(--chart-2)" opacity = "0.6" / >
< rect x = "308" y = "160" width = "16" height = "10" rx = "1" fill = "var(--chart-1)" opacity = "0.5" / >
<!-- 06:15 -->
< rect x = "330" y = "152" width = "16" height = "18" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
< rect x = "330" y = "158" width = "16" height = "12" rx = "1" fill = "var(--chart-2)" opacity = "0.6" / >
<!-- 06:30 -->
< rect x = "352" y = "144" width = "16" height = "26" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
< rect x = "352" y = "150" width = "16" height = "20" rx = "1" fill = "var(--chart-2)" opacity = "0.6" / >
< rect x = "352" y = "158" width = "16" height = "12" rx = "1" fill = "var(--chart-1)" opacity = "0.5" / >
<!-- 06:45 -->
< rect x = "374" y = "150" width = "16" height = "20" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
< rect x = "374" y = "156" width = "16" height = "14" rx = "1" fill = "var(--chart-2)" opacity = "0.6" / >
<!-- 07:00 — peak errors -->
< rect x = "396" y = "130" width = "16" height = "40" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
< rect x = "396" y = "140" width = "16" height = "30" rx = "1" fill = "var(--chart-2)" opacity = "0.6" / >
< rect x = "396" y = "150" width = "16" height = "20" rx = "1" fill = "var(--chart-1)" opacity = "0.5" / >
< rect x = "396" y = "158" width = "16" height = "12" rx = "1" fill = "var(--chart-3)" opacity = "0.5" / >
<!-- 07:15 -->
< rect x = "418" y = "138" width = "16" height = "32" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
< rect x = "418" y = "146" width = "16" height = "24" rx = "1" fill = "var(--chart-2)" opacity = "0.6" / >
< rect x = "418" y = "156" width = "16" height = "14" rx = "1" fill = "var(--chart-1)" opacity = "0.5" / >
<!-- 07:30 -->
< rect x = "440" y = "142" width = "16" height = "28" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
< rect x = "440" y = "150" width = "16" height = "20" rx = "1" fill = "var(--chart-2)" opacity = "0.6" / >
<!-- 07:45 -->
< rect x = "462" y = "148" width = "16" height = "22" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
< rect x = "462" y = "156" width = "16" height = "14" rx = "1" fill = "var(--chart-2)" opacity = "0.6" / >
<!-- 08:00 -->
< rect x = "484" y = "144" width = "16" height = "26" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
< rect x = "484" y = "152" width = "16" height = "18" rx = "1" fill = "var(--chart-2)" opacity = "0.6" / >
< rect x = "484" y = "160" width = "16" height = "10" rx = "1" fill = "var(--chart-1)" opacity = "0.5" / >
<!-- 08:15 -->
< rect x = "506" y = "150" width = "16" height = "20" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
< rect x = "506" y = "158" width = "16" height = "12" rx = "1" fill = "var(--chart-2)" opacity = "0.6" / >
<!-- 08:30 -->
< rect x = "528" y = "154" width = "16" height = "16" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
< rect x = "528" y = "160" width = "16" height = "10" rx = "1" fill = "var(--chart-2)" opacity = "0.6" / >
<!-- 08:45 -->
< rect x = "550" y = "156" width = "16" height = "14" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
< rect x = "550" y = "162" width = "16" height = "8" rx = "1" fill = "var(--chart-2)" opacity = "0.6" / >
<!-- 09:00 (current, partial) -->
< rect x = "572" y = "158" width = "8" height = "12" rx = "1" fill = "var(--chart-7)" opacity = "0.8" / >
< rect x = "572" y = "162" width = "8" height = "8" rx = "1" fill = "var(--chart-2)" opacity = "0.6" / >
<!-- Total errors overlay line -->
< polyline points = "52,164 74,168 96,164 118,166 140,160 162,166 184,162 206,164 228,160 250,166 272,164 294,162 316,150 338,154 360,146 382,152 404,132 426,140 448,144 470,150 492,146 514,152 536,156 558,158 576,160" fill = "none" stroke = "var(--text-secondary)" stroke-width = "1.5" stroke-dasharray = "4 2" stroke-linecap = "round" stroke-linejoin = "round" / >
< / svg >
< / div >
< div class = "chart-legend" >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--chart-7);" > < / div > retry-handler< / div >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--chart-2);" > < / div > payment-flow< / div >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--chart-1);" > < / div > order-processing< / div >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--chart-3);" > < / div > shipment-notify< / div >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--text-secondary); height:1px; border-top: 1.5px dashed var(--text-secondary); width:12px;" > < / div > total (overlay)< / div >
< / div >
< / div >
<!-- Chart 4: Exchange Volume Heatmap -->
< div class = "chart-card" >
< div class = "chart-card-header" >
< div >
< div class = "chart-card-title" > Exchange Volume Heatmap< / div >
< div class = "chart-card-subtitle" > Hourly volume by route · last 6h< / div >
< / div >
< / div >
< div class = "chart-body" style = "padding: 12px 16px;" >
< div class = "heatmap-grid" >
<!-- Row: order - processing -->
< div class = "heatmap-row" >
< div class = "heatmap-label" > order-proc< / div >
< div class = "heatmap-cells" >
< div class = "heatmap-cell heat-2" title = "03:00 — 142" > < / div >
< div class = "heatmap-cell heat-3" title = "04:00 — 287" > < / div >
< div class = "heatmap-cell heat-4" title = "05:00 — 456" > < / div >
< div class = "heatmap-cell heat-7" title = "06:00 — 812" > < / div >
< div class = "heatmap-cell heat-8" title = "07:00 — 923" > < / div >
< div class = "heatmap-cell heat-8" title = "08:00 — 891" > < / div >
< div class = "heatmap-cell heat-6" title = "09:00 — 653 (partial)" > < / div >
< / div >
< / div >
<!-- Row: payment - flow -->
< div class = "heatmap-row" >
< div class = "heatmap-label" > payment-flow< / div >
< div class = "heatmap-cells" >
< div class = "heatmap-cell heat-1" title = "03:00 — 68" > < / div >
< div class = "heatmap-cell heat-2" title = "04:00 — 134" > < / div >
< div class = "heatmap-cell heat-3" title = "05:00 — 245" > < / div >
< div class = "heatmap-cell heat-6" title = "06:00 — 587" > < / div >
< div class = "heatmap-cell heat-7" title = "07:00 — 712" > < / div >
< div class = "heatmap-cell heat-6" title = "08:00 — 645" > < / div >
< div class = "heatmap-cell heat-5" title = "09:00 — 401 (partial)" > < / div >
< / div >
< / div >
<!-- Row: shipment - notify -->
< div class = "heatmap-row" >
< div class = "heatmap-label" > ship-notify< / div >
< div class = "heatmap-cells" >
< div class = "heatmap-cell heat-1" title = "03:00 — 52" > < / div >
< div class = "heatmap-cell heat-2" title = "04:00 — 118" > < / div >
< div class = "heatmap-cell heat-3" title = "05:00 — 210" > < / div >
< div class = "heatmap-cell heat-5" title = "06:00 — 478" > < / div >
< div class = "heatmap-cell heat-6" title = "07:00 — 534" > < / div >
< div class = "heatmap-cell heat-6" title = "08:00 — 521" > < / div >
< div class = "heatmap-cell heat-4" title = "09:00 — 341 (partial)" > < / div >
< / div >
< / div >
<!-- Row: inventory - check -->
< div class = "heatmap-row" >
< div class = "heatmap-label" > inv-check< / div >
< div class = "heatmap-cells" >
< div class = "heatmap-cell heat-1" title = "03:00 — 38" > < / div >
< div class = "heatmap-cell heat-2" title = "04:00 — 97" > < / div >
< div class = "heatmap-cell heat-2" title = "05:00 — 178" > < / div >
< div class = "heatmap-cell heat-5" title = "06:00 — 412" > < / div >
< div class = "heatmap-cell heat-5" title = "07:00 — 467" > < / div >
< div class = "heatmap-cell heat-5" title = "08:00 — 445" > < / div >
< div class = "heatmap-cell heat-4" title = "09:00 — 298 (partial)" > < / div >
< / div >
< / div >
<!-- Row: customer - validation -->
< div class = "heatmap-row" >
< div class = "heatmap-label" > cust-valid< / div >
< div class = "heatmap-cells" >
< div class = "heatmap-cell heat-1" title = "03:00 — 24" > < / div >
< div class = "heatmap-cell heat-1" title = "04:00 — 76" > < / div >
< div class = "heatmap-cell heat-2" title = "05:00 — 145" > < / div >
< div class = "heatmap-cell heat-4" title = "06:00 — 356" > < / div >
< div class = "heatmap-cell heat-5" title = "07:00 — 398" > < / div >
< div class = "heatmap-cell heat-4" title = "08:00 — 367" > < / div >
< div class = "heatmap-cell heat-3" title = "09:00 — 234 (partial)" > < / div >
< / div >
< / div >
<!-- Row: email - notification -->
< div class = "heatmap-row" >
< div class = "heatmap-label" > email-notif< / div >
< div class = "heatmap-cells" >
< div class = "heatmap-cell heat-0" title = "03:00 — 8" > < / div >
< div class = "heatmap-cell heat-1" title = "04:00 — 42" > < / div >
< div class = "heatmap-cell heat-1" title = "05:00 — 78" > < / div >
< div class = "heatmap-cell heat-3" title = "06:00 — 245" > < / div >
< div class = "heatmap-cell heat-4" title = "07:00 — 312" > < / div >
< div class = "heatmap-cell heat-3" title = "08:00 — 278" > < / div >
< div class = "heatmap-cell heat-2" title = "09:00 — 167 (partial)" > < / div >
< / div >
< / div >
<!-- Row: retry - handler -->
< div class = "heatmap-row" >
< div class = "heatmap-label" > retry-hdlr< / div >
< div class = "heatmap-cells" >
< div class = "heatmap-cell heat-0" title = "03:00 — 4" > < / div >
< div class = "heatmap-cell heat-1" title = "04:00 — 12" > < / div >
< div class = "heatmap-cell heat-1" title = "05:00 — 18" > < / div >
< div class = "heatmap-cell heat-2" title = "06:00 — 89" > < / div >
< div class = "heatmap-cell heat-3" title = "07:00 — 134" > < / div >
< div class = "heatmap-cell heat-2" title = "08:00 — 112" > < / div >
< div class = "heatmap-cell heat-1" title = "09:00 — 67 (partial)" > < / div >
< / div >
< / div >
<!-- Row: dead - letter - processor -->
< div class = "heatmap-row" >
< div class = "heatmap-label" style = "color:var(--text-faint);" > dead-letter< / div >
< div class = "heatmap-cells" >
< div class = "heatmap-cell heat-0" title = "03:00 — 0" > < / div >
< div class = "heatmap-cell heat-0" title = "04:00 — 0" > < / div >
< div class = "heatmap-cell heat-0" title = "05:00 — 0" > < / div >
< div class = "heatmap-cell heat-0" title = "06:00 — 0" > < / div >
< div class = "heatmap-cell heat-0" title = "07:00 — 0" > < / div >
< div class = "heatmap-cell heat-0" title = "08:00 — 0" > < / div >
< div class = "heatmap-cell heat-0" title = "09:00 — 0" > < / div >
< / div >
< / div >
< / div >
< div class = "heatmap-hour-labels" >
< div class = "heatmap-hour-label" > 03:00< / div >
< div class = "heatmap-hour-label" > 04:00< / div >
< div class = "heatmap-hour-label" > 05:00< / div >
< div class = "heatmap-hour-label" > 06:00< / div >
< div class = "heatmap-hour-label" > 07:00< / div >
< div class = "heatmap-hour-label" > 08:00< / div >
< div class = "heatmap-hour-label" > 09:00< / div >
< / div >
< / div >
< div class = "heatmap-scale" >
< span class = "heatmap-scale-label" > Low< / span >
< div class = "heatmap-scale-cells" >
< div class = "heatmap-scale-cell heat-0" > < / div >
< div class = "heatmap-scale-cell heat-1" > < / div >
< div class = "heatmap-scale-cell heat-2" > < / div >
< div class = "heatmap-scale-cell heat-3" > < / div >
< div class = "heatmap-scale-cell heat-4" > < / div >
< div class = "heatmap-scale-cell heat-5" > < / div >
< div class = "heatmap-scale-cell heat-6" > < / div >
< div class = "heatmap-scale-cell heat-7" > < / div >
< div class = "heatmap-scale-cell heat-8" > < / div >
< / div >
< span class = "heatmap-scale-label" > High< / span >
< / div >
< / div >
< / div >
<!-- ================================================================
LIVE EVENT FEED
================================================================ -->
< div class = "live-feed-section animate-in delay-4" >
< div class = "live-feed-header" >
< div class = "live-feed-title" >
< span class = "live-feed-dot" > < / span >
Live Event Feed
< / div >
< span class = "table-meta" > Last 25 events< / span >
< / div >
< div class = "live-feed-list" >
< div class = "feed-item" >
< div class = "feed-icon feed-error" > ⚠ < / div >
< div class = "feed-content" >
< div class = "feed-text" > < strong > Threshold breached< / strong > — P99 latency exceeded SLA on < span class = "feed-route" > payment-flow< / span > (312ms > 300ms)< / div >
< div class = "feed-meta" > 09:12:44 · 2 min ago< / div >
< / div >
< / div >
< div class = "feed-item" >
< div class = "feed-icon feed-error" > ✕ < / div >
< div class = "feed-content" >
< div class = "feed-text" > < strong > Error< / strong > — < span class = "feed-route" > retry-handler< / span > exhausted retries: CamelExecutionException after 3 attempts< / div >
< div class = "feed-meta" > 09:11:18 · 3 min ago< / div >
< / div >
< / div >
< div class = "feed-item" >
< div class = "feed-icon feed-warn" > △ < / div >
< div class = "feed-content" >
< div class = "feed-text" > < strong > Warning< / strong > — < span class = "feed-route" > payment-flow< / span > latency spike: P95 jumped from 198ms to 245ms< / div >
< div class = "feed-meta" > 09:08:32 · 6 min ago< / div >
< / div >
< / div >
< div class = "feed-item" >
< div class = "feed-icon feed-error" > ✕ < / div >
< div class = "feed-content" >
< div class = "feed-text" > < strong > Error< / strong > — < span class = "feed-route" > payment-flow< / span > HTTP 504 from payment gateway (timeout after 5000ms)< / div >
< div class = "feed-meta" > 09:06:11 · 8 min ago< / div >
< / div >
< / div >
< div class = "feed-item" >
< div class = "feed-icon feed-ok" > ✓ < / div >
< div class = "feed-content" >
< div class = "feed-text" > < strong > Recovery< / strong > — < span class = "feed-route" > order-processing< / span > error rate returned below 1% threshold< / div >
< div class = "feed-meta" > 09:04:55 · 10 min ago< / div >
< / div >
< / div >
< div class = "feed-item" >
< div class = "feed-icon feed-info" > ⓘ < / div >
< div class = "feed-content" >
< div class = "feed-text" > < strong > In-flight high-water< / strong > — 67 concurrent exchanges (capacity 78%). Auto-scaled connection pool.< / div >
< div class = "feed-meta" > 07:22:10 · 1h 52m ago< / div >
< / div >
< / div >
< div class = "feed-item" >
< div class = "feed-icon feed-info" > ▶ < / div >
< div class = "feed-content" >
< div class = "feed-text" > < strong > Shift started< / strong > — Day shift (06:00-18:00). Metrics counters reset.< / div >
< div class = "feed-meta" > 06:00:00 · 3h 14m ago< / div >
< / div >
< / div >
< div class = "feed-item" >
< div class = "feed-icon feed-warn" > △ < / div >
< div class = "feed-content" >
< div class = "feed-text" > < strong > Route stopped< / strong > — < span class = "feed-route" > dead-letter-processor< / span > manually stopped by operator hendrik< / div >
< div class = "feed-meta" > 05:48:22 · 3h 26m ago< / div >
< / div >
< / div >
< / div >
< / div >
< / div > <!-- /content -->
< / div > <!-- /main -->
< / div > <!-- /app -->
< / body >
< / html >