refactor: rename group/groupName to application/applicationName
The execution-related "group" concept actually represents the
application name. Rename all Java fields, API parameters, and frontend
types from groupName→applicationName and group→application for clarity.
- Java records: ExecutionSummary, ExecutionDetail, ExecutionDocument,
ExecutionRecord, ProcessorRecord
- API params: SearchRequest.group→application, SearchController
@RequestParam group→application
- Services: IngestionService, DetailService, SearchIndexer, StatsStore
- Frontend: schema.d.ts, Dashboard, ExchangeDetail, RouteDetail,
executions query hooks
Database column names (group_name) and OpenSearch field names are
unchanged — only the API-facing Java/TS field names are renamed.
RBAC group references (groups table, GroupRepository, GroupsTab) are
a separate domain concept and are NOT affected by this change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:21:38 +01:00
<!--
============================================================================
2026-04-15 15:28:42 +02:00
CAMELEER v3 — Route Detail Page (Light Theme)
refactor: rename group/groupName to application/applicationName
The execution-related "group" concept actually represents the
application name. Rename all Java fields, API parameters, and frontend
types from groupName→applicationName and group→application for clarity.
- Java records: ExecutionSummary, ExecutionDetail, ExecutionDocument,
ExecutionRecord, ProcessorRecord
- API params: SearchRequest.group→application, SearchController
@RequestParam group→application
- Services: IngestionService, DetailService, SearchIndexer, StatsStore
- Frontend: schema.d.ts, Dashboard, ExchangeDetail, RouteDetail,
executions query hooks
Database column names (group_name) and OpenSearch field names are
unchanged — only the API-facing Java/TS field names are renamed.
RBAC group references (groups table, GroupRepository, GroupsTab) are
a separate domain concept and are NOT affected by this change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:21:38 +01:00
============================================================================
DESIGN NOTES
============
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" >
2026-04-15 15:28:42 +02:00
< title > Cameleer — Route: order-processing< / title >
refactor: rename group/groupName to application/applicationName
The execution-related "group" concept actually represents the
application name. Rename all Java fields, API parameters, and frontend
types from groupName→applicationName and group→application for clarity.
- Java records: ExecutionSummary, ExecutionDetail, ExecutionDocument,
ExecutionRecord, ProcessorRecord
- API params: SearchRequest.group→application, SearchController
@RequestParam group→application
- Services: IngestionService, DetailService, SearchIndexer, StatsStore
- Frontend: schema.d.ts, Dashboard, ExchangeDetail, RouteDetail,
executions query hooks
Database column names (group_name) and OpenSearch field names are
unchanged — only the API-facing Java/TS field names are renamed.
RBAC group references (groups table, GroupRepository, GroupsTab) are
a separate domain concept and are NOT affected by this change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:21:38 +01:00
< link href = "https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel = "stylesheet" >
< style >
/* ==========================================================================
RESET & FOUNDATIONS (identical to v2)
========================================================================== */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
/* Surface palette (warm parchment) */
--bg-body: #F5F2ED;
--bg-surface: #FFFFFF;
--bg-raised: #FAF8F5;
--bg-inset: #F0EDE8;
--bg-hover: #F5F0EA;
/* Sidebar (warm charcoal) */
--sidebar-bg: #2C2520;
--sidebar-hover: #3A322C;
--sidebar-active: #4A3F38;
--sidebar-text: #BFB5A8;
--sidebar-muted: #7A6F63;
/* Text */
--text-primary: #1A1612;
--text-secondary: #5C5347;
--text-muted: #9C9184;
--text-faint: #C4BAB0;
/* Borders */
--border: #E4DFD8;
--border-subtle: #EDE9E3;
/* Brand accent (amber-gold) */
--amber: #C6820E;
--amber-light: #F0D9A8;
--amber-bg: #FDF6E9;
--amber-deep: #8B5A06;
/* Status colors (warm) */
--success: #3D7C47;
--success-bg: #EFF7F0;
--success-border: #C2DFC6;
--warning: #C27516;
--warning-bg: #FEF5E7;
--warning-border: #F0D9A8;
--error: #C0392B;
--error-bg: #FDF0EE;
--error-border: #F0C4BE;
--running: #1A7F8E;
--running-bg: #E8F5F7;
--running-border: #B0DDE4;
/* Typography */
--font-body: 'DM Sans', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
/* Spacing & Radii */
--radius-sm: 5px;
--radius-md: 8px;
--radius-lg: 12px;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(44, 37, 32, 0.06);
--shadow-md: 0 2px 8px rgba(44, 37, 32, 0.08);
--shadow-lg: 0 4px 16px rgba(44, 37, 32, 0.10);
--shadow-card: 0 1px 3px rgba(44, 37, 32, 0.04), 0 0 0 1px rgba(44, 37, 32, 0.04);
}
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" >
2026-04-15 15:28:42 +02:00
< div class = "error-exception-class" > com.cameleer.validation.OrderValidationException< / div >
refactor: rename group/groupName to application/applicationName
The execution-related "group" concept actually represents the
application name. Rename all Java fields, API parameters, and frontend
types from groupName→applicationName and group→application for clarity.
- Java records: ExecutionSummary, ExecutionDetail, ExecutionDocument,
ExecutionRecord, ProcessorRecord
- API params: SearchRequest.group→application, SearchController
@RequestParam group→application
- Services: IngestionService, DetailService, SearchIndexer, StatsStore
- Frontend: schema.d.ts, Dashboard, ExchangeDetail, RouteDetail,
executions query hooks
Database column names (group_name) and OpenSearch field names are
unchanged — only the API-facing Java/TS field names are renamed.
RBAC group references (groups table, GroupRepository, GroupsTab) are
a separate domain concept and are NOT affected by this change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:21:38 +01:00
< 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 >