2026-03-13 10:52:43 +01:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
2026-04-15 15:28:42 +02:00
< title > Cameleer — Route Detail: content-based-routing< / title >
2026-03-13 10:52:43 +01:00
< link rel = "preconnect" href = "https://fonts.googleapis.com" >
< link rel = "preconnect" href = "https://fonts.gstatic.com" crossorigin >
< 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@300;400;500;600&display=swap" rel = "stylesheet" >
< style >
:root {
--bg-deep: #060a13;
--bg-base: #0a0e17;
--bg-surface: #111827;
--bg-raised: #1a2332;
--bg-hover: #1e2d3d;
--border: #1e2d3d;
--border-subtle: #152030;
--text-primary: #e2e8f0;
--text-secondary: #8b9cb6;
--text-muted: #4a5e7a;
--amber: #f0b429;
--amber-dim: #b8860b;
--amber-glow: rgba(240, 180, 41, 0.15);
--cyan: #22d3ee;
--cyan-dim: #0e7490;
--cyan-glow: rgba(34, 211, 238, 0.12);
--rose: #f43f5e;
--rose-dim: #9f1239;
--rose-glow: rgba(244, 63, 94, 0.12);
--blue: #3b82f6;
--green: #10b981;
--green-glow: rgba(16, 185, 129, 0.12);
--purple: #a855f7;
--font-body: 'DM Sans', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 14px;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg-deep);
color: var(--text-primary);
font-family: var(--font-body);
font-size: 14px;
line-height: 1.5;
min-height: 100vh;
}
body::before {
content: '';
position: fixed;
inset: 0;
background:
radial-gradient(ellipse 800px 400px at 20% 20%, rgba(240, 180, 41, 0.03), transparent),
radial-gradient(ellipse 600px 600px at 80% 80%, rgba(34, 211, 238, 0.02), transparent);
pointer-events: none;
z-index: 0;
}
body::after {
content: '';
position: fixed;
inset: 0;
opacity: 0.025;
background-image: url("data:image/svg+xml,%3Csvg width='400' height='400' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 200 Q100 150 200 200 T400 200' fill='none' stroke='%23f0b429' stroke-width='1'/%3E%3Cpath d='M0 220 Q120 170 200 220 T400 220' fill='none' stroke='%23f0b429' stroke-width='0.5'/%3E%3Cpath d='M0 180 Q80 130 200 180 T400 180' fill='none' stroke='%23f0b429' stroke-width='0.5'/%3E%3Cpath d='M0 100 Q150 60 200 100 T400 100' fill='none' stroke='%2322d3ee' stroke-width='0.5'/%3E%3Cpath d='M0 300 Q100 260 200 300 T400 300' fill='none' stroke='%2322d3ee' stroke-width='0.5'/%3E%3C/svg%3E");
background-size: 400px 400px;
pointer-events: none;
z-index: 0;
}
a { color: var(--amber); text-decoration: none; }
a:hover { color: var(--text-primary); }
.topnav {
position: sticky;
top: 0;
z-index: 100;
background: rgba(6, 10, 19, 0.85);
backdrop-filter: blur(20px) saturate(1.2);
border-bottom: 1px solid var(--border-subtle);
padding: 0 24px;
display: flex;
align-items: center;
height: 56px;
gap: 32px;
}
.topnav .logo {
font-family: var(--font-mono);
font-weight: 600;
font-size: 16px;
color: var(--amber);
letter-spacing: -0.5px;
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
}
.topnav .logo svg { width: 22px; height: 22px; }
.topnav .nav-links {
display: flex;
gap: 4px;
list-style: none;
}
.topnav .nav-links a {
padding: 8px 16px;
border-radius: var(--radius-sm);
font-size: 13px;
font-weight: 500;
color: var(--text-secondary);
transition: all 0.15s;
}
.topnav .nav-links a:hover { color: var(--text-primary); background: var(--bg-raised); }
.topnav .nav-links a.active { color: var(--amber); background: var(--amber-glow); }
.topnav .nav-right {
margin-left: auto;
display: flex;
align-items: center;
gap: 16px;
}
.env-badge {
font-family: var(--font-mono);
font-size: 11px;
padding: 4px 10px;
border-radius: 99px;
background: var(--green-glow);
color: var(--green);
border: 1px solid rgba(16, 185, 129, 0.2);
font-weight: 500;
}
.cluster-count {
font-size: 12px;
color: var(--text-muted);
font-family: var(--font-mono);
}
.main {
position: relative;
z-index: 1;
max-width: 1440px;
margin: 0 auto;
padding: 24px;
}
/* ─── Breadcrumb ─── */
.breadcrumb {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--text-muted);
margin-bottom: 16px;
}
.breadcrumb a { color: var(--text-secondary); font-weight: 500; }
.breadcrumb a:hover { color: var(--amber); }
.breadcrumb .sep { color: var(--border); }
/* ─── Route Header ─── */
.route-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 24px;
margin-bottom: 24px;
padding: 24px;
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
position: relative;
overflow: hidden;
}
.route-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, var(--amber), var(--cyan), transparent);
}
.route-header-left h1 {
font-family: var(--font-mono);
font-size: 22px;
font-weight: 600;
letter-spacing: -0.5px;
margin-bottom: 6px;
}
.route-meta {
display: flex;
gap: 20px;
flex-wrap: wrap;
align-items: center;
}
.route-meta-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--text-muted);
}
.route-meta-item strong { color: var(--text-secondary); font-weight: 600; }
.route-meta-item .dot { width: 7px; height: 7px; border-radius: 50%; }
.route-header-stats {
display: flex;
gap: 24px;
flex-shrink: 0;
}
.route-stat {
text-align: center;
}
.route-stat .val {
font-family: var(--font-mono);
font-size: 22px;
font-weight: 600;
letter-spacing: -0.5px;
}
.route-stat .lbl {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.8px;
color: var(--text-muted);
margin-top: 2px;
}
/* ─── Tabs ─── */
.tabs {
display: flex;
gap: 2px;
margin-bottom: 20px;
border-bottom: 1px solid var(--border-subtle);
padding-bottom: 0;
}
.tab {
padding: 10px 20px;
font-size: 13px;
font-weight: 500;
color: var(--text-muted);
cursor: pointer;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
transition: all 0.15s;
}
.tab:hover { color: var(--text-secondary); }
.tab.active { color: var(--amber); border-bottom-color: var(--amber); }
.tab-panel { display: none; }
.tab-panel.active { display: block; }
/* ─── Cards ─── */
.card {
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
overflow: hidden;
transition: border-color 0.2s;
}
.card:hover { border-color: var(--border); }
.card-header {
padding: 16px 20px 12px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--border-subtle);
}
.card-title {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
.card-subtitle {
font-size: 11px;
color: var(--text-muted);
font-family: var(--font-mono);
}
.card-body { padding: 20px; }
/* ─── Route Diagram ─── */
.diagram-container {
background: var(--bg-base);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
padding: 24px;
overflow-x: auto;
position: relative;
min-height: 400px;
}
.diagram-toolbar {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
}
.diagram-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 14px;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
background: var(--bg-raised);
color: var(--text-secondary);
font-family: var(--font-body);
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.15s;
}
.diagram-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
.diagram-btn.active { background: var(--green-glow); border-color: rgba(16,185,129,0.3); color: var(--green); }
.diagram-btn .kbd {
font-size: 9px;
padding: 1px 5px;
border-radius: 3px;
background: var(--bg-base);
border: 1px solid var(--border);
color: var(--text-muted);
font-family: var(--font-mono);
}
.exec-badge {
font-size: 11px;
padding: 4px 10px;
border-radius: 99px;
font-weight: 600;
font-family: var(--font-mono);
background: rgba(16,185,129,0.1);
color: var(--green);
border: 1px solid rgba(16,185,129,0.2);
margin-left: auto;
}
/* SVG Diagram */
.diagram-svg {
width: 100%;
min-width: 800px;
}
.diagram-svg text { font-family: var(--font-mono); }
.node-group { cursor: pointer; transition: opacity 0.2s; }
.node-group:hover .node-rect {
filter: drop-shadow(0 0 8px var(--glow-color, rgba(240,180,41,0.5)));
}
.overlay-mode .node-group.dim { opacity: 0.15; }
.overlay-mode .node-group.dim:hover { opacity: 0.4; }
.overlay-mode .edge-path.dim { opacity: 0.08; }
.overlay-mode .node-group.hot .node-rect {
filter: drop-shadow(0 0 12px rgba(16,185,129,0.6));
}
/* ─── Grid layouts ─── */
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 16px;
}
.grid-3 {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 16px;
margin-bottom: 16px;
}
/* ─── Recent Executions Timeline ─── */
.timeline {
position: relative;
}
.timeline::before {
content: '';
position: absolute;
left: 20px;
top: 0;
bottom: 0;
width: 1px;
background: var(--border);
}
.timeline-item {
display: flex;
gap: 16px;
padding: 12px 0;
position: relative;
cursor: pointer;
transition: background 0.1s;
border-radius: var(--radius-sm);
padding-left: 4px;
}
.timeline-item:hover { background: var(--bg-raised); }
.timeline-dot {
width: 12px;
height: 12px;
border-radius: 50%;
flex-shrink: 0;
margin-top: 4px;
z-index: 1;
border: 2px solid var(--bg-surface);
}
.timeline-dot.completed { background: var(--green); box-shadow: 0 0 0 3px var(--green-glow); }
.timeline-dot.failed { background: var(--rose); box-shadow: 0 0 0 3px var(--rose-glow); }
.timeline-content { flex: 1; min-width: 0; }
.timeline-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.timeline-id {
font-family: var(--font-mono);
font-size: 12px;
color: var(--text-primary);
font-weight: 500;
}
.timeline-time {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-muted);
flex-shrink: 0;
}
.timeline-details {
display: flex;
gap: 16px;
margin-top: 4px;
font-size: 11px;
color: var(--text-muted);
}
.timeline-details span {
display: flex;
align-items: center;
gap: 4px;
}
.timeline-error {
font-family: var(--font-mono);
font-size: 11px;
color: var(--rose);
margin-top: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ─── Performance Histogram ─── */
.histogram {
display: flex;
align-items: flex-end;
gap: 3px;
height: 140px;
padding-top: 10px;
}
.histogram-bar-wrap {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
justify-content: flex-end;
cursor: pointer;
position: relative;
}
.histogram-bar {
width: 100%;
border-radius: 3px 3px 0 0;
transition: all 0.2s;
min-height: 2px;
position: relative;
}
.histogram-bar-wrap:hover .histogram-bar {
filter: brightness(1.3);
box-shadow: 0 0 8px rgba(240, 180, 41, 0.3);
}
.histogram-bar-wrap .bar-tooltip {
display: none;
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
background: var(--bg-raised);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 6px 10px;
font-size: 11px;
font-family: var(--font-mono);
color: var(--text-primary);
white-space: nowrap;
z-index: 10;
pointer-events: none;
}
.histogram-bar-wrap:hover .bar-tooltip { display: block; }
.histogram-labels {
display: flex;
gap: 3px;
margin-top: 6px;
}
.histogram-labels span {
flex: 1;
text-align: center;
font-family: var(--font-mono);
font-size: 9px;
color: var(--text-muted);
}
.histogram-percentiles {
display: flex;
gap: 24px;
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid var(--border-subtle);
}
.percentile {
text-align: center;
}
.percentile .pval {
font-family: var(--font-mono);
font-size: 18px;
font-weight: 600;
letter-spacing: -0.5px;
}
.percentile .plbl {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-muted);
margin-top: 2px;
}
/* ─── Throughput Chart ─── */
.svg-chart {
width: 100%;
overflow: visible;
}
.svg-chart text {
font-family: var(--font-mono);
fill: var(--text-muted);
font-size: 10px;
}
.chart-legend {
display: flex;
gap: 16px;
margin-top: 12px;
}
.chart-legend .legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
color: var(--text-muted);
}
.chart-legend .legend-dot {
width: 8px;
height: 8px;
border-radius: 2px;
}
/* ─── Processor Table ─── */
.proc-table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
}
.proc-table thead th {
padding: 10px 12px;
text-align: left;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.8px;
color: var(--text-muted);
border-bottom: 1px solid var(--border);
background: var(--bg-raised);
}
.proc-table tbody td {
padding: 10px 12px;
border-bottom: 1px solid var(--border-subtle);
vertical-align: middle;
}
.proc-table tbody tr:hover { background: var(--bg-raised); }
.proc-type-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
border-radius: 4px;
font-size: 11px;
font-weight: 600;
}
.proc-type-badge.endpoint { background: rgba(59,130,246,0.12); color: var(--blue); }
.proc-type-badge.eip { background: rgba(168,85,247,0.12); color: var(--purple); }
.proc-type-badge.processor { background: var(--green-glow); color: var(--green); }
.pct-bar {
width: 60px;
height: 4px;
background: var(--bg-base);
border-radius: 2px;
overflow: hidden;
display: inline-block;
vertical-align: middle;
margin-right: 6px;
}
.pct-bar-fill {
height: 100%;
border-radius: 2px;
}
/* ─── Animations ─── */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes livePulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4); }
50% { box-shadow: 0 0 0 6px rgba(16, 185, 129, 0); }
}
.animate-in { animation: fadeIn 0.3s ease-out both; }
.delay-1 { animation-delay: 0.05s; }
.delay-2 { animation-delay: 0.1s; }
.delay-3 { animation-delay: 0.15s; }
.delay-4 { animation-delay: 0.2s; }
::-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-muted); }
@media (max-width: 1200px) {
.grid-2, .grid-3 { grid-template-columns: 1fr; }
.route-header { flex-direction: column; }
.route-header-stats { align-self: stretch; justify-content: space-around; }
}
< / style >
< / head >
< body >
< nav class = "topnav" >
< div class = "logo" >
< svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" >
< path d = "M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2" / >
< path d = "M12 6v6l4 2" / >
< / svg >
2026-04-15 15:28:42 +02:00
cameleer
2026-03-13 10:52:43 +01:00
< / div >
< ul class = "nav-links" >
< li > < a href = "dashboard.html" > Dashboard< / a > < / li >
< li > < a href = "transaction-explorer.html" > Transactions< / a > < / li >
< li > < a href = "route-detail.html" class = "active" > Routes< / a > < / li >
< / ul >
< div class = "nav-right" >
< span class = "env-badge" > PRODUCTION< / span >
< span class = "cluster-count" > 47 apps · 1,283 routes< / span >
< / div >
< / nav >
< main class = "main" >
<!-- Breadcrumb -->
< div class = "breadcrumb animate-in" >
< a href = "dashboard.html" > Dashboard< / a >
< span class = "sep" > › < / span >
< a href = "#" > order-service-eu< / a >
< span class = "sep" > › < / span >
< span style = "color:var(--text-secondary)" > content-based-routing< / span >
< / div >
<!-- Route Header -->
< div class = "route-header animate-in delay-1" >
< div class = "route-header-left" >
< h1 > content-based-routing< / h1 >
< div class = "route-meta" >
< div class = "route-meta-item" >
< span class = "dot" style = "background:var(--green)" > < / span >
< strong > Running< / strong >
< / div >
< div class = "route-meta-item" >
Application: < strong > order-service-eu< / strong >
< / div >
< div class = "route-meta-item" >
From: < strong style = "font-family:var(--font-mono);font-size:11px" > direct:content-based-routing< / strong >
< / div >
< div class = "route-meta-item" >
Processors: < strong > 14< / strong >
< / div >
< div class = "route-meta-item" >
Uptime: < strong > 12d 7h 43m< / strong >
< / div >
< / div >
< / div >
< div class = "route-header-stats" >
< div class = "route-stat" >
< div class = "val" style = "color:var(--amber)" > 89.4K< / div >
< div class = "lbl" > Today< / div >
< / div >
< div class = "route-stat" >
< div class = "val" style = "color:var(--green)" > 99.72%< / div >
< div class = "lbl" > Success< / div >
< / div >
< div class = "route-stat" >
< div class = "val" style = "color:var(--cyan)" > 47ms< / div >
< div class = "lbl" > p50< / div >
< / div >
< div class = "route-stat" >
< div class = "val" style = "color:var(--rose)" > 312ms< / div >
< div class = "lbl" > p99< / div >
< / div >
< / div >
< / div >
<!-- Tabs -->
< div class = "tabs animate-in delay-2" >
< div class = "tab active" data-tab = "diagram" > Diagram< / div >
< div class = "tab" data-tab = "performance" > Performance< / div >
< div class = "tab" data-tab = "executions" > Recent Executions< / div >
< div class = "tab" data-tab = "processors" > Processor Breakdown< / div >
< / div >
<!-- ═══ TAB: Diagram ═══ -->
< div class = "tab-panel active" id = "tab-diagram" >
< div class = "card animate-in delay-3" >
< div class = "card-body" style = "padding:16px" >
< div class = "diagram-toolbar" >
< button class = "diagram-btn" id = "btn-overlay" onclick = "toggleOverlay()" >
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < path d = "M9 18l6-6-6-6" / > < / svg >
Execution Overlay
< span class = "kbd" > E< / span >
< / button >
< button class = "diagram-btn" onclick = "this.classList.toggle('active')" >
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < circle cx = "11" cy = "11" r = "8" / > < path d = "M21 21l-4.35-4.35" / > < / svg >
Zoom
< / button >
< span class = "exec-badge" id = "exec-badge" style = "display:none" > COMPLETED · 47ms · 8 processors< / span >
< / div >
< div class = "diagram-container" id = "diagram-container" >
< svg class = "diagram-svg" id = "route-diagram" viewBox = "0 0 900 480" > < / svg >
< / div >
< / div >
< / div >
< / div >
<!-- ═══ TAB: Performance ═══ -->
< div class = "tab-panel" id = "tab-performance" >
< div class = "grid-2 animate-in delay-3" >
<!-- Duration Histogram -->
< div class = "card" >
< div class = "card-header" >
< div >
< div class = "card-title" > Duration Distribution< / div >
< div class = "card-subtitle" > Last 24h · 89,420 exchanges< / div >
< / div >
< / div >
< div class = "card-body" >
< div class = "histogram" id = "histogram" > < / div >
< div class = "histogram-labels" id = "histogram-labels" > < / div >
< div class = "histogram-percentiles" >
< div class = "percentile" > < div class = "pval" style = "color:var(--green)" > 23ms< / div > < div class = "plbl" > p50< / div > < / div >
< div class = "percentile" > < div class = "pval" style = "color:var(--cyan)" > 47ms< / div > < div class = "plbl" > p75< / div > < / div >
< div class = "percentile" > < div class = "pval" style = "color:var(--amber)" > 142ms< / div > < div class = "plbl" > p95< / div > < / div >
< div class = "percentile" > < div class = "pval" style = "color:var(--rose)" > 312ms< / div > < div class = "plbl" > p99< / div > < / div >
< / div >
< / div >
< / div >
<!-- Throughput Over Time -->
< div class = "card" >
< div class = "card-header" >
< div >
< div class = "card-title" > Throughput Over Time< / div >
< div class = "card-subtitle" > Exchanges/min · last 24h< / div >
< / div >
< / div >
< div class = "card-body" >
< svg class = "svg-chart" viewBox = "0 0 500 200" id = "chart-throughput" > < / svg >
< div class = "chart-legend" >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--amber)" > < / div > Volume< / div >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--rose)" > < / div > Failures< / div >
< / div >
< / div >
< / div >
< / div >
<!-- Error Rate vs Latency Correlation -->
< div class = "card animate-in delay-4" >
< div class = "card-header" >
< div >
< div class = "card-title" > Error Rate vs Latency< / div >
< div class = "card-subtitle" > Last 24h · 15-min windows< / div >
< / div >
< / div >
< div class = "card-body" >
< svg class = "svg-chart" viewBox = "0 0 900 180" id = "chart-correlation" > < / svg >
< div class = "chart-legend" >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--cyan)" > < / div > Avg Latency (ms)< / div >
< div class = "legend-item" > < div class = "legend-dot" style = "background:var(--rose)" > < / div > Error Rate (%)< / div >
< / div >
< / div >
< / div >
< / div >
<!-- ═══ TAB: Recent Executions ═══ -->
< div class = "tab-panel" id = "tab-executions" >
< div class = "card animate-in delay-3" >
< div class = "card-header" >
< div >
< div class = "card-title" > Recent Executions< / div >
< div class = "card-subtitle" > Last 50 exchanges< / div >
< / div >
< / div >
< div class = "card-body" >
< div class = "timeline" id = "timeline" > < / div >
< / div >
< / div >
< / div >
<!-- ═══ TAB: Processor Breakdown ═══ -->
< div class = "tab-panel" id = "tab-processors" >
< div class = "card animate-in delay-3" >
< div class = "card-header" >
< div >
< div class = "card-title" > Processor Performance Breakdown< / div >
< div class = "card-subtitle" > Average across last 1,000 exchanges< / div >
< / div >
< / div >
< div class = "card-body" style = "padding:0" >
< table class = "proc-table" >
< thead >
< tr >
< th > #< / th >
< th > Processor< / th >
< th > Type< / th >
< th > Endpoint< / th >
< th > Avg Duration< / th >
< th > % of Total< / th >
< th > Calls< / th >
< th > Error Rate< / th >
< / tr >
< / thead >
< tbody id = "proc-table-body" > < / tbody >
< / table >
< / div >
< / div >
< / div >
< / main >
< script >
// ─── Tab Switching ───
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', function() {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
this.classList.add('active');
document.getElementById('tab-' + this.dataset.tab).classList.add('active');
});
});
// ─── Route Diagram ───
const NODES = [
{ id: 'from', label: 'direct:content-based-routing', type: 'endpoint', x: 100, y: 30, w: 200, h: 38 },
{ id: 'log1', label: 'Log: Received', type: 'processor', x: 100, y: 90, w: 200, h: 38 },
{ id: 'setheader', label: 'SetHeader: correlationId', type: 'processor', x: 100, y: 150, w: 200, h: 38 },
{ id: 'choice', label: 'Content Based Router', type: 'eip', x: 100, y: 220, w: 200, h: 38 },
// HIGH branch
{ id: 'when-high', label: 'When: priority=HIGH', type: 'eip', x: 20, y: 290, w: 180, h: 34 },
{ id: 'to-priority', label: 'direct:priority-processing', type: 'endpoint', x: 20, y: 340, w: 180, h: 34 },
// NORMAL branch
{ id: 'when-normal', label: 'When: priority=NORMAL', type: 'eip', x: 220, y: 290, w: 180, h: 34 },
{ id: 'to-standard', label: 'direct:standard-processing', type: 'endpoint', x: 220, y: 340, w: 180, h: 34 },
// LOW branch
{ id: 'otherwise', label: 'Otherwise', type: 'eip', x: 420, y: 290, w: 180, h: 34 },
{ id: 'to-batch', label: 'seda:batch-queue', type: 'endpoint', x: 420, y: 340, w: 180, h: 34 },
// Cross-route references
{ id: 'cross-priority', label: 'priority-processing', type: 'cross-route', x: 20, y: 400, w: 180, h: 30 },
{ id: 'cross-standard', label: 'standard-processing', type: 'cross-route', x: 220, y: 400, w: 180, h: 30 },
// Converge
{ id: 'marshal', label: 'Marshal: JSON', type: 'processor', x: 220, y: 440, w: 180, h: 34 },
{ id: 'to-audit', label: 'jms:queue:AUDIT.LOG', type: 'endpoint', x: 620, y: 220, w: 180, h: 38 },
];
const EDGES = [
{ from: 'from', to: 'log1', type: 'flow' },
{ from: 'log1', to: 'setheader', type: 'flow' },
{ from: 'setheader', to: 'choice', type: 'flow' },
{ from: 'choice', to: 'when-high', type: 'branch', label: 'HIGH' },
{ from: 'choice', to: 'when-normal', type: 'branch', label: 'NORMAL' },
{ from: 'choice', to: 'otherwise', type: 'branch', label: 'OTHER' },
{ from: 'when-high', to: 'to-priority', type: 'flow' },
{ from: 'when-normal', to: 'to-standard', type: 'flow' },
{ from: 'otherwise', to: 'to-batch', type: 'flow' },
{ from: 'to-priority', to: 'cross-priority', type: 'cross-route' },
{ from: 'to-standard', to: 'cross-standard', type: 'cross-route' },
{ from: 'choice', to: 'to-audit', type: 'flow' },
];
// Execution path (highlighted in overlay)
const EXEC_PATH = ['from', 'log1', 'setheader', 'choice', 'when-high', 'to-priority', 'cross-priority', 'marshal'];
const EXEC_DURATIONS = { from: 0, log1: 1, setheader: 1, choice: 0, 'when-high': 0, 'to-priority': 35, 'cross-priority': 0, marshal: 2 };
function getNodeColor(type) {
switch(type) {
case 'endpoint': return { fill: 'rgba(59,130,246,0.12)', stroke: 'rgba(59,130,246,0.5)', text: '#3b82f6' };
case 'processor': return { fill: 'rgba(16,185,129,0.12)', stroke: 'rgba(16,185,129,0.5)', text: '#10b981' };
case 'eip': return { fill: 'rgba(168,85,247,0.12)', stroke: 'rgba(168,85,247,0.5)', text: '#a855f7' };
case 'cross-route': return { fill: 'rgba(34,211,238,0.08)', stroke: 'rgba(34,211,238,0.4)', text: '#22d3ee' };
default: return { fill: 'rgba(139,156,182,0.12)', stroke: 'rgba(139,156,182,0.4)', text: '#8b9cb6' };
}
}
function getEdgeColor(type) {
switch(type) {
case 'flow': return '#4a5e7a';
case 'branch': return '#f0b429';
case 'cross-route': return '#22d3ee';
case 'error': return '#f43f5e';
default: return '#4a5e7a';
}
}
function drawDiagram() {
const svg = document.getElementById('route-diagram');
let html = '';
// Defs for markers
html += `< defs >
< marker id = "arrow-flow" markerWidth = "8" markerHeight = "6" refX = "8" refY = "3" orient = "auto" >
< path d = "M0,0 L8,3 L0,6" fill = "#4a5e7a" / >
< / marker >
< marker id = "arrow-branch" markerWidth = "8" markerHeight = "6" refX = "8" refY = "3" orient = "auto" >
< path d = "M0,0 L8,3 L0,6" fill = "#f0b429" / >
< / marker >
< marker id = "arrow-cross" markerWidth = "8" markerHeight = "6" refX = "8" refY = "3" orient = "auto" >
< path d = "M0,0 L8,3 L0,6" fill = "#22d3ee" / >
< / marker >
< marker id = "arrow-exec" markerWidth = "8" markerHeight = "6" refX = "8" refY = "3" orient = "auto" >
< path d = "M0,0 L8,3 L0,6" fill = "#10b981" / >
< / marker >
< filter id = "glow-green" >
< feGaussianBlur stdDeviation = "3" result = "blur" / >
< feMerge > < feMergeNode in = "blur" / > < feMergeNode in = "SourceGraphic" / > < / feMerge >
< / filter >
< / defs > `;
const nodeMap = {};
NODES.forEach(n => nodeMap[n.id] = n);
// Draw edges
EDGES.forEach(e => {
const from = nodeMap[e.from];
const to = nodeMap[e.to];
if (!from || !to) return;
const x1 = from.x + from.w / 2;
const y1 = from.y + from.h;
const x2 = to.x + to.w / 2;
const y2 = to.y;
const color = getEdgeColor(e.type);
const markerId = e.type === 'branch' ? 'arrow-branch' : e.type === 'cross-route' ? 'arrow-cross' : 'arrow-flow';
const dash = e.type === 'cross-route' ? 'stroke-dasharray="5,3"' : '';
// Bezier for non-straight paths
const midY = (y1 + y2) / 2;
const path = `M${x1},${y1} C${x1},${midY} ${x2},${midY} ${x2},${y2}`;
html += `< path class = "edge-path" data-from = "${e.from}" data-to = "${e.to}" d = "${path}" fill = "none" stroke = "${color}" stroke-width = "1.5" marker-end = "url(#${markerId})" $ { dash } opacity = "0.6" / > `;
// Branch label
if (e.label) {
const lx = (x1 + x2) / 2 + (x1 < x2 ? -20 : 20 ) ;
const ly = midY - 4;
html += `< text x = "${lx}" y = "${ly}" font-size = "9" fill = "#f0b429" text-anchor = "middle" font-weight = "600" > ${e.label}< / text > `;
}
});
// Draw nodes
NODES.forEach(n => {
const c = getNodeColor(n.type);
const isExec = EXEC_PATH.includes(n.id);
html += `< g class = "node-group" data-id = "${n.id}" style = "--glow-color:${c.stroke}" > `;
// Rect
const rx = n.type === 'cross-route' ? 14 : 8;
const dashArr = n.type === 'cross-route' ? 'stroke-dasharray="4,3"' : '';
html += `< rect class = "node-rect" x = "${n.x}" y = "${n.y}" width = "${n.w}" height = "${n.h}" rx = "${rx}" fill = "${c.fill}" stroke = "${c.stroke}" stroke-width = "1.5" $ { dashArr } / > `;
// Label
const fontSize = n.label.length > 28 ? 9 : 10;
html += `< text x = "${n.x + n.w/2}" y = "${n.y + n.h/2 + 3}" text-anchor = "middle" fill = "${c.text}" font-size = "${fontSize}" font-weight = "500" > ${n.label}< / text > `;
// Type badge (small)
if (n.type === 'cross-route') {
html += `< text x = "${n.x + n.w - 8}" y = "${n.y + 10}" text-anchor = "end" fill = "${c.text}" font-size = "7" opacity = "0.6" > ↗ route< / text > `;
}
html += `< / g > `;
});
svg.innerHTML = html;
}
let overlayActive = false;
function toggleOverlay() {
overlayActive = !overlayActive;
const container = document.getElementById('diagram-container');
const btn = document.getElementById('btn-overlay');
const badge = document.getElementById('exec-badge');
if (overlayActive) {
container.classList.add('overlay-mode');
btn.classList.add('active');
badge.style.display = 'inline-flex';
// Dim non-executed nodes
document.querySelectorAll('.node-group').forEach(g => {
const id = g.dataset.id;
if (EXEC_PATH.includes(id)) {
g.classList.add('hot');
g.classList.remove('dim');
// Change stroke to green
const rect = g.querySelector('.node-rect');
rect.setAttribute('stroke', '#10b981');
rect.setAttribute('stroke-width', '2');
rect.setAttribute('filter', 'url(#glow-green)');
} else {
g.classList.add('dim');
g.classList.remove('hot');
}
});
// Dim non-executed edges
document.querySelectorAll('.edge-path').forEach(e => {
const from = e.dataset.from;
const to = e.dataset.to;
const fromIdx = EXEC_PATH.indexOf(from);
const toIdx = EXEC_PATH.indexOf(to);
if (fromIdx >= 0 & & toIdx >= 0 & & Math.abs(fromIdx - toIdx) < = 2) {
e.classList.remove('dim');
e.setAttribute('stroke', '#10b981');
e.setAttribute('stroke-width', '2.5');
e.setAttribute('opacity', '0.9');
e.setAttribute('marker-end', 'url(#arrow-exec)');
} else {
e.classList.add('dim');
}
});
// Add sequence numbers and duration badges
const svg = document.getElementById('route-diagram');
EXEC_PATH.forEach((id, idx) => {
const node = NODES.find(n => n.id === id);
if (!node) return;
const dur = EXEC_DURATIONS[id] || 0;
// Sequence badge
const badge = document.createElementNS('http://www.w3.org/2000/svg', 'g');
badge.classList.add('exec-overlay-badge');
badge.innerHTML = `
< circle cx = "${node.x - 4}" cy = "${node.y + node.h/2}" r = "10" fill = "#10b981" opacity = "0.9" / >
< text x = "${node.x - 4}" y = "${node.y + node.h/2 + 3.5}" text-anchor = "middle" fill = "#060a13" font-size = "9" font-weight = "700" > ${idx + 1}< / text >
`;
svg.appendChild(badge);
// Duration pill
if (dur > 0) {
const pill = document.createElementNS('http://www.w3.org/2000/svg', 'g');
pill.classList.add('exec-overlay-badge');
const tw = dur.toString().length * 7 + 24;
pill.innerHTML = `
< rect x = "${node.x + node.w/2 - tw/2}" y = "${node.y - 18}" width = "${tw}" height = "16" rx = "8" fill = "rgba(16,185,129,0.2)" stroke = "rgba(16,185,129,0.4)" stroke-width = "0.5" / >
< text x = "${node.x + node.w/2}" y = "${node.y - 7}" text-anchor = "middle" fill = "#10b981" font-size = "9" font-weight = "600" > ${dur}ms< / text >
`;
svg.appendChild(pill);
}
});
} else {
container.classList.remove('overlay-mode');
btn.classList.remove('active');
badge.style.display = 'none';
// Remove overlays and restore
document.querySelectorAll('.exec-overlay-badge').forEach(b => b.remove());
document.querySelectorAll('.node-group').forEach(g => {
g.classList.remove('hot', 'dim');
const rect = g.querySelector('.node-rect');
const id = g.dataset.id;
const node = NODES.find(n => n.id === id);
if (node) {
const c = getNodeColor(node.type);
rect.setAttribute('stroke', c.stroke);
rect.setAttribute('stroke-width', '1.5');
rect.removeAttribute('filter');
}
});
document.querySelectorAll('.edge-path').forEach(e => {
e.classList.remove('dim');
const from = e.dataset.from;
const edge = EDGES.find(ed => ed.from === from & & ed.to === e.dataset.to);
if (edge) {
e.setAttribute('stroke', getEdgeColor(edge.type));
e.setAttribute('stroke-width', '1.5');
e.setAttribute('opacity', '0.6');
const markerId = edge.type === 'branch' ? 'arrow-branch' : edge.type === 'cross-route' ? 'arrow-cross' : 'arrow-flow';
e.setAttribute('marker-end', `url(#${markerId})`);
}
});
}
}
document.addEventListener('keydown', e => {
if (e.key === 'e' || e.key === 'E') toggleOverlay();
});
drawDiagram();
// ─── Histogram ───
(function() {
const hist = document.getElementById('histogram');
const labels = document.getElementById('histogram-labels');
const buckets = [
{ range: '0-5', count: 8420, pct: 9.4 },
{ range: '5-10', count: 14230, pct: 15.9 },
{ range: '10-20', count: 21560, pct: 24.1 },
{ range: '20-50', count: 25340, pct: 28.3 },
{ range: '50-100', count: 11200, pct: 12.5 },
{ range: '100-200', count: 4890, pct: 5.5 },
{ range: '200-500', count: 2340, pct: 2.6 },
{ range: '500-1k', count: 980, pct: 1.1 },
{ range: '1-2k', count: 340, pct: 0.4 },
{ range: '2-5k', count: 98, pct: 0.1 },
{ range: '5k+', count: 22, pct: 0.02 },
];
const maxCount = Math.max(...buckets.map(b => b.count));
buckets.forEach(b => {
const height = (b.count / maxCount) * 100;
const hue = height > 70 ? 150 : height > 40 ? 40 : 0;
const color = height > 70 ? 'var(--amber)' : height > 40 ? 'rgba(240,180,41,0.7)' : 'rgba(240,180,41,0.4)';
hist.innerHTML += `
< div class = "histogram-bar-wrap" >
< div class = "bar-tooltip" > ${b.range}ms: ${b.count.toLocaleString()} (${b.pct}%)< / div >
< div class = "histogram-bar" style = "height:${height}%;background:${color}" > < / div >
< / div > `;
labels.innerHTML += `< span > ${b.range}< / span > `;
});
})();
// ─── Throughput Chart ───
(function() {
const svg = document.getElementById('chart-throughput');
const W = 500, H = 200;
const pad = { top: 10, right: 10, bottom: 25, left: 40 };
const cw = W - pad.left - pad.right;
const ch = H - pad.top - pad.bottom;
let html = '';
// Grid
[0, 25, 50, 75, 100].forEach(val => {
const y = pad.top + ch - (val / 100) * ch;
html += `< line x1 = "${pad.left}" y1 = "${y}" x2 = "${W-pad.right}" y2 = "${y}" stroke = "var(--border-subtle)" stroke-width = "0.5" / > `;
html += `< text x = "${pad.left-6}" y = "${y+3}" text-anchor = "end" font-size = "9" fill = "var(--text-muted)" > ${val}< / text > `;
});
// X labels
['00:00','06:00','12:00','18:00','24:00'].forEach((l, i) => {
const x = pad.left + (i/4) * cw;
html += `< text x = "${x}" y = "${H-4}" text-anchor = "middle" font-size = "9" fill = "var(--text-muted)" > ${l}< / text > `;
});
// Volume area
const volData = [];
for (let i = 0; i < 96 ; i + + ) {
const hour = i / 4;
const base = hour >= 8 & & hour < = 18 ? 70 : hour >= 6 & & hour < = 20 ? 40 : 10;
volData.push(base + Math.random() * 20 - 10);
}
const volPoints = volData.map((v, i) => {
const x = pad.left + (i / 95) * cw;
const y = pad.top + ch - (v / 100) * ch;
return `${x},${y}`;
});
html += `< defs > < linearGradient id = "vol-grad" x1 = "0" y1 = "0" x2 = "0" y2 = "1" >
< stop offset = "0%" stop-color = "var(--amber)" stop-opacity = "0.2" / >
< stop offset = "100%" stop-color = "var(--amber)" stop-opacity = "0" / >
< / linearGradient > < / defs > `;
html += `< polygon points = "${pad.left},${pad.top+ch} ${volPoints.join(' ')} ${W-pad.right},${pad.top+ch}" fill = "url(#vol-grad)" / > `;
html += `< polyline points = "${volPoints.join(' ')}" fill = "none" stroke = "var(--amber)" stroke-width = "1.5" / > `;
// Failure line (scaled up for visibility)
const failPoints = volData.map((v, i) => {
const x = pad.left + (i / 95) * cw;
const fRate = Math.random() * 3 + 0.5;
const y = pad.top + ch - (fRate / 100) * ch * 20; // scaled
return `${x},${y}`;
});
html += `< polyline points = "${failPoints.join(' ')}" fill = "none" stroke = "var(--rose)" stroke-width = "1" opacity = "0.7" / > `;
svg.innerHTML = html;
})();
// ─── Correlation Chart ───
(function() {
const svg = document.getElementById('chart-correlation');
const W = 900, H = 180;
const pad = { top: 10, right: 10, bottom: 25, left: 40 };
const cw = W - pad.left - pad.right;
const ch = H - pad.top - pad.bottom;
let html = '';
// Grid
[0, 50, 100, 150, 200].forEach(val => {
const y = pad.top + ch - (val / 200) * ch;
html += `< line x1 = "${pad.left}" y1 = "${y}" x2 = "${W-pad.right}" y2 = "${y}" stroke = "var(--border-subtle)" stroke-width = "0.5" / > `;
html += `< text x = "${pad.left-6}" y = "${y+3}" text-anchor = "end" font-size = "9" fill = "var(--text-muted)" > ${val}ms< / text > `;
});
const labels = ['00:00','03:00','06:00','09:00','12:00','15:00','18:00','21:00','24:00'];
labels.forEach((l, i) => {
const x = pad.left + (i / (labels.length-1)) * cw;
html += `< text x = "${x}" y = "${H-4}" text-anchor = "middle" font-size = "9" fill = "var(--text-muted)" > ${l}< / text > `;
});
// Latency area
const latData = [];
for (let i = 0; i < 96 ; i + + ) latData . push ( 30 + Math . random ( ) * 80 ) ;
const latPoints = latData.map((v, i) => `${pad.left + (i/95)*cw},${pad.top + ch - (v/200)*ch}`);
html += `< defs > < linearGradient id = "lat-grad" x1 = "0" y1 = "0" x2 = "0" y2 = "1" >
< stop offset = "0%" stop-color = "var(--cyan)" stop-opacity = "0.15" / >
< stop offset = "100%" stop-color = "var(--cyan)" stop-opacity = "0" / >
< / linearGradient > < / defs > `;
html += `< polygon points = "${pad.left},${pad.top+ch} ${latPoints.join(' ')} ${W-pad.right},${pad.top+ch}" fill = "url(#lat-grad)" / > `;
html += `< polyline points = "${latPoints.join(' ')}" fill = "none" stroke = "var(--cyan)" stroke-width = "1.5" / > `;
// Error rate bars
for (let i = 0; i < 96 ; i + + ) {
const x = pad.left + (i / 95) * cw;
const rate = Math.random() * 2;
const barH = (rate / 200) * ch * 30;
html += `< rect x = "${x-2}" y = "${pad.top + ch - barH}" width = "4" height = "${barH}" fill = "var(--rose)" opacity = "0.3" rx = "1" / > `;
}
svg.innerHTML = html;
})();
// ─── Timeline ───
(function() {
const timeline = document.getElementById('timeline');
const now = Date.now();
const executions = [];
for (let i = 0; i < 30 ; i + + ) {
const failed = Math.random() < 0.05 ;
executions.push({
id: Math.random().toString(16).slice(2, 18),
time: new Date(now - i * (Math.random() * 3000 + 1000)),
status: failed ? 'failed' : 'completed',
duration: failed ? Math.floor(Math.random() * 5000) + 200 : Math.floor(Math.random() * 300) + 10,
processors: Math.floor(Math.random() * 10) + 4,
correlationId: Math.random().toString(16).slice(2, 18),
error: failed ? 'HttpOperationFailedException: HTTP 503 from payment-provider-api' : null,
});
}
executions.forEach(exec => {
const timeStr = exec.time.toTimeString().slice(0, 12);
const durColor = exec.duration > 1000 ? 'var(--rose)' : exec.duration > 200 ? 'var(--amber)' : 'var(--green)';
timeline.innerHTML += `
< div class = "timeline-item" >
< div class = "timeline-dot ${exec.status}" > < / div >
< div class = "timeline-content" >
< div class = "timeline-top" >
< span class = "timeline-id" > ${exec.id}< / span >
< span class = "timeline-time" > ${timeStr}< / span >
< / div >
< div class = "timeline-details" >
< span style = "color:${durColor};font-family:var(--font-mono);font-weight:600" > ${exec.duration}ms< / span >
< span > ${exec.processors} processors< / span >
< span style = "color:var(--text-muted);font-family:var(--font-mono);font-size:10px" > corr: ${exec.correlationId}< / span >
< / div >
${exec.error ? `< div class = "timeline-error" > ${exec.error}< / div > ` : ''}
< / div >
< / div > `;
});
})();
// ─── Processor Breakdown Table ───
(function() {
const tbody = document.getElementById('proc-table-body');
const processors = [
{ id: 'from1', type: 'Endpoint', cat: 'endpoint', uri: 'direct:content-based-routing', avgMs: 0.3, pct: 0.6, calls: 89420, errRate: 0 },
{ id: 'log1', type: 'Log', cat: 'processor', uri: 'Received order: ${body}', avgMs: 0.8, pct: 1.7, calls: 89420, errRate: 0 },
{ id: 'setHeader1', type: 'SetHeader', cat: 'processor', uri: 'X-Cameleer-CorrelationId', avgMs: 0.4, pct: 0.8, calls: 89420, errRate: 0 },
{ id: 'choice1', type: 'Choice (CBR)', cat: 'eip', uri: 'content-based-router', avgMs: 0.2, pct: 0.4, calls: 89420, errRate: 0 },
{ id: 'when1', type: 'When', cat: 'eip', uri: 'priority == HIGH', avgMs: 0.1, pct: 0.2, calls: 34210, errRate: 0 },
{ id: 'to-prio', type: 'To', cat: 'endpoint', uri: 'direct:priority-processing', avgMs: 35.2, pct: 74.5, calls: 34210, errRate: 0.31 },
{ id: 'when2', type: 'When', cat: 'eip', uri: 'priority == NORMAL', avgMs: 0.1, pct: 0.2, calls: 42850, errRate: 0 },
{ id: 'to-std', type: 'To', cat: 'endpoint', uri: 'direct:standard-processing', avgMs: 8.4, pct: 17.8, calls: 42850, errRate: 0.08 },
{ id: 'other', type: 'Otherwise', cat: 'eip', uri: '(default branch)', avgMs: 0.1, pct: 0.2, calls: 12360, errRate: 0 },
{ id: 'to-batch', type: 'To', cat: 'endpoint', uri: 'seda:batch-queue', avgMs: 1.2, pct: 2.5, calls: 12360, errRate: 0 },
{ id: 'marshal', type: 'Marshal', cat: 'processor', uri: 'JSON (Jackson)', avgMs: 0.5, pct: 1.1, calls: 89420, errRate: 0 },
];
processors.forEach((p, idx) => {
const errColor = p.errRate > 0.1 ? 'var(--rose)' : p.errRate > 0 ? 'var(--amber)' : 'var(--text-muted)';
const pctColor = p.pct > 50 ? 'var(--rose)' : p.pct > 20 ? 'var(--amber)' : 'var(--green)';
tbody.innerHTML += `
< tr >
< td style = "font-family:var(--font-mono);font-size:11px;color:var(--text-muted)" > ${idx + 1}< / td >
< td style = "font-weight:600;font-size:12px" > ${p.id}< / td >
< td > < span class = "proc-type-badge ${p.cat}" > ${p.type}< / span > < / td >
< td style = "font-family:var(--font-mono);font-size:11px;color:var(--text-muted);max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" > ${p.uri}< / td >
< td style = "font-family:var(--font-mono);font-size:12px;font-weight:600" > ${p.avgMs}ms< / td >
< td >
< div class = "pct-bar" > < div class = "pct-bar-fill" style = "width:${p.pct}%;background:${pctColor}" > < / div > < / div >
< span style = "font-family:var(--font-mono);font-size:11px;color:${pctColor}" > ${p.pct}%< / span >
< / td >
< td style = "font-family:var(--font-mono);font-size:11px;color:var(--text-secondary)" > ${p.calls.toLocaleString()}< / td >
< td style = "font-family:var(--font-mono);font-size:11px;color:${errColor}" > ${p.errRate > 0 ? p.errRate + '%' : '—'}< / td >
< / tr > `;
});
})();
< / script >
< / body >
< / html >