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 — Exchange / Message Inspector (Light Theme)
refactor: rename group/groupName to application/applicationName
The execution-related "group" concept actually represents the
application name. Rename all Java fields, API parameters, and frontend
types from groupName→applicationName and group→application for clarity.
- Java records: ExecutionSummary, ExecutionDetail, ExecutionDocument,
ExecutionRecord, ProcessorRecord
- API params: SearchRequest.group→application, SearchController
@RequestParam group→application
- Services: IngestionService, DetailService, SearchIndexer, StatsStore
- Frontend: schema.d.ts, Dashboard, ExchangeDetail, RouteDetail,
executions query hooks
Database column names (group_name) and OpenSearch field names are
unchanged — only the API-facing Java/TS field names are renamed.
RBAC group references (groups table, GroupRepository, GroupsTab) are
a separate domain concept and are NOT affected by this change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:21:38 +01:00
============================================================================
DESIGN NOTES
============
PURPOSE:
2026-04-15 15:28:42 +02:00
This is the deepest drill-down view in Cameleer — the Exchange Inspector.
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
A developer arrives here after clicking a failed execution row in the
Operations Dashboard (mock-v2). The page answers the #1 question:
"What happened to my message as it flowed through the route?"
LAYOUT:
Two-region layout inside the standard sidebar + main shell:
1. Exchange Header Card — identity, status, timing at a glance
2. Processor Timeline — Gantt-style horizontal bars showing where time was spent
3. Processor Detail Panel — split IN / OUT columns with headers, body, diffs
4. Properties & Correlation — collapsible bottom section
DATA SCENARIO:
Order OP-88421 entered via direct:order-intake, passed validation, DB insert,
priority check, then FAILED at http:payment-api/charge with a 503 timeout
after 2m 18s. The payment-api processor consumed 94% of total wall time.
DESIGN SYSTEM:
Inherits 100% from mock-v2-light.html — same CSS variables, fonts, sidebar,
topbar. No new colors or typefaces introduced.
OPTIMIZED FOR: 1920x1080, Chrome/Firefox/Edge
============================================================================
-->
<!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 — Exchange OP-88421< / 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 mock-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 errorPulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(192, 57, 43, 0.2); }
50% { box-shadow: 0 0 0 4px rgba(192, 57, 43, 0); }
}
.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 mock-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);
text-decoration: none;
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-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-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;
}
/* ==========================================================================
STATUS BADGES (reused from mock-v2)
========================================================================== */
.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-error { background: var(--error-bg); color: var(--error); border: 1px solid var(--error-border); }
.status-dot { width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
/* ==========================================================================
EXCHANGE HEADER CARD
========================================================================== */
.exchange-header {
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
padding: 18px 22px;
box-shadow: var(--shadow-card);
margin-bottom: 16px;
border-left: 4px solid var(--error);
}
.exchange-header-top {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 14px;
}
.exchange-title-area {
display: flex;
align-items: center;
gap: 12px;
}
.exchange-route {
font-size: 18px;
font-weight: 700;
color: var(--text-primary);
}
.exchange-status-lg {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 14px;
border-radius: 16px;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.3px;
text-transform: uppercase;
background: var(--error-bg);
color: var(--error);
border: 1px solid var(--error-border);
animation: errorPulse 2.5s ease-in-out infinite;
}
.exchange-status-lg .status-dot { width: 6px; height: 6px; }
.exchange-actions {
display: flex;
gap: 6px;
}
.action-btn {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 5px 12px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--bg-surface);
color: var(--text-secondary);
font-family: var(--font-body);
font-size: 11px;
font-weight: 500;
cursor: pointer;
transition: all 0.12s;
}
.action-btn:hover {
border-color: var(--text-faint);
background: var(--bg-raised);
color: var(--text-primary);
}
.action-btn.primary {
background: var(--amber-bg);
border-color: var(--amber-light);
color: var(--amber-deep);
}
.action-btn.primary:hover {
background: var(--amber-light);
}
.exchange-meta-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0;
border-top: 1px solid var(--border-subtle);
padding-top: 14px;
}
.meta-item {
padding: 0 16px;
border-right: 1px solid var(--border-subtle);
}
.meta-item:first-child { padding-left: 0; }
.meta-item:last-child { border-right: none; }
.meta-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.6px;
color: var(--text-muted);
margin-bottom: 3px;
}
.meta-value {
font-family: var(--font-mono);
font-size: 12px;
font-weight: 500;
color: var(--text-primary);
}
.meta-value.mono-sm {
font-size: 11px;
color: var(--text-secondary);
word-break: break-all;
}
.meta-value .biz-tag {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 1px 8px;
border-radius: 10px;
font-size: 11px;
font-weight: 500;
margin-right: 6px;
}
.biz-tag.order { background: var(--running-bg); color: var(--running); border: 1px solid var(--running-border); }
.biz-tag.customer { background: var(--amber-bg); color: var(--amber-deep); border: 1px solid var(--amber-light); }
.meta-value .duration-lg {
font-size: 16px;
font-weight: 700;
color: var(--error);
}
.meta-value .duration-label {
font-size: 11px;
color: var(--text-muted);
font-weight: 400;
margin-left: 4px;
}
.meta-sub {
font-size: 10px;
color: var(--text-muted);
font-family: var(--font-mono);
margin-top: 2px;
}
/* ==========================================================================
PROCESSOR TIMELINE (Gantt-style)
========================================================================== */
.timeline-section {
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-card);
margin-bottom: 16px;
overflow: hidden;
}
.timeline-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 18px;
border-bottom: 1px solid var(--border-subtle);
}
.timeline-title {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 8px;
}
.timeline-title .proc-count {
font-family: var(--font-mono);
font-size: 10px;
padding: 1px 8px;
border-radius: 10px;
background: var(--bg-inset);
color: var(--text-muted);
}
.timeline-legend {
display: flex;
gap: 14px;
font-size: 11px;
color: var(--text-muted);
}
.legend-item {
display: flex;
align-items: center;
gap: 5px;
}
.legend-dot {
width: 8px;
height: 8px;
border-radius: 2px;
}
.legend-dot.l-success { background: var(--success); }
.legend-dot.l-error { background: var(--error); }
.legend-dot.l-slow { background: var(--warning); }
.timeline-body {
padding: 6px 0;
}
.timeline-row {
display: grid;
grid-template-columns: 260px 1fr 80px 32px;
align-items: center;
padding: 6px 18px;
gap: 12px;
cursor: pointer;
transition: background 0.08s;
border-left: 3px solid transparent;
}
.timeline-row:hover { background: var(--bg-hover); }
.timeline-row.selected { background: var(--amber-bg); border-left-color: var(--amber); }
.timeline-row.row-error { border-left-color: var(--error); }
.timeline-row.row-error.selected { border-left-color: var(--error); background: var(--error-bg); }
.proc-label {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
min-width: 0;
}
.proc-index {
font-family: var(--font-mono);
font-size: 10px;
font-weight: 600;
color: var(--text-faint);
width: 18px;
text-align: right;
flex-shrink: 0;
}
.proc-type {
font-family: var(--font-mono);
font-size: 10px;
font-weight: 500;
padding: 1px 6px;
border-radius: 3px;
background: var(--bg-inset);
color: var(--text-muted);
flex-shrink: 0;
}
.proc-type.type-from { background: var(--running-bg); color: var(--running); }
.proc-type.type-to { background: var(--amber-bg); color: var(--amber-deep); }
.proc-type.type-process { background: var(--success-bg); color: var(--success); }
.proc-type.type-choice { background: #F3EFF8; color: #6B4FA0; }
.proc-name {
font-weight: 500;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.proc-bar-area {
position: relative;
height: 20px;
background: var(--bg-inset);
border-radius: 3px;
overflow: hidden;
}
.proc-bar {
height: 100%;
border-radius: 3px;
min-width: 3px;
transition: width 0.3s ease;
position: relative;
}
.proc-bar.bar-success { background: linear-gradient(90deg, var(--success), #5DB866); }
.proc-bar.bar-error { background: linear-gradient(90deg, var(--error), #E74C3C); }
.proc-bar.bar-slow { background: linear-gradient(90deg, var(--warning), #E8A838); }
.proc-bar .bar-pct {
position: absolute;
right: 6px;
top: 50%;
transform: translateY(-50%);
font-family: var(--font-mono);
font-size: 9px;
font-weight: 600;
color: white;
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
}
.proc-duration {
font-family: var(--font-mono);
font-size: 11px;
font-weight: 500;
color: var(--text-secondary);
text-align: right;
}
.proc-duration.dur-error { color: var(--error); font-weight: 600; }
.proc-status-icon {
font-size: 14px;
text-align: center;
}
.proc-status-icon.icon-ok { color: var(--success); }
.proc-status-icon.icon-fail { color: var(--error); }
/* Bottleneck callout */
.bottleneck-callout {
display: flex;
align-items: center;
gap: 8px;
margin: 4px 18px 10px;
padding: 8px 14px;
background: var(--error-bg);
border: 1px solid var(--error-border);
border-left: 3px solid var(--error);
border-radius: var(--radius-sm);
font-size: 12px;
color: var(--error);
}
.bottleneck-callout strong { font-weight: 700; }
.bottleneck-pct {
font-family: var(--font-mono);
font-weight: 700;
font-size: 14px;
}
/* ==========================================================================
PROCESSOR DETAIL PANEL (split IN / OUT)
========================================================================== */
.detail-split {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 16px;
}
.detail-panel {
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-card);
overflow: hidden;
}
.detail-panel.panel-error {
border-color: var(--error-border);
}
.detail-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 16px;
border-bottom: 1px solid var(--border-subtle);
background: var(--bg-raised);
}
.detail-panel.panel-error .detail-panel-header {
background: var(--error-bg);
border-bottom-color: var(--error-border);
}
.panel-title {
font-size: 12px;
font-weight: 600;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 6px;
}
.panel-title .arrow-in { color: var(--success); }
.panel-title .arrow-out { color: var(--error); }
.panel-tag {
font-family: var(--font-mono);
font-size: 10px;
padding: 1px 6px;
border-radius: 8px;
background: var(--bg-inset);
color: var(--text-muted);
font-weight: 500;
}
.detail-panel-body {
padding: 14px 16px;
}
/* Headers section */
.headers-section { margin-bottom: 16px; }
.section-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.6px;
color: var(--text-muted);
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 6px;
}
.section-label .count {
font-family: var(--font-mono);
font-size: 10px;
padding: 0 5px;
border-radius: 8px;
background: var(--bg-inset);
color: var(--text-faint);
}
.header-list {
display: flex;
flex-direction: column;
gap: 3px;
}
.header-row {
display: grid;
grid-template-columns: 180px 1fr;
gap: 8px;
padding: 3px 8px;
border-radius: 3px;
font-family: var(--font-mono);
font-size: 11px;
transition: background 0.08s;
}
.header-row:hover { background: var(--bg-hover); }
.header-row.h-added { background: var(--success-bg); }
.header-row.h-removed { background: var(--error-bg); text-decoration: line-through; }
.header-key {
color: var(--text-secondary);
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.header-value {
color: var(--text-primary);
word-break: break-all;
}
.header-row.h-added .header-key { color: var(--success); }
.header-row.h-added .header-value { color: var(--success); }
.header-row.h-removed .header-key { color: var(--error); opacity: 0.7; }
.header-row.h-removed .header-value { color: var(--error); opacity: 0.7; }
.diff-badge {
font-family: var(--font-mono);
font-size: 9px;
font-weight: 600;
padding: 0 5px;
border-radius: 8px;
margin-left: 4px;
}
.diff-badge.added { background: var(--success-bg); color: var(--success); border: 1px solid var(--success-border); }
.diff-badge.removed { background: var(--error-bg); color: var(--error); border: 1px solid var(--error-border); }
/* Body section */
.body-section { margin-bottom: 0; }
.body-toolbar {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.body-size {
font-family: var(--font-mono);
font-size: 10px;
color: var(--text-muted);
}
.body-format-tag {
font-family: var(--font-mono);
font-size: 10px;
font-weight: 600;
padding: 1px 6px;
border-radius: 8px;
background: var(--running-bg);
color: var(--running);
border: 1px solid var(--running-border);
}
.body-toggle {
font-size: 10px;
font-weight: 500;
padding: 2px 8px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--bg-surface);
color: var(--text-muted);
cursor: pointer;
transition: all 0.12s;
margin-left: auto;
}
.body-toggle:hover { border-color: var(--text-faint); color: var(--text-secondary); }
.body-toggle.active { background: var(--amber-bg); border-color: var(--amber-light); color: var(--amber-deep); }
.body-content {
background: var(--bg-inset);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
padding: 12px;
overflow-x: auto;
max-height: 280px;
overflow-y: auto;
}
.body-content pre {
font-family: var(--font-mono);
font-size: 11px;
line-height: 1.6;
color: var(--text-primary);
margin: 0;
white-space: pre;
}
/* JSON syntax highlighting */
.json-key { color: #6B4FA0; }
.json-str { color: var(--success); }
.json-num { color: var(--amber-deep); }
.json-bool { color: var(--running); }
.json-null { color: var(--text-muted); }
.json-brace { color: var(--text-secondary); }
/* ==========================================================================
ERROR DISPLAY
========================================================================== */
.error-section {
margin-top: 0;
}
.error-badge-row {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
}
.error-class {
font-family: var(--font-mono);
font-size: 12px;
font-weight: 600;
color: var(--error);
}
.error-http-badge {
font-family: var(--font-mono);
font-size: 11px;
font-weight: 700;
padding: 2px 10px;
border-radius: 10px;
background: var(--error-bg);
color: var(--error);
border: 1px solid var(--error-border);
}
.error-message-box {
background: var(--error-bg);
border: 1px solid var(--error-border);
border-left: 3px solid var(--error);
border-radius: var(--radius-sm);
padding: 10px 14px;
margin-bottom: 12px;
font-family: var(--font-mono);
font-size: 12px;
color: var(--error);
line-height: 1.6;
}
.error-detail-grid {
display: grid;
grid-template-columns: 100px 1fr;
gap: 4px 12px;
margin-bottom: 14px;
font-size: 12px;
}
.error-detail-key {
font-weight: 600;
color: var(--text-muted);
text-align: right;
}
.error-detail-val {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-primary);
word-break: break-all;
}
.stack-section { }
.stack-toggle {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
font-weight: 600;
color: var(--text-secondary);
cursor: pointer;
padding: 4px 0;
margin-bottom: 6px;
background: none;
border: none;
font-family: var(--font-body);
}
.stack-toggle:hover { color: var(--text-primary); }
.stack-content {
background: var(--sidebar-bg);
border-radius: var(--radius-sm);
padding: 12px 14px;
overflow-x: auto;
max-height: 220px;
overflow-y: auto;
}
.stack-content pre {
font-family: var(--font-mono);
font-size: 10px;
line-height: 1.7;
color: #D4C8B8;
margin: 0;
white-space: pre;
}
.stack-content .stack-highlight {
color: #F0C4BE;
font-weight: 600;
}
.stack-content .stack-cause {
color: #E8A838;
font-weight: 600;
}
.stack-content .stack-at {
color: #8B9DAF;
}
/* ==========================================================================
PROPERTIES & CORRELATION
========================================================================== */
.props-section {
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-card);
overflow: hidden;
margin-bottom: 16px;
}
.props-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 18px;
border-bottom: 1px solid var(--border-subtle);
cursor: pointer;
transition: background 0.08s;
}
.props-header:hover { background: var(--bg-hover); }
.props-title {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
.props-chevron {
color: var(--text-faint);
font-size: 12px;
transition: transform 0.2s;
}
.props-body {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 0;
padding: 16px 18px;
}
.props-column {
padding: 0 16px;
border-right: 1px solid var(--border-subtle);
}
.props-column:first-child { padding-left: 0; }
.props-column:last-child { border-right: none; }
.props-col-title {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.6px;
color: var(--text-muted);
margin-bottom: 10px;
}
.prop-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px;
padding: 3px 0;
border-bottom: 1px solid var(--border-subtle);
font-size: 11px;
}
.prop-row:last-child { border-bottom: none; }
.prop-key {
font-weight: 500;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.prop-val {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-primary);
word-break: break-all;
}
/* Correlation chain */
.correlation-chain {
display: flex;
align-items: center;
gap: 0;
margin-top: 16px;
padding: 12px 18px;
border-top: 1px solid var(--border-subtle);
}
.chain-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.6px;
color: var(--text-muted);
margin-right: 14px;
white-space: nowrap;
}
.chain-node {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 4px 12px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
font-family: var(--font-mono);
font-size: 11px;
font-weight: 500;
color: var(--text-secondary);
cursor: pointer;
transition: all 0.12s;
background: var(--bg-surface);
}
.chain-node:hover { border-color: var(--amber); color: var(--amber); }
.chain-node.current { background: var(--amber-bg); border-color: var(--amber-light); color: var(--amber-deep); font-weight: 600; }
.chain-node.chain-error { border-color: var(--error-border); color: var(--error); }
.chain-arrow {
color: var(--text-faint);
font-size: 14px;
margin: 0 6px;
}
/* ==========================================================================
KEYBOARD HINTS
========================================================================== */
.keyboard-bar {
position: fixed;
bottom: 0;
left: 220px;
right: 0;
display: flex;
align-items: center;
gap: 16px;
padding: 6px 24px;
background: var(--sidebar-bg);
border-top: 1px solid rgba(255,255,255,0.06);
z-index: 100;
}
.kb-hint {
display: flex;
align-items: center;
gap: 5px;
font-size: 11px;
color: var(--sidebar-muted);
}
.kb-key {
font-family: var(--font-mono);
font-size: 10px;
padding: 1px 5px;
border-radius: 3px;
background: rgba(255,255,255,0.08);
color: var(--sidebar-text);
border: 1px solid rgba(255,255,255,0.1);
}
< / style >
< / head >
< body >
< div class = "app" >
<!-- ====================================================================
SIDEBAR (identical structure to mock-v2)
==================================================================== -->
< 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 active" >
< span class = "health health-live" > < / span >
< div class = "item-info" >
< div class = "item-name" > order-service< / div >
< div class = "item-meta" > 2 agents< / div >
< / div >
< span class = "item-count" > 1,847< / span >
< / div >
< div class = "sidebar-item" >
< span class = "health health-live" > < / span >
< div class = "item-info" >
< div class = "item-name" > payment-gateway< / div >
< div class = "item-meta" > 2 agents< / div >
< / div >
< span class = "item-count" > 923< / span >
< / div >
< div class = "sidebar-item" >
< span class = "health health-live" > < / span >
< div class = "item-info" >
< div class = "item-name" > shipment-tracker< / div >
< div class = "item-meta" > 2 agents< / div >
< / div >
< span class = "item-count" > 471< / span >
< / div >
< div class = "sidebar-item" >
< span class = "health health-stale" > < / span >
< div class = "item-info" >
< div class = "item-name" > notification-hub< / div >
< div class = "item-meta" > 1 agent< / div >
< / div >
< span class = "item-count" > 128< / span >
< / div >
< div 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" 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" > 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" > payment-process< / div > < / div >
< span class = "item-count" > 414< / span >
< / div >
< / div >
<!-- Agent health -->
< div class = "sidebar-agents-header" >
< span > Agents< / span >
< span class = "sidebar-agent-badge" > 4/4 live< / span >
< / div >
< div style = "padding: 0 0 6px; overflow-y: auto; max-height: 180px;" >
< div class = "agent-item" >
< span class = "agent-dot health-live" > < / span >
< div class = "agent-info" >
< div class = "agent-name" > prod-node-01< / 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-node-02< / 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 class = "agent-item" >
< span class = "agent-dot health-live" > < / span >
< div class = "agent-info" >
< div class = "agent-name" > prod-node-03< / div >
< div class = "agent-detail" > payment-svc v3.2.1< / div >
< / div >
< div class = "agent-stats-col" >
< div class = "agent-tps" > 12.1/s< / div >
< div > 5s ago< / div >
< / div >
< / div >
< div class = "agent-item" >
< span class = "agent-dot health-live" > < / span >
< div class = "agent-info" >
< div class = "agent-name" > prod-node-04< / div >
< div class = "agent-detail" > shipment-svc v3.2.0< / div >
< / div >
< div class = "agent-stats-col" >
< div class = "agent-tps" > 9.1/s< / div >
< div > 3s ago< / div >
< / div >
< / div >
< / div >
< div class = "sidebar-bottom" >
< div class = "sidebar-item" > < span style = "font-size: 13px; width: 18px; text-align: center;" > ⚙ < / span > < div class = "item-info" > < div class = "item-name" > Admin< / div > < / div > < / div >
< div class = "sidebar-item" > < span style = "font-size: 13px; width: 18px; text-align: center;" > ☰ < / span > < div class = "item-info" > < div class = "item-name" > API Docs< / div > < / div > < / div >
< / div >
< / aside >
<!-- ====================================================================
MAIN CONTENT — Exchange Detail
==================================================================== -->
< div class = "main" >
<!-- Top bar -->
< div class = "topbar" >
< div class = "topbar-breadcrumb" >
< span class = "crumb-link" > order-service< / span >
< span class = "crumb-sep" > /< / span >
< span class = "crumb-link" > order-processing< / span >
< span class = "crumb-sep" > /< / span >
< span class = "crumb-active" > Exchange OP-88421< / span >
< / div >
< div class = "topbar-right" >
< span class = "topbar-env" > PRODUCTION< / span >
< div class = "topbar-user" >
< span > hendrik< / span >
< div class = "topbar-avatar" > H< / div >
< / div >
< / div >
< / div >
<!-- Scrollable content -->
< div class = "content" style = "padding-bottom: 60px;" >
<!-- ============================================================
1. EXCHANGE HEADER CARD
============================================================ -->
< div class = "exchange-header animate-in" >
< div class = "exchange-header-top" >
< div class = "exchange-title-area" >
< span class = "exchange-route" > order-processing< / span >
< span class = "exchange-status-lg" >
< span class = "status-dot" > < / span >
FAILED
< / span >
< / div >
< div class = "exchange-actions" >
< button class = "action-btn" title = "Copy Exchange ID" > 📋 Copy ID< / button >
< button class = "action-btn" title = "Copy full trace as JSON" > 📄 Export JSON< / button >
< button class = "action-btn primary" title = "Retry this exchange" > ↻ Retry< / button >
< / div >
< / div >
< div class = "exchange-meta-grid" >
< div class = "meta-item" >
< div class = "meta-label" > Business IDs< / div >
< div class = "meta-value" >
< span class = "biz-tag order" > Order: OP-88421< / span >
< span class = "biz-tag customer" > Customer: DE-40912< / span >
< / div >
< / div >
< div class = "meta-item" >
< div class = "meta-label" > Duration< / div >
< div class = "meta-value" >
< span class = "duration-lg" > 2m 33s< / span >
< span class = "duration-label" > total< / span >
< / div >
< div class = "meta-sub" > Started 09:14:22 (2 min ago)< / div >
< / div >
< div class = "meta-item" >
< div class = "meta-label" > Exchange ID< / div >
< div class = "meta-value mono-sm" > ID-20260317-091422-prod02-88421< / div >
< div class = "meta-sub" > Breadcrumb: BC-88421-001< / div >
< / div >
< div class = "meta-item" >
< div class = "meta-label" > Agent< / div >
< div class = "meta-value" style = "display: flex; align-items: center; gap: 6px;" >
< span style = "width: 6px; height: 6px; border-radius: 50%; background: #5DB866; display: inline-block;" > < / span >
prod-node-02
< / div >
< div class = "meta-sub" > order-service v3.2.1< / div >
< / div >
< / div >
< / div >
<!-- ============================================================
2. PROCESSOR TIMELINE (Gantt-style)
============================================================ -->
< div class = "timeline-section animate-in delay-1" >
< div class = "timeline-header" >
< div class = "timeline-title" >
Processor Timeline
< span class = "proc-count" > 5 processors< / span >
< / div >
< div class = "timeline-legend" >
< div class = "legend-item" > < span class = "legend-dot l-success" > < / span > Success< / div >
< div class = "legend-item" > < span class = "legend-dot l-error" > < / span > Failed< / div >
< div class = "legend-item" > < span class = "legend-dot l-slow" > < / span > Slow (> SLA)< / div >
< / div >
< / div >
< div class = "timeline-body" >
<!-- Row 1: from(direct:order - intake) — 12ms -->
< div class = "timeline-row" onclick = "selectRow(this, 0)" >
< div class = "proc-label" >
< span class = "proc-index" > 1< / span >
< span class = "proc-type type-from" > from< / span >
< span class = "proc-name" > direct:order-intake< / span >
< / div >
< div class = "proc-bar-area" >
< div class = "proc-bar bar-success" style = "width: 0.8%;" > < / div >
< / div >
< div class = "proc-duration" > 12ms< / div >
< div class = "proc-status-icon icon-ok" > ✓ < / div >
< / div >
<!-- Row 2: process(OrderValidator) — 45ms -->
< div class = "timeline-row" onclick = "selectRow(this, 1)" >
< div class = "proc-label" >
< span class = "proc-index" > 2< / span >
< span class = "proc-type type-process" > process< / span >
< span class = "proc-name" > OrderValidator< / span >
< / div >
< div class = "proc-bar-area" >
< div class = "proc-bar bar-success" style = "width: 2.9%;" > < / div >
< / div >
< div class = "proc-duration" > 45ms< / div >
< div class = "proc-status-icon icon-ok" > ✓ < / div >
< / div >
<!-- Row 3: to(sql:INSERT INTO orders) — 89ms -->
< div class = "timeline-row" onclick = "selectRow(this, 2)" >
< div class = "proc-label" >
< span class = "proc-index" > 3< / span >
< span class = "proc-type type-to" > to< / span >
< span class = "proc-name" > sql:INSERT INTO orders< / span >
< / div >
< div class = "proc-bar-area" >
< div class = "proc-bar bar-success" style = "width: 5.8%;" > < / div >
< / div >
< div class = "proc-duration" > 89ms< / div >
< div class = "proc-status-icon icon-ok" > ✓ < / div >
< / div >
<!-- Row 4: choice(priority check) — 2ms -->
< div class = "timeline-row" onclick = "selectRow(this, 3)" >
< div class = "proc-label" >
< span class = "proc-index" > 4< / span >
< span class = "proc-type type-choice" > choice< / span >
< span class = "proc-name" > priority-check< / span >
< / div >
< div class = "proc-bar-area" >
< div class = "proc-bar bar-success" style = "width: 0.3%;" > < / div >
< / div >
< div class = "proc-duration" > 2ms< / div >
< div class = "proc-status-icon icon-ok" > ✓ < / div >
< / div >
<!-- Row 5: to(http:payment - api/charge) — 2m 18s — FAILED -->
< div class = "timeline-row row-error selected" onclick = "selectRow(this, 4)" >
< div class = "proc-label" >
< span class = "proc-index" > 5< / span >
< span class = "proc-type type-to" > to< / span >
< span class = "proc-name" style = "color: var(--error); font-weight: 600;" > http:payment-api/charge< / span >
< / div >
< div class = "proc-bar-area" >
< div class = "proc-bar bar-error" style = "width: 94%;" >
< span class = "bar-pct" > 94%< / span >
< / div >
< / div >
< div class = "proc-duration dur-error" > 2m 18s< / div >
< div class = "proc-status-icon icon-fail" > ✗ < / div >
< / div >
< / div >
<!-- Bottleneck callout -->
< div class = "bottleneck-callout" >
< span style = "font-size: 16px;" > ⚠ < / span >
< span > < strong > Bottleneck detected:< / strong > Processor #5 < code style = "font-family: var(--font-mono); font-size: 11px; background: rgba(192,57,43,0.1); padding: 1px 4px; border-radius: 3px;" > http:payment-api/charge< / code > consumed< / span >
< span class = "bottleneck-pct" > 94%< / span >
< span > of total execution time (2m 18s of 2m 33s). 503 Service Unavailable after 30s timeout with 4 retries.< / span >
< / div >
< / div >
<!-- ============================================================
3. PROCESSOR DETAIL PANEL (split IN / OUT)
Selected: #5 http:payment-api/charge (FAILED)
============================================================ -->
< div class = "detail-split animate-in delay-2" >
<!-- LEFT: Message IN (at entry to this processor) -->
< div class = "detail-panel" >
< div class = "detail-panel-header" >
< div class = "panel-title" >
< span class = "arrow-in" style = "font-size: 16px;" > → < / span >
Message IN
< span class = "panel-tag" > at processor #5 entry< / span >
< / div >
< / div >
< div class = "detail-panel-body" >
<!-- Headers at entry -->
< div class = "headers-section" >
< div class = "section-label" >
Headers < span class = "count" > 9< / span >
< / div >
< div class = "header-list" >
< div class = "header-row" >
< span class = "header-key" > breadcrumbId< / span >
< span class = "header-value" > BC-88421-001< / span >
< / div >
< div class = "header-row" >
< span class = "header-key" > CamelHttpMethod< / span >
< span class = "header-value" > POST< / span >
< / div >
< div class = "header-row" >
< span class = "header-key" > Content-Type< / span >
< span class = "header-value" > application/json< / span >
< / div >
< div class = "header-row" >
< span class = "header-key" > orderId< / span >
< span class = "header-value" > OP-88421< / span >
< / div >
< div class = "header-row" >
< span class = "header-key" > customerId< / span >
< span class = "header-value" > DE-40912< / span >
< / div >
< div class = "header-row" >
< span class = "header-key" > correlationId< / span >
< span class = "header-value" > corr-a7f3e9d1-88421< / span >
< / div >
< div class = "header-row" >
< span class = "header-key" > orderPriority< / span >
< span class = "header-value" > HIGH< / span >
< / div >
< div class = "header-row h-added" >
< span class = "header-key" > orderDbId < span class = "diff-badge added" > +added< / span > < / span >
< span class = "header-value" > 48291< / span >
< / div >
< div class = "header-row h-added" >
< span class = "header-key" > priorityTier < span class = "diff-badge added" > +added< / span > < / span >
< span class = "header-value" > express< / span >
< / div >
< / div >
< / div >
<!-- Body at entry -->
< div class = "body-section" >
< div class = "section-label" > Body< / div >
< div class = "body-toolbar" >
< span class = "body-format-tag" > JSON< / span >
< span class = "body-size" > 1.2 KB< / span >
< button class = "body-toggle" > Show diff from #4< / button >
< / div >
< div class = "body-content" >
< pre > < span class = "json-brace" > {< / span >
< span class = "json-key" > "orderId"< / span > : < span class = "json-str" > "OP-88421"< / span > ,
< span class = "json-key" > "customerId"< / span > : < span class = "json-str" > "DE-40912"< / span > ,
< span class = "json-key" > "timestamp"< / span > : < span class = "json-str" > "2026-03-17T09:14:22.184Z"< / span > ,
< span class = "json-key" > "items"< / span > : < span class = "json-brace" > [< / span >
< span class = "json-brace" > {< / span >
< span class = "json-key" > "sku"< / span > : < span class = "json-str" > "WDG-4420-BLK"< / span > ,
< span class = "json-key" > "name"< / span > : < span class = "json-str" > "Industrial Pressure Valve DN50"< / span > ,
< span class = "json-key" > "quantity"< / span > : < span class = "json-num" > 12< / span > ,
< span class = "json-key" > "unitPrice"< / span > : < span class = "json-num" > 284.50< / span > ,
< span class = "json-key" > "currency"< / span > : < span class = "json-str" > "EUR"< / span >
< span class = "json-brace" > }< / span > ,
< span class = "json-brace" > {< / span >
< span class = "json-key" > "sku"< / span > : < span class = "json-str" > "FLG-1180-SS"< / span > ,
< span class = "json-key" > "name"< / span > : < span class = "json-str" > "Stainless Steel Flange PN16"< / span > ,
< span class = "json-key" > "quantity"< / span > : < span class = "json-num" > 24< / span > ,
< span class = "json-key" > "unitPrice"< / span > : < span class = "json-num" > 67.90< / span > ,
< span class = "json-key" > "currency"< / span > : < span class = "json-str" > "EUR"< / span >
< span class = "json-brace" > }< / span >
< span class = "json-brace" > ]< / span > ,
< span class = "json-key" > "totalAmount"< / span > : < span class = "json-num" > 5043.60< / span > ,
< span class = "json-key" > "shippingAddress"< / span > : < span class = "json-brace" > {< / span >
< span class = "json-key" > "street"< / span > : < span class = "json-str" > "Industriestr. 42"< / span > ,
< span class = "json-key" > "city"< / span > : < span class = "json-str" > "Stuttgart"< / span > ,
< span class = "json-key" > "zip"< / span > : < span class = "json-str" > "70178"< / span > ,
< span class = "json-key" > "country"< / span > : < span class = "json-str" > "DE"< / span >
< span class = "json-brace" > }< / span > ,
< span class = "json-key" > "priority"< / span > : < span class = "json-str" > "HIGH"< / span > ,
< span class = "json-key" > "paymentMethod"< / span > : < span class = "json-str" > "invoice_30d"< / span >
< span class = "json-brace" > }< / span > < / pre >
< / div >
< / div >
< / div >
< / div >
<!-- RIGHT: Message OUT / Error -->
< div class = "detail-panel panel-error" >
< div class = "detail-panel-header" >
< div class = "panel-title" >
< span class = "arrow-out" style = "font-size: 16px;" > ✗ < / span >
Error at Processor #5
< span class = "panel-tag" style = "background: var(--error-bg); color: var(--error); border: 1px solid var(--error-border);" > FAILED< / span >
< / div >
< / div >
< div class = "detail-panel-body" >
<!-- Error info -->
< div class = "error-section" >
< div class = "error-badge-row" >
< span class = "error-http-badge" > 503< / span >
< span class = "error-class" > java.net.http.HttpTimeoutException< / span >
< / div >
< div class = "error-message-box" >
request timed out after 30000ms — HTTP POST http://payment-api:8080/v2/charge returned 503 Service Unavailable after 4 retry attempts (backoff: 1s, 2s, 4s, 8s)
< / div >
< div class = "error-detail-grid" >
< span class = "error-detail-key" > Endpoint< / span >
< span class = "error-detail-val" > http://payment-api:8080/v2/charge< / span >
< span class = "error-detail-key" > HTTP Method< / span >
< span class = "error-detail-val" > POST< / span >
< span class = "error-detail-key" > HTTP Status< / span >
< span class = "error-detail-val" style = "color: var(--error); font-weight: 600;" > 503 Service Unavailable< / span >
< span class = "error-detail-key" > Timeout< / span >
< span class = "error-detail-val" > 30,000ms (configured)< / span >
< span class = "error-detail-key" > Retries< / span >
< span class = "error-detail-val" > 4 of 4 (exhausted)< / span >
< span class = "error-detail-key" > Last Attempt< / span >
< span class = "error-detail-val" > 09:16:55.412 (after 8s backoff)< / span >
< / div >
<!-- Stack trace -->
< div class = "stack-section" >
< button class = "stack-toggle" >
< span style = "font-size: 9px;" > ▼ < / span >
Stack Trace (42 frames)
< / button >
< div class = "stack-content" >
< pre > < span class = "stack-highlight" > java.net.http.HttpTimeoutException: request timed out after 30000ms< / span >
< span class = "stack-at" > at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:586)< / span >
< span class = "stack-at" > at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:538)< / span >
< span class = "stack-at" > at org.apache.camel.component.http.HttpProducer.executeMethod(HttpProducer.java:347)< / span >
< span class = "stack-at" > at org.apache.camel.component.http.HttpProducer.process(HttpProducer.java:198)< / span >
< span class = "stack-at" > at org.apache.camel.processor.SendProcessor.process(SendProcessor.java:172)< / span >
< span class = "stack-at" > at org.apache.camel.processor.errorhandler.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:614)< / span >
< span class = "stack-at" > at org.apache.camel.processor.errorhandler.RedeliveryErrorHandler.processErrorHandler(RedeliveryErrorHandler.java:258)< / span >
< span class = "stack-at" > at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:399)< / span >
< span class = "stack-at" > at org.apache.camel.processor.Pipeline.process(Pipeline.java:184)< / span >
< span class = "stack-at" > at org.apache.camel.processor.Pipeline.process(Pipeline.java:74)< / span >
< span class = "stack-at" > at org.apache.camel.impl.engine.DefaultReactiveExecutor$Worker.run(DefaultReactiveExecutor.java:182)< / span >
< span class = "stack-at" > at org.apache.camel.impl.engine.DefaultReactiveExecutor.scheduleMain(DefaultReactiveExecutor.java:64)< / span >
< span class = "stack-at" > at org.apache.camel.processor.Pipeline.process(Pipeline.java:184)< / span >
< span class = "stack-at" > at org.apache.camel.spring.boot.SpringBootRoutesCollector.process(SpringBootRoutesCollector.java:89)< / span >
< span class = "stack-cause" > Caused by: java.net.ConnectException: Connection refused: payment-api:8080< / span >
< span class = "stack-at" > at java.base/sun.nio.ch.Net.connect0(Native Method)< / span >
< span class = "stack-at" > at java.base/sun.nio.ch.Net.connect(Net.java:579)< / span >
< span class = "stack-at" > at java.base/sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:726)< / span >
< span class = "stack-at" > at java.net.http/jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:198)< / span >
< span class = "stack-at" > ... 8 more< / span > < / pre >
< / div >
< / div >
< / div >
<!-- Response headers (from the 503) -->
< div class = "headers-section" style = "margin-top: 16px;" >
< div class = "section-label" >
Response Headers < span class = "count" > 4< / span >
< / div >
< div class = "header-list" >
< div class = "header-row" >
< span class = "header-key" > Date< / span >
< span class = "header-value" > Mon, 17 Mar 2026 09:16:55 GMT< / span >
< / div >
< div class = "header-row" >
< span class = "header-key" > Content-Length< / span >
< span class = "header-value" > 0< / span >
< / div >
< div class = "header-row" >
< span class = "header-key" > Retry-After< / span >
< span class = "header-value" > 60< / span >
< / div >
< div class = "header-row" >
< span class = "header-key" > X-Request-Id< / span >
< span class = "header-value" > pay-req-f42a8c91< / span >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
<!-- ============================================================
4. PROPERTIES & CORRELATION
============================================================ -->
< div class = "props-section animate-in delay-3" >
< div class = "props-header" >
< span class = "props-title" > Exchange Properties & Context< / span >
< span class = "props-chevron" > ▼ < / span >
< / div >
< div class = "props-body" >
<!-- Column 1: Exchange Properties -->
< div class = "props-column" >
< div class = "props-col-title" > Exchange Properties< / div >
< div class = "prop-row" >
< span class = "prop-key" > CamelCreatedTimestamp< / span >
< span class = "prop-val" > 2026-03-17T09:14:22.184Z< / span >
< / div >
< div class = "prop-row" >
< span class = "prop-key" > CamelExternalRedelivered< / span >
< span class = "prop-val" > false< / span >
< / div >
< div class = "prop-row" >
< span class = "prop-key" > CamelToEndpoint< / span >
< span class = "prop-val" > http://payment-api:8080/v2/charge< / span >
< / div >
< div class = "prop-row" >
< span class = "prop-key" > CamelFailureEndpoint< / span >
< span class = "prop-val" > http://payment-api:8080/v2/charge< / span >
< / div >
< div class = "prop-row" >
< span class = "prop-key" > CamelRedeliveryCounter< / span >
< span class = "prop-val" > 4< / span >
< / div >
< div class = "prop-row" >
< span class = "prop-key" > CamelRedelivered< / span >
< span class = "prop-val" > true< / span >
< / div >
< / div >
<!-- Column 2: Route Context -->
< div class = "props-column" >
< div class = "props-col-title" > Route Context< / div >
< div class = "prop-row" >
< span class = "prop-key" > CamelContext< / span >
< span class = "prop-val" > order-service-ctx< / span >
< / div >
< div class = "prop-row" >
< span class = "prop-key" > Route ID< / span >
< span class = "prop-val" > order-processing< / span >
< / div >
< div class = "prop-row" >
< span class = "prop-key" > Route Version< / span >
< span class = "prop-val" > 3.2.1-20260315< / span >
< / div >
< div class = "prop-row" >
< span class = "prop-key" > From Endpoint< / span >
< span class = "prop-val" > direct:order-intake< / span >
< / div >
< div class = "prop-row" >
< span class = "prop-key" > Error Handler< / span >
< span class = "prop-val" > DefaultErrorHandler< / span >
< / div >
< / div >
<!-- Column 3: Environment -->
< div class = "props-column" >
< div class = "props-col-title" > Environment< / div >
< div class = "prop-row" >
< span class = "prop-key" > Agent ID< / span >
< span class = "prop-val" > prod-node-02< / span >
< / div >
< div class = "prop-row" >
< span class = "prop-key" > Hostname< / span >
< span class = "prop-val" > order-svc-6f8d4b-xk2p9< / span >
< / div >
< div class = "prop-row" >
< span class = "prop-key" > JVM< / span >
< span class = "prop-val" > Eclipse Temurin 17.0.10< / span >
< / div >
< div class = "prop-row" >
< span class = "prop-key" > Camel Version< / span >
< span class = "prop-val" > 4.4.1< / span >
< / div >
< div class = "prop-row" >
< span class = "prop-key" > Heap Used< / span >
< span class = "prop-val" > 412 MB / 1024 MB< / span >
< / div >
< / div >
< / div >
<!-- Correlation chain -->
< div class = "correlation-chain" >
< span class = "chain-label" > Correlation Chain< / span >
< span class = "chain-node current chain-error" >
< span style = "width: 5px; height: 5px; border-radius: 50; background: var(--error); display: inline-block;" > < / span >
order-processing
< / span >
< span class = "chain-arrow" > → < / span >
< span class = "chain-node" >
payment-flow
< / span >
< span class = "chain-arrow" > → < / span >
< span class = "chain-node" >
notification-sender
< / span >
< / div >
< / div >
< / div > <!-- /content -->
<!-- Keyboard hints bar -->
< div class = "keyboard-bar" >
< div class = "kb-hint" > < span class = "kb-key" > ↑ ↓ < / span > Navigate processors< / div >
< div class = "kb-hint" > < span class = "kb-key" > Enter< / span > Select processor< / div >
< div class = "kb-hint" > < span class = "kb-key" > D< / span > Toggle diff< / div >
< div class = "kb-hint" > < span class = "kb-key" > J< / span > View raw JSON< / div >
< div class = "kb-hint" > < span class = "kb-key" > R< / span > Retry exchange< / div >
< div class = "kb-hint" > < span class = "kb-key" > Esc< / span > Back to list< / div >
< / div >
< / div > <!-- /main -->
< / div > <!-- /app -->
< script >
// Simple row selection for the timeline
function selectRow(el, index) {
document.querySelectorAll('.timeline-row').forEach(function(row) {
row.classList.remove('selected');
});
el.classList.add('selected');
}
< / script >
< / body >
< / html >