2337 lines
88 KiB
HTML
2337 lines
88 KiB
HTML
|
|
<!--
|
||
|
|
============================================================================
|
||
|
|
CAMELEER3 v3 — Route Detail Page (Light Theme)
|
||
|
|
============================================================================
|
||
|
|
|
||
|
|
DESIGN NOTES
|
||
|
|
============
|
||
|
|
|
||
|
|
PAGE PURPOSE:
|
||
|
|
This is the Route Detail page — what a Camel developer sees after clicking
|
||
|
|
on a specific route (e.g., "order-processing") from the dashboard. It shows
|
||
|
|
everything about that route: flow diagram, per-processor stats, performance
|
||
|
|
metrics, recent executions, and error patterns.
|
||
|
|
|
||
|
|
LAYOUT:
|
||
|
|
- Sidebar (220px, warm charcoal) — same app/route tree as dashboard,
|
||
|
|
with this route highlighted under its parent application.
|
||
|
|
- Topbar — breadcrumb showing Apps > order-service > order-processing,
|
||
|
|
same search, env badge, user avatar.
|
||
|
|
- Main content scrolls vertically:
|
||
|
|
1. Route Header Card — status, uptime, version, inflight exchanges
|
||
|
|
2. Route Diagram + Processor Stats — side-by-side, the diagram shows
|
||
|
|
the DAG of processors with execution overlay, the table shows
|
||
|
|
per-processor performance data.
|
||
|
|
3. Tabbed section — Performance | Recent Executions | Error Patterns
|
||
|
|
|
||
|
|
DESIGN SYSTEM:
|
||
|
|
Identical to mock-v2-light.html:
|
||
|
|
- Color palette: warm parchment #F5F2ED, amber-gold #C6820E, charcoal #2C2520
|
||
|
|
- Typography: DM Sans body + JetBrains Mono for data
|
||
|
|
- Same sidebar, topbar, card styles, shadows, border radius, status colors
|
||
|
|
|
||
|
|
DATA MODEL:
|
||
|
|
Route "order-processing" with processors:
|
||
|
|
- from(direct:order-intake)
|
||
|
|
- process(OrderValidator)
|
||
|
|
- to(sql:INSERT INTO orders...)
|
||
|
|
- choice() [when(header.priority == 'HIGH')]
|
||
|
|
- to(http:payment-api/charge)
|
||
|
|
- process(ResponseMapper)
|
||
|
|
- to(kafka:order-completed)
|
||
|
|
- Error handler: to(dead-letter:failed-orders)
|
||
|
|
|
||
|
|
OPTIMIZED FOR: 1920x1080
|
||
|
|
|
||
|
|
============================================================================
|
||
|
|
-->
|
||
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="en">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=1920">
|
||
|
|
<title>Cameleer3 — Route: order-processing</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);
|
||
|
|
}
|
||
|
|
|
||
|
|
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 flowPulse {
|
||
|
|
0% { stroke-dashoffset: 0; }
|
||
|
|
100% { stroke-dashoffset: -16; }
|
||
|
|
}
|
||
|
|
.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 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); }
|
||
|
|
|
||
|
|
.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;
|
||
|
|
}
|
||
|
|
|
||
|
|
.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-link { color: var(--text-muted); cursor: pointer; transition: color 0.12s; }
|
||
|
|
.topbar-breadcrumb .crumb-link:hover { color: var(--amber); }
|
||
|
|
.topbar-breadcrumb .crumb-active { color: var(--text-primary); font-weight: 600; }
|
||
|
|
.topbar-breadcrumb .crumb-sep { color: var(--text-faint); font-size: 11px; }
|
||
|
|
|
||
|
|
.topbar-search {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 6px;
|
||
|
|
padding: 5px 12px;
|
||
|
|
border: 1px solid var(--border);
|
||
|
|
border-radius: var(--radius-sm);
|
||
|
|
background: var(--bg-raised);
|
||
|
|
color: var(--text-muted);
|
||
|
|
font-size: 12px;
|
||
|
|
cursor: pointer;
|
||
|
|
transition: border-color 0.15s;
|
||
|
|
min-width: 280px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.topbar-search:hover { border-color: var(--text-faint); }
|
||
|
|
|
||
|
|
.topbar-kbd {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 10px;
|
||
|
|
background: var(--bg-surface);
|
||
|
|
border: 1px solid var(--border);
|
||
|
|
padding: 0 4px;
|
||
|
|
border-radius: 3px;
|
||
|
|
color: var(--text-faint);
|
||
|
|
margin-left: auto;
|
||
|
|
}
|
||
|
|
|
||
|
|
.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;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ==========================================================================
|
||
|
|
ROUTE HEADER CARD
|
||
|
|
========================================================================== */
|
||
|
|
.route-header {
|
||
|
|
background: var(--bg-surface);
|
||
|
|
border: 1px solid var(--border-subtle);
|
||
|
|
border-radius: var(--radius-lg);
|
||
|
|
box-shadow: var(--shadow-card);
|
||
|
|
padding: 16px 20px;
|
||
|
|
margin-bottom: 16px;
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.route-header-main { flex: 1; min-width: 0; }
|
||
|
|
|
||
|
|
.route-title-row {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 10px;
|
||
|
|
margin-bottom: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.route-title {
|
||
|
|
font-size: 18px;
|
||
|
|
font-weight: 700;
|
||
|
|
color: var(--text-primary);
|
||
|
|
letter-spacing: -0.3px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.route-status-badge {
|
||
|
|
display: inline-flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 5px;
|
||
|
|
padding: 3px 12px;
|
||
|
|
border-radius: 12px;
|
||
|
|
font-size: 11px;
|
||
|
|
font-weight: 600;
|
||
|
|
letter-spacing: 0.3px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.route-status-started {
|
||
|
|
background: var(--success-bg);
|
||
|
|
color: var(--success);
|
||
|
|
border: 1px solid var(--success-border);
|
||
|
|
}
|
||
|
|
|
||
|
|
.route-status-dot {
|
||
|
|
width: 6px;
|
||
|
|
height: 6px;
|
||
|
|
border-radius: 50%;
|
||
|
|
background: var(--success);
|
||
|
|
animation: pulse 2s ease-in-out infinite;
|
||
|
|
}
|
||
|
|
|
||
|
|
.route-subtitle {
|
||
|
|
font-size: 12px;
|
||
|
|
color: var(--text-muted);
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.route-subtitle .mono {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 11px;
|
||
|
|
color: var(--text-secondary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.route-subtitle .dot-sep {
|
||
|
|
width: 3px;
|
||
|
|
height: 3px;
|
||
|
|
border-radius: 50%;
|
||
|
|
background: var(--text-faint);
|
||
|
|
}
|
||
|
|
|
||
|
|
.route-header-stats {
|
||
|
|
display: flex;
|
||
|
|
gap: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.route-stat {
|
||
|
|
text-align: center;
|
||
|
|
padding: 8px 16px;
|
||
|
|
border-radius: var(--radius-md);
|
||
|
|
background: var(--bg-raised);
|
||
|
|
border: 1px solid var(--border-subtle);
|
||
|
|
min-width: 100px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.route-stat-label {
|
||
|
|
font-size: 10px;
|
||
|
|
font-weight: 600;
|
||
|
|
text-transform: uppercase;
|
||
|
|
letter-spacing: 0.5px;
|
||
|
|
color: var(--text-muted);
|
||
|
|
margin-bottom: 2px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.route-stat-value {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 16px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: var(--text-primary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.route-stat-value.val-green { color: var(--success); }
|
||
|
|
.route-stat-value.val-amber { color: var(--amber); }
|
||
|
|
.route-stat-value.val-teal { color: var(--running); }
|
||
|
|
.route-stat-value.val-error { color: var(--error); }
|
||
|
|
|
||
|
|
.route-stat-detail {
|
||
|
|
font-size: 10px;
|
||
|
|
color: var(--text-muted);
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
}
|
||
|
|
|
||
|
|
.route-header-actions {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 6px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.route-action-btn {
|
||
|
|
padding: 6px 14px;
|
||
|
|
border-radius: var(--radius-sm);
|
||
|
|
font-size: 11px;
|
||
|
|
font-weight: 500;
|
||
|
|
cursor: pointer;
|
||
|
|
transition: all 0.15s;
|
||
|
|
font-family: var(--font-body);
|
||
|
|
white-space: nowrap;
|
||
|
|
}
|
||
|
|
|
||
|
|
.route-action-btn.primary {
|
||
|
|
background: var(--amber);
|
||
|
|
color: white;
|
||
|
|
border: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
.route-action-btn.primary:hover { background: var(--amber-deep); }
|
||
|
|
|
||
|
|
.route-action-btn.secondary {
|
||
|
|
background: none;
|
||
|
|
border: 1px solid var(--border);
|
||
|
|
color: var(--text-secondary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.route-action-btn.secondary:hover { border-color: var(--text-faint); color: var(--text-primary); }
|
||
|
|
|
||
|
|
/* ==========================================================================
|
||
|
|
ROUTE DIAGRAM + PROCESSOR STATS (side by side)
|
||
|
|
========================================================================== */
|
||
|
|
.diagram-stats-row {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: 380px 1fr;
|
||
|
|
gap: 16px;
|
||
|
|
margin-bottom: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Route Diagram */
|
||
|
|
.diagram-card {
|
||
|
|
background: var(--bg-surface);
|
||
|
|
border: 1px solid var(--border-subtle);
|
||
|
|
border-radius: var(--radius-lg);
|
||
|
|
box-shadow: var(--shadow-card);
|
||
|
|
overflow: hidden;
|
||
|
|
}
|
||
|
|
|
||
|
|
.diagram-card-header {
|
||
|
|
padding: 10px 16px;
|
||
|
|
border-bottom: 1px solid var(--border-subtle);
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: space-between;
|
||
|
|
}
|
||
|
|
|
||
|
|
.diagram-card-title {
|
||
|
|
font-size: 13px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: var(--text-primary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.diagram-legend {
|
||
|
|
display: flex;
|
||
|
|
gap: 12px;
|
||
|
|
font-size: 10px;
|
||
|
|
color: var(--text-muted);
|
||
|
|
}
|
||
|
|
|
||
|
|
.legend-item {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.legend-dot {
|
||
|
|
width: 8px;
|
||
|
|
height: 8px;
|
||
|
|
border-radius: 2px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.diagram-body {
|
||
|
|
padding: 20px 16px;
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
align-items: center;
|
||
|
|
gap: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Processor nodes in diagram */
|
||
|
|
.diagram-node {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 10px;
|
||
|
|
width: 320px;
|
||
|
|
padding: 8px 12px;
|
||
|
|
border-radius: var(--radius-md);
|
||
|
|
border: 1px solid var(--border-subtle);
|
||
|
|
background: var(--bg-surface);
|
||
|
|
cursor: pointer;
|
||
|
|
transition: all 0.12s;
|
||
|
|
position: relative;
|
||
|
|
}
|
||
|
|
|
||
|
|
.diagram-node:hover {
|
||
|
|
box-shadow: var(--shadow-md);
|
||
|
|
border-color: var(--text-faint);
|
||
|
|
}
|
||
|
|
|
||
|
|
.diagram-node.node-healthy { border-left: 3px solid var(--success); }
|
||
|
|
.diagram-node.node-slow { border-left: 3px solid var(--warning); }
|
||
|
|
.diagram-node.node-error { border-left: 3px solid var(--error); }
|
||
|
|
.diagram-node.node-bottleneck {
|
||
|
|
border-left: 3px solid var(--error);
|
||
|
|
background: var(--warning-bg);
|
||
|
|
border-color: var(--warning-border);
|
||
|
|
}
|
||
|
|
|
||
|
|
.node-icon {
|
||
|
|
width: 28px;
|
||
|
|
height: 28px;
|
||
|
|
border-radius: var(--radius-sm);
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
font-size: 12px;
|
||
|
|
flex-shrink: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.node-icon.icon-from { background: var(--running-bg); color: var(--running); }
|
||
|
|
.node-icon.icon-process { background: var(--amber-bg); color: var(--amber); }
|
||
|
|
.node-icon.icon-to { background: var(--success-bg); color: var(--success); }
|
||
|
|
.node-icon.icon-choice { background: #F3EEFA; color: #7C3AED; }
|
||
|
|
.node-icon.icon-error-handler { background: var(--error-bg); color: var(--error); }
|
||
|
|
|
||
|
|
.node-info { flex: 1; min-width: 0; }
|
||
|
|
|
||
|
|
.node-type {
|
||
|
|
font-size: 10px;
|
||
|
|
font-weight: 600;
|
||
|
|
text-transform: uppercase;
|
||
|
|
letter-spacing: 0.5px;
|
||
|
|
color: var(--text-muted);
|
||
|
|
}
|
||
|
|
|
||
|
|
.node-label {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 11px;
|
||
|
|
color: var(--text-primary);
|
||
|
|
white-space: nowrap;
|
||
|
|
overflow: hidden;
|
||
|
|
text-overflow: ellipsis;
|
||
|
|
}
|
||
|
|
|
||
|
|
.node-stats {
|
||
|
|
text-align: right;
|
||
|
|
flex-shrink: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.node-duration {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 11px;
|
||
|
|
font-weight: 600;
|
||
|
|
}
|
||
|
|
|
||
|
|
.node-duration.dur-fast { color: var(--success); }
|
||
|
|
.node-duration.dur-normal { color: var(--text-primary); }
|
||
|
|
.node-duration.dur-slow { color: var(--warning); }
|
||
|
|
.node-duration.dur-breach { color: var(--error); }
|
||
|
|
|
||
|
|
.node-counts {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 9px;
|
||
|
|
color: var(--text-muted);
|
||
|
|
}
|
||
|
|
|
||
|
|
.node-counts .err-count { color: var(--error); font-weight: 600; }
|
||
|
|
|
||
|
|
/* Bottleneck badge */
|
||
|
|
.bottleneck-badge {
|
||
|
|
position: absolute;
|
||
|
|
top: -8px;
|
||
|
|
right: 10px;
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 9px;
|
||
|
|
font-weight: 600;
|
||
|
|
padding: 1px 8px;
|
||
|
|
border-radius: 10px;
|
||
|
|
background: var(--error);
|
||
|
|
color: white;
|
||
|
|
letter-spacing: 0.3px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Connector arrows between nodes */
|
||
|
|
.diagram-connector {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
align-items: center;
|
||
|
|
height: 24px;
|
||
|
|
position: relative;
|
||
|
|
}
|
||
|
|
|
||
|
|
.connector-line {
|
||
|
|
width: 2px;
|
||
|
|
flex: 1;
|
||
|
|
background: var(--border);
|
||
|
|
}
|
||
|
|
|
||
|
|
.connector-arrow {
|
||
|
|
width: 0;
|
||
|
|
height: 0;
|
||
|
|
border-left: 5px solid transparent;
|
||
|
|
border-right: 5px solid transparent;
|
||
|
|
border-top: 6px solid var(--border);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Choice branch visual */
|
||
|
|
.diagram-branch {
|
||
|
|
display: flex;
|
||
|
|
gap: 12px;
|
||
|
|
align-items: flex-start;
|
||
|
|
width: 340px;
|
||
|
|
position: relative;
|
||
|
|
}
|
||
|
|
|
||
|
|
.branch-label {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 9px;
|
||
|
|
color: var(--text-muted);
|
||
|
|
padding: 2px 8px;
|
||
|
|
background: var(--bg-raised);
|
||
|
|
border: 1px solid var(--border-subtle);
|
||
|
|
border-radius: 10px;
|
||
|
|
white-space: nowrap;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Error handler section in diagram */
|
||
|
|
.diagram-error-section {
|
||
|
|
margin-top: 8px;
|
||
|
|
padding-top: 12px;
|
||
|
|
border-top: 1px dashed var(--error-border);
|
||
|
|
width: 340px;
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
align-items: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
.error-handler-label {
|
||
|
|
font-size: 9px;
|
||
|
|
font-weight: 600;
|
||
|
|
text-transform: uppercase;
|
||
|
|
letter-spacing: 0.8px;
|
||
|
|
color: var(--error);
|
||
|
|
margin-bottom: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ==========================================================================
|
||
|
|
PROCESSOR STATS TABLE
|
||
|
|
========================================================================== */
|
||
|
|
.stats-card {
|
||
|
|
background: var(--bg-surface);
|
||
|
|
border: 1px solid var(--border-subtle);
|
||
|
|
border-radius: var(--radius-lg);
|
||
|
|
box-shadow: var(--shadow-card);
|
||
|
|
overflow: hidden;
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
}
|
||
|
|
|
||
|
|
.stats-card-header {
|
||
|
|
padding: 10px 16px;
|
||
|
|
border-bottom: 1px solid var(--border-subtle);
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: space-between;
|
||
|
|
}
|
||
|
|
|
||
|
|
.stats-card-title {
|
||
|
|
font-size: 13px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: var(--text-primary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.stats-card-meta {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 11px;
|
||
|
|
color: var(--text-muted);
|
||
|
|
}
|
||
|
|
|
||
|
|
.stats-table-wrap {
|
||
|
|
overflow-x: auto;
|
||
|
|
flex: 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
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; }
|
||
|
|
th.text-right { text-align: right; }
|
||
|
|
|
||
|
|
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); }
|
||
|
|
tbody tr.row-highlight { background: var(--warning-bg); }
|
||
|
|
|
||
|
|
td {
|
||
|
|
padding: 9px 14px;
|
||
|
|
font-size: 13px;
|
||
|
|
vertical-align: middle;
|
||
|
|
white-space: nowrap;
|
||
|
|
}
|
||
|
|
|
||
|
|
td.text-right { text-align: right; }
|
||
|
|
|
||
|
|
.proc-type-badge {
|
||
|
|
display: inline-flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 4px;
|
||
|
|
padding: 2px 8px;
|
||
|
|
border-radius: 10px;
|
||
|
|
font-size: 10px;
|
||
|
|
font-weight: 600;
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
letter-spacing: 0.3px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.proc-type-from { background: var(--running-bg); color: var(--running); }
|
||
|
|
.proc-type-process { background: var(--amber-bg); color: var(--amber-deep); }
|
||
|
|
.proc-type-to { background: var(--success-bg); color: var(--success); }
|
||
|
|
.proc-type-choice { background: #F3EEFA; color: #7C3AED; }
|
||
|
|
.proc-type-error { background: var(--error-bg); color: var(--error); }
|
||
|
|
|
||
|
|
.proc-name-cell {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 12px;
|
||
|
|
color: var(--text-primary);
|
||
|
|
font-weight: 500;
|
||
|
|
}
|
||
|
|
|
||
|
|
.duration-cell {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.dur-fast { color: var(--success); }
|
||
|
|
.dur-normal { color: var(--text-primary); }
|
||
|
|
.dur-slow { color: var(--warning); }
|
||
|
|
.dur-breach { color: var(--error); font-weight: 600; }
|
||
|
|
|
||
|
|
.invocations-cell {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 12px;
|
||
|
|
color: var(--text-secondary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.error-pct {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 11px;
|
||
|
|
font-weight: 600;
|
||
|
|
}
|
||
|
|
|
||
|
|
.error-pct.low { color: var(--success); }
|
||
|
|
.error-pct.medium { color: var(--warning); }
|
||
|
|
.error-pct.high { color: var(--error); }
|
||
|
|
|
||
|
|
/* Duration bar (inline sparkline) */
|
||
|
|
.dur-bar-wrap {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 6px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.dur-bar {
|
||
|
|
width: 60px;
|
||
|
|
height: 6px;
|
||
|
|
background: var(--bg-inset);
|
||
|
|
border-radius: 3px;
|
||
|
|
overflow: hidden;
|
||
|
|
flex-shrink: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.dur-bar-fill {
|
||
|
|
height: 100%;
|
||
|
|
border-radius: 3px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.dur-bar-fill.fill-fast { background: var(--success); }
|
||
|
|
.dur-bar-fill.fill-normal { background: var(--amber-light); }
|
||
|
|
.dur-bar-fill.fill-slow { background: var(--warning); }
|
||
|
|
.dur-bar-fill.fill-breach { background: var(--error); }
|
||
|
|
|
||
|
|
/* ==========================================================================
|
||
|
|
TABBED SECTION
|
||
|
|
========================================================================== */
|
||
|
|
.tabs-section {
|
||
|
|
background: var(--bg-surface);
|
||
|
|
border: 1px solid var(--border-subtle);
|
||
|
|
border-radius: var(--radius-lg);
|
||
|
|
box-shadow: var(--shadow-card);
|
||
|
|
overflow: hidden;
|
||
|
|
}
|
||
|
|
|
||
|
|
.tabs-header {
|
||
|
|
display: flex;
|
||
|
|
border-bottom: 1px solid var(--border-subtle);
|
||
|
|
}
|
||
|
|
|
||
|
|
.tab-btn {
|
||
|
|
padding: 11px 20px;
|
||
|
|
font-size: 13px;
|
||
|
|
font-weight: 500;
|
||
|
|
color: var(--text-muted);
|
||
|
|
cursor: pointer;
|
||
|
|
border: none;
|
||
|
|
border-bottom: 2px solid transparent;
|
||
|
|
background: none;
|
||
|
|
font-family: var(--font-body);
|
||
|
|
transition: all 0.15s;
|
||
|
|
}
|
||
|
|
|
||
|
|
.tab-btn:hover { color: var(--text-secondary); }
|
||
|
|
.tab-btn.active { color: var(--amber); border-bottom-color: var(--amber); }
|
||
|
|
|
||
|
|
.tab-btn .tab-count {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 10px;
|
||
|
|
padding: 1px 6px;
|
||
|
|
border-radius: 10px;
|
||
|
|
background: var(--bg-inset);
|
||
|
|
color: var(--text-muted);
|
||
|
|
margin-left: 6px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.tab-btn.active .tab-count {
|
||
|
|
background: var(--amber-bg);
|
||
|
|
color: var(--amber-deep);
|
||
|
|
}
|
||
|
|
|
||
|
|
.tab-content { display: none; }
|
||
|
|
.tab-content.active { display: block; }
|
||
|
|
|
||
|
|
/* Performance tab */
|
||
|
|
.perf-summary {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: repeat(4, 1fr);
|
||
|
|
gap: 12px;
|
||
|
|
padding: 16px;
|
||
|
|
border-bottom: 1px solid var(--border-subtle);
|
||
|
|
}
|
||
|
|
|
||
|
|
.perf-stat {
|
||
|
|
background: var(--bg-raised);
|
||
|
|
border: 1px solid var(--border-subtle);
|
||
|
|
border-radius: var(--radius-md);
|
||
|
|
padding: 12px 14px;
|
||
|
|
position: relative;
|
||
|
|
overflow: hidden;
|
||
|
|
}
|
||
|
|
|
||
|
|
.perf-stat::before {
|
||
|
|
content: '';
|
||
|
|
position: absolute;
|
||
|
|
top: 0;
|
||
|
|
left: 0;
|
||
|
|
right: 0;
|
||
|
|
height: 2px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.perf-stat.stat-throughput::before { background: linear-gradient(90deg, var(--running), transparent); }
|
||
|
|
.perf-stat.stat-error-rate::before { background: linear-gradient(90deg, var(--error), transparent); }
|
||
|
|
.perf-stat.stat-avg-latency::before { background: linear-gradient(90deg, var(--amber), transparent); }
|
||
|
|
.perf-stat.stat-p99::before { background: linear-gradient(90deg, var(--warning), transparent); }
|
||
|
|
|
||
|
|
.perf-stat-label {
|
||
|
|
font-size: 10px;
|
||
|
|
font-weight: 600;
|
||
|
|
text-transform: uppercase;
|
||
|
|
letter-spacing: 0.5px;
|
||
|
|
color: var(--text-muted);
|
||
|
|
margin-bottom: 2px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.perf-stat-value {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 20px;
|
||
|
|
font-weight: 600;
|
||
|
|
line-height: 1.2;
|
||
|
|
}
|
||
|
|
|
||
|
|
.perf-stat-value.val-teal { color: var(--running); }
|
||
|
|
.perf-stat-value.val-error { color: var(--error); }
|
||
|
|
.perf-stat-value.val-amber { color: var(--amber); }
|
||
|
|
.perf-stat-value.val-warn { color: var(--warning); }
|
||
|
|
|
||
|
|
.perf-stat-unit {
|
||
|
|
font-size: 12px;
|
||
|
|
color: var(--text-muted);
|
||
|
|
margin-left: 2px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.perf-stat-trend {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 11px;
|
||
|
|
display: inline-flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 2px;
|
||
|
|
margin-left: 6px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.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); }
|
||
|
|
|
||
|
|
.perf-stat-detail {
|
||
|
|
font-size: 10px;
|
||
|
|
color: var(--text-muted);
|
||
|
|
margin-top: 2px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Charts row */
|
||
|
|
.charts-row {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: 1fr 1fr 1fr;
|
||
|
|
gap: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.chart-panel {
|
||
|
|
padding: 16px;
|
||
|
|
border-right: 1px solid var(--border-subtle);
|
||
|
|
}
|
||
|
|
|
||
|
|
.chart-panel:last-child { border-right: none; }
|
||
|
|
|
||
|
|
.chart-panel-title {
|
||
|
|
font-size: 11px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: var(--text-muted);
|
||
|
|
text-transform: uppercase;
|
||
|
|
letter-spacing: 0.5px;
|
||
|
|
margin-bottom: 10px;
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: space-between;
|
||
|
|
}
|
||
|
|
|
||
|
|
.chart-panel-value {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 13px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: var(--text-primary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.chart-area-wrapper {
|
||
|
|
height: 100px;
|
||
|
|
position: relative;
|
||
|
|
}
|
||
|
|
|
||
|
|
.chart-line { fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
|
||
|
|
.chart-area { opacity: 0.12; }
|
||
|
|
.chart-grid-line { stroke: var(--border-subtle); stroke-width: 1; stroke-dasharray: 3 3; }
|
||
|
|
.chart-label { font-family: var(--font-mono); font-size: 9px; fill: var(--text-faint); }
|
||
|
|
.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; }
|
||
|
|
|
||
|
|
/* Executions tab */
|
||
|
|
.exec-table-wrap {
|
||
|
|
overflow-x: auto;
|
||
|
|
}
|
||
|
|
|
||
|
|
.exec-table-header {
|
||
|
|
padding: 10px 16px;
|
||
|
|
border-bottom: 1px solid var(--border-subtle);
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: space-between;
|
||
|
|
}
|
||
|
|
|
||
|
|
.exec-table-meta {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 11px;
|
||
|
|
color: var(--text-muted);
|
||
|
|
}
|
||
|
|
|
||
|
|
.live-btn {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 6px;
|
||
|
|
padding: 5px 12px;
|
||
|
|
border: 1px solid var(--success-border);
|
||
|
|
border-radius: var(--radius-sm);
|
||
|
|
background: var(--success-bg);
|
||
|
|
color: var(--success);
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 11px;
|
||
|
|
font-weight: 500;
|
||
|
|
cursor: pointer;
|
||
|
|
transition: all 0.15s;
|
||
|
|
}
|
||
|
|
|
||
|
|
.live-btn:hover { background: #E2F0E4; }
|
||
|
|
|
||
|
|
.live-dot {
|
||
|
|
width: 6px;
|
||
|
|
height: 6px;
|
||
|
|
border-radius: 50%;
|
||
|
|
background: var(--success);
|
||
|
|
animation: pulse 2s ease-in-out infinite;
|
||
|
|
}
|
||
|
|
|
||
|
|
.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-running { background: var(--running-bg); color: var(--running); border: 1px solid var(--running-border); }
|
||
|
|
|
||
|
|
.status-dot {
|
||
|
|
width: 5px;
|
||
|
|
height: 5px;
|
||
|
|
border-radius: 50%;
|
||
|
|
background: currentColor;
|
||
|
|
}
|
||
|
|
|
||
|
|
.time-relative { color: var(--text-secondary); font-size: 13px; }
|
||
|
|
.time-absolute { font-family: var(--font-mono); font-size: 10px; color: var(--text-muted); display: block; }
|
||
|
|
|
||
|
|
.biz-id { font-family: var(--font-mono); font-weight: 500; color: var(--running); font-size: 12px; }
|
||
|
|
|
||
|
|
tbody tr.row-success { border-left: 3px solid transparent; }
|
||
|
|
tbody tr.row-error { border-left: 3px solid var(--error); }
|
||
|
|
tbody tr.row-warning { border-left: 3px solid var(--warning); }
|
||
|
|
tbody tr.row-running { border-left: 3px solid var(--running); }
|
||
|
|
|
||
|
|
/* Error patterns tab */
|
||
|
|
.error-patterns {
|
||
|
|
padding: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.error-pattern {
|
||
|
|
display: flex;
|
||
|
|
align-items: flex-start;
|
||
|
|
gap: 14px;
|
||
|
|
padding: 14px 16px;
|
||
|
|
background: var(--bg-raised);
|
||
|
|
border: 1px solid var(--border-subtle);
|
||
|
|
border-left: 3px solid var(--error);
|
||
|
|
border-radius: var(--radius-md);
|
||
|
|
margin-bottom: 10px;
|
||
|
|
transition: box-shadow 0.12s;
|
||
|
|
cursor: pointer;
|
||
|
|
}
|
||
|
|
|
||
|
|
.error-pattern:hover { box-shadow: var(--shadow-md); }
|
||
|
|
|
||
|
|
.error-pattern-count {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
align-items: center;
|
||
|
|
min-width: 50px;
|
||
|
|
flex-shrink: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.error-count-num {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 22px;
|
||
|
|
font-weight: 700;
|
||
|
|
color: var(--error);
|
||
|
|
line-height: 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
.error-count-label {
|
||
|
|
font-size: 9px;
|
||
|
|
font-weight: 600;
|
||
|
|
text-transform: uppercase;
|
||
|
|
letter-spacing: 0.5px;
|
||
|
|
color: var(--text-muted);
|
||
|
|
}
|
||
|
|
|
||
|
|
.error-pattern-info { flex: 1; min-width: 0; }
|
||
|
|
|
||
|
|
.error-exception-class {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 12px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: var(--error);
|
||
|
|
margin-bottom: 3px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.error-message-preview {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 11px;
|
||
|
|
color: var(--text-secondary);
|
||
|
|
line-height: 1.5;
|
||
|
|
margin-bottom: 6px;
|
||
|
|
display: -webkit-box;
|
||
|
|
-webkit-line-clamp: 2;
|
||
|
|
-webkit-box-orient: vertical;
|
||
|
|
overflow: hidden;
|
||
|
|
}
|
||
|
|
|
||
|
|
.error-pattern-meta {
|
||
|
|
display: flex;
|
||
|
|
gap: 16px;
|
||
|
|
font-size: 10px;
|
||
|
|
color: var(--text-muted);
|
||
|
|
}
|
||
|
|
|
||
|
|
.error-pattern-meta .mono {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
}
|
||
|
|
|
||
|
|
.error-trend-bar {
|
||
|
|
display: flex;
|
||
|
|
align-items: flex-end;
|
||
|
|
gap: 2px;
|
||
|
|
height: 24px;
|
||
|
|
flex-shrink: 0;
|
||
|
|
align-self: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
.error-trend-bar-segment {
|
||
|
|
width: 4px;
|
||
|
|
background: var(--error-border);
|
||
|
|
border-radius: 1px;
|
||
|
|
transition: height 0.2s;
|
||
|
|
}
|
||
|
|
|
||
|
|
.error-trend-bar-segment.active { background: var(--error); }
|
||
|
|
|
||
|
|
/* ==========================================================================
|
||
|
|
KEYBOARD SHORTCUTS BAR
|
||
|
|
========================================================================== */
|
||
|
|
.shortcuts-bar {
|
||
|
|
position: fixed;
|
||
|
|
bottom: 12px;
|
||
|
|
right: 12px;
|
||
|
|
display: flex;
|
||
|
|
gap: 10px;
|
||
|
|
z-index: 50;
|
||
|
|
animation: fadeIn 0.5s ease-out 0.5s both;
|
||
|
|
}
|
||
|
|
|
||
|
|
.shortcut-hint {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 5px;
|
||
|
|
font-size: 10px;
|
||
|
|
color: var(--text-muted);
|
||
|
|
background: var(--bg-surface);
|
||
|
|
border: 1px solid var(--border-subtle);
|
||
|
|
border-radius: var(--radius-sm);
|
||
|
|
padding: 4px 8px;
|
||
|
|
box-shadow: var(--shadow-sm);
|
||
|
|
}
|
||
|
|
|
||
|
|
.shortcut-hint kbd {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 9px;
|
||
|
|
background: var(--bg-raised);
|
||
|
|
border: 1px solid var(--border);
|
||
|
|
border-radius: 3px;
|
||
|
|
padding: 0 4px;
|
||
|
|
color: var(--text-secondary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.mono { font-family: var(--font-mono); }
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div class="app">
|
||
|
|
|
||
|
|
<!-- ====================================================================
|
||
|
|
SIDEBAR
|
||
|
|
==================================================================== -->
|
||
|
|
<aside class="sidebar">
|
||
|
|
<div class="sidebar-logo">
|
||
|
|
<svg width="24" height="26" viewBox="940 300 900 1000" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
|
|
<path style="fill:#C6820E" d="M 1385.2768,1205.0273 c -1.7727,-1.0809 -3.4419,-2.9917 -3.7093,-4.2463 -0.2674,-1.2545 -0.6851,-23.881 -0.9282,-50.281 -0.4544,-49.3389 -1.4003,-65.7355 -5.6645,-98.1921 -10.4552,-79.57719 -28.3441,-125.96796 -62.5438,-162.19311 -9.2529,-9.80096 -19.9748,-18.38696 -33.931,-27.17162 -12.6119,-7.93853 -24.8581,-13.73088 -49.5,-23.41316 -41.7081,-16.38789 -67.8677,-32.29827 -89.0588,-54.16599 -34.7586,-35.86827 -54.3653,-83.30392 -59.0504,-142.86402 -0.8357,-10.6245 -0.92,-22.5842 -0.2891,-41 0.4899,-14.3 0.8164,-35.225 0.7255,-46.5 -0.1983,-24.59199 -2.0251,-34.76973 -8.4617,-47.14159 -6.2021,-11.92135 -10.364,-15.10789 -36.2345,-27.74305 -25.4032,-12.40694 -27.7701,-13.30168 -35.4717,-13.40961 -4.63013,-0.0649 -7.47783,0.72754 -15.6593,4.35747 -9.86218,4.37563 -10.15161,4.43672 -21,4.43267 -10.86566,-0.004 -11.08917,-0.0517 -18.30169,-3.90103 -17.43524,-9.3052 -21.50075,-23.5772 -13.55784,-47.59486 2.73631,-8.27402 10.00732,-22.43593 14.69263,-28.61719 5.4984,-7.25394 10.89014,-11.83885 20.83968,-17.72117 15.06768,-8.90827 29.60254,-11.98884 63.90372,-13.54403 l 16.5765,-0.75156 14.9235,-7.05735 c 8.2079,-3.88154 17.1735,-7.76831 19.9235,-8.63727 7.7052,-2.43474 21.059,-4.67186 27.8605,-4.66741 8.0518,0.005 22.643,2.41202 30.0139,4.95066 l 5.8744,2.02321 4.8856,-4.09041 c 10.341,-8.65797 18.6496,-12.95738 28.96,-14.98583 6.9966,-1.37649 26.3532,-0.64631 32.1116,1.21134 4.5531,1.46885 5.4951,3.7902 6.3689,15.69592 1.5167,20.66426 -5.0112,40.44987 -18.1772,55.09363 -4.3065,4.78983 -4.5016,5.25488 -3.4977,8.33516 4.5184,13.86447 5.3154,38.07517 2.1537,65.42564 -5.169,44.71349 -5.0411,70.7797 0.5404,110.15637 6.8135,48.06863 22.3335,73.51874 48.2051,79.04779 24.1748,5.16643 45.2921,-5.78181 66.8353,-34.65077 4.809,-6.4442 11.1363,-14.93006 14.0608,-18.85747 7.3865,-9.91943 25.6102,-27.11708 35.952,-33.92766 12.5235,-8.24739 26.8808,-14.74833 42.8527,-19.40354 15.4108,-4.49168 26.7091,-9.74984 36.2432,-16.86731 3.4338,-2.56341 13.3338,-12.03104 22,-21.03916 26.2356,-27.27067 44.5755,-40.32368 66.9928,-47.68062 19.1052,-6.26998 35.4927,-7.73718 50.8681,-4.55427 37.0856,7.67726 63.5507,26.77589 100.2888,72.37362 16.6011,20.60463 29.9711,32.07977 51.6071,44.29313 39.3131,22.19195 50.1228,30.50985 68.8076,52.94655 5.4963,6.6 12.9187,14.91604 16.4941,18.4801 33.6796,33.57259 54.1965,72.51092 61.6587,117.0199 2.7315,16.29242 3.3374,26.62538 3.1861,54.33713 l -0.1465,26.83714 -2.7975,2.40572 c -3.9132,3.36522 -7.2806,3.99163 -11.2591,2.09442 -6.4731,-3.08682 -6.5715,-3.57833 -6.878,-34.36929 -0.2908,-29.2175 -2.0265,-46.13705 -6.6479,-64.80512 -3.2997,-13.32891 -12.2529,-34.61943 -17.0313,-40.5 l -2.0314,-2.5 0.5698,3.5 c 5.8574,35.97875 4.2855,72.40287 -5.3946,125 -8.4016,45.65054 -4.3665,69.39588 20.7318,122 11.4175,23.93009 12.5452,27.25907 12.4212,36.6684 -0.1018,7.7227 -2.5346,19.5162 -13.1674,63.8316 -1.0558,4.4 -3.5336,14.975 -5.5063,23.5 -1.9727,8.525 -4.2259,18.2 -5.0072,21.5 -0.7812,3.3 -3.4495,15.225 -5.9296,26.5 -9.1636,41.6596 -13.4372,59.8787 -14.5204,61.9027 -2.4994,4.6702 -5.2312,5.0973 -32.6024,5.0973 H 1765.8 l -3.4,-3.4 c -2.3518,-2.3518 -3.4,-4.3226 -3.4,-6.3925 0,-1.6458 2.2347,-12.1533 4.966,-23.35 8.6906,-35.6259 11.6969,-54.778 12.6921,-80.8575 1.3475,-35.3073 -4.6406,-62.7687 -18.7825,-86.137 -7.6672,-12.66954 -11.9163,-17.84148 -29.8756,-36.36415 -22.9362,-23.65574 -34.6222,-39.72583 -47.9268,-65.90697 -5.7294,-11.27448 -13.6061,-31.99995 -15.5051,-40.79778 -0.3212,-1.48824 -1.0176,-3.84619 -1.5475,-5.2399 l -0.9634,-2.534 -11.7786,7.15145 c -38.634,23.45687 -74.5513,34.71091 -124.1895,38.91257 -14.5402,1.23075 -58.2359,0.66344 -71.9142,-0.93369 -5.4419,-0.63542 -6.4542,-0.49723 -7.25,0.98972 -0.5545,1.03621 -0.9249,60.63835 -0.9249,148.84365 0,161.6262 0.4025,151.2052 -5.9673,154.4992 -2.3865,1.2341 -7.4633,1.5162 -27.233,1.5132 -22.3926,-0 -24.5527,-0.158 -27.5229,-1.969 z M 1424,1045.6452 c 0,-87.21053 0.3878,-144.17994 1.0365,-152.24998 1.8115,-22.53829 7.2373,-44.8067 16.1142,-66.13567 20.7842,-49.93942 66.8961,-95.01414 129.8493,-126.92865 14.9469,-7.57742 29.283
|
||
|
|
</svg>
|
||
|
|
<div>
|
||
|
|
<span class="brand">cameleer</span><span class="version">v3</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="sidebar-search">
|
||
|
|
<div class="sidebar-search-wrap">
|
||
|
|
<span class="search-icon">◇</span>
|
||
|
|
<input type="text" placeholder="Filter applications...">
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="sidebar-section">Applications</div>
|
||
|
|
|
||
|
|
<div class="sidebar-items">
|
||
|
|
<div class="sidebar-item">
|
||
|
|
<span class="health health-live"></span>
|
||
|
|
<div class="item-info"><div class="item-name">All Applications</div></div>
|
||
|
|
<span class="item-count">4</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="sidebar-divider"></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-divider"></div>
|
||
|
|
<div class="sidebar-section">Routes</div>
|
||
|
|
|
||
|
|
<div class="sidebar-item" style="padding-left: 22px;">
|
||
|
|
<span style="color: var(--sidebar-muted); font-size: 10px;">▸</span>
|
||
|
|
<div class="item-info"><div class="item-name">order-intake</div></div>
|
||
|
|
<span class="item-count">892</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="sidebar-item active" style="padding-left: 22px;">
|
||
|
|
<span style="color: var(--amber-light); font-size: 10px;">▸</span>
|
||
|
|
<div class="item-info"><div class="item-name">order-processing</div></div>
|
||
|
|
<span class="item-count">541</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="sidebar-item" style="padding-left: 22px;">
|
||
|
|
<span style="color: var(--sidebar-muted); font-size: 10px;">▸</span>
|
||
|
|
<div class="item-info"><div class="item-name">order-enrichment</div></div>
|
||
|
|
<span class="item-count">414</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="sidebar-divider"></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: 140px;">
|
||
|
|
<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">order-service 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>
|
||
|
|
|
||
|
|
<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 class="crumb-link">Applications</span>
|
||
|
|
<span class="crumb-sep">/</span>
|
||
|
|
<span class="crumb-link">order-service</span>
|
||
|
|
<span class="crumb-sep">/</span>
|
||
|
|
<span class="crumb-active">order-processing</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="topbar-search">
|
||
|
|
<span style="font-size: 12px; color: var(--text-faint);">🔎</span>
|
||
|
|
<span>Search by Order ID, route, error...</span>
|
||
|
|
<span class="topbar-kbd">Ctrl+K</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 -->
|
||
|
|
<div class="content">
|
||
|
|
|
||
|
|
<!-- ================================================================
|
||
|
|
ROUTE HEADER CARD
|
||
|
|
================================================================ -->
|
||
|
|
<div class="route-header animate-in">
|
||
|
|
<div class="route-header-main">
|
||
|
|
<div class="route-title-row">
|
||
|
|
<span class="route-title">order-processing</span>
|
||
|
|
<span class="route-status-badge route-status-started">
|
||
|
|
<span class="route-status-dot"></span>
|
||
|
|
Started
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
<div class="route-subtitle">
|
||
|
|
<span>CamelContext: <span class="mono">order-service-ctx</span></span>
|
||
|
|
<span class="dot-sep"></span>
|
||
|
|
<span>Uptime: <span class="mono">14d 6h 22m</span></span>
|
||
|
|
<span class="dot-sep"></span>
|
||
|
|
<span>Last restart: <span class="mono">Mar 3, 08:41 UTC</span></span>
|
||
|
|
<span class="dot-sep"></span>
|
||
|
|
<span>Version: <span class="mono">a3f8c2d</span></span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="route-header-stats">
|
||
|
|
<div class="route-stat">
|
||
|
|
<div class="route-stat-label">Throughput</div>
|
||
|
|
<div class="route-stat-value val-teal">12.4<span style="font-size: 11px; color: var(--text-muted); font-weight: 400;">/s</span></div>
|
||
|
|
<div class="route-stat-detail">peak 28.1/s</div>
|
||
|
|
</div>
|
||
|
|
<div class="route-stat">
|
||
|
|
<div class="route-stat-label">Inflight</div>
|
||
|
|
<div class="route-stat-value val-amber">3</div>
|
||
|
|
<div class="route-stat-detail">max 10</div>
|
||
|
|
</div>
|
||
|
|
<div class="route-stat">
|
||
|
|
<div class="route-stat-label">Success Rate</div>
|
||
|
|
<div class="route-stat-value val-green">96.8%</div>
|
||
|
|
<div class="route-stat-detail">523/541 ok</div>
|
||
|
|
</div>
|
||
|
|
<div class="route-stat">
|
||
|
|
<div class="route-stat-label">Errors (shift)</div>
|
||
|
|
<div class="route-stat-value val-error">18</div>
|
||
|
|
<div class="route-stat-detail">+5 since 06:00</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="route-header-actions">
|
||
|
|
<button class="route-action-btn primary">Suspend Route</button>
|
||
|
|
<button class="route-action-btn secondary">View Config</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- ================================================================
|
||
|
|
ROUTE DIAGRAM + PROCESSOR STATS (side by side)
|
||
|
|
================================================================ -->
|
||
|
|
<div class="diagram-stats-row animate-in delay-1">
|
||
|
|
|
||
|
|
<!-- Route Flow Diagram -->
|
||
|
|
<div class="diagram-card">
|
||
|
|
<div class="diagram-card-header">
|
||
|
|
<span class="diagram-card-title">Route Flow</span>
|
||
|
|
<div class="diagram-legend">
|
||
|
|
<div class="legend-item"><div class="legend-dot" style="background: var(--success);"></div> Healthy</div>
|
||
|
|
<div class="legend-item"><div class="legend-dot" style="background: var(--warning);"></div> Slow</div>
|
||
|
|
<div class="legend-item"><div class="legend-dot" style="background: var(--error);"></div> Errors</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="diagram-body">
|
||
|
|
|
||
|
|
<!-- Node 1: from -->
|
||
|
|
<div class="diagram-node node-healthy">
|
||
|
|
<div class="node-icon icon-from">▶</div>
|
||
|
|
<div class="node-info">
|
||
|
|
<div class="node-type">from</div>
|
||
|
|
<div class="node-label">direct:order-intake</div>
|
||
|
|
</div>
|
||
|
|
<div class="node-stats">
|
||
|
|
<div class="node-duration dur-fast">2ms</div>
|
||
|
|
<div class="node-counts">12,841 / <span class="err-count">0</span></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="diagram-connector"><div class="connector-line"></div><div class="connector-arrow"></div></div>
|
||
|
|
|
||
|
|
<!-- Node 2: process -->
|
||
|
|
<div class="diagram-node node-healthy">
|
||
|
|
<div class="node-icon icon-process">⚙</div>
|
||
|
|
<div class="node-info">
|
||
|
|
<div class="node-type">process</div>
|
||
|
|
<div class="node-label">OrderValidator</div>
|
||
|
|
</div>
|
||
|
|
<div class="node-stats">
|
||
|
|
<div class="node-duration dur-fast">8ms</div>
|
||
|
|
<div class="node-counts">12,841 / <span class="err-count">12</span></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="diagram-connector"><div class="connector-line"></div><div class="connector-arrow"></div></div>
|
||
|
|
|
||
|
|
<!-- Node 3: to SQL -->
|
||
|
|
<div class="diagram-node node-healthy">
|
||
|
|
<div class="node-icon icon-to">▢</div>
|
||
|
|
<div class="node-info">
|
||
|
|
<div class="node-type">to</div>
|
||
|
|
<div class="node-label" title="sql:INSERT INTO orders...">sql:INSERT INTO orders...</div>
|
||
|
|
</div>
|
||
|
|
<div class="node-stats">
|
||
|
|
<div class="node-duration dur-normal">24ms</div>
|
||
|
|
<div class="node-counts">12,829 / <span class="err-count">3</span></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="diagram-connector"><div class="connector-line"></div><div class="connector-arrow"></div></div>
|
||
|
|
|
||
|
|
<!-- Node 4: choice -->
|
||
|
|
<div class="diagram-node node-healthy">
|
||
|
|
<div class="node-icon icon-choice">♦</div>
|
||
|
|
<div class="node-info">
|
||
|
|
<div class="node-type">choice</div>
|
||
|
|
<div class="node-label">header.priority == 'HIGH'</div>
|
||
|
|
</div>
|
||
|
|
<div class="node-stats">
|
||
|
|
<div class="node-duration dur-fast">1ms</div>
|
||
|
|
<div class="node-counts">12,826 / <span class="err-count">0</span></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="diagram-connector"><div class="connector-line"></div><div class="connector-arrow"></div></div>
|
||
|
|
|
||
|
|
<!-- Node 5: to payment-api — BOTTLENECK -->
|
||
|
|
<div class="diagram-node node-bottleneck">
|
||
|
|
<span class="bottleneck-badge">BOTTLENECK</span>
|
||
|
|
<div class="node-icon icon-to" style="background: var(--warning-bg); color: var(--warning);">⚡</div>
|
||
|
|
<div class="node-info">
|
||
|
|
<div class="node-type">to</div>
|
||
|
|
<div class="node-label">http:payment-api/charge</div>
|
||
|
|
</div>
|
||
|
|
<div class="node-stats">
|
||
|
|
<div class="node-duration dur-breach">187ms</div>
|
||
|
|
<div class="node-counts">4,218 / <span class="err-count">47</span></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="diagram-connector"><div class="connector-line"></div><div class="connector-arrow"></div></div>
|
||
|
|
|
||
|
|
<!-- Node 6: process ResponseMapper -->
|
||
|
|
<div class="diagram-node node-healthy">
|
||
|
|
<div class="node-icon icon-process">⚙</div>
|
||
|
|
<div class="node-info">
|
||
|
|
<div class="node-type">process</div>
|
||
|
|
<div class="node-label">ResponseMapper</div>
|
||
|
|
</div>
|
||
|
|
<div class="node-stats">
|
||
|
|
<div class="node-duration dur-fast">3ms</div>
|
||
|
|
<div class="node-counts">12,779 / <span class="err-count">0</span></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="diagram-connector"><div class="connector-line"></div><div class="connector-arrow"></div></div>
|
||
|
|
|
||
|
|
<!-- Node 7: to kafka -->
|
||
|
|
<div class="diagram-node node-healthy">
|
||
|
|
<div class="node-icon icon-to">▢</div>
|
||
|
|
<div class="node-info">
|
||
|
|
<div class="node-type">to</div>
|
||
|
|
<div class="node-label">kafka:order-completed</div>
|
||
|
|
</div>
|
||
|
|
<div class="node-stats">
|
||
|
|
<div class="node-duration dur-normal">11ms</div>
|
||
|
|
<div class="node-counts">12,779 / <span class="err-count">2</span></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Error handler section -->
|
||
|
|
<div class="diagram-error-section">
|
||
|
|
<div class="error-handler-label">Error Handler</div>
|
||
|
|
<div class="diagram-node node-error">
|
||
|
|
<div class="node-icon icon-error-handler">⚠</div>
|
||
|
|
<div class="node-info">
|
||
|
|
<div class="node-type">to</div>
|
||
|
|
<div class="node-label">dead-letter:failed-orders</div>
|
||
|
|
</div>
|
||
|
|
<div class="node-stats">
|
||
|
|
<div class="node-duration dur-normal">14ms</div>
|
||
|
|
<div class="node-counts">62 total</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Processor Statistics Table -->
|
||
|
|
<div class="stats-card">
|
||
|
|
<div class="stats-card-header">
|
||
|
|
<span class="stats-card-title">Processor Statistics</span>
|
||
|
|
<span class="stats-card-meta">Last 6 hours · 8 processors</span>
|
||
|
|
</div>
|
||
|
|
<div class="stats-table-wrap">
|
||
|
|
<table>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Processor</th>
|
||
|
|
<th>Type</th>
|
||
|
|
<th class="sorted">Avg Duration <span class="sort-arrow">▼</span></th>
|
||
|
|
<th>P99</th>
|
||
|
|
<th class="text-right">Invocations</th>
|
||
|
|
<th class="text-right">Errors</th>
|
||
|
|
<th class="text-right">Error %</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<!-- BOTTLENECK ROW: payment-api -->
|
||
|
|
<tr class="row-highlight">
|
||
|
|
<td><span class="proc-name-cell">http:payment-api/charge</span></td>
|
||
|
|
<td><span class="proc-type-badge proc-type-to">TO</span></td>
|
||
|
|
<td>
|
||
|
|
<div class="dur-bar-wrap">
|
||
|
|
<span class="duration-cell dur-breach">187ms</span>
|
||
|
|
<div class="dur-bar"><div class="dur-bar-fill fill-breach" style="width: 100%;"></div></div>
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
<td><span class="duration-cell dur-breach">892ms</span></td>
|
||
|
|
<td class="text-right"><span class="invocations-cell">4,218</span></td>
|
||
|
|
<td class="text-right"><span class="duration-cell dur-breach">47</span></td>
|
||
|
|
<td class="text-right"><span class="error-pct high">1.11%</span></td>
|
||
|
|
</tr>
|
||
|
|
|
||
|
|
<tr>
|
||
|
|
<td><span class="proc-name-cell">sql:INSERT INTO orders...</span></td>
|
||
|
|
<td><span class="proc-type-badge proc-type-to">TO</span></td>
|
||
|
|
<td>
|
||
|
|
<div class="dur-bar-wrap">
|
||
|
|
<span class="duration-cell dur-normal">24ms</span>
|
||
|
|
<div class="dur-bar"><div class="dur-bar-fill fill-normal" style="width: 13%;"></div></div>
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
<td><span class="duration-cell dur-slow">78ms</span></td>
|
||
|
|
<td class="text-right"><span class="invocations-cell">12,829</span></td>
|
||
|
|
<td class="text-right"><span class="duration-cell">3</span></td>
|
||
|
|
<td class="text-right"><span class="error-pct low">0.02%</span></td>
|
||
|
|
</tr>
|
||
|
|
|
||
|
|
<tr>
|
||
|
|
<td><span class="proc-name-cell">dead-letter:failed-orders</span></td>
|
||
|
|
<td><span class="proc-type-badge proc-type-error">DLQ</span></td>
|
||
|
|
<td>
|
||
|
|
<div class="dur-bar-wrap">
|
||
|
|
<span class="duration-cell dur-normal">14ms</span>
|
||
|
|
<div class="dur-bar"><div class="dur-bar-fill fill-normal" style="width: 7%;"></div></div>
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
<td><span class="duration-cell dur-normal">31ms</span></td>
|
||
|
|
<td class="text-right"><span class="invocations-cell">62</span></td>
|
||
|
|
<td class="text-right"><span class="duration-cell">0</span></td>
|
||
|
|
<td class="text-right"><span class="error-pct low">0.00%</span></td>
|
||
|
|
</tr>
|
||
|
|
|
||
|
|
<tr>
|
||
|
|
<td><span class="proc-name-cell">kafka:order-completed</span></td>
|
||
|
|
<td><span class="proc-type-badge proc-type-to">TO</span></td>
|
||
|
|
<td>
|
||
|
|
<div class="dur-bar-wrap">
|
||
|
|
<span class="duration-cell dur-normal">11ms</span>
|
||
|
|
<div class="dur-bar"><div class="dur-bar-fill fill-normal" style="width: 6%;"></div></div>
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
<td><span class="duration-cell dur-normal">42ms</span></td>
|
||
|
|
<td class="text-right"><span class="invocations-cell">12,779</span></td>
|
||
|
|
<td class="text-right"><span class="duration-cell">2</span></td>
|
||
|
|
<td class="text-right"><span class="error-pct low">0.02%</span></td>
|
||
|
|
</tr>
|
||
|
|
|
||
|
|
<tr>
|
||
|
|
<td><span class="proc-name-cell">OrderValidator</span></td>
|
||
|
|
<td><span class="proc-type-badge proc-type-process">PROC</span></td>
|
||
|
|
<td>
|
||
|
|
<div class="dur-bar-wrap">
|
||
|
|
<span class="duration-cell dur-fast">8ms</span>
|
||
|
|
<div class="dur-bar"><div class="dur-bar-fill fill-fast" style="width: 4%;"></div></div>
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
<td><span class="duration-cell dur-normal">19ms</span></td>
|
||
|
|
<td class="text-right"><span class="invocations-cell">12,841</span></td>
|
||
|
|
<td class="text-right"><span class="duration-cell">12</span></td>
|
||
|
|
<td class="text-right"><span class="error-pct low">0.09%</span></td>
|
||
|
|
</tr>
|
||
|
|
|
||
|
|
<tr>
|
||
|
|
<td><span class="proc-name-cell">ResponseMapper</span></td>
|
||
|
|
<td><span class="proc-type-badge proc-type-process">PROC</span></td>
|
||
|
|
<td>
|
||
|
|
<div class="dur-bar-wrap">
|
||
|
|
<span class="duration-cell dur-fast">3ms</span>
|
||
|
|
<div class="dur-bar"><div class="dur-bar-fill fill-fast" style="width: 2%;"></div></div>
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
<td><span class="duration-cell dur-fast">7ms</span></td>
|
||
|
|
<td class="text-right"><span class="invocations-cell">12,779</span></td>
|
||
|
|
<td class="text-right"><span class="duration-cell">0</span></td>
|
||
|
|
<td class="text-right"><span class="error-pct low">0.00%</span></td>
|
||
|
|
</tr>
|
||
|
|
|
||
|
|
<tr>
|
||
|
|
<td><span class="proc-name-cell">direct:order-intake</span></td>
|
||
|
|
<td><span class="proc-type-badge proc-type-from">FROM</span></td>
|
||
|
|
<td>
|
||
|
|
<div class="dur-bar-wrap">
|
||
|
|
<span class="duration-cell dur-fast">2ms</span>
|
||
|
|
<div class="dur-bar"><div class="dur-bar-fill fill-fast" style="width: 1%;"></div></div>
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
<td><span class="duration-cell dur-fast">4ms</span></td>
|
||
|
|
<td class="text-right"><span class="invocations-cell">12,841</span></td>
|
||
|
|
<td class="text-right"><span class="duration-cell">0</span></td>
|
||
|
|
<td class="text-right"><span class="error-pct low">0.00%</span></td>
|
||
|
|
</tr>
|
||
|
|
|
||
|
|
<tr>
|
||
|
|
<td><span class="proc-name-cell">choice(priority)</span></td>
|
||
|
|
<td><span class="proc-type-badge proc-type-choice">CHOICE</span></td>
|
||
|
|
<td>
|
||
|
|
<div class="dur-bar-wrap">
|
||
|
|
<span class="duration-cell dur-fast">1ms</span>
|
||
|
|
<div class="dur-bar"><div class="dur-bar-fill fill-fast" style="width: 0.5%;"></div></div>
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
<td><span class="duration-cell dur-fast">2ms</span></td>
|
||
|
|
<td class="text-right"><span class="invocations-cell">12,826</span></td>
|
||
|
|
<td class="text-right"><span class="duration-cell">0</span></td>
|
||
|
|
<td class="text-right"><span class="error-pct low">0.00%</span></td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- ================================================================
|
||
|
|
TABBED SECTION: Performance | Recent Executions | Error Patterns
|
||
|
|
================================================================ -->
|
||
|
|
<div class="tabs-section animate-in delay-3">
|
||
|
|
<div class="tabs-header">
|
||
|
|
<button class="tab-btn active" onclick="switchTab('performance')">Performance<span class="tab-count">6h</span></button>
|
||
|
|
<button class="tab-btn" onclick="switchTab('executions')">Recent Executions<span class="tab-count">20</span></button>
|
||
|
|
<button class="tab-btn" onclick="switchTab('errors')">Error Patterns<span class="tab-count">5</span></button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- ============================================================
|
||
|
|
TAB 1: Performance
|
||
|
|
============================================================ -->
|
||
|
|
<div class="tab-content active" id="tab-performance">
|
||
|
|
<div class="perf-summary">
|
||
|
|
<div class="perf-stat stat-throughput">
|
||
|
|
<div class="perf-stat-label">Throughput</div>
|
||
|
|
<div>
|
||
|
|
<span class="perf-stat-value val-teal">12.4</span>
|
||
|
|
<span class="perf-stat-unit">msg/s</span>
|
||
|
|
<span class="perf-stat-trend trend-up-good">▲ +8%</span>
|
||
|
|
</div>
|
||
|
|
<div class="perf-stat-detail">Peak: 28.1/s at 08:42</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="perf-stat stat-error-rate">
|
||
|
|
<div class="perf-stat-label">Error Rate</div>
|
||
|
|
<div>
|
||
|
|
<span class="perf-stat-value val-error">3.2%</span>
|
||
|
|
<span class="perf-stat-trend trend-up-bad">▲ +0.8%</span>
|
||
|
|
</div>
|
||
|
|
<div class="perf-stat-detail">18 errors / 541 total</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="perf-stat stat-avg-latency">
|
||
|
|
<div class="perf-stat-label">Avg Latency</div>
|
||
|
|
<div>
|
||
|
|
<span class="perf-stat-value val-amber">142</span>
|
||
|
|
<span class="perf-stat-unit">ms</span>
|
||
|
|
<span class="perf-stat-trend trend-up-bad">▲ +18ms</span>
|
||
|
|
</div>
|
||
|
|
<div class="perf-stat-detail">Median: 98ms</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="perf-stat stat-p99">
|
||
|
|
<div class="perf-stat-label">P99 Latency</div>
|
||
|
|
<div>
|
||
|
|
<span class="perf-stat-value val-warn">487</span>
|
||
|
|
<span class="perf-stat-unit">ms</span>
|
||
|
|
<span class="perf-stat-trend trend-up-bad">▲ +52ms</span>
|
||
|
|
</div>
|
||
|
|
<div class="perf-stat-detail">SLA: <500ms · <strong style="color: var(--warning);">CLOSE</strong></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="charts-row">
|
||
|
|
<!-- Throughput chart -->
|
||
|
|
<div class="chart-panel">
|
||
|
|
<div class="chart-panel-title">
|
||
|
|
<span>Throughput (msg/s)</span>
|
||
|
|
<span class="chart-panel-value">12.4/s</span>
|
||
|
|
</div>
|
||
|
|
<div class="chart-area-wrapper">
|
||
|
|
<svg width="100%" height="100" viewBox="0 0 400 100" preserveAspectRatio="none">
|
||
|
|
<line x1="0" y1="25" x2="400" y2="25" class="chart-grid-line"/>
|
||
|
|
<line x1="0" y1="50" x2="400" y2="50" class="chart-grid-line"/>
|
||
|
|
<line x1="0" y1="75" x2="400" y2="75" class="chart-grid-line"/>
|
||
|
|
<text x="2" y="24" class="chart-label">30</text>
|
||
|
|
<text x="2" y="49" class="chart-label">20</text>
|
||
|
|
<text x="2" y="74" class="chart-label">10</text>
|
||
|
|
<text x="2" y="97" class="chart-label">0</text>
|
||
|
|
<path d="M20,78 L60,72 L100,65 L140,58 L180,45 L220,35 L260,42 L300,48 L340,52 L380,55" class="chart-line" stroke="#1A7F8E"/>
|
||
|
|
<path d="M20,78 L60,72 L100,65 L140,58 L180,45 L220,35 L260,42 L300,48 L340,52 L380,55 L380,100 L20,100 Z" class="chart-area" fill="#1A7F8E"/>
|
||
|
|
<text x="60" y="97" class="chart-label">04:00</text>
|
||
|
|
<text x="180" y="97" class="chart-label">06:00</text>
|
||
|
|
<text x="300" y="97" class="chart-label">08:00</text>
|
||
|
|
</svg>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Latency distribution chart -->
|
||
|
|
<div class="chart-panel">
|
||
|
|
<div class="chart-panel-title">
|
||
|
|
<span>Latency Distribution</span>
|
||
|
|
<span class="chart-panel-value">avg 142ms</span>
|
||
|
|
</div>
|
||
|
|
<div class="chart-area-wrapper">
|
||
|
|
<svg width="100%" height="100" viewBox="0 0 400 100" preserveAspectRatio="none">
|
||
|
|
<line x1="0" y1="25" x2="400" y2="25" class="chart-grid-line"/>
|
||
|
|
<line x1="0" y1="50" x2="400" y2="50" class="chart-grid-line"/>
|
||
|
|
<line x1="0" y1="75" x2="400" y2="75" class="chart-grid-line"/>
|
||
|
|
<!-- Histogram bars -->
|
||
|
|
<rect x="30" y="15" width="30" height="80" rx="2" fill="#C6820E" opacity="0.15"/>
|
||
|
|
<rect x="65" y="25" width="30" height="70" rx="2" fill="#C6820E" opacity="0.2"/>
|
||
|
|
<rect x="100" y="8" width="30" height="87" rx="2" fill="#C6820E" opacity="0.25"/>
|
||
|
|
<rect x="135" y="20" width="30" height="75" rx="2" fill="#C6820E" opacity="0.3"/>
|
||
|
|
<rect x="170" y="35" width="30" height="60" rx="2" fill="#C6820E" opacity="0.35"/>
|
||
|
|
<rect x="205" y="50" width="30" height="45" rx="2" fill="#C6820E" opacity="0.3"/>
|
||
|
|
<rect x="240" y="60" width="30" height="35" rx="2" fill="#C6820E" opacity="0.25"/>
|
||
|
|
<rect x="275" y="72" width="30" height="23" rx="2" fill="#C6820E" opacity="0.2"/>
|
||
|
|
<rect x="310" y="80" width="30" height="15" rx="2" fill="#C0392B" opacity="0.3"/>
|
||
|
|
<rect x="345" y="85" width="30" height="10" rx="2" fill="#C0392B" opacity="0.3"/>
|
||
|
|
<!-- SLA line -->
|
||
|
|
<line x1="310" y1="0" x2="310" y2="95" class="sla-line"/>
|
||
|
|
<text x="312" y="10" class="sla-label-text">SLA 500ms</text>
|
||
|
|
<text x="30" y="97" class="chart-label">0ms</text>
|
||
|
|
<text x="170" y="97" class="chart-label">250ms</text>
|
||
|
|
<text x="310" y="97" class="chart-label">500ms</text>
|
||
|
|
</svg>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Error rate trend chart -->
|
||
|
|
<div class="chart-panel">
|
||
|
|
<div class="chart-panel-title">
|
||
|
|
<span>Error Rate Trend</span>
|
||
|
|
<span class="chart-panel-value" style="color: var(--error);">3.2%</span>
|
||
|
|
</div>
|
||
|
|
<div class="chart-area-wrapper">
|
||
|
|
<svg width="100%" height="100" viewBox="0 0 400 100" preserveAspectRatio="none">
|
||
|
|
<line x1="0" y1="25" x2="400" y2="25" class="chart-grid-line"/>
|
||
|
|
<line x1="0" y1="50" x2="400" y2="50" class="chart-grid-line"/>
|
||
|
|
<line x1="0" y1="75" x2="400" y2="75" class="chart-grid-line"/>
|
||
|
|
<text x="2" y="24" class="chart-label">10%</text>
|
||
|
|
<text x="2" y="49" class="chart-label">5%</text>
|
||
|
|
<text x="2" y="74" class="chart-label">2%</text>
|
||
|
|
<text x="2" y="97" class="chart-label">0%</text>
|
||
|
|
<!-- SLA line at 5% -->
|
||
|
|
<line x1="20" y1="50" x2="380" y2="50" class="sla-line"/>
|
||
|
|
<text x="350" y="47" class="sla-label-text">SLA 5%</text>
|
||
|
|
<path d="M20,88 L60,85 L100,80 L140,82 L180,75 L220,70 L260,72 L300,68 L340,65 L380,62" class="chart-line" stroke="#C0392B"/>
|
||
|
|
<path d="M20,88 L60,85 L100,80 L140,82 L180,75 L220,70 L260,72 L300,68 L340,65 L380,62 L380,100 L20,100 Z" class="chart-area" fill="#C0392B"/>
|
||
|
|
<text x="60" y="97" class="chart-label">04:00</text>
|
||
|
|
<text x="180" y="97" class="chart-label">06:00</text>
|
||
|
|
<text x="300" y="97" class="chart-label">08:00</text>
|
||
|
|
</svg>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- ============================================================
|
||
|
|
TAB 2: Recent Executions
|
||
|
|
============================================================ -->
|
||
|
|
<div class="tab-content" id="tab-executions">
|
||
|
|
<div class="exec-table-header">
|
||
|
|
<span class="exec-table-meta">Showing 1-20 of 541 · This route only</span>
|
||
|
|
<button class="live-btn"><span class="live-dot"></span> LIVE</button>
|
||
|
|
</div>
|
||
|
|
<div class="exec-table-wrap">
|
||
|
|
<table>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th style="width: 80px;">Status</th>
|
||
|
|
<th>Business ID</th>
|
||
|
|
<th class="sorted">Started <span class="sort-arrow">▼</span></th>
|
||
|
|
<th>Duration</th>
|
||
|
|
<th>Processors</th>
|
||
|
|
<th>Agent</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr class="row-success">
|
||
|
|
<td><span class="status-badge status-success"><span class="status-dot"></span>OK</span></td>
|
||
|
|
<td><span class="biz-id">OP-92184</span></td>
|
||
|
|
<td><span class="time-relative">2 min ago</span><span class="time-absolute">09:12:04</span></td>
|
||
|
|
<td><span class="duration-cell dur-fast">87ms</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--text-muted);">7/7</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--text-muted);">prod-1</span></td>
|
||
|
|
</tr>
|
||
|
|
<tr class="row-success">
|
||
|
|
<td><span class="status-badge status-success"><span class="status-dot"></span>OK</span></td>
|
||
|
|
<td><span class="biz-id">OP-92183</span></td>
|
||
|
|
<td><span class="time-relative">3 min ago</span><span class="time-absolute">09:11:22</span></td>
|
||
|
|
<td><span class="duration-cell dur-normal">164ms</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--text-muted);">7/7</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--text-muted);">prod-2</span></td>
|
||
|
|
</tr>
|
||
|
|
<tr class="row-running">
|
||
|
|
<td><span class="status-badge status-running"><span class="status-dot" style="animation: pulse 1.5s ease-in-out infinite;"></span>RUN</span></td>
|
||
|
|
<td><span class="biz-id">OP-92185</span></td>
|
||
|
|
<td><span class="time-relative">30s ago</span><span class="time-absolute">09:13:44</span></td>
|
||
|
|
<td><span class="duration-cell" style="color: var(--running);">~30s</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--running);">5/7</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--text-muted);">prod-1</span></td>
|
||
|
|
</tr>
|
||
|
|
<tr class="row-error">
|
||
|
|
<td><span class="status-badge status-error"><span class="status-dot"></span>ERR</span></td>
|
||
|
|
<td><span class="biz-id">OP-92181</span></td>
|
||
|
|
<td><span class="time-relative">5 min ago</span><span class="time-absolute">09:09:12</span></td>
|
||
|
|
<td><span class="duration-cell dur-breach">412ms</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--error);">5/7</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--text-muted);">prod-2</span></td>
|
||
|
|
</tr>
|
||
|
|
<tr class="row-success">
|
||
|
|
<td><span class="status-badge status-success"><span class="status-dot"></span>OK</span></td>
|
||
|
|
<td><span class="biz-id">OP-92180</span></td>
|
||
|
|
<td><span class="time-relative">6 min ago</span><span class="time-absolute">09:08:47</span></td>
|
||
|
|
<td><span class="duration-cell dur-fast">91ms</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--text-muted);">7/7</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--text-muted);">prod-1</span></td>
|
||
|
|
</tr>
|
||
|
|
<tr class="row-warning">
|
||
|
|
<td><span class="status-badge status-warning"><span class="status-dot"></span>WARN</span></td>
|
||
|
|
<td><span class="biz-id">OP-92179</span></td>
|
||
|
|
<td><span class="time-relative">8 min ago</span><span class="time-absolute">09:06:33</span></td>
|
||
|
|
<td><span class="duration-cell dur-slow">348ms</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--text-muted);">7/7</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--text-muted);">prod-2</span></td>
|
||
|
|
</tr>
|
||
|
|
<tr class="row-success">
|
||
|
|
<td><span class="status-badge status-success"><span class="status-dot"></span>OK</span></td>
|
||
|
|
<td><span class="biz-id">OP-92178</span></td>
|
||
|
|
<td><span class="time-relative">9 min ago</span><span class="time-absolute">09:05:11</span></td>
|
||
|
|
<td><span class="duration-cell dur-fast">72ms</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--text-muted);">7/7</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--text-muted);">prod-1</span></td>
|
||
|
|
</tr>
|
||
|
|
<tr class="row-success">
|
||
|
|
<td><span class="status-badge status-success"><span class="status-dot"></span>OK</span></td>
|
||
|
|
<td><span class="biz-id">OP-92177</span></td>
|
||
|
|
<td><span class="time-relative">11 min ago</span><span class="time-absolute">09:03:29</span></td>
|
||
|
|
<td><span class="duration-cell dur-normal">134ms</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--text-muted);">7/7</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--text-muted);">prod-2</span></td>
|
||
|
|
</tr>
|
||
|
|
<tr class="row-error">
|
||
|
|
<td><span class="status-badge status-error"><span class="status-dot"></span>ERR</span></td>
|
||
|
|
<td><span class="biz-id">OP-92174</span></td>
|
||
|
|
<td><span class="time-relative">14 min ago</span><span class="time-absolute">09:00:02</span></td>
|
||
|
|
<td><span class="duration-cell dur-breach">5,041ms</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--error);">5/7</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--text-muted);">prod-1</span></td>
|
||
|
|
</tr>
|
||
|
|
<tr class="row-success">
|
||
|
|
<td><span class="status-badge status-success"><span class="status-dot"></span>OK</span></td>
|
||
|
|
<td><span class="biz-id">OP-92173</span></td>
|
||
|
|
<td><span class="time-relative">16 min ago</span><span class="time-absolute">08:58:44</span></td>
|
||
|
|
<td><span class="duration-cell dur-fast">68ms</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--text-muted);">7/7</span></td>
|
||
|
|
<td><span class="mono" style="font-size: 12px; color: var(--text-muted);">prod-2</span></td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- ============================================================
|
||
|
|
TAB 3: Error Patterns
|
||
|
|
============================================================ -->
|
||
|
|
<div class="tab-content" id="tab-errors">
|
||
|
|
<div class="error-patterns">
|
||
|
|
|
||
|
|
<!-- Error Pattern 1 -->
|
||
|
|
<div class="error-pattern">
|
||
|
|
<div class="error-pattern-count">
|
||
|
|
<div class="error-count-num">47</div>
|
||
|
|
<div class="error-count-label">times</div>
|
||
|
|
</div>
|
||
|
|
<div class="error-pattern-info">
|
||
|
|
<div class="error-exception-class">java.net.http.HttpConnectTimeoutException</div>
|
||
|
|
<div class="error-message-preview">HTTP connect timed out after 5000ms — POST https://payment-api.internal/v2/charge. Connection pool exhausted, all 20 connections in use. Retry exhausted (3/3 attempts).</div>
|
||
|
|
<div class="error-pattern-meta">
|
||
|
|
<span>Processor: <span class="mono">http:payment-api/charge</span></span>
|
||
|
|
<span>Last seen: <span class="mono">2 min ago</span></span>
|
||
|
|
<span>First seen: <span class="mono">03:14 today</span></span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="error-trend-bar" title="Last 6 hours, hourly buckets">
|
||
|
|
<div class="error-trend-bar-segment" style="height: 4px;"></div>
|
||
|
|
<div class="error-trend-bar-segment" style="height: 6px;"></div>
|
||
|
|
<div class="error-trend-bar-segment" style="height: 8px;"></div>
|
||
|
|
<div class="error-trend-bar-segment active" style="height: 14px;"></div>
|
||
|
|
<div class="error-trend-bar-segment active" style="height: 18px;"></div>
|
||
|
|
<div class="error-trend-bar-segment active" style="height: 24px;"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Error Pattern 2 -->
|
||
|
|
<div class="error-pattern">
|
||
|
|
<div class="error-pattern-count">
|
||
|
|
<div class="error-count-num">12</div>
|
||
|
|
<div class="error-count-label">times</div>
|
||
|
|
</div>
|
||
|
|
<div class="error-pattern-info">
|
||
|
|
<div class="error-exception-class">com.cameleer3.validation.OrderValidationException</div>
|
||
|
|
<div class="error-message-preview">Order validation failed: required field 'shippingAddress.postalCode' is null or empty. Validation rule: ADDR-001. Source system: EDI-US.</div>
|
||
|
|
<div class="error-pattern-meta">
|
||
|
|
<span>Processor: <span class="mono">OrderValidator</span></span>
|
||
|
|
<span>Last seen: <span class="mono">18 min ago</span></span>
|
||
|
|
<span>First seen: <span class="mono">yesterday 22:07</span></span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="error-trend-bar" title="Last 6 hours, hourly buckets">
|
||
|
|
<div class="error-trend-bar-segment" style="height: 8px;"></div>
|
||
|
|
<div class="error-trend-bar-segment active" style="height: 12px;"></div>
|
||
|
|
<div class="error-trend-bar-segment" style="height: 6px;"></div>
|
||
|
|
<div class="error-trend-bar-segment" style="height: 4px;"></div>
|
||
|
|
<div class="error-trend-bar-segment active" style="height: 10px;"></div>
|
||
|
|
<div class="error-trend-bar-segment" style="height: 6px;"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Error Pattern 3 -->
|
||
|
|
<div class="error-pattern">
|
||
|
|
<div class="error-pattern-count">
|
||
|
|
<div class="error-count-num">3</div>
|
||
|
|
<div class="error-count-label">times</div>
|
||
|
|
</div>
|
||
|
|
<div class="error-pattern-info">
|
||
|
|
<div class="error-exception-class">java.sql.SQLTransientConnectionException</div>
|
||
|
|
<div class="error-message-preview">HikariPool-1 — Connection not available, request timed out after 1000ms. Pool stats: total=10, active=10, idle=0, waiting=3.</div>
|
||
|
|
<div class="error-pattern-meta">
|
||
|
|
<span>Processor: <span class="mono">sql:INSERT INTO orders...</span></span>
|
||
|
|
<span>Last seen: <span class="mono">41 min ago</span></span>
|
||
|
|
<span>First seen: <span class="mono">today 07:22</span></span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="error-trend-bar" title="Last 6 hours, hourly buckets">
|
||
|
|
<div class="error-trend-bar-segment" style="height: 0px;"></div>
|
||
|
|
<div class="error-trend-bar-segment" style="height: 0px;"></div>
|
||
|
|
<div class="error-trend-bar-segment" style="height: 4px;"></div>
|
||
|
|
<div class="error-trend-bar-segment active" style="height: 10px;"></div>
|
||
|
|
<div class="error-trend-bar-segment" style="height: 6px;"></div>
|
||
|
|
<div class="error-trend-bar-segment" style="height: 0px;"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Error Pattern 4 -->
|
||
|
|
<div class="error-pattern">
|
||
|
|
<div class="error-pattern-count">
|
||
|
|
<div class="error-count-num">2</div>
|
||
|
|
<div class="error-count-label">times</div>
|
||
|
|
</div>
|
||
|
|
<div class="error-pattern-info">
|
||
|
|
<div class="error-exception-class">org.apache.kafka.common.errors.TimeoutException</div>
|
||
|
|
<div class="error-message-preview">Expiring 1 record(s) for order-completed-0: 30004 ms has passed since batch creation. Topic partition: order-completed-0, broker: kafka-0.kafka.svc:9092.</div>
|
||
|
|
<div class="error-pattern-meta">
|
||
|
|
<span>Processor: <span class="mono">kafka:order-completed</span></span>
|
||
|
|
<span>Last seen: <span class="mono">2h ago</span></span>
|
||
|
|
<span>First seen: <span class="mono">today 04:11</span></span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="error-trend-bar" title="Last 6 hours, hourly buckets">
|
||
|
|
<div class="error-trend-bar-segment" style="height: 6px;"></div>
|
||
|
|
<div class="error-trend-bar-segment active" style="height: 8px;"></div>
|
||
|
|
<div class="error-trend-bar-segment" style="height: 0px;"></div>
|
||
|
|
<div class="error-trend-bar-segment" style="height: 0px;"></div>
|
||
|
|
<div class="error-trend-bar-segment" style="height: 0px;"></div>
|
||
|
|
<div class="error-trend-bar-segment" style="height: 0px;"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Error Pattern 5 -->
|
||
|
|
<div class="error-pattern">
|
||
|
|
<div class="error-pattern-count">
|
||
|
|
<div class="error-count-num">1</div>
|
||
|
|
<div class="error-count-label">times</div>
|
||
|
|
</div>
|
||
|
|
<div class="error-pattern-info">
|
||
|
|
<div class="error-exception-class">com.fasterxml.jackson.databind.JsonMappingException</div>
|
||
|
|
<div class="error-message-preview">Cannot deserialize value of type `java.math.BigDecimal` from String "N/A": not a valid representation at [Source: (byte[]); line: 1, column: 847] (through reference chain: Order["totalAmount"]).</div>
|
||
|
|
<div class="error-pattern-meta">
|
||
|
|
<span>Processor: <span class="mono">ResponseMapper</span></span>
|
||
|
|
<span>Last seen: <span class="mono">5h ago</span></span>
|
||
|
|
<span>First seen: <span class="mono">today 04:33</span></span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="error-trend-bar" title="Last 6 hours, hourly buckets">
|
||
|
|
<div class="error-trend-bar-segment active" style="height: 6px;"></div>
|
||
|
|
<div class="error-trend-bar-segment" style="height: 0px;"></div>
|
||
|
|
<div class="error-trend-bar-segment" style="height: 0px;"></div>
|
||
|
|
<div class="error-trend-bar-segment" style="height: 0px;"></div>
|
||
|
|
<div class="error-trend-bar-segment" style="height: 0px;"></div>
|
||
|
|
<div class="error-trend-bar-segment" style="height: 0px;"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
</div><!-- /tabs-section -->
|
||
|
|
|
||
|
|
</div><!-- /content -->
|
||
|
|
</div><!-- /main -->
|
||
|
|
|
||
|
|
</div><!-- /app -->
|
||
|
|
|
||
|
|
<!-- Keyboard shortcuts bar -->
|
||
|
|
<div class="shortcuts-bar">
|
||
|
|
<div class="shortcut-hint"><kbd>Esc</kbd> Back to list</div>
|
||
|
|
<div class="shortcut-hint"><kbd>R</kbd> Refresh</div>
|
||
|
|
<div class="shortcut-hint"><kbd>S</kbd> Suspend route</div>
|
||
|
|
<div class="shortcut-hint"><kbd>1</kbd><kbd>2</kbd><kbd>3</kbd> Switch tabs</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
function switchTab(tabId) {
|
||
|
|
// Deactivate all tabs and content
|
||
|
|
document.querySelectorAll('.tab-btn').forEach(function(btn) { btn.classList.remove('active'); });
|
||
|
|
document.querySelectorAll('.tab-content').forEach(function(tc) { tc.classList.remove('active'); });
|
||
|
|
|
||
|
|
// Activate the selected tab
|
||
|
|
var tabMap = { 'performance': 0, 'executions': 1, 'errors': 2 };
|
||
|
|
var idx = tabMap[tabId];
|
||
|
|
document.querySelectorAll('.tab-btn')[idx].classList.add('active');
|
||
|
|
document.getElementById('tab-' + tabId).classList.add('active');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Keyboard shortcuts
|
||
|
|
document.addEventListener('keydown', function(e) {
|
||
|
|
if (e.target.tagName === 'INPUT') return;
|
||
|
|
if (e.key === '1') switchTab('performance');
|
||
|
|
if (e.key === '2') switchTab('executions');
|
||
|
|
if (e.key === '3') switchTab('errors');
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|