Files
cameleer-server/docs/ui-mocks/mock-v3-agent-health.html

1491 lines
59 KiB
HTML
Raw Normal View History

<!--
============================================================================
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.283
</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;">&#9673;</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;">&#9670;</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;">&#9711;</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;">&#9881;</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;">&#9881;</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;">&#9776;</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">&#9656;</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 &middot; <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">&#9650; 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">&#8593; 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">&#8593; 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">&#8593; 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">&#8593; 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">&#8593; 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">&#8593; 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">&#8593; 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);">&#8212;</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);">&#8212;</span>
<span class="instance-throughput" style="color: var(--text-faint);">0/min</span>
<span class="instance-errors" style="color: var(--text-faint);">&#8212;</span>
<span class="instance-heartbeat dead-hb">45m ago</span>
</div>
</div>
<div class="group-alert-banner">
<span class="alert-icon">&#9888;</span>
<span>Single point of failure &mdash; 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">&#9888;</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> &mdash; missed 3 consecutive heartbeats
</div>
<div class="timeline-time">3 minutes ago &middot; 17 Mar 2026, 14:57 UTC</div>
</div>
</div>
<div class="timeline-event">
<div class="timeline-icon icon-dead">&#10006;</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> &mdash; no heartbeat for 15 minutes
</div>
<div class="timeline-time">45 minutes ago &middot; 17 Mar 2026, 14:15 UTC</div>
</div>
</div>
<div class="timeline-event">
<div class="timeline-icon icon-route">&#8635;</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 &middot; 17 Mar 2026, 14:00 UTC</div>
</div>
</div>
<div class="timeline-event">
<div class="timeline-icon icon-start">&#9654;</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> &mdash; registered with server, 8 routes loaded
</div>
<div class="timeline-time">2 hours 15 minutes ago &middot; 17 Mar 2026, 12:45 UTC</div>
</div>
</div>
<div class="timeline-event">
<div class="timeline-icon icon-config">&#9881;</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>
&mdash; updated log level to DEBUG for route <strong>payment-charge</strong>
</div>
<div class="timeline-time">3 hours ago &middot; 17 Mar 2026, 12:00 UTC</div>
</div>
</div>
<div class="timeline-event">
<div class="timeline-icon icon-stale">&#9888;</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> &mdash; first missed heartbeat
</div>
<div class="timeline-time">1 hour ago &middot; 17 Mar 2026, 14:00 UTC</div>
</div>
</div>
<div class="timeline-event">
<div class="timeline-icon icon-start">&#9654;</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> &mdash; rolling update completed
</div>
<div class="timeline-time">6 hours ago &middot; 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">&#10003;</span></td>
<td><span class="route-check present">&#10003;</span></td>
<td><span class="route-check present">&#10003;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</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">&#10003;</span></td>
<td><span class="route-check present">&#10003;</span></td>
<td><span class="route-check present">&#10003;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</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">&#10003;</span></td>
<td><span class="route-check present">&#10003;</span></td>
<td><span class="route-check present">&#10003;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</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">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check present">&#10003;</span></td>
<td><span class="route-check present">&#10003;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</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">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check present">&#10003;</span></td>
<td><span class="route-check present">&#10003;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</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">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check present">&#10003;</span></td>
<td><span class="route-check present">&#10003;</span></td>
<td><span class="route-check absent">&mdash;</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">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check present">&#10003;</span></td>
<td><span class="route-check present">&#10003;</span></td>
<td><span class="route-check absent">&mdash;</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">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check dead">&#10006;</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">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check absent">&mdash;</span></td>
<td><span class="route-check dead">&#10006;</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>