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:
280
src/pages/RouteDetail/RouteDetail.module.css
Normal file
280
src/pages/RouteDetail/RouteDetail.module.css
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user