Rename Java packages from com.cameleer3 to com.cameleer, module directories from cameleer3-* to cameleer-*, and all references throughout workflows, Dockerfiles, docs, migrations, and pom.xml. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2178 lines
91 KiB
HTML
2178 lines
91 KiB
HTML
<!--
|
|
============================================================================
|
|
CAMELEER v3 — Metrics & KPI Dashboard (Light Theme)
|
|
============================================================================
|
|
|
|
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">
|
|
<title>Cameleer — Metrics Dashboard</title>
|
|
<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.2834,-13.56192 41.5,-17.32336 3.85,-1.1854 7.9,-2.55654 9,-3.04699 6.0644,-2.70386 29.8837,-6.94397 39.0685,-6.95465 16.5354,-0.0192 29.4209,10.53866 32.4491,26.58755 1.5763,8.35375 1.0812,11.63345 -4.5147,29.90655 -10.1916,33.28037 -13.3229,61.96134 -10.0442,92 3.2262,29.55737 10.5673,53.87028 23.6898,78.45848 11.023,20.65405 21.7624,35.21113 38.7623,52.54152 17.2909,17.62693 24.0427,25.40931 30.3653,35 26.0619,39.5327 33.1081,82.6442 23.6173,144.5 -1.6936,11.0379 -7.1852,37.5413 -10.412,50.25 l -0.6982,2.75 h 11.041 c 9.6675,0 11.1002,-0.2177 11.5165,-1.75 0.2615,-0.9625 1.1691,-4.9 2.0169,-8.75 4.2214,-19.1689 6.8202,-30.513 8.1326,-35.5 1.361,-5.1717 9.3524,-39.8413 18.5555,-80.5 2.2408,-9.9 5.3554,-23.4 6.9213,-30 4.9646,-20.9246 5.3189,-23.3077 4.1531,-27.9371 -0.5856,-2.32595 -4.0498,-10.49161 -7.6981,-18.14594 -9.222,-19.34804 -12.2871,-26.05688 -14.0769,-30.8114 -5.3001,-14.07878 -9.5733,-27.65251 -11.9961,-38.10556 -2.5547,-11.02237 -2.7797,-13.62939 -2.7612,-32 0.019,-19.36303 0.1714,-20.85991 4.7729,-47 6.161,-34.99931 7.6003,-48.16042 7.6003,-69.5 -10e-5,-33.76835 -6.5703,-63.86472 -19.8286,-90.82854 -6.3511,-12.91659 -11.7543,-20.74668 -21.037,-30.48614 -12.8375,-13.46928 -22.9472,-20.76621 -47.0954,-33.9923 -11.7394,-6.42975 -28.8576,-17.82564 -36.5,-24.2987 -10.6672,-9.03504 -17.2351,-15.91058 -32.8553,-34.39432 -16.848,-19.93653 -31.9446,-35.04489 -42.6447,-42.67772 -10.3431,-7.37816 -15.6552,-10.10769 -28,-14.38707 -9.1747,-3.18048 -10.0479,-3.29466 -25.5,-3.33475 -15.5657,-0.0404 -16.2561,0.0478 -25.4354,3.24965 -23.091,8.0544 -33.3943,15.26018 -59.5646,41.65777 -30.5973,30.86299 -38.6661,35.8414 -77,47.50846 -16.2269,4.93872 -34.1287,15.69697 -44.617,26.81301 -10.992,11.6499 -13.307,14.35965 -22.3589,26.17065 -19.0735,24.88726 -27.0799,33.14259 -41.0241,42.29946 -10.0586,6.60527 -20.3327,9.75956 -33.9507,10.42334 -37.2243,1.81441 -62.5379,-17.55119 -76.1054,-58.2228 -11.652,-34.92922 -16.8948,-93.36051 -12.158,-135.5 5.892,-52.41494 5.8488,-63.62373 -0.3099,-80.57433 -4.7448,-13.05916 -4.7675,-12.96705 5.716,-23.17933 5.7642,-5.61509 9.7601,-10.45668 11.1613,-13.52373 2.7419,-6.00142 5.1126,-15.9207 5.1319,-21.47261 L 1198,352 l -5.9651,0 c -11.169,0 -21.7558,5.31928 -28.8986,14.51992 -5.5094,7.09667 -9.8889,9.41118 -14.4261,7.62413 -22.7413,-8.95689 -39.8334,-10.59845 -57.5511,-5.52733 -3.1125,0.89085 -11.6238,4.5155 -18.9141,8.05478 -15.4059,7.47927 -22.5958,9.28006 -37.245,9.32847 -31.9108,0.10543 -56.25892,6.94953 -67.17127,18.88141 -8.24539,9.01574 -18.57001,32.98439 -17.63456,40.9387 0.20038,1.70385 1.68501,3.56683 4.30583,5.40313 6.15036,4.30931 11.64453,4.02586 24.53725,-1.26587 9.37877,-3.84945 11.49879,-4.33904 20.46275,-4.7256 12.5127,-0.5396 16.1351,0.57224 41.5,12.73763 28.326,13.58558 38.6034,16.26339 54.7762,14.27212 10.7814,-1.32746 19.6601,-4.77604 26.9813,-10.47981 10.8957,-8.48862 12.7264,-9.11418 18.2752,-6.24475 3.4802,1.79966 5.9673,6.17527 5.9673,10.49833 0,5.2273 -12.3619,16.07427 -24.373,21.38616 -8.3575,3.69608 -19.5481,6.59858 -25.4408,6.59858 -2.8524,0 -5.1862,0.40847 -5.1862,0.90771 0,0.49925 1.1261,3.08675 2.5024,5.75 7.4849,14.48347 10.3469,40.93277 8.5814,79.30477 -1.8809,40.87852 -1.063,65.17107 3.0314,90.03752 1.9205,11.66347 7.4026,31.07934 11.5058,40.75 3.6541,8.61199 4.8919,11.32366 8.5592,18.75 7.5496,15.28823 16.3315,27.16362 30.7439,41.57393 20.487,20.48403 39.3629,32.17675 72.4974,44.90876 32.2544,12.39381 46.3332,19.15119 64.0627,30.7482 24.2965,15.89251 40.6199,31.86253 55.9811,54.76911 15.3367,22.87022 26.7091,49.93352 35.5759,84.66142 7.4833,29.30908 14.5942,79.14828 16.5733,116.15948 0.3771,7.0514 0.7727,14.0264 0.879,15.5 0.1064,1.4735 0.2639,17.1916 0.35,34.9291 L 1403,1185 h 10.5 10.5 z m 109.6148,-166.12974 c 32.3584,-3.61532 59.7448,-11.3892 85.9116,-24.38672 13.8501,-6.87965 33.6049,-18.83328 37.1094,-22.455 0.7986,-0.82533 0.878,-3.10315 0.2676,-7.67374 -0.4775,-3.575 -0.9114,-15.05 -0.9643,-25.5 -0.1555,-30.74924 3.0015,-49.74257 13.6751,-82.27105 3.3477,-10.20267 3.1984,-15.57022 -0.5373,-19.30587 -2.563,-2.563 -3.6408,-2.92269 -8.75,-2.91989 -17.9704,0.01 -50.057,9.8542 -78.4604,24.07205 -31.505,15.77045 -56.5528,33.57164 -78.5866,55.85076 -22.9969,23.25282 -37.4392,45.78809 -46.737,72.92624 -3.3375,9.74165 -8.0905,29.93345 -7.2451,30.77886 2.3874,2.38739 64.9877,3.04398 84.317,0.88436 z"/>
|
|
</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>
|