1491 lines
59 KiB
HTML
1491 lines
59 KiB
HTML
<!--
|
|
============================================================================
|
|
CAMELEER3 v3 — Agent Health & System Overview (Light Theme)
|
|
============================================================================
|
|
|
|
DESIGN NOTES
|
|
============
|
|
|
|
PURPOSE:
|
|
This page is the "Agents" view of the Cameleer3 monitoring dashboard. It
|
|
shows the health of all Camel application instances (agents) reporting to
|
|
the server, grouped by application group.
|
|
|
|
INFORMATION HIERARCHY:
|
|
1. System Overview Strip — 5 summary cards (total agents, groups, routes,
|
|
uptime, ingestion rate) give instant situational awareness.
|
|
2. Agent Group Cards — 2-column grid of cards, one per application group.
|
|
Each card lists its instances with status dot, state badge, uptime,
|
|
throughput, error rate, and last heartbeat. The inventory-check card
|
|
has a single DEAD instance with a prominent alert.
|
|
3. Bottom Tabs — Timeline (lifecycle events) and Route Distribution
|
|
(which routes run on which agents).
|
|
|
|
DESIGN SYSTEM:
|
|
Exact match of mock-v2-light.html: warm parchment (#F5F2ED), amber-gold
|
|
(#C6820E), warm charcoal sidebar (#2C2520), DM Sans + JetBrains Mono,
|
|
identical card styles, shadows, border radius, status colors.
|
|
|
|
DATA STORY:
|
|
- 12 agents across 4 groups. 9 LIVE, 2 STALE, 1 DEAD.
|
|
- order-processing: 3 healthy instances, one recently restarted.
|
|
- payment-flow: 2 instances, one STALE (missed heartbeats 3m ago).
|
|
- shipment-notify: 2 healthy instances.
|
|
- inventory-check: 1 DEAD instance — single point of failure alert.
|
|
|
|
============================================================================
|
|
-->
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=1920">
|
|
<title>Cameleer3 — Agent Health & System Overview</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
<style>
|
|
/* ==========================================================================
|
|
RESET & FOUNDATIONS (identical to v2)
|
|
========================================================================== */
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
:root {
|
|
/* Surface palette (warm parchment) */
|
|
--bg-body: #F5F2ED;
|
|
--bg-surface: #FFFFFF;
|
|
--bg-raised: #FAF8F5;
|
|
--bg-inset: #F0EDE8;
|
|
--bg-hover: #F5F0EA;
|
|
|
|
/* Sidebar (warm charcoal) */
|
|
--sidebar-bg: #2C2520;
|
|
--sidebar-hover: #3A322C;
|
|
--sidebar-active: #4A3F38;
|
|
--sidebar-text: #BFB5A8;
|
|
--sidebar-muted: #7A6F63;
|
|
|
|
/* Text */
|
|
--text-primary: #1A1612;
|
|
--text-secondary: #5C5347;
|
|
--text-muted: #9C9184;
|
|
--text-faint: #C4BAB0;
|
|
|
|
/* Borders */
|
|
--border: #E4DFD8;
|
|
--border-subtle: #EDE9E3;
|
|
|
|
/* Brand accent (amber-gold) */
|
|
--amber: #C6820E;
|
|
--amber-light: #F0D9A8;
|
|
--amber-bg: #FDF6E9;
|
|
--amber-deep: #8B5A06;
|
|
|
|
/* Status colors (warm) */
|
|
--success: #3D7C47;
|
|
--success-bg: #EFF7F0;
|
|
--success-border: #C2DFC6;
|
|
--warning: #C27516;
|
|
--warning-bg: #FEF5E7;
|
|
--warning-border: #F0D9A8;
|
|
--error: #C0392B;
|
|
--error-bg: #FDF0EE;
|
|
--error-border: #F0C4BE;
|
|
--running: #1A7F8E;
|
|
--running-bg: #E8F5F7;
|
|
--running-border: #B0DDE4;
|
|
|
|
/* Typography */
|
|
--font-body: 'DM Sans', system-ui, -apple-system, sans-serif;
|
|
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
|
|
|
|
/* Spacing & Radii */
|
|
--radius-sm: 5px;
|
|
--radius-md: 8px;
|
|
--radius-lg: 12px;
|
|
|
|
/* Shadows */
|
|
--shadow-sm: 0 1px 2px rgba(44, 37, 32, 0.06);
|
|
--shadow-md: 0 2px 8px rgba(44, 37, 32, 0.08);
|
|
--shadow-lg: 0 4px 16px rgba(44, 37, 32, 0.10);
|
|
--shadow-card: 0 1px 3px rgba(44, 37, 32, 0.04), 0 0 0 1px rgba(44, 37, 32, 0.04);
|
|
}
|
|
|
|
html { font-size: 14px; }
|
|
body {
|
|
font-family: var(--font-body);
|
|
background: var(--bg-body);
|
|
color: var(--text-primary);
|
|
line-height: 1.5;
|
|
min-height: 100vh;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
::-webkit-scrollbar-track { background: transparent; }
|
|
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
|
::-webkit-scrollbar-thumb:hover { background: var(--text-faint); }
|
|
|
|
/* ==========================================================================
|
|
ANIMATIONS
|
|
========================================================================== */
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: translateY(6px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
@keyframes pulse {
|
|
0%, 100% { box-shadow: 0 0 0 0 rgba(61, 124, 71, 0.35); }
|
|
50% { box-shadow: 0 0 0 5px rgba(61, 124, 71, 0); }
|
|
}
|
|
@keyframes 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); }
|
|
}
|
|
@keyframes warningPulse {
|
|
0%, 100% { box-shadow: 0 0 0 0 rgba(194, 117, 22, 0.2); }
|
|
50% { box-shadow: 0 0 0 4px rgba(194, 117, 22, 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; }
|
|
.delay-6 { animation-delay: 0.24s; }
|
|
.delay-7 { animation-delay: 0.28s; }
|
|
|
|
/* ==========================================================================
|
|
LAYOUT
|
|
========================================================================== */
|
|
.app {
|
|
display: flex;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* ==========================================================================
|
|
SIDEBAR (220px, warm charcoal) — identical structure to v2
|
|
========================================================================== */
|
|
.sidebar {
|
|
width: 220px;
|
|
flex-shrink: 0;
|
|
background: var(--sidebar-bg);
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.sidebar-logo {
|
|
padding: 16px 18px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
border-bottom: 1px solid rgba(255,255,255,0.06);
|
|
}
|
|
|
|
.sidebar-logo .brand {
|
|
font-family: var(--font-mono);
|
|
font-weight: 600;
|
|
font-size: 15px;
|
|
color: var(--amber-light);
|
|
letter-spacing: -0.3px;
|
|
}
|
|
|
|
.sidebar-logo .version {
|
|
font-family: var(--font-mono);
|
|
font-size: 10px;
|
|
color: var(--sidebar-muted);
|
|
margin-left: 2px;
|
|
}
|
|
|
|
.sidebar-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 .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-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); }
|
|
.health-dead { background: var(--sidebar-muted); }
|
|
|
|
.sidebar-divider {
|
|
height: 1px;
|
|
background: rgba(255,255,255,0.06);
|
|
margin: 6px 12px;
|
|
}
|
|
|
|
.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-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-shift {
|
|
font-family: var(--font-mono);
|
|
font-size: 10px;
|
|
padding: 3px 10px;
|
|
border-radius: 10px;
|
|
background: var(--running-bg);
|
|
color: var(--running);
|
|
border: 1px solid var(--running-border);
|
|
}
|
|
|
|
.topbar-user {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.topbar-avatar {
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: 50%;
|
|
background: var(--amber-bg);
|
|
color: var(--amber);
|
|
font-weight: 600;
|
|
font-size: 11px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border: 1px solid var(--amber-light);
|
|
}
|
|
|
|
/* Content scroll container */
|
|
.content {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 20px 24px 40px;
|
|
min-width: 0;
|
|
}
|
|
|
|
/* ==========================================================================
|
|
SYSTEM OVERVIEW STRIP (top 5 cards)
|
|
========================================================================== */
|
|
.health-strip {
|
|
display: grid;
|
|
grid-template-columns: repeat(5, 1fr);
|
|
gap: 10px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.stat-card {
|
|
background: var(--bg-surface);
|
|
border: 1px solid var(--border-subtle);
|
|
border-radius: var(--radius-md);
|
|
padding: 12px 16px;
|
|
box-shadow: var(--shadow-card);
|
|
position: relative;
|
|
overflow: hidden;
|
|
transition: box-shadow 0.15s;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.stat-card:hover { box-shadow: var(--shadow-md); }
|
|
|
|
.stat-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 2px;
|
|
}
|
|
|
|
.stat-card.card-amber::before { background: linear-gradient(90deg, var(--amber), transparent); }
|
|
.stat-card.card-green::before { background: linear-gradient(90deg, var(--success), transparent); }
|
|
.stat-card.card-error::before { background: linear-gradient(90deg, var(--error), transparent); }
|
|
.stat-card.card-teal::before { background: linear-gradient(90deg, var(--running), transparent); }
|
|
.stat-card.card-warn::before { background: linear-gradient(90deg, var(--warning), transparent); }
|
|
|
|
.stat-card.has-warning { animation: warningPulse 2.5s ease-in-out infinite; }
|
|
|
|
.stat-label {
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.6px;
|
|
color: var(--text-muted);
|
|
margin-bottom: 3px;
|
|
}
|
|
|
|
.stat-value-row {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 6px;
|
|
}
|
|
|
|
.stat-value {
|
|
font-family: var(--font-mono);
|
|
font-size: 20px;
|
|
font-weight: 600;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.stat-value.val-amber { color: var(--amber); }
|
|
.stat-value.val-green { color: var(--success); }
|
|
.stat-value.val-error { color: var(--error); }
|
|
.stat-value.val-teal { color: var(--running); }
|
|
.stat-value.val-warn { color: var(--warning); }
|
|
|
|
.stat-unit {
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.stat-detail {
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.stat-detail strong { color: var(--text-secondary); }
|
|
|
|
.stat-breakdown {
|
|
display: flex;
|
|
gap: 8px;
|
|
margin-top: 4px;
|
|
font-size: 11px;
|
|
font-family: var(--font-mono);
|
|
}
|
|
|
|
.stat-breakdown .bp-live { color: var(--success); }
|
|
.stat-breakdown .bp-stale { color: var(--warning); }
|
|
.stat-breakdown .bp-dead { color: var(--error); }
|
|
|
|
.stat-breakdown .bp-dot {
|
|
display: inline-block;
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 50%;
|
|
margin-right: 3px;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.stat-breakdown .bp-dot-live { background: #5DB866; }
|
|
.stat-breakdown .bp-dot-stale { background: var(--warning); }
|
|
.stat-breakdown .bp-dot-dead { background: var(--text-muted); }
|
|
|
|
/* ==========================================================================
|
|
AGENT GROUP CARDS
|
|
========================================================================== */
|
|
.group-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 16px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.group-card {
|
|
background: var(--bg-surface);
|
|
border: 1px solid var(--border-subtle);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow-card);
|
|
overflow: hidden;
|
|
transition: box-shadow 0.15s;
|
|
}
|
|
|
|
.group-card:hover { box-shadow: var(--shadow-md); }
|
|
|
|
.group-card.group-alert {
|
|
border-color: var(--error-border);
|
|
animation: errorPulse 2.5s ease-in-out infinite;
|
|
}
|
|
|
|
.group-card-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid var(--border-subtle);
|
|
background: var(--bg-raised);
|
|
}
|
|
|
|
.group-name {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
font-family: var(--font-mono);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.group-instance-count {
|
|
font-size: 11px;
|
|
font-family: var(--font-mono);
|
|
color: var(--text-muted);
|
|
background: var(--bg-inset);
|
|
padding: 2px 8px;
|
|
border-radius: 10px;
|
|
}
|
|
|
|
.group-meta {
|
|
display: flex;
|
|
gap: 16px;
|
|
padding: 8px 16px;
|
|
border-bottom: 1px solid var(--border-subtle);
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
background: var(--bg-surface);
|
|
}
|
|
|
|
.group-meta-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.group-meta-item strong {
|
|
font-family: var(--font-mono);
|
|
color: var(--text-secondary);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.group-instances {
|
|
padding: 0;
|
|
}
|
|
|
|
.instance-row {
|
|
display: grid;
|
|
grid-template-columns: 8px 1fr auto auto auto auto auto;
|
|
gap: 12px;
|
|
align-items: center;
|
|
padding: 8px 16px;
|
|
border-bottom: 1px solid var(--border-subtle);
|
|
font-size: 12px;
|
|
transition: background 0.1s;
|
|
}
|
|
|
|
.instance-row:last-child { border-bottom: none; }
|
|
.instance-row:hover { background: var(--bg-hover); }
|
|
|
|
.instance-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.dot-live { background: #5DB866; box-shadow: 0 0 6px rgba(93, 184, 102, 0.4); }
|
|
.dot-stale { background: var(--warning); box-shadow: 0 0 6px rgba(194, 117, 22, 0.3); }
|
|
.dot-dead { background: var(--text-muted); }
|
|
|
|
.instance-name {
|
|
font-family: var(--font-mono);
|
|
font-weight: 500;
|
|
font-size: 12px;
|
|
color: var(--text-primary);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.instance-badge {
|
|
font-family: var(--font-mono);
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
padding: 2px 8px;
|
|
border-radius: 10px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.3px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.badge-live {
|
|
background: var(--success-bg);
|
|
color: var(--success);
|
|
border: 1px solid var(--success-border);
|
|
}
|
|
|
|
.badge-stale {
|
|
background: var(--warning-bg);
|
|
color: var(--warning);
|
|
border: 1px solid var(--warning-border);
|
|
}
|
|
|
|
.badge-dead {
|
|
background: var(--error-bg);
|
|
color: var(--error);
|
|
border: 1px solid var(--error-border);
|
|
}
|
|
|
|
.instance-uptime {
|
|
font-family: var(--font-mono);
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.instance-throughput {
|
|
font-family: var(--font-mono);
|
|
font-size: 11px;
|
|
color: var(--text-secondary);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.instance-errors {
|
|
font-family: var(--font-mono);
|
|
font-size: 11px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.instance-errors.low { color: var(--success); }
|
|
.instance-errors.med { color: var(--warning); }
|
|
.instance-errors.high { color: var(--error); }
|
|
|
|
.instance-heartbeat {
|
|
font-family: var(--font-mono);
|
|
font-size: 10px;
|
|
color: var(--text-muted);
|
|
white-space: nowrap;
|
|
text-align: right;
|
|
}
|
|
|
|
.instance-heartbeat.stale-hb { color: var(--warning); font-weight: 600; }
|
|
.instance-heartbeat.dead-hb { color: var(--error); font-weight: 600; }
|
|
|
|
.instance-note {
|
|
font-size: 10px;
|
|
color: var(--amber);
|
|
font-style: italic;
|
|
margin-left: 4px;
|
|
}
|
|
|
|
/* Alert banner inside group card */
|
|
.group-alert-banner {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 8px 16px;
|
|
background: var(--error-bg);
|
|
border-top: 1px solid var(--error-border);
|
|
font-size: 11px;
|
|
color: var(--error);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.group-alert-banner .alert-icon {
|
|
font-size: 14px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* ==========================================================================
|
|
BOTTOM TABS (Timeline + Route Distribution)
|
|
========================================================================== */
|
|
.bottom-section {
|
|
background: var(--bg-surface);
|
|
border: 1px solid var(--border-subtle);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow-card);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.tab-bar {
|
|
display: flex;
|
|
border-bottom: 1px solid var(--border-subtle);
|
|
padding: 0 16px;
|
|
gap: 0;
|
|
}
|
|
|
|
.tab-item {
|
|
padding: 10px 16px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
border-bottom: 2px solid transparent;
|
|
transition: all 0.15s;
|
|
margin-bottom: -1px;
|
|
}
|
|
|
|
.tab-item:hover { color: var(--text-primary); }
|
|
.tab-item.active {
|
|
color: var(--amber);
|
|
border-bottom-color: var(--amber);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.tab-content { padding: 0; }
|
|
.tab-panel { display: none; }
|
|
.tab-panel.active { display: block; }
|
|
|
|
/* Timeline */
|
|
.timeline-list { padding: 8px 0; }
|
|
|
|
.timeline-event {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 12px;
|
|
padding: 8px 16px;
|
|
transition: background 0.1s;
|
|
}
|
|
|
|
.timeline-event:hover { background: var(--bg-hover); }
|
|
|
|
.timeline-icon {
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 12px;
|
|
flex-shrink: 0;
|
|
margin-top: 1px;
|
|
}
|
|
|
|
.timeline-icon.icon-start { background: var(--success-bg); color: var(--success); border: 1px solid var(--success-border); }
|
|
.timeline-icon.icon-dead { background: var(--error-bg); color: var(--error); border: 1px solid var(--error-border); }
|
|
.timeline-icon.icon-stale { background: var(--warning-bg); color: var(--warning); border: 1px solid var(--warning-border); }
|
|
.timeline-icon.icon-route { background: var(--running-bg); color: var(--running); border: 1px solid var(--running-border); }
|
|
.timeline-icon.icon-config { background: var(--amber-bg); color: var(--amber); border: 1px solid var(--amber-light); }
|
|
|
|
.timeline-body { flex: 1; min-width: 0; }
|
|
|
|
.timeline-text {
|
|
font-size: 12px;
|
|
color: var(--text-primary);
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.timeline-text .tl-agent {
|
|
font-family: var(--font-mono);
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.timeline-text .tl-group {
|
|
font-family: var(--font-mono);
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.timeline-text .tl-highlight-dead { color: var(--error); font-weight: 600; }
|
|
.timeline-text .tl-highlight-stale { color: var(--warning); font-weight: 600; }
|
|
.timeline-text .tl-highlight-start { color: var(--success); font-weight: 600; }
|
|
|
|
.timeline-time {
|
|
font-size: 10px;
|
|
font-family: var(--font-mono);
|
|
color: var(--text-muted);
|
|
margin-top: 1px;
|
|
}
|
|
|
|
/* Route Distribution Table */
|
|
.route-table-wrap { overflow-x: auto; }
|
|
|
|
.route-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.route-table th {
|
|
padding: 8px 12px;
|
|
text-align: left;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
color: var(--text-muted);
|
|
background: var(--bg-raised);
|
|
border-bottom: 1px solid var(--border-subtle);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.route-table td {
|
|
padding: 6px 12px;
|
|
border-bottom: 1px solid var(--border-subtle);
|
|
font-family: var(--font-mono);
|
|
font-size: 11px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.route-table tbody tr:hover { background: var(--bg-hover); }
|
|
.route-table tbody tr:last-child td { border-bottom: none; }
|
|
|
|
.route-table td.route-name {
|
|
font-weight: 500;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.route-check {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: 50%;
|
|
font-size: 11px;
|
|
}
|
|
|
|
.route-check.present { background: var(--success-bg); color: var(--success); }
|
|
.route-check.absent { background: var(--bg-inset); color: var(--text-faint); }
|
|
.route-check.dead { background: var(--error-bg); color: var(--error); }
|
|
|
|
.route-status {
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
padding: 2px 8px;
|
|
border-radius: 10px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.route-status.status-healthy { background: var(--success-bg); color: var(--success); border: 1px solid var(--success-border); }
|
|
.route-status.status-partial { background: var(--warning-bg); color: var(--warning); border: 1px solid var(--warning-border); }
|
|
.route-status.status-degraded { background: var(--error-bg); color: var(--error); border: 1px solid var(--error-border); }
|
|
|
|
/* ==========================================================================
|
|
RESPONSIVE INSTANCE ROW (adjust grid for content)
|
|
========================================================================== */
|
|
.instance-row {
|
|
grid-template-columns: 8px minmax(100px, 1.2fr) auto auto auto auto auto;
|
|
}
|
|
|
|
/* Column headers for instance list */
|
|
.instance-header {
|
|
display: grid;
|
|
grid-template-columns: 8px minmax(100px, 1.2fr) auto auto auto auto auto;
|
|
gap: 12px;
|
|
padding: 4px 16px;
|
|
font-size: 9px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
color: var(--text-faint);
|
|
border-bottom: 1px solid var(--border-subtle);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="app">
|
|
|
|
<!-- ====================================================================
|
|
SIDEBAR
|
|
==================================================================== -->
|
|
<aside class="sidebar">
|
|
<div class="sidebar-logo">
|
|
<svg width="24" height="26" viewBox="940 300 900 1000" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path style="fill:#C6820E" d="M 1385.2768,1205.0273 c -1.7727,-1.0809 -3.4419,-2.9917 -3.7093,-4.2463 -0.2674,-1.2545 -0.6851,-23.881 -0.9282,-50.281 -0.4544,-49.3389 -1.4003,-65.7355 -5.6645,-98.1921 -10.4552,-79.57719 -28.3441,-125.96796 -62.5438,-162.19311 -9.2529,-9.80096 -19.9748,-18.38696 -33.931,-27.17162 -12.6119,-7.93853 -24.8581,-13.73088 -49.5,-23.41316 -41.7081,-16.38789 -67.8677,-32.29827 -89.0588,-54.16599 -34.7586,-35.86827 -54.3653,-83.30392 -59.0504,-142.86402 -0.8357,-10.6245 -0.92,-22.5842 -0.2891,-41 0.4899,-14.3 0.8164,-35.225 0.7255,-46.5 -0.1983,-24.59199 -2.0251,-34.76973 -8.4617,-47.14159 -6.2021,-11.92135 -10.364,-15.10789 -36.2345,-27.74305 -25.4032,-12.40694 -27.7701,-13.30168 -35.4717,-13.40961 -4.63013,-0.0649 -7.47783,0.72754 -15.6593,4.35747 -9.86218,4.37563 -10.15161,4.43672 -21,4.43267 -10.86566,-0.004 -11.08917,-0.0517 -18.30169,-3.90103 -17.43524,-9.3052 -21.50075,-23.5772 -13.55784,-47.59486 2.73631,-8.27402 10.00732,-22.43593 14.69263,-28.61719 5.4984,-7.25394 10.89014,-11.83885 20.83968,-17.72117 15.06768,-8.90827 29.60254,-11.98884 63.90372,-13.54403 l 16.5765,-0.75156 14.9235,-7.05735 c 8.2079,-3.88154 17.1735,-7.76831 19.9235,-8.63727 7.7052,-2.43474 21.059,-4.67186 27.8605,-4.66741 8.0518,0.005 22.643,2.41202 30.0139,4.95066 l 5.8744,2.02321 4.8856,-4.09041 c 10.341,-8.65797 18.6496,-12.95738 28.96,-14.98583 6.9966,-1.37649 26.3532,-0.64631 32.1116,1.21134 4.5531,1.46885 5.4951,3.7902 6.3689,15.69592 1.5167,20.66426 -5.0112,40.44987 -18.1772,55.09363 -4.3065,4.78983 -4.5016,5.25488 -3.4977,8.33516 4.5184,13.86447 5.3154,38.07517 2.1537,65.42564 -5.169,44.71349 -5.0411,70.7797 0.5404,110.15637 6.8135,48.06863 22.3335,73.51874 48.2051,79.04779 24.1748,5.16643 45.2921,-5.78181 66.8353,-34.65077 4.809,-6.4442 11.1363,-14.93006 14.0608,-18.85747 7.3865,-9.91943 25.6102,-27.11708 35.952,-33.92766 12.5235,-8.24739 26.8808,-14.74833 42.8527,-19.40354 15.4108,-4.49168 26.7091,-9.74984 36.2432,-16.86731 3.4338,-2.56341 13.3338,-12.03104 22,-21.03916 26.2356,-27.27067 44.5755,-40.32368 66.9928,-47.68062 19.1052,-6.26998 35.4927,-7.73718 50.8681,-4.55427 37.0856,7.67726 63.5507,26.77589 100.2888,72.37362 16.6011,20.60463 29.9711,32.07977 51.6071,44.29313 39.3131,22.19195 50.1228,30.50985 68.8076,52.94655 5.4963,6.6 12.9187,14.91604 16.4941,18.4801 33.6796,33.57259 54.1965,72.51092 61.6587,117.0199 2.7315,16.29242 3.3374,26.62538 3.1861,54.33713 l -0.1465,26.83714 -2.7975,2.40572 c -3.9132,3.36522 -7.2806,3.99163 -11.2591,2.09442 -6.4731,-3.08682 -6.5715,-3.57833 -6.878,-34.36929 -0.2908,-29.2175 -2.0265,-46.13705 -6.6479,-64.80512 -3.2997,-13.32891 -12.2529,-34.61943 -17.0313,-40.5 l -2.0314,-2.5 0.5698,3.5 c 5.8574,35.97875 4.2855,72.40287 -5.3946,125 -8.4016,45.65054 -4.3665,69.39588 20.7318,122 11.4175,23.93009 12.5452,27.25907 12.4212,36.6684 -0.1018,7.7227 -2.5346,19.5162 -13.1674,63.8316 -1.0558,4.4 -3.5336,14.975 -5.5063,23.5 -1.9727,8.525 -4.2259,18.2 -5.0072,21.5 -0.7812,3.3 -3.4495,15.225 -5.9296,26.5 -9.1636,41.6596 -13.4372,59.8787 -14.5204,61.9027 -2.4994,4.6702 -5.2312,5.0973 -32.6024,5.0973 H 1765.8 l -3.4,-3.4 c -2.3518,-2.3518 -3.4,-4.3226 -3.4,-6.3925 0,-1.6458 2.2347,-12.1533 4.966,-23.35 8.6906,-35.6259 11.6969,-54.778 12.6921,-80.8575 1.3475,-35.3073 -4.6406,-62.7687 -18.7825,-86.137 -7.6672,-12.66954 -11.9163,-17.84148 -29.8756,-36.36415 -22.9362,-23.65574 -34.6222,-39.72583 -47.9268,-65.90697 -5.7294,-11.27448 -13.6061,-31.99995 -15.5051,-40.79778 -0.3212,-1.48824 -1.0176,-3.84619 -1.5475,-5.2399 l -0.9634,-2.534 -11.7786,7.15145 c -38.634,23.45687 -74.5513,34.71091 -124.1895,38.91257 -14.5402,1.23075 -58.2359,0.66344 -71.9142,-0.93369 -5.4419,-0.63542 -6.4542,-0.49723 -7.25,0.98972 -0.5545,1.03621 -0.9249,60.63835 -0.9249,148.84365 0,161.6262 0.4025,151.2052 -5.9673,154.4992 -2.3865,1.2341 -7.4633,1.5162 -27.233,1.5132 -22.3926,-0 -24.5527,-0.158 -27.5229,-1.969 z M 1424,1045.6452 c 0,-87.21053 0.3878,-144.17994 1.0365,-152.24998 1.8115,-22.53829 7.2373,-44.8067 16.1142,-66.13567 20.7842,-49.93942 66.8961,-95.01414 129.8493,-126.92865 14.9469,-7.57742 29.2834,-13.56192 41.5,-17.32336 3.85,-1.1854 7.9,-2.55654 9,-3.04699 6.0644,-2.70386 29.8837,-6.94397 39.0685,-6.95465 16.5354,-0.0192 29.4209,10.53866 32.4491,26.58755 1.5763,8.35375 1.0812,11.63345 -4.5147,29.90655 -10.1916,33.28037 -13.3229,61.96134 -10.0442,92 3.2262,29.55737 10.5673,53.87028 23.6898,78.45848 11.023,20.65405 21.7624,35.21113 38.7623,52.54152 17.2909,17.62693 24.0427,25.40931 30.3653,35 26.0619,39.5327 33.1081,82.6442 23.6173,144.5 -1.6936,11.0379 -7.1852,37.5413 -10.412,50.25 l -0.6982,2.75 h 11.041 c 9.6675,0 11.1002,-0.2177 11.5165,-1.75 0.2615,-0.9625 1.1691,-4.9 2.0169,-8.75 4.2214,-19.1689 6.8202,-30.513 8.1326,-35.5 1.361,-5.1717 9.3524,-39.8413 18.5555,-80.5 2.2408,-9.9 5.3554,-23.4 6.9213,-30 4.9646,-20.9246 5.3189,-23.3077 4.1531,-27.9371 -0.5856,-2.32595 -4.0498,-10.49161 -7.6981,-18.14594 -9.222,-19.34804 -12.2871,-26.05688 -14.0769,-30.8114 -5.3001,-14.07878 -9.5733,-27.65251 -11.9961,-38.10556 -2.5547,-11.02237 -2.7797,-13.62939 -2.7612,-32 0.019,-19.36303 0.1714,-20.85991 4.7729,-47 6.161,-34.99931 7.6003,-48.16042 7.6003,-69.5 -10e-5,-33.76835 -6.5703,-63.86472 -19.8286,-90.82854 -6.3511,-12.91659 -11.7543,-20.74668 -21.037,-30.48614 -12.8375,-13.46928 -22.9472,-20.76621 -47.0954,-33.9923 -11.7394,-6.42975 -28.8576,-17.82564 -36.5,-24.2987 -10.6672,-9.03504 -17.2351,-15.91058 -32.8553,-34.39432 -16.848,-19.93653 -31.9446,-35.04489 -42.6447,-42.67772 -10.3431,-7.37816 -15.6552,-10.10769 -28,-14.38707 -9.1747,-3.18048 -10.0479,-3.29466 -25.5,-3.33475 -15.5657,-0.0404 -16.2561,0.0478 -25.4354,3.24965 -23.091,8.0544 -33.3943,15.26018 -59.5646,41.65777 -30.5973,30.86299 -38.6661,35.8414 -77,47.50846 -16.2269,4.93872 -34.1287,15.69697 -44.617,26.81301 -10.992,11.6499 -13.307,14.35965 -22.3589,26.17065 -19.0735,24.88726 -27.0799,33.14259 -41.0241,42.29946 -10.0586,6.60527 -20.3327,9.75956 -33.9507,10.42334 -37.2243,1.81441 -62.5379,-17.55119 -76.1054,-58.2228 -11.652,-34.92922 -16.8948,-93.36051 -12.158,-135.5 5.892,-52.41494 5.8488,-63.62373 -0.3099,-80.57433 -4.7448,-13.05916 -4.7675,-12.96705 5.716,-23.17933 5.7642,-5.61509 9.7601,-10.45668 11.1613,-13.52373 2.7419,-6.00142 5.1126,-15.9207 5.1319,-21.47261 L 1198,352 l -5.9651,0 c -11.169,0 -21.7558,5.31928 -28.8986,14.51992 -5.5094,7.09667 -9.8889,9.41118 -14.4261,7.62413 -22.7413,-8.95689 -39.8334,-10.59845 -57.5511,-5.52733 -3.1125,0.89085 -11.6238,4.5155 -18.9141,8.05478 -15.4059,7.47927 -22.5958,9.28006 -37.245,9.32847 -31.9108,0.10543 -56.25892,6.94953 -67.17127,18.88141 -8.24539,9.01574 -18.57001,32.98439 -17.63456,40.9387 0.20038,1.70385 1.68501,3.56683 4.30583,5.40313 6.15036,4.30931 11.64453,4.02586 24.53725,-1.26587 9.37877,-3.84945 11.49879,-4.33904 20.46275,-4.7256 12.5127,-0.5396 16.1351,0.57224 41.5,12.73763 28.326,13.58558 38.6034,16.26339 54.7762,14.27212 10.7814,-1.32746 19.6601,-4.77604 26.9813,-10.47981 10.8957,-8.48862 12.7264,-9.11418 18.2752,-6.24475 3.4802,1.79966 5.9673,6.17527 5.9673,10.49833 0,5.2273 -12.3619,16.07427 -24.373,21.38616 -8.3575,3.69608 -19.5481,6.59858 -25.4408,6.59858 -2.8524,0 -5.1862,0.40847 -5.1862,0.90771 0,0.49925 1.1261,3.08675 2.5024,5.75 7.4849,14.48347 10.3469,40.93277 8.5814,79.30477 -1.8809,40.87852 -1.063,65.17107 3.0314,90.03752 1.9205,11.66347 7.4026,31.07934 11.5058,40.75 3.6541,8.61199 4.8919,11.32366 8.5592,18.75 7.5496,15.28823 16.3315,27.16362 30.7439,41.57393 20.487,20.48403 39.3629,32.17675 72.4974,44.90876 32.2544,12.39381 46.3332,19.15119 64.0627,30.7482 24.2965,15.89251 40.6199,31.86253 55.9811,54.76911 15.3367,22.87022 26.7091,49.93352 35.5759,84.66142 7.4833,29.30908 14.5942,79.14828 16.5733,116.15948 0.3771,7.0514 0.7727,14.0264 0.879,15.5 0.1064,1.4735 0.2639,17.1916 0.35,34.9291 L 1403,1185 h 10.5 10.5 z m 109.6148,-166.12974 c 32.3584,-3.61532 59.7448,-11.3892 85.9116,-24.38672 13.8501,-6.87965 33.6049,-18.83328 37.1094,-22.455 0.7986,-0.82533 0.878,-3.10315 0.2676,-7.67374 -0.4775,-3.575 -0.9114,-15.05 -0.9643,-25.5 -0.1555,-30.74924 3.0015,-49.74257 13.6751,-82.27105 3.3477,-10.20267 3.1984,-15.57022 -0.5373,-19.30587 -2.563,-2.563 -3.6408,-2.92269 -8.75,-2.91989 -17.9704,0.01 -50.057,9.8542 -78.4604,24.07205 -31.505,15.77045 -56.5528,33.57164 -78.5866,55.85076 -22.9969,23.25282 -37.4392,45.78809 -46.737,72.92624 -3.3375,9.74165 -8.0905,29.93345 -7.2451,30.77886 2.3874,2.38739 64.9877,3.04398 84.317,0.88436 z"/>
|
|
</svg>
|
|
<div>
|
|
<span class="brand">cameleer</span><span class="version">v3</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">Navigation</div>
|
|
|
|
<div class="sidebar-items">
|
|
<div class="sidebar-item">
|
|
<span style="font-size: 13px; width: 18px; text-align: center;">◉</span>
|
|
<div class="item-info"><div class="item-name">Executions</div></div>
|
|
</div>
|
|
|
|
<div class="sidebar-item active">
|
|
<span style="font-size: 13px; width: 18px; text-align: center;">◆</span>
|
|
<div class="item-info"><div class="item-name">Agents</div></div>
|
|
<span class="item-count">12</span>
|
|
</div>
|
|
|
|
<div class="sidebar-item">
|
|
<span style="font-size: 13px; width: 18px; text-align: center;">◯</span>
|
|
<div class="item-info"><div class="item-name">Routes</div></div>
|
|
<span class="item-count">24</span>
|
|
</div>
|
|
|
|
<div class="sidebar-item">
|
|
<span style="font-size: 13px; width: 18px; text-align: center;">⚙</span>
|
|
<div class="item-info"><div class="item-name">Diagrams</div></div>
|
|
</div>
|
|
|
|
<div class="sidebar-divider"></div>
|
|
<div class="sidebar-section">Groups</div>
|
|
|
|
<div class="sidebar-item">
|
|
<span class="health health-live"></span>
|
|
<div class="item-info">
|
|
<div class="item-name">order-processing</div>
|
|
<div class="item-meta">3 agents</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-item">
|
|
<span class="health health-stale"></span>
|
|
<div class="item-info">
|
|
<div class="item-name">payment-flow</div>
|
|
<div class="item-meta">2 agents</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-item">
|
|
<span class="health health-live"></span>
|
|
<div class="item-info">
|
|
<div class="item-name">shipment-notify</div>
|
|
<div class="item-meta">2 agents</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-item">
|
|
<span class="health health-dead"></span>
|
|
<div class="item-info">
|
|
<div class="item-name">inventory-check</div>
|
|
<div class="item-meta">1 agent</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-divider"></div>
|
|
<div class="sidebar-section">Quick Filters</div>
|
|
|
|
<div class="sidebar-item">
|
|
<span class="health health-stale"></span>
|
|
<div class="item-info"><div class="item-name">Stale Agents</div></div>
|
|
<span class="item-count">2</span>
|
|
</div>
|
|
|
|
<div class="sidebar-item">
|
|
<span class="health health-dead"></span>
|
|
<div class="item-info"><div class="item-name">Dead Agents</div></div>
|
|
<span class="item-count">1</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-bottom">
|
|
<div class="sidebar-item"><span style="font-size: 13px; width: 18px; text-align: center;">⚙</span><div class="item-info"><div class="item-name">Admin</div></div></div>
|
|
<div class="sidebar-item"><span style="font-size: 13px; width: 18px; text-align: center;">☰</span><div class="item-info"><div class="item-name">API Docs</div></div></div>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- ====================================================================
|
|
MAIN CONTENT
|
|
==================================================================== -->
|
|
<div class="main">
|
|
|
|
<!-- Top bar -->
|
|
<div class="topbar">
|
|
<div class="topbar-breadcrumb">
|
|
<span>System</span>
|
|
<span class="crumb-sep">▸</span>
|
|
<span class="crumb-active">Agents</span>
|
|
</div>
|
|
<div class="topbar-right">
|
|
<span class="topbar-env">Production</span>
|
|
<span class="topbar-shift">Since 06:00</span>
|
|
<div class="topbar-user">
|
|
<span>ops-admin</span>
|
|
<div class="topbar-avatar">OA</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Scrollable content -->
|
|
<div class="content">
|
|
|
|
<!-- ================================================================
|
|
SYSTEM OVERVIEW STRIP
|
|
================================================================ -->
|
|
<div class="health-strip animate-in">
|
|
|
|
<div class="stat-card card-amber has-warning">
|
|
<div class="stat-label">Total Agents</div>
|
|
<div class="stat-value-row">
|
|
<span class="stat-value val-amber">12</span>
|
|
<span class="stat-unit">reporting</span>
|
|
</div>
|
|
<div class="stat-breakdown">
|
|
<span class="bp-live"><span class="bp-dot bp-dot-live"></span>9</span>
|
|
<span class="bp-stale"><span class="bp-dot bp-dot-stale"></span>2</span>
|
|
<span class="bp-dead"><span class="bp-dot bp-dot-dead"></span>1</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card card-teal">
|
|
<div class="stat-label">Groups</div>
|
|
<div class="stat-value-row">
|
|
<span class="stat-value val-teal">4</span>
|
|
<span class="stat-unit">applications</span>
|
|
</div>
|
|
<div class="stat-detail"><strong>3</strong> healthy · <strong>1</strong> degraded</div>
|
|
</div>
|
|
|
|
<div class="stat-card card-green">
|
|
<div class="stat-label">Active Routes</div>
|
|
<div class="stat-value-row">
|
|
<span class="stat-value val-green">24</span>
|
|
<span class="stat-unit">routes</span>
|
|
</div>
|
|
<div class="stat-detail">across all agents</div>
|
|
</div>
|
|
|
|
<div class="stat-card card-green">
|
|
<div class="stat-label">System Uptime</div>
|
|
<div class="stat-value-row">
|
|
<span class="stat-value val-green">14d</span>
|
|
<span class="stat-unit">3h 22m</span>
|
|
</div>
|
|
<div class="stat-detail">since Mar 3 02:38 UTC</div>
|
|
</div>
|
|
|
|
<div class="stat-card card-amber">
|
|
<div class="stat-label">Data Ingestion</div>
|
|
<div class="stat-value-row">
|
|
<span class="stat-value val-amber">847</span>
|
|
<span class="stat-unit">msg/min</span>
|
|
</div>
|
|
<div class="stat-detail">▲ 4.2% vs yesterday</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- ================================================================
|
|
AGENT GROUP CARDS (2-column grid)
|
|
================================================================ -->
|
|
<div class="group-grid">
|
|
|
|
<!-- order-processing — 3 healthy instances -->
|
|
<div class="group-card animate-in delay-1">
|
|
<div class="group-card-header">
|
|
<span class="group-name">order-processing</span>
|
|
<span class="group-instance-count">3 instances</span>
|
|
</div>
|
|
<div class="group-meta">
|
|
<div class="group-meta-item">Routes: <strong>8</strong></div>
|
|
<div class="group-meta-item">Avg Throughput: <strong>248/min</strong></div>
|
|
<div class="group-meta-item">Errors: <strong>0.3%</strong></div>
|
|
</div>
|
|
<div class="instance-header">
|
|
<span></span>
|
|
<span>Agent ID</span>
|
|
<span>State</span>
|
|
<span>Uptime</span>
|
|
<span>Throughput</span>
|
|
<span>Err %</span>
|
|
<span style="text-align: right;">Heartbeat</span>
|
|
</div>
|
|
<div class="group-instances">
|
|
<div class="instance-row">
|
|
<span class="instance-dot dot-live"></span>
|
|
<span class="instance-name">prod-node-01</span>
|
|
<span class="instance-badge badge-live">Live</span>
|
|
<span class="instance-uptime">↑ 14d 3h</span>
|
|
<span class="instance-throughput">248/min</span>
|
|
<span class="instance-errors low">0.2%</span>
|
|
<span class="instance-heartbeat">5s ago</span>
|
|
</div>
|
|
<div class="instance-row">
|
|
<span class="instance-dot dot-live"></span>
|
|
<span class="instance-name">prod-node-02</span>
|
|
<span class="instance-badge badge-live">Live</span>
|
|
<span class="instance-uptime">↑ 14d 3h</span>
|
|
<span class="instance-throughput">251/min</span>
|
|
<span class="instance-errors low">0.4%</span>
|
|
<span class="instance-heartbeat">3s ago</span>
|
|
</div>
|
|
<div class="instance-row">
|
|
<span class="instance-dot dot-live"></span>
|
|
<span class="instance-name">prod-node-03</span>
|
|
<span class="instance-badge badge-live">Live</span>
|
|
<span class="instance-uptime">↑ 2h 15m<span class="instance-note">restarted</span></span>
|
|
<span class="instance-throughput">245/min</span>
|
|
<span class="instance-errors low">0.3%</span>
|
|
<span class="instance-heartbeat">8s ago</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- payment-flow — 2 instances, one STALE -->
|
|
<div class="group-card animate-in delay-2">
|
|
<div class="group-card-header">
|
|
<span class="group-name">payment-flow</span>
|
|
<span class="group-instance-count">2 instances</span>
|
|
</div>
|
|
<div class="group-meta">
|
|
<div class="group-meta-item">Routes: <strong>6</strong></div>
|
|
<div class="group-meta-item">Avg Throughput: <strong>182/min</strong></div>
|
|
<div class="group-meta-item">Errors: <strong>0.8%</strong></div>
|
|
</div>
|
|
<div class="instance-header">
|
|
<span></span>
|
|
<span>Agent ID</span>
|
|
<span>State</span>
|
|
<span>Uptime</span>
|
|
<span>Throughput</span>
|
|
<span>Err %</span>
|
|
<span style="text-align: right;">Heartbeat</span>
|
|
</div>
|
|
<div class="group-instances">
|
|
<div class="instance-row">
|
|
<span class="instance-dot dot-live"></span>
|
|
<span class="instance-name">prod-pay-01</span>
|
|
<span class="instance-badge badge-live">Live</span>
|
|
<span class="instance-uptime">↑ 14d 3h</span>
|
|
<span class="instance-throughput">194/min</span>
|
|
<span class="instance-errors low">0.5%</span>
|
|
<span class="instance-heartbeat">2s ago</span>
|
|
</div>
|
|
<div class="instance-row">
|
|
<span class="instance-dot dot-stale"></span>
|
|
<span class="instance-name">prod-pay-02</span>
|
|
<span class="instance-badge badge-stale">Stale</span>
|
|
<span class="instance-uptime">↑ 14d 3h</span>
|
|
<span class="instance-throughput">170/min</span>
|
|
<span class="instance-errors med">1.1%</span>
|
|
<span class="instance-heartbeat stale-hb">3m ago</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- shipment-notify — 2 healthy instances -->
|
|
<div class="group-card animate-in delay-3">
|
|
<div class="group-card-header">
|
|
<span class="group-name">shipment-notify</span>
|
|
<span class="group-instance-count">2 instances</span>
|
|
</div>
|
|
<div class="group-meta">
|
|
<div class="group-meta-item">Routes: <strong>4</strong></div>
|
|
<div class="group-meta-item">Avg Throughput: <strong>96/min</strong></div>
|
|
<div class="group-meta-item">Errors: <strong>0.1%</strong></div>
|
|
</div>
|
|
<div class="instance-header">
|
|
<span></span>
|
|
<span>Agent ID</span>
|
|
<span>State</span>
|
|
<span>Uptime</span>
|
|
<span>Throughput</span>
|
|
<span>Err %</span>
|
|
<span style="text-align: right;">Heartbeat</span>
|
|
</div>
|
|
<div class="group-instances">
|
|
<div class="instance-row">
|
|
<span class="instance-dot dot-live"></span>
|
|
<span class="instance-name">prod-ship-01</span>
|
|
<span class="instance-badge badge-live">Live</span>
|
|
<span class="instance-uptime">↑ 14d 3h</span>
|
|
<span class="instance-throughput">98/min</span>
|
|
<span class="instance-errors low">0.1%</span>
|
|
<span class="instance-heartbeat">6s ago</span>
|
|
</div>
|
|
<div class="instance-row">
|
|
<span class="instance-dot dot-live"></span>
|
|
<span class="instance-name">prod-ship-02</span>
|
|
<span class="instance-badge badge-live">Live</span>
|
|
<span class="instance-uptime">↑ 14d 3h</span>
|
|
<span class="instance-throughput">94/min</span>
|
|
<span class="instance-errors low">0.1%</span>
|
|
<span class="instance-heartbeat">4s ago</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- inventory-check — 1 DEAD instance, SPOF alert -->
|
|
<div class="group-card group-alert animate-in delay-4">
|
|
<div class="group-card-header" style="background: var(--error-bg);">
|
|
<span class="group-name" style="color: var(--error);">inventory-check</span>
|
|
<span class="group-instance-count" style="background: var(--error-bg); color: var(--error); border: 1px solid var(--error-border);">1 instance</span>
|
|
</div>
|
|
<div class="group-meta" style="background: var(--error-bg); border-bottom-color: var(--error-border);">
|
|
<div class="group-meta-item">Routes: <strong>6</strong></div>
|
|
<div class="group-meta-item">Avg Throughput: <strong style="color: var(--error);">0/min</strong></div>
|
|
<div class="group-meta-item">Errors: <strong style="color: var(--error);">—</strong></div>
|
|
</div>
|
|
<div class="instance-header">
|
|
<span></span>
|
|
<span>Agent ID</span>
|
|
<span>State</span>
|
|
<span>Uptime</span>
|
|
<span>Throughput</span>
|
|
<span>Err %</span>
|
|
<span style="text-align: right;">Heartbeat</span>
|
|
</div>
|
|
<div class="group-instances">
|
|
<div class="instance-row">
|
|
<span class="instance-dot dot-dead"></span>
|
|
<span class="instance-name">staging-01</span>
|
|
<span class="instance-badge badge-dead">Dead</span>
|
|
<span class="instance-uptime" style="color: var(--text-faint);">—</span>
|
|
<span class="instance-throughput" style="color: var(--text-faint);">0/min</span>
|
|
<span class="instance-errors" style="color: var(--text-faint);">—</span>
|
|
<span class="instance-heartbeat dead-hb">45m ago</span>
|
|
</div>
|
|
</div>
|
|
<div class="group-alert-banner">
|
|
<span class="alert-icon">⚠</span>
|
|
<span>Single point of failure — this group has only 1 instance and it is <strong>DEAD</strong>. Last heartbeat 45 minutes ago. Inventory checking is fully offline.</span>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- ================================================================
|
|
BOTTOM TABS: Timeline | Route Distribution
|
|
================================================================ -->
|
|
<div class="bottom-section animate-in delay-5">
|
|
<div class="tab-bar">
|
|
<div class="tab-item active" onclick="switchTab('timeline')">Timeline</div>
|
|
<div class="tab-item" onclick="switchTab('routes')">Route Distribution</div>
|
|
</div>
|
|
<div class="tab-content">
|
|
|
|
<!-- Timeline Tab -->
|
|
<div class="tab-panel active" id="tab-timeline">
|
|
<div class="timeline-list">
|
|
|
|
<div class="timeline-event">
|
|
<div class="timeline-icon icon-stale">⚠</div>
|
|
<div class="timeline-body">
|
|
<div class="timeline-text">
|
|
<span class="tl-agent">prod-pay-02</span>
|
|
<span class="tl-group">(payment-flow)</span>
|
|
went <span class="tl-highlight-stale">STALE</span> — missed 3 consecutive heartbeats
|
|
</div>
|
|
<div class="timeline-time">3 minutes ago · 17 Mar 2026, 14:57 UTC</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="timeline-event">
|
|
<div class="timeline-icon icon-dead">✖</div>
|
|
<div class="timeline-body">
|
|
<div class="timeline-text">
|
|
<span class="tl-agent">staging-01</span>
|
|
<span class="tl-group">(inventory-check)</span>
|
|
went <span class="tl-highlight-dead">DEAD</span> — no heartbeat for 15 minutes
|
|
</div>
|
|
<div class="timeline-time">45 minutes ago · 17 Mar 2026, 14:15 UTC</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="timeline-event">
|
|
<div class="timeline-icon icon-route">↻</div>
|
|
<div class="timeline-body">
|
|
<div class="timeline-text">
|
|
<span class="tl-agent">prod-node-01</span>
|
|
<span class="tl-group">(order-processing)</span>
|
|
route <strong>order-retry</strong> restarted after error threshold reached
|
|
</div>
|
|
<div class="timeline-time">1 hour ago · 17 Mar 2026, 14:00 UTC</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="timeline-event">
|
|
<div class="timeline-icon icon-start">▶</div>
|
|
<div class="timeline-body">
|
|
<div class="timeline-text">
|
|
<span class="tl-agent">prod-node-03</span>
|
|
<span class="tl-group">(order-processing)</span>
|
|
<span class="tl-highlight-start">started</span> — registered with server, 8 routes loaded
|
|
</div>
|
|
<div class="timeline-time">2 hours 15 minutes ago · 17 Mar 2026, 12:45 UTC</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="timeline-event">
|
|
<div class="timeline-icon icon-config">⚙</div>
|
|
<div class="timeline-body">
|
|
<div class="timeline-text">
|
|
Config push to <span class="tl-agent">prod-pay-01</span>
|
|
<span class="tl-group">(payment-flow)</span>
|
|
— updated log level to DEBUG for route <strong>payment-charge</strong>
|
|
</div>
|
|
<div class="timeline-time">3 hours ago · 17 Mar 2026, 12:00 UTC</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="timeline-event">
|
|
<div class="timeline-icon icon-stale">⚠</div>
|
|
<div class="timeline-body">
|
|
<div class="timeline-text">
|
|
<span class="tl-agent">staging-01</span>
|
|
<span class="tl-group">(inventory-check)</span>
|
|
went <span class="tl-highlight-stale">STALE</span> — first missed heartbeat
|
|
</div>
|
|
<div class="timeline-time">1 hour ago · 17 Mar 2026, 14:00 UTC</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="timeline-event">
|
|
<div class="timeline-icon icon-start">▶</div>
|
|
<div class="timeline-body">
|
|
<div class="timeline-text">
|
|
<span class="tl-agent">prod-ship-01</span>
|
|
<span class="tl-group">(shipment-notify)</span>
|
|
deployed version <strong>v3.2.1</strong> — rolling update completed
|
|
</div>
|
|
<div class="timeline-time">6 hours ago · 17 Mar 2026, 09:00 UTC</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Route Distribution Tab -->
|
|
<div class="tab-panel" id="tab-routes">
|
|
<div class="route-table-wrap">
|
|
<table class="route-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Route</th>
|
|
<th>Group</th>
|
|
<th>prod-node-01</th>
|
|
<th>prod-node-02</th>
|
|
<th>prod-node-03</th>
|
|
<th>prod-pay-01</th>
|
|
<th>prod-pay-02</th>
|
|
<th>prod-ship-01</th>
|
|
<th>prod-ship-02</th>
|
|
<th>staging-01</th>
|
|
<th>Coverage</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td class="route-name">order-intake</td>
|
|
<td>order-processing</td>
|
|
<td><span class="route-check present">✓</span></td>
|
|
<td><span class="route-check present">✓</span></td>
|
|
<td><span class="route-check present">✓</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-status status-healthy">3/3</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="route-name">order-enrichment</td>
|
|
<td>order-processing</td>
|
|
<td><span class="route-check present">✓</span></td>
|
|
<td><span class="route-check present">✓</span></td>
|
|
<td><span class="route-check present">✓</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-status status-healthy">3/3</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="route-name">order-retry</td>
|
|
<td>order-processing</td>
|
|
<td><span class="route-check present">✓</span></td>
|
|
<td><span class="route-check present">✓</span></td>
|
|
<td><span class="route-check present">✓</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-status status-healthy">3/3</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="route-name">payment-charge</td>
|
|
<td>payment-flow</td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check present">✓</span></td>
|
|
<td><span class="route-check present">✓</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-status status-partial">1/2 live</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="route-name">payment-refund</td>
|
|
<td>payment-flow</td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check present">✓</span></td>
|
|
<td><span class="route-check present">✓</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-status status-partial">1/2 live</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="route-name">ship-dispatch</td>
|
|
<td>shipment-notify</td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check present">✓</span></td>
|
|
<td><span class="route-check present">✓</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-status status-healthy">2/2</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="route-name">ship-track-update</td>
|
|
<td>shipment-notify</td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check present">✓</span></td>
|
|
<td><span class="route-check present">✓</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-status status-healthy">2/2</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="route-name">inv-stock-check</td>
|
|
<td>inventory-check</td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check dead">✖</span></td>
|
|
<td><span class="route-status status-degraded">0/1 dead</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="route-name">inv-reorder</td>
|
|
<td>inventory-check</td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check absent">—</span></td>
|
|
<td><span class="route-check dead">✖</span></td>
|
|
<td><span class="route-status status-degraded">0/1 dead</span></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div><!-- /content -->
|
|
</div><!-- /main -->
|
|
</div><!-- /app -->
|
|
|
|
<script>
|
|
function switchTab(tab) {
|
|
// Update tab bar
|
|
document.querySelectorAll('.tab-item').forEach(function(el) {
|
|
el.classList.remove('active');
|
|
});
|
|
event.target.classList.add('active');
|
|
|
|
// Update tab panels
|
|
document.querySelectorAll('.tab-panel').forEach(function(el) {
|
|
el.classList.remove('active');
|
|
});
|
|
document.getElementById('tab-' + tab).classList.add('active');
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|