1652 lines
49 KiB
HTML
1652 lines
49 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Cameleer3 — Operations Console</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
<style>
|
|
/*
|
|
* ============================================================================
|
|
* CAMELEER3 OPERATIONS CONSOLE — DESIGN RATIONALE
|
|
* ============================================================================
|
|
*
|
|
* This mock is designed from 15 years of middleware operations experience.
|
|
* Here is what typical monitoring UIs get WRONG and how this addresses it:
|
|
*
|
|
* 1. BUSINESS CONTEXT IS KING
|
|
* Most monitoring tools show exchangeId, correlationId, routeId — all
|
|
* technical identifiers that mean nothing when a business user calls and
|
|
* says "order OP-88421 didn't go through." This console surfaces business
|
|
* IDs (Order ID, Customer ID, Source System) as first-class columns,
|
|
* extracted from exchange headers/properties during ingestion.
|
|
*
|
|
* 2. TIME-RELATIVE DISPLAY
|
|
* "2 min ago" is instantly parseable; "09:14:33.127" requires mental math.
|
|
* Recent items use relative time, older items switch to absolute. Both are
|
|
* shown on hover for precision when needed (e.g., incident reports).
|
|
*
|
|
* 3. ERRORS WITHOUT CLICKING
|
|
* The #1 time-waster in incident response: clicking through 40 failed
|
|
* executions one by one to read error messages. This table shows error
|
|
* previews inline and expands to full stack trace without navigation.
|
|
*
|
|
* 4. SLA AWARENESS
|
|
* Operators don't care that p99 latency is 342ms. They care that it's
|
|
* ABOVE the 300ms SLA threshold. Color-coding and threshold lines on
|
|
* charts make SLA violations visible at a glance.
|
|
*
|
|
* 5. TREND DIRECTION
|
|
* A metric value alone is useless without context. "Error rate: 2.3%"
|
|
* — is that going up or down? Trend arrows answer the only question
|
|
* that matters: "Is it getting better or worse?"
|
|
*
|
|
* 6. AGENT HEALTH CORRELATION
|
|
* When errors spike, the first question is "is it the app or the infra?"
|
|
* Seeing agent health alongside execution data answers this immediately.
|
|
* Dead/stale agents explain missing data; overloaded agents explain latency.
|
|
*
|
|
* 7. SHIFT-AWARE SUMMARIES
|
|
* Operators work in shifts. "Overnight failures" is a real concept.
|
|
* The health bar shows "since last shift" counts, not just "last 24h."
|
|
*
|
|
* 8. WHAT'S MISSING FROM THIS MOCK (would need backend support):
|
|
* - Reprocessing/retry actions (needs agent command channel)
|
|
* - Correlation chains (order -> payment -> shipment linked view)
|
|
* - Time-shifted comparison overlays (today vs yesterday)
|
|
* - Saved filter presets per operator
|
|
* - Alert rule configuration
|
|
* - Export to CSV/PDF for incident reports
|
|
* - Annotation markers on charts (deploys, config changes)
|
|
* - Multi-tenant/team-scoped views
|
|
* ============================================================================
|
|
*/
|
|
|
|
/* ─── Reset & Base ─── */
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
:root {
|
|
--bg-deep: #060a13;
|
|
--bg-base: #0a0e17;
|
|
--bg-surface: #111827;
|
|
--bg-raised: #1a2332;
|
|
--bg-hover: #1e2d3d;
|
|
--border: #1e2d3d;
|
|
--border-subtle: #152030;
|
|
--text-primary: #e2e8f0;
|
|
--text-secondary: #8b9cb6;
|
|
--text-muted: #4a5e7a;
|
|
--amber: #f0b429;
|
|
--amber-dim: #b8860b;
|
|
--amber-glow: rgba(240, 180, 41, 0.15);
|
|
--cyan: #22d3ee;
|
|
--cyan-dim: #0e7490;
|
|
--cyan-glow: rgba(34, 211, 238, 0.12);
|
|
--rose: #f43f5e;
|
|
--rose-dim: #9f1239;
|
|
--rose-glow: rgba(244, 63, 94, 0.12);
|
|
--green: #10b981;
|
|
--green-glow: rgba(16, 185, 129, 0.12);
|
|
--blue: #3b82f6;
|
|
--purple: #a855f7;
|
|
--font-body: 'DM Sans', system-ui, sans-serif;
|
|
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
|
|
--radius-sm: 6px;
|
|
--radius-md: 10px;
|
|
--radius-lg: 14px;
|
|
}
|
|
|
|
body {
|
|
background: var(--bg-deep);
|
|
color: var(--text-primary);
|
|
font-family: var(--font-body);
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
min-height: 100vh;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
body::before {
|
|
content: '';
|
|
position: fixed;
|
|
inset: 0;
|
|
background:
|
|
radial-gradient(ellipse 800px 400px at 20% 20%, rgba(240, 180, 41, 0.03), transparent),
|
|
radial-gradient(ellipse 600px 600px at 80% 80%, rgba(34, 211, 238, 0.02), transparent);
|
|
pointer-events: none;
|
|
z-index: 0;
|
|
}
|
|
|
|
::-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-muted); }
|
|
|
|
/* ─── Layout ─── */
|
|
.console {
|
|
position: relative;
|
|
z-index: 1;
|
|
max-width: 1920px;
|
|
margin: 0 auto;
|
|
padding: 0 24px 24px;
|
|
}
|
|
|
|
/* ─── Top Nav ─── */
|
|
.topnav {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 12px 24px;
|
|
background: rgba(6, 10, 19, 0.85);
|
|
backdrop-filter: blur(12px);
|
|
border-bottom: 1px solid var(--border-subtle);
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
margin: 0 -24px 20px;
|
|
}
|
|
|
|
.topnav-brand {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.topnav-logo {
|
|
font-family: var(--font-mono);
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: var(--amber);
|
|
letter-spacing: -0.5px;
|
|
}
|
|
|
|
.topnav-sep {
|
|
color: var(--text-muted);
|
|
font-size: 18px;
|
|
font-weight: 300;
|
|
}
|
|
|
|
.topnav-page {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.topnav-right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
}
|
|
|
|
.topnav-clock {
|
|
font-family: var(--font-mono);
|
|
font-size: 13px;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.topnav-shift {
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
color: var(--cyan);
|
|
padding: 3px 10px;
|
|
border: 1px solid var(--cyan-dim);
|
|
border-radius: 99px;
|
|
background: var(--cyan-glow);
|
|
}
|
|
|
|
.live-indicator {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-family: var(--font-mono);
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
color: var(--green);
|
|
}
|
|
|
|
.live-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: var(--green);
|
|
animation: livePulse 2s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes livePulse {
|
|
0%, 100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4); }
|
|
50% { box-shadow: 0 0 0 6px rgba(16, 185, 129, 0); }
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: translateY(8px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
.animate-in { animation: fadeIn 0.3s ease-out both; }
|
|
|
|
/* ─── Health / SLA Status Bar ─── */
|
|
.health-bar {
|
|
display: grid;
|
|
grid-template-columns: repeat(6, 1fr);
|
|
gap: 12px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.health-card {
|
|
background: var(--bg-surface);
|
|
border: 1px solid var(--border-subtle);
|
|
border-radius: var(--radius-md);
|
|
padding: 14px 16px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.health-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 3px;
|
|
}
|
|
|
|
.health-card.ok::before { background: var(--green); }
|
|
.health-card.warn::before { background: var(--amber); }
|
|
.health-card.error::before { background: var(--rose); }
|
|
.health-card.info::before { background: var(--cyan); }
|
|
|
|
.health-label {
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
color: var(--text-muted);
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.health-value-row {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 8px;
|
|
}
|
|
|
|
.health-value {
|
|
font-family: var(--font-mono);
|
|
font-size: 24px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.health-value.ok { color: var(--green); }
|
|
.health-value.warn { color: var(--amber); }
|
|
.health-value.error { color: var(--rose); }
|
|
.health-value.info { color: var(--cyan); }
|
|
.health-value.neutral { color: var(--text-primary); }
|
|
|
|
.health-unit {
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.health-trend {
|
|
font-size: 12px;
|
|
font-family: var(--font-mono);
|
|
font-weight: 500;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 2px;
|
|
margin-left: auto;
|
|
}
|
|
|
|
.health-trend.up-good { color: var(--green); }
|
|
.health-trend.up-bad { color: var(--rose); }
|
|
.health-trend.down-good { color: var(--green); }
|
|
.health-trend.down-bad { color: var(--rose); }
|
|
.health-trend.flat { color: var(--text-muted); }
|
|
|
|
.health-detail {
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.health-detail strong {
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
/* ─── Summary Badges Row ─── */
|
|
.summary-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
margin-bottom: 16px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.summary-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 5px 14px;
|
|
border-radius: 99px;
|
|
font-family: var(--font-mono);
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
border: 1px solid;
|
|
}
|
|
|
|
.summary-badge.ok {
|
|
color: var(--green);
|
|
border-color: rgba(16, 185, 129, 0.3);
|
|
background: var(--green-glow);
|
|
}
|
|
|
|
.summary-badge.warn {
|
|
color: var(--amber);
|
|
border-color: rgba(240, 180, 41, 0.3);
|
|
background: var(--amber-glow);
|
|
}
|
|
|
|
.summary-badge.error {
|
|
color: var(--rose);
|
|
border-color: rgba(244, 63, 94, 0.3);
|
|
background: var(--rose-glow);
|
|
}
|
|
|
|
.summary-badge-dot {
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.summary-badge.ok .summary-badge-dot { background: var(--green); }
|
|
.summary-badge.warn .summary-badge-dot { background: var(--amber); }
|
|
.summary-badge.error .summary-badge-dot { background: var(--rose); }
|
|
|
|
.summary-sep {
|
|
flex: 1;
|
|
}
|
|
|
|
.summary-timerange {
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
font-family: var(--font-mono);
|
|
}
|
|
|
|
/* ─── Two-Column Layout ─── */
|
|
.main-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 320px;
|
|
gap: 16px;
|
|
}
|
|
|
|
/* ─── Filter / Search Bar ─── */
|
|
.filter-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
margin-bottom: 12px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.search-box {
|
|
flex: 1;
|
|
min-width: 240px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
background: var(--bg-surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
padding: 8px 12px;
|
|
color: var(--text-primary);
|
|
font-size: 13px;
|
|
}
|
|
|
|
.search-box input {
|
|
flex: 1;
|
|
background: none;
|
|
border: none;
|
|
outline: none;
|
|
color: var(--text-primary);
|
|
font-family: var(--font-body);
|
|
font-size: 13px;
|
|
}
|
|
|
|
.search-box input::placeholder { color: var(--text-muted); }
|
|
|
|
.search-icon { color: var(--text-muted); font-size: 14px; }
|
|
|
|
.filter-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 6px 12px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 99px;
|
|
background: none;
|
|
color: var(--text-secondary);
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
transition: all 0.15s;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.filter-chip:hover {
|
|
background: var(--bg-raised);
|
|
border-color: var(--text-muted);
|
|
}
|
|
|
|
.filter-chip.active {
|
|
background: var(--amber-glow);
|
|
color: var(--amber);
|
|
border-color: rgba(240, 180, 41, 0.3);
|
|
}
|
|
|
|
.filter-chip .chip-count {
|
|
font-family: var(--font-mono);
|
|
font-weight: 600;
|
|
font-size: 11px;
|
|
}
|
|
|
|
.filter-chip.active-error {
|
|
background: var(--rose-glow);
|
|
color: var(--rose);
|
|
border-color: rgba(244, 63, 94, 0.3);
|
|
}
|
|
|
|
/* ─── Execution Table ─── */
|
|
.table-wrap {
|
|
background: var(--bg-surface);
|
|
border: 1px solid var(--border-subtle);
|
|
border-radius: var(--radius-lg);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.exec-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.exec-table thead {
|
|
background: var(--bg-raised);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.exec-table th {
|
|
padding: 10px 14px;
|
|
text-align: left;
|
|
font-size: 11px;
|
|
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.15s;
|
|
}
|
|
|
|
.exec-table th:hover { color: var(--text-secondary); }
|
|
|
|
.exec-table td {
|
|
padding: 10px 14px;
|
|
vertical-align: top;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.exec-table tbody tr {
|
|
border-bottom: 1px solid var(--border-subtle);
|
|
transition: background 0.1s;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.exec-table tbody tr:last-child { border-bottom: none; }
|
|
.exec-table tbody tr:hover { background: var(--bg-raised); }
|
|
|
|
/* Status indicator strip on left */
|
|
.exec-table tbody tr {
|
|
border-left: 3px solid transparent;
|
|
}
|
|
|
|
.exec-table tbody tr.status-ok { border-left-color: var(--green); }
|
|
.exec-table tbody tr.status-warn { border-left-color: var(--amber); }
|
|
.exec-table tbody tr.status-error { border-left-color: var(--rose); }
|
|
|
|
/* Status pill */
|
|
.status-pill {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
padding: 3px 10px;
|
|
border-radius: 99px;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.3px;
|
|
}
|
|
|
|
.status-pill.ok {
|
|
background: var(--green-glow);
|
|
color: var(--green);
|
|
}
|
|
|
|
.status-pill.warn {
|
|
background: var(--amber-glow);
|
|
color: var(--amber);
|
|
}
|
|
|
|
.status-pill.error {
|
|
background: var(--rose-glow);
|
|
color: var(--rose);
|
|
}
|
|
|
|
.status-pill-dot {
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 50%;
|
|
background: currentColor;
|
|
}
|
|
|
|
/* SLA violation marker */
|
|
.sla-breach {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 3px;
|
|
color: var(--rose);
|
|
font-size: 11px;
|
|
font-family: var(--font-mono);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.sla-breach::before {
|
|
content: '';
|
|
display: inline-block;
|
|
width: 0;
|
|
height: 0;
|
|
border-left: 4px solid transparent;
|
|
border-right: 4px solid transparent;
|
|
border-bottom: 7px solid var(--rose);
|
|
}
|
|
|
|
/* Duration with SLA color */
|
|
.duration {
|
|
font-family: var(--font-mono);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.duration.fast { color: var(--green); }
|
|
.duration.normal { color: var(--text-primary); }
|
|
.duration.slow { color: var(--amber); }
|
|
.duration.breach { color: var(--rose); font-weight: 600; }
|
|
|
|
/* Business ID styling */
|
|
.biz-id {
|
|
font-family: var(--font-mono);
|
|
font-weight: 500;
|
|
color: var(--cyan);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.biz-id-secondary {
|
|
font-family: var(--font-mono);
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
/* Route name */
|
|
.route-name {
|
|
font-family: var(--font-mono);
|
|
font-size: 12px;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.route-group {
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
/* Time display */
|
|
.time-relative {
|
|
color: var(--text-secondary);
|
|
font-size: 13px;
|
|
}
|
|
|
|
.time-absolute {
|
|
font-family: var(--font-mono);
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
/* Error preview row */
|
|
.error-preview-row td {
|
|
padding: 0 14px 10px 20px !important;
|
|
border-bottom: 1px solid var(--border-subtle);
|
|
}
|
|
|
|
.error-preview {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 8px;
|
|
padding: 8px 12px;
|
|
background: rgba(244, 63, 94, 0.06);
|
|
border: 1px solid rgba(244, 63, 94, 0.15);
|
|
border-radius: var(--radius-sm);
|
|
font-family: var(--font-mono);
|
|
font-size: 11px;
|
|
color: var(--rose);
|
|
line-height: 1.6;
|
|
max-width: 100%;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.error-preview-icon {
|
|
flex-shrink: 0;
|
|
margin-top: 1px;
|
|
}
|
|
|
|
.error-preview-text {
|
|
white-space: normal;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.error-expand-hint {
|
|
color: var(--text-muted);
|
|
font-size: 10px;
|
|
margin-top: 4px;
|
|
font-family: var(--font-body);
|
|
}
|
|
|
|
/* Agent badge in table */
|
|
.agent-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-family: var(--font-mono);
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.agent-badge-dot {
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.agent-badge-dot.live { background: var(--green); }
|
|
.agent-badge-dot.stale { background: var(--amber); }
|
|
.agent-badge-dot.dead { background: var(--rose); }
|
|
|
|
/* ─── Right Sidebar ─── */
|
|
.sidebar {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
}
|
|
|
|
/* ─── Agent Health Panel ─── */
|
|
.panel {
|
|
background: var(--bg-surface);
|
|
border: 1px solid var(--border-subtle);
|
|
border-radius: var(--radius-md);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.panel-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid var(--border-subtle);
|
|
}
|
|
|
|
.panel-title {
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.panel-badge {
|
|
font-family: var(--font-mono);
|
|
font-size: 11px;
|
|
padding: 2px 8px;
|
|
border-radius: 99px;
|
|
}
|
|
|
|
.panel-body {
|
|
padding: 12px 16px;
|
|
}
|
|
|
|
/* Agent list */
|
|
.agent-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.agent-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 10px 12px;
|
|
background: var(--bg-raised);
|
|
border-radius: var(--radius-sm);
|
|
border: 1px solid var(--border-subtle);
|
|
transition: border-color 0.15s;
|
|
}
|
|
|
|
.agent-item:hover { border-color: var(--border); }
|
|
|
|
.agent-status-dot {
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.agent-status-dot.live {
|
|
background: var(--green);
|
|
box-shadow: 0 0 6px rgba(16, 185, 129, 0.4);
|
|
}
|
|
|
|
.agent-status-dot.stale {
|
|
background: var(--amber);
|
|
box-shadow: 0 0 6px rgba(240, 180, 41, 0.3);
|
|
}
|
|
|
|
.agent-status-dot.dead {
|
|
background: var(--rose);
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.agent-info { flex: 1; min-width: 0; }
|
|
|
|
.agent-name {
|
|
font-family: var(--font-mono);
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
color: var(--text-primary);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.agent-meta {
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
display: flex;
|
|
gap: 8px;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.agent-stats {
|
|
text-align: right;
|
|
font-family: var(--font-mono);
|
|
font-size: 11px;
|
|
}
|
|
|
|
.agent-tps { color: var(--text-secondary); }
|
|
.agent-errors { color: var(--rose); }
|
|
.agent-heartbeat { font-size: 10px; color: var(--text-muted); }
|
|
|
|
/* ─── Metrics Panels ─── */
|
|
.metric-chart {
|
|
padding: 16px;
|
|
}
|
|
|
|
.metric-header {
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.metric-title {
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.metric-current {
|
|
font-family: var(--font-mono);
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.metric-current .trend-arrow {
|
|
font-size: 14px;
|
|
margin-left: 4px;
|
|
}
|
|
|
|
.metric-current .trend-arrow.up-bad { color: var(--rose); }
|
|
.metric-current .trend-arrow.down-good { color: var(--green); }
|
|
.metric-current .trend-arrow.up-good { color: var(--green); }
|
|
.metric-current .trend-arrow.flat { color: var(--text-muted); }
|
|
|
|
/* SVG Chart area */
|
|
.chart-area {
|
|
width: 100%;
|
|
height: 80px;
|
|
position: relative;
|
|
}
|
|
|
|
.chart-area svg {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
/* Sparkline-style mini charts */
|
|
.sparkline {
|
|
display: block;
|
|
}
|
|
|
|
/* SLA threshold line */
|
|
.sla-line {
|
|
stroke: var(--rose);
|
|
stroke-width: 1;
|
|
stroke-dasharray: 4 3;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.sla-label {
|
|
fill: var(--rose);
|
|
font-size: 9px;
|
|
font-family: 'JetBrains Mono', monospace;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
/* ─── Top Errors Panel ─── */
|
|
.error-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
}
|
|
|
|
.error-item {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 10px;
|
|
padding: 8px 10px;
|
|
background: var(--bg-raised);
|
|
border-radius: var(--radius-sm);
|
|
border-left: 3px solid var(--rose);
|
|
}
|
|
|
|
.error-count {
|
|
font-family: var(--font-mono);
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--rose);
|
|
min-width: 28px;
|
|
text-align: center;
|
|
}
|
|
|
|
.error-info { flex: 1; min-width: 0; }
|
|
|
|
.error-route {
|
|
font-family: var(--font-mono);
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.error-message {
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.error-since {
|
|
font-size: 10px;
|
|
color: var(--text-muted);
|
|
margin-top: 2px;
|
|
}
|
|
|
|
/* ─── Table footer / pagination ─── */
|
|
.table-footer {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 10px 14px;
|
|
border-top: 1px solid var(--border-subtle);
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
background: var(--bg-raised);
|
|
}
|
|
|
|
.table-footer-info {
|
|
font-family: var(--font-mono);
|
|
font-size: 11px;
|
|
}
|
|
|
|
.pagination {
|
|
display: flex;
|
|
gap: 4px;
|
|
}
|
|
|
|
.page-btn {
|
|
padding: 4px 10px;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
background: none;
|
|
color: var(--text-secondary);
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
transition: all 0.15s;
|
|
}
|
|
|
|
.page-btn:hover {
|
|
background: var(--bg-hover);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.page-btn.active {
|
|
background: var(--amber-glow);
|
|
color: var(--amber);
|
|
border-color: rgba(240, 180, 41, 0.3);
|
|
}
|
|
|
|
/* ─── Utility ─── */
|
|
.mono { font-family: var(--font-mono); }
|
|
.flex-center { display: flex; align-items: center; }
|
|
|
|
/* ─── Chart fill colors ─── */
|
|
.fill-green { fill: rgba(16, 185, 129, 0.15); }
|
|
.stroke-green { stroke: var(--green); }
|
|
.fill-amber { fill: rgba(240, 180, 41, 0.1); }
|
|
.stroke-amber { stroke: var(--amber); }
|
|
.fill-cyan { fill: rgba(34, 211, 238, 0.1); }
|
|
.stroke-cyan { stroke: var(--cyan); }
|
|
.fill-rose { fill: rgba(244, 63, 94, 0.1); }
|
|
.stroke-rose { stroke: var(--rose); }
|
|
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="console">
|
|
|
|
<!-- ─── Top Navigation ─── -->
|
|
<nav class="topnav">
|
|
<div class="topnav-brand">
|
|
<span class="topnav-logo">cameleer3</span>
|
|
<span class="topnav-sep">/</span>
|
|
<span class="topnav-page">Operations Console</span>
|
|
</div>
|
|
<div class="topnav-right">
|
|
<span class="topnav-shift">Shift: Day (06:00-18:00)</span>
|
|
<span class="topnav-clock">2026-03-17 09:14 CET</span>
|
|
<span class="live-indicator">
|
|
<span class="live-dot"></span>
|
|
LIVE
|
|
</span>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- ─── Health / SLA Status Bar ─── -->
|
|
<div class="health-bar animate-in">
|
|
|
|
<div class="health-card ok">
|
|
<div class="health-label">System Health</div>
|
|
<div class="health-value-row">
|
|
<span class="health-value ok">HEALTHY</span>
|
|
</div>
|
|
<div class="health-detail">All 4 agents reporting · No SLA breaches</div>
|
|
</div>
|
|
|
|
<div class="health-card ok">
|
|
<div class="health-label">Executions (shift)</div>
|
|
<div class="health-value-row">
|
|
<span class="health-value neutral">3,241</span>
|
|
<span class="health-trend up-good">+12%</span>
|
|
</div>
|
|
<div class="health-detail"><strong>97.1%</strong> success rate since 06:00</div>
|
|
</div>
|
|
|
|
<div class="health-card warn">
|
|
<div class="health-label">Failures (shift)</div>
|
|
<div class="health-value-row">
|
|
<span class="health-value error">38</span>
|
|
<span class="health-trend up-bad">+5</span>
|
|
</div>
|
|
<div class="health-detail"><strong>23</strong> overnight · <strong>15</strong> since 06:00</div>
|
|
</div>
|
|
|
|
<div class="health-card ok">
|
|
<div class="health-label">Throughput</div>
|
|
<div class="health-value-row">
|
|
<span class="health-value neutral">47.2</span>
|
|
<span class="health-unit">msg/s</span>
|
|
<span class="health-trend flat">↔</span>
|
|
</div>
|
|
<div class="health-detail">Capacity: 120 msg/s · <strong>39%</strong> utilized</div>
|
|
</div>
|
|
|
|
<div class="health-card ok">
|
|
<div class="health-label">Latency p50</div>
|
|
<div class="health-value-row">
|
|
<span class="health-value neutral">142</span>
|
|
<span class="health-unit">ms</span>
|
|
<span class="health-trend down-good">-8ms</span>
|
|
</div>
|
|
<div class="health-detail">SLA: <200ms · <strong style="color: var(--green);">OK</strong></div>
|
|
</div>
|
|
|
|
<div class="health-card warn">
|
|
<div class="health-label">Latency p99</div>
|
|
<div class="health-value-row">
|
|
<span class="health-value warn">287</span>
|
|
<span class="health-unit">ms</span>
|
|
<span class="health-trend up-bad">+23ms</span>
|
|
</div>
|
|
<div class="health-detail">SLA: <300ms · <strong style="color: var(--amber);">CLOSE</strong></div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- ─── Summary Badges ─── -->
|
|
<div class="summary-row animate-in" style="animation-delay: 0.05s">
|
|
<span class="summary-badge ok">
|
|
<span class="summary-badge-dot"></span>
|
|
3,197 ok
|
|
</span>
|
|
<span class="summary-badge warn">
|
|
<span class="summary-badge-dot"></span>
|
|
6 warn
|
|
</span>
|
|
<span class="summary-badge error">
|
|
<span class="summary-badge-dot"></span>
|
|
38 error
|
|
</span>
|
|
<span class="summary-sep"></span>
|
|
<span class="summary-timerange">Last 12h · All groups</span>
|
|
</div>
|
|
|
|
<!-- ─── Main Grid: Table + Sidebar ─── -->
|
|
<div class="main-grid">
|
|
|
|
<!-- ─── Left: Execution Table ─── -->
|
|
<div class="animate-in" style="animation-delay: 0.1s">
|
|
|
|
<!-- Filter Bar -->
|
|
<div class="filter-bar">
|
|
<div class="search-box">
|
|
<span class="search-icon">🔍</span>
|
|
<input type="text" placeholder="Search by Order ID, Customer ID, Correlation ID, Error message...">
|
|
</div>
|
|
<button class="filter-chip active">All Routes</button>
|
|
<button class="filter-chip">order-flow <span class="chip-count">1,847</span></button>
|
|
<button class="filter-chip">payment-flow <span class="chip-count">923</span></button>
|
|
<button class="filter-chip">shipment-flow <span class="chip-count">471</span></button>
|
|
<button class="filter-chip active-error">Errors only <span class="chip-count">38</span></button>
|
|
</div>
|
|
|
|
<!-- Execution Table -->
|
|
<div class="table-wrap">
|
|
<table class="exec-table">
|
|
<thead>
|
|
<tr>
|
|
<th style="width: 30px;"></th>
|
|
<th>Status</th>
|
|
<th>Route / Group</th>
|
|
<th>Order ID</th>
|
|
<th>Customer</th>
|
|
<th>Started</th>
|
|
<th>Duration</th>
|
|
<th>Agent</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
|
|
<!-- Row 1: Recent success -->
|
|
<tr class="status-ok">
|
|
<td></td>
|
|
<td><span class="status-pill ok"><span class="status-pill-dot"></span>OK</span></td>
|
|
<td>
|
|
<div class="route-name">order-intake</div>
|
|
<div class="route-group">order-flow</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id">OP-92184</div>
|
|
<div class="biz-id-secondary">SAP-DE</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id-secondary">CUST-44210</div>
|
|
</td>
|
|
<td>
|
|
<div class="time-relative">2 min ago</div>
|
|
<div class="time-absolute">09:12:04</div>
|
|
</td>
|
|
<td><span class="duration fast">87ms</span></td>
|
|
<td>
|
|
<span class="agent-badge"><span class="agent-badge-dot live"></span>prod-1</span>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Row 2: Recent success, slightly slow -->
|
|
<tr class="status-ok">
|
|
<td></td>
|
|
<td><span class="status-pill ok"><span class="status-pill-dot"></span>OK</span></td>
|
|
<td>
|
|
<div class="route-name">payment-validate</div>
|
|
<div class="route-group">payment-flow</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id">OP-92183</div>
|
|
<div class="biz-id-secondary">WEB-EU</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id-secondary">CUST-18773</div>
|
|
</td>
|
|
<td>
|
|
<div class="time-relative">3 min ago</div>
|
|
<div class="time-absolute">09:11:22</div>
|
|
</td>
|
|
<td><span class="duration normal">164ms</span></td>
|
|
<td>
|
|
<span class="agent-badge"><span class="agent-badge-dot live"></span>prod-2</span>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Row 3: WARNING - slow but succeeded -->
|
|
<tr class="status-warn">
|
|
<td></td>
|
|
<td><span class="status-pill warn"><span class="status-pill-dot"></span>WARN</span></td>
|
|
<td>
|
|
<div class="route-name">shipment-dispatch</div>
|
|
<div class="route-group">shipment-flow</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id">OP-92180</div>
|
|
<div class="biz-id-secondary">SAP-DE</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id-secondary">CUST-55019</div>
|
|
</td>
|
|
<td>
|
|
<div class="time-relative">5 min ago</div>
|
|
<div class="time-absolute">09:09:47</div>
|
|
</td>
|
|
<td>
|
|
<span class="duration slow">248ms</span>
|
|
<span class="sla-breach" title="Approaching SLA threshold (300ms)">SLA</span>
|
|
</td>
|
|
<td>
|
|
<span class="agent-badge"><span class="agent-badge-dot live"></span>prod-3</span>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Row 4: ERROR with inline preview -->
|
|
<tr class="status-error">
|
|
<td></td>
|
|
<td><span class="status-pill error"><span class="status-pill-dot"></span>ERROR</span></td>
|
|
<td>
|
|
<div class="route-name">payment-process</div>
|
|
<div class="route-group">payment-flow</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id">OP-88421</div>
|
|
<div class="biz-id-secondary">WEB-EU</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id-secondary">CUST-31094</div>
|
|
</td>
|
|
<td>
|
|
<div class="time-relative">8 min ago</div>
|
|
<div class="time-absolute">09:06:11</div>
|
|
</td>
|
|
<td><span class="duration breach">412ms</span></td>
|
|
<td>
|
|
<span class="agent-badge"><span class="agent-badge-dot live"></span>prod-2</span>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Error preview row (expanded inline) -->
|
|
<tr class="error-preview-row">
|
|
<td colspan="8">
|
|
<div class="error-preview">
|
|
<span class="error-preview-icon">⚠</span>
|
|
<div>
|
|
<div class="error-preview-text">org.apache.camel.CamelExecutionException: Payment gateway timeout after 5000ms — POST https://pay.provider.com/v2/charge returned HTTP 504. Retry exhausted (3/3 attempts). CorrelationId: c7e2a1f0-9b3d</div>
|
|
<div class="error-expand-hint">Click to view full stack trace and exchange snapshot</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Row 5: Another error -->
|
|
<tr class="status-error">
|
|
<td></td>
|
|
<td><span class="status-pill error"><span class="status-pill-dot"></span>ERROR</span></td>
|
|
<td>
|
|
<div class="route-name">order-enrichment</div>
|
|
<div class="route-group">order-flow</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id">OP-88419</div>
|
|
<div class="biz-id-secondary">EDI-US</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id-secondary">CUST-72841</div>
|
|
</td>
|
|
<td>
|
|
<div class="time-relative">12 min ago</div>
|
|
<div class="time-absolute">09:02:38</div>
|
|
</td>
|
|
<td><span class="duration breach">1,247ms</span></td>
|
|
<td>
|
|
<span class="agent-badge"><span class="agent-badge-dot live"></span>prod-1</span>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr class="error-preview-row">
|
|
<td colspan="8">
|
|
<div class="error-preview">
|
|
<span class="error-preview-icon">⚠</span>
|
|
<div>
|
|
<div class="error-preview-text">java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 1000ms. (pool size: 10, active: 10, idle: 0, waiting: 3)</div>
|
|
<div class="error-expand-hint">Click to view full stack trace and exchange snapshot</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Row 6: Success -->
|
|
<tr class="status-ok">
|
|
<td></td>
|
|
<td><span class="status-pill ok"><span class="status-pill-dot"></span>OK</span></td>
|
|
<td>
|
|
<div class="route-name">order-intake</div>
|
|
<div class="route-group">order-flow</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id">OP-92179</div>
|
|
<div class="biz-id-secondary">WEB-EU</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id-secondary">CUST-90123</div>
|
|
</td>
|
|
<td>
|
|
<div class="time-relative">14 min ago</div>
|
|
<div class="time-absolute">09:00:15</div>
|
|
</td>
|
|
<td><span class="duration fast">62ms</span></td>
|
|
<td>
|
|
<span class="agent-badge"><span class="agent-badge-dot live"></span>prod-3</span>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Row 7: Success -->
|
|
<tr class="status-ok">
|
|
<td></td>
|
|
<td><span class="status-pill ok"><span class="status-pill-dot"></span>OK</span></td>
|
|
<td>
|
|
<div class="route-name">payment-validate</div>
|
|
<div class="route-group">payment-flow</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id">OP-92178</div>
|
|
<div class="biz-id-secondary">SAP-DE</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id-secondary">CUST-44210</div>
|
|
</td>
|
|
<td>
|
|
<div class="time-relative">16 min ago</div>
|
|
<div class="time-absolute">08:58:33</div>
|
|
</td>
|
|
<td><span class="duration normal">131ms</span></td>
|
|
<td>
|
|
<span class="agent-badge"><span class="agent-badge-dot live"></span>prod-1</span>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Row 8: Success -->
|
|
<tr class="status-ok">
|
|
<td></td>
|
|
<td><span class="status-pill ok"><span class="status-pill-dot"></span>OK</span></td>
|
|
<td>
|
|
<div class="route-name">shipment-track</div>
|
|
<div class="route-group">shipment-flow</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id">OP-92175</div>
|
|
<div class="biz-id-secondary">EDI-US</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id-secondary">CUST-67892</div>
|
|
</td>
|
|
<td>
|
|
<div class="time-relative">21 min ago</div>
|
|
<div class="time-absolute">08:53:07</div>
|
|
</td>
|
|
<td><span class="duration fast">94ms</span></td>
|
|
<td>
|
|
<span class="agent-badge"><span class="agent-badge-dot live"></span>prod-4</span>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Row 9: Error from overnight -->
|
|
<tr class="status-error">
|
|
<td></td>
|
|
<td><span class="status-pill error"><span class="status-pill-dot"></span>ERROR</span></td>
|
|
<td>
|
|
<div class="route-name">payment-process</div>
|
|
<div class="route-group">payment-flow</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id">OP-91844</div>
|
|
<div class="biz-id-secondary">WEB-EU</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id-secondary">CUST-10082</div>
|
|
</td>
|
|
<td>
|
|
<div class="time-relative">5 hours ago</div>
|
|
<div class="time-absolute">04:12:55</div>
|
|
</td>
|
|
<td><span class="duration breach">timeout</span></td>
|
|
<td>
|
|
<span class="agent-badge"><span class="agent-badge-dot live"></span>prod-2</span>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr class="error-preview-row">
|
|
<td colspan="8">
|
|
<div class="error-preview">
|
|
<span class="error-preview-icon">⚠</span>
|
|
<div>
|
|
<div class="error-preview-text">org.apache.camel.CamelExecutionException: Payment gateway timeout after 5000ms — POST https://pay.provider.com/v2/charge returned HTTP 504. Retry exhausted (3/3 attempts). Overnight batch: provider maintenance window?</div>
|
|
<div class="error-expand-hint">Click to view full stack trace and exchange snapshot</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Row 10: Success -->
|
|
<tr class="status-ok">
|
|
<td></td>
|
|
<td><span class="status-pill ok"><span class="status-pill-dot"></span>OK</span></td>
|
|
<td>
|
|
<div class="route-name">order-intake</div>
|
|
<div class="route-group">order-flow</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id">OP-91840</div>
|
|
<div class="biz-id-secondary">EDI-US</div>
|
|
</td>
|
|
<td>
|
|
<div class="biz-id-secondary">CUST-38291</div>
|
|
</td>
|
|
<td>
|
|
<div class="time-relative">5 hours ago</div>
|
|
<div class="time-absolute">04:10:22</div>
|
|
</td>
|
|
<td><span class="duration fast">78ms</span></td>
|
|
<td>
|
|
<span class="agent-badge"><span class="agent-badge-dot live"></span>prod-1</span>
|
|
</td>
|
|
</tr>
|
|
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="table-footer">
|
|
<div class="table-footer-info">
|
|
Showing 1-20 of 3,241 executions
|
|
</div>
|
|
<div class="pagination">
|
|
<button class="page-btn active">1</button>
|
|
<button class="page-btn">2</button>
|
|
<button class="page-btn">3</button>
|
|
<button class="page-btn">...</button>
|
|
<button class="page-btn">162</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ─── Right Sidebar ─── -->
|
|
<div class="sidebar animate-in" style="animation-delay: 0.15s">
|
|
|
|
<!-- Agent Health -->
|
|
<div class="panel">
|
|
<div class="panel-header">
|
|
<span class="panel-title">Agent Health</span>
|
|
<span class="panel-badge" style="color: var(--green); background: var(--green-glow); border: 1px solid rgba(16,185,129,0.3); border-radius: 99px;">4 / 4 live</span>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div class="agent-list">
|
|
|
|
<div class="agent-item">
|
|
<span class="agent-status-dot live"></span>
|
|
<div class="agent-info">
|
|
<div class="agent-name">prod-1</div>
|
|
<div class="agent-meta">
|
|
<span>order-service</span>
|
|
<span>v3.2.1</span>
|
|
</div>
|
|
</div>
|
|
<div class="agent-stats">
|
|
<div class="agent-tps">14.2 msg/s</div>
|
|
<div class="agent-heartbeat">12s ago</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="agent-item">
|
|
<span class="agent-status-dot live"></span>
|
|
<div class="agent-info">
|
|
<div class="agent-name">prod-2</div>
|
|
<div class="agent-meta">
|
|
<span>payment-service</span>
|
|
<span>v3.2.1</span>
|
|
</div>
|
|
</div>
|
|
<div class="agent-stats">
|
|
<div class="agent-tps">11.8 msg/s</div>
|
|
<div class="agent-errors">3 err/h</div>
|
|
<div class="agent-heartbeat">8s ago</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="agent-item">
|
|
<span class="agent-status-dot live"></span>
|
|
<div class="agent-info">
|
|
<div class="agent-name">prod-3</div>
|
|
<div class="agent-meta">
|
|
<span>shipment-service</span>
|
|
<span>v3.2.0</span>
|
|
</div>
|
|
</div>
|
|
<div class="agent-stats">
|
|
<div class="agent-tps">12.1 msg/s</div>
|
|
<div class="agent-heartbeat">5s ago</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="agent-item">
|
|
<span class="agent-status-dot live"></span>
|
|
<div class="agent-info">
|
|
<div class="agent-name">prod-4</div>
|
|
<div class="agent-meta">
|
|
<span>shipment-service</span>
|
|
<span>v3.2.0</span>
|
|
</div>
|
|
</div>
|
|
<div class="agent-stats">
|
|
<div class="agent-tps">9.1 msg/s</div>
|
|
<div class="agent-heartbeat">3s ago</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Throughput Chart -->
|
|
<div class="panel">
|
|
<div class="metric-chart">
|
|
<div class="metric-header">
|
|
<span class="metric-title">Throughput (1h)</span>
|
|
<span class="metric-current">47.2 <span class="trend-arrow flat">↔</span></span>
|
|
</div>
|
|
<div class="chart-area">
|
|
<svg viewBox="0 0 288 80" preserveAspectRatio="none">
|
|
<!-- Grid lines -->
|
|
<line x1="0" y1="20" x2="288" y2="20" stroke="#1e2d3d" stroke-width="0.5"/>
|
|
<line x1="0" y1="40" x2="288" y2="40" stroke="#1e2d3d" stroke-width="0.5"/>
|
|
<line x1="0" y1="60" x2="288" y2="60" stroke="#1e2d3d" stroke-width="0.5"/>
|
|
<!-- Capacity line at ~33% (40 of 120) -->
|
|
<!-- Area fill -->
|
|
<path d="M0,55 L24,52 L48,48 L72,50 L96,45 L120,42 L144,44 L168,40 L192,38 L216,40 L240,42 L264,39 L288,38 L288,80 L0,80 Z" class="fill-cyan" />
|
|
<!-- Line -->
|
|
<path d="M0,55 L24,52 L48,48 L72,50 L96,45 L120,42 L144,44 L168,40 L192,38 L216,40 L240,42 L264,39 L288,38" fill="none" class="stroke-cyan" stroke-width="1.5"/>
|
|
<!-- Time labels -->
|
|
<text x="0" y="78" fill="#4a5e7a" font-size="8" font-family="JetBrains Mono">08:15</text>
|
|
<text x="138" y="78" fill="#4a5e7a" font-size="8" font-family="JetBrains Mono">08:45</text>
|
|
<text x="272" y="78" fill="#4a5e7a" font-size="8" font-family="JetBrains Mono">09:14</text>
|
|
<!-- Scale -->
|
|
<text x="278" y="18" fill="#4a5e7a" font-size="7" font-family="JetBrains Mono" text-anchor="end">80</text>
|
|
<text x="278" y="38" fill="#4a5e7a" font-size="7" font-family="JetBrains Mono" text-anchor="end">50</text>
|
|
<text x="278" y="58" fill="#4a5e7a" font-size="7" font-family="JetBrains Mono" text-anchor="end">20</text>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Latency Chart with SLA line -->
|
|
<div class="panel">
|
|
<div class="metric-chart">
|
|
<div class="metric-header">
|
|
<span class="metric-title">Latency p99 (1h)</span>
|
|
<span class="metric-current" style="color: var(--amber);">287ms <span class="trend-arrow up-bad">↗</span></span>
|
|
</div>
|
|
<div class="chart-area">
|
|
<svg viewBox="0 0 288 80" preserveAspectRatio="none">
|
|
<!-- Grid lines -->
|
|
<line x1="0" y1="20" x2="288" y2="20" stroke="#1e2d3d" stroke-width="0.5"/>
|
|
<line x1="0" y1="40" x2="288" y2="40" stroke="#1e2d3d" stroke-width="0.5"/>
|
|
<line x1="0" y1="60" x2="288" y2="60" stroke="#1e2d3d" stroke-width="0.5"/>
|
|
<!-- SLA threshold line at 300ms (~y=18 on our 0-350ms scale) -->
|
|
<line x1="0" y1="12" x2="288" y2="12" class="sla-line"/>
|
|
<text x="4" y="10" class="sla-label">SLA 300ms</text>
|
|
<!-- Danger zone fill above SLA -->
|
|
<rect x="0" y="0" width="288" height="12" fill="rgba(244, 63, 94, 0.04)"/>
|
|
<!-- Area fill -->
|
|
<path d="M0,48 L24,45 L48,42 L72,40 L96,38 L120,35 L144,32 L168,28 L192,24 L216,22 L240,20 L264,18 L288,16 L288,80 L0,80 Z" class="fill-amber" />
|
|
<!-- Line -->
|
|
<path d="M0,48 L24,45 L48,42 L72,40 L96,38 L120,35 L144,32 L168,28 L192,24 L216,22 L240,20 L264,18 L288,16" fill="none" class="stroke-amber" stroke-width="1.5"/>
|
|
<!-- p50 line (reference) -->
|
|
<path d="M0,60 L24,62 L48,60 L72,58 L96,60 L120,59 L144,58 L168,60 L192,58 L216,57 L240,58 L264,56 L288,55" fill="none" stroke="#22d3ee" stroke-width="1" opacity="0.5"/>
|
|
<text x="288" y="54" fill="#22d3ee" font-size="7" font-family="JetBrains Mono" text-anchor="end" opacity="0.6">p50</text>
|
|
<!-- Time labels -->
|
|
<text x="0" y="78" fill="#4a5e7a" font-size="8" font-family="JetBrains Mono">08:15</text>
|
|
<text x="138" y="78" fill="#4a5e7a" font-size="8" font-family="JetBrains Mono">08:45</text>
|
|
<text x="272" y="78" fill="#4a5e7a" font-size="8" font-family="JetBrains Mono">09:14</text>
|
|
<!-- Scale -->
|
|
<text x="278" y="18" fill="#4a5e7a" font-size="7" font-family="JetBrains Mono" text-anchor="end">300</text>
|
|
<text x="278" y="38" fill="#4a5e7a" font-size="7" font-family="JetBrains Mono" text-anchor="end">200</text>
|
|
<text x="278" y="58" fill="#4a5e7a" font-size="7" font-family="JetBrains Mono" text-anchor="end">100</text>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Error Rate Chart -->
|
|
<div class="panel">
|
|
<div class="metric-chart">
|
|
<div class="metric-header">
|
|
<span class="metric-title">Error Rate (1h)</span>
|
|
<span class="metric-current" style="color: var(--rose);">2.9% <span class="trend-arrow up-bad">↗</span></span>
|
|
</div>
|
|
<div class="chart-area">
|
|
<svg viewBox="0 0 288 80" preserveAspectRatio="none">
|
|
<!-- Grid lines -->
|
|
<line x1="0" y1="20" x2="288" y2="20" stroke="#1e2d3d" stroke-width="0.5"/>
|
|
<line x1="0" y1="40" x2="288" y2="40" stroke="#1e2d3d" stroke-width="0.5"/>
|
|
<line x1="0" y1="60" x2="288" y2="60" stroke="#1e2d3d" stroke-width="0.5"/>
|
|
<!-- SLA threshold at 5% (y=20) -->
|
|
<line x1="0" y1="20" x2="288" y2="20" class="sla-line"/>
|
|
<text x="4" y="18" class="sla-label">SLA 5%</text>
|
|
<rect x="0" y="0" width="288" height="20" fill="rgba(244, 63, 94, 0.04)"/>
|
|
<!-- Bar chart style for errors -->
|
|
<rect x="2" y="62" width="20" height="18" rx="2" fill="rgba(244, 63, 94, 0.3)"/>
|
|
<rect x="26" y="65" width="20" height="15" rx="2" fill="rgba(244, 63, 94, 0.3)"/>
|
|
<rect x="50" y="68" width="20" height="12" rx="2" fill="rgba(244, 63, 94, 0.3)"/>
|
|
<rect x="74" y="64" width="20" height="16" rx="2" fill="rgba(244, 63, 94, 0.3)"/>
|
|
<rect x="98" y="60" width="20" height="20" rx="2" fill="rgba(244, 63, 94, 0.3)"/>
|
|
<rect x="122" y="66" width="20" height="14" rx="2" fill="rgba(244, 63, 94, 0.3)"/>
|
|
<rect x="146" y="70" width="20" height="10" rx="2" fill="rgba(244, 63, 94, 0.3)"/>
|
|
<rect x="170" y="58" width="20" height="22" rx="2" fill="rgba(244, 63, 94, 0.4)"/>
|
|
<rect x="194" y="52" width="20" height="28" rx="2" fill="rgba(244, 63, 94, 0.5)"/>
|
|
<rect x="218" y="48" width="20" height="32" rx="2" fill="rgba(244, 63, 94, 0.6)"/>
|
|
<rect x="242" y="44" width="20" height="36" rx="2" fill="rgba(244, 63, 94, 0.7)"/>
|
|
<rect x="266" y="42" width="20" height="38" rx="2" fill="rgba(244, 63, 94, 0.8)"/>
|
|
<!-- Time labels -->
|
|
<text x="0" y="78" fill="#4a5e7a" font-size="8" font-family="JetBrains Mono">08:15</text>
|
|
<text x="138" y="78" fill="#4a5e7a" font-size="8" font-family="JetBrains Mono">08:45</text>
|
|
<text x="272" y="78" fill="#4a5e7a" font-size="8" font-family="JetBrains Mono">09:14</text>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Top Errors Panel -->
|
|
<div class="panel">
|
|
<div class="panel-header">
|
|
<span class="panel-title">Top Errors (shift)</span>
|
|
<span class="panel-badge" style="color: var(--rose); background: var(--rose-glow); border: 1px solid rgba(244,63,94,0.3); border-radius: 99px;">3 patterns</span>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div class="error-list">
|
|
|
|
<div class="error-item">
|
|
<div class="error-count">23</div>
|
|
<div class="error-info">
|
|
<div class="error-route">payment-flow / payment-process</div>
|
|
<div class="error-message">Payment gateway timeout (HTTP 504)</div>
|
|
<div class="error-since">First: 03:42 · Last: 09:06</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="error-item">
|
|
<div class="error-count">11</div>
|
|
<div class="error-info">
|
|
<div class="error-route">order-flow / order-enrichment</div>
|
|
<div class="error-message">HikariPool connection timeout</div>
|
|
<div class="error-since">First: 08:55 · Last: 09:02</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="error-item">
|
|
<div class="error-count">4</div>
|
|
<div class="error-info">
|
|
<div class="error-route">shipment-flow / shipment-dispatch</div>
|
|
<div class="error-message">NullPointerException at ShipmentMapper:142</div>
|
|
<div class="error-since">First: 07:11 · Last: 08:34</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div><!-- /sidebar -->
|
|
|
|
</div><!-- /main-grid -->
|
|
|
|
</div><!-- /console -->
|
|
|
|
</body>
|
|
</html>
|