feat: RouteDetail page

Implements the /routes/:id route with route header card (name, status
badge, description), 5-card KPI strip (total executions, success rate,
p50/p99 latency, inflight count), ProcessorTimeline showing aggregate
processor stats across all executions, filtered DataTable of recent
executions for the route, and error patterns section grouped by
exception class. Uses useParams() to get route ID and navigates to
/exchanges/:id on row click.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-18 10:22:02 +01:00
parent 9dd78a7d2e
commit ebf653e848
2 changed files with 691 additions and 0 deletions

View File

@@ -0,0 +1,280 @@
/* Scrollable content area */
.content {
flex: 1;
overflow-y: auto;
padding: 20px 24px 40px;
min-width: 0;
background: var(--bg-body);
}
/* Route header card */
.routeHeader {
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: 14px;
}
.routeTitleRow {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.routeTitleGroup {
display: flex;
align-items: center;
gap: 10px;
}
.routeName {
font-size: 18px;
font-weight: 700;
color: var(--text-primary);
font-family: var(--font-mono);
letter-spacing: -0.3px;
}
.routeMeta {
display: flex;
align-items: center;
gap: 12px;
}
.routeGroup {
font-size: 11px;
font-family: var(--font-mono);
color: var(--text-muted);
background: var(--bg-inset);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
padding: 2px 8px;
}
.routeDescription {
font-size: 12px;
color: var(--text-secondary);
line-height: 1.5;
}
/* KPI strip */
.kpiStrip {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 10px;
margin-bottom: 16px;
}
.kpiCard {
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
box-shadow: var(--shadow-card);
padding: 12px 16px;
text-align: center;
}
.kpiLabel {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.6px;
color: var(--text-muted);
margin-bottom: 6px;
}
.kpiValue {
font-size: 22px;
font-weight: 700;
font-family: var(--font-mono);
color: var(--text-primary);
line-height: 1.2;
}
.kpiGood { color: var(--success); }
.kpiWarn { color: var(--warning); }
.kpiError { color: var(--error); }
.kpiRunning { color: var(--running); }
/* Section layout */
.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;
}
.sectionHeader {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border-bottom: 1px solid var(--border-subtle);
}
.sectionTitle {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
.sectionMeta {
font-size: 11px;
color: var(--text-muted);
font-family: var(--font-mono);
}
.emptyMsg {
padding: 32px;
text-align: center;
font-size: 12px;
color: var(--text-muted);
}
/* Executions table */
.tableSection {
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;
}
.tableHeader {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border-bottom: 1px solid var(--border-subtle);
}
.tableTitle {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
.tableRight {
display: flex;
align-items: center;
gap: 10px;
}
.tableMeta {
font-size: 11px;
color: var(--text-muted);
font-family: var(--font-mono);
}
/* Status cell */
.statusCell {
display: flex;
align-items: center;
gap: 5px;
}
/* Customer text */
.customerText {
color: var(--text-secondary);
}
/* Duration color classes */
.durFast { color: var(--success); }
.durNormal { color: var(--text-secondary); }
.durSlow { color: var(--warning); }
.durBreach { color: var(--error); }
/* Agent badge */
.agentBadge {
display: inline-flex;
align-items: center;
gap: 5px;
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-secondary);
}
.agentDot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #5db866;
box-shadow: 0 0 4px rgba(93, 184, 102, 0.4);
flex-shrink: 0;
}
/* Inline error row */
.inlineError {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 8px 12px;
background: var(--error-bg);
border-left: 3px solid var(--error-border);
}
.inlineErrorIcon {
color: var(--error);
font-size: 14px;
flex-shrink: 0;
margin-top: 1px;
}
.errorClass {
font-family: var(--font-mono);
font-size: 10px;
font-weight: 600;
color: var(--error);
margin-bottom: 4px;
}
.errorText {
font-size: 11px;
color: var(--error);
font-family: var(--font-mono);
line-height: 1.4;
}
/* Error patterns section */
.errorPatterns {
padding: 12px 16px;
display: flex;
flex-direction: column;
gap: 10px;
}
.errorPattern {
background: var(--error-bg);
border: 1px solid var(--error-border);
border-radius: var(--radius-md);
padding: 10px 14px;
}
.errorPatternHeader {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 6px;
}
.errorPatternMessage {
font-size: 11px;
color: var(--text-secondary);
font-family: var(--font-mono);
line-height: 1.5;
margin-bottom: 4px;
white-space: pre-wrap;
word-break: break-word;
}
.errorPatternTime {
font-size: 10px;
color: var(--text-muted);
font-family: var(--font-mono);
}