Dashboard — Real-time monitoring
A live infrastructure monitoring dashboard built with vanilla HTML, CSS and JavaScript. Streaming inline-SVG line charts for CPU, memory and requests scroll leftward as new points arrive every second, three live half-circle gauges sweep their needles, and KPI cards tick with up or down deltas and sparklines. A dashed threshold line flashes the chart and KPI tile red on breach, raising an aria-live alert and a timestamped error in the rolling event log. A pause control plus a speed selector and a live or reconnecting connection indicator round it out.
MCP
Code
:root {
--brand: #5b5bf0;
--brand-d: #4646d6;
--brand-700: #3a3ab8;
--brand-50: #eef0ff;
--accent: #00b4a6;
--accent-soft: #d8f5f2;
--ink: #101322;
--ink-2: #3a4060;
--muted: #6c7393;
--bg: #f6f7fb;
--white: #ffffff;
--surface: #ffffff;
--line: rgba(16, 19, 34, 0.1);
--line-2: rgba(16, 19, 34, 0.16);
--ok: #2f9e6f;
--warn: #d98a2b;
--danger: #d4503e;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--sh-1: 0 1px 2px rgba(16, 19, 34, 0.08);
--sh-2: 0 8px 24px rgba(16, 19, 34, 0.08);
--sidebar-w: 232px;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
font-size: 14px;
line-height: 1.5;
color: var(--ink);
background: var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-osx-font-smoothing: grayscale;
}
h1, h2, p, ul, ol { margin: 0; }
ul, ol { list-style: none; padding: 0; }
button { font: inherit; cursor: pointer; }
:focus-visible {
outline: 2px solid var(--brand);
outline-offset: 2px;
border-radius: 4px;
}
/* ---------- Shell ---------- */
.shell {
display: grid;
grid-template-columns: var(--sidebar-w) 1fr;
min-height: 100vh;
}
/* ---------- Sidebar ---------- */
.sidebar {
background: var(--white);
border-right: 1px solid var(--line);
padding: 18px 14px;
display: flex;
flex-direction: column;
gap: 18px;
position: sticky;
top: 0;
height: 100vh;
}
.brand {
display: flex;
align-items: center;
gap: 10px;
padding: 4px 6px;
}
.brand-mark {
width: 30px;
height: 30px;
display: grid;
place-items: center;
border-radius: var(--r-sm);
background: linear-gradient(135deg, var(--brand), var(--accent));
color: #fff;
font-size: 15px;
}
.brand-name { font-weight: 800; letter-spacing: -0.01em; }
.nav-toggle {
display: none;
background: none;
border: 1px solid var(--line);
border-radius: var(--r-sm);
padding: 6px 10px;
color: var(--ink-2);
}
.nav-list { display: flex; flex-direction: column; gap: 2px; }
.nav-item {
display: flex;
align-items: center;
gap: 11px;
padding: 9px 11px;
border-radius: var(--r-sm);
color: var(--ink-2);
text-decoration: none;
font-weight: 500;
font-size: 13.5px;
transition: background 0.15s, color 0.15s;
}
.nav-item:hover { background: var(--brand-50); color: var(--ink); }
.nav-item.is-active { background: var(--brand-50); color: var(--brand-700); font-weight: 600; }
.ni-ico { font-size: 13px; width: 16px; text-align: center; opacity: 0.85; }
.nav-badge {
margin-left: auto;
min-width: 20px;
text-align: center;
background: var(--danger);
color: #fff;
font-size: 11px;
font-weight: 700;
padding: 1px 6px;
border-radius: 999px;
}
.nav-badge[data-zero="true"] { background: var(--line-2); color: var(--muted); }
.sidebar-foot { margin-top: auto; display: flex; flex-direction: column; gap: 10px; }
.conn {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 10px;
border-radius: var(--r-sm);
background: var(--bg);
font-size: 12.5px;
font-weight: 600;
}
.conn-dot {
width: 9px;
height: 9px;
border-radius: 50%;
background: var(--ok);
box-shadow: 0 0 0 4px rgba(47, 158, 111, 0.16);
}
.conn[data-state="live"] .conn-dot { animation: pulse 1.6s ease-in-out infinite; }
.conn[data-state="reconnecting"] { color: var(--warn); }
.conn[data-state="reconnecting"] .conn-dot { background: var(--warn); box-shadow: 0 0 0 4px rgba(217, 138, 43, 0.18); }
.conn[data-state="paused"] { color: var(--muted); }
.conn[data-state="paused"] .conn-dot { background: var(--muted); box-shadow: none; animation: none; }
.sidebar-region { font-size: 12px; color: var(--muted); padding-left: 2px; }
.sidebar-region strong { color: var(--ink-2); }
@keyframes pulse {
0%, 100% { box-shadow: 0 0 0 4px rgba(47, 158, 111, 0.16); }
50% { box-shadow: 0 0 0 7px rgba(47, 158, 111, 0.04); }
}
/* ---------- Main ---------- */
.main { padding: 22px 26px 40px; min-width: 0; }
.page-head {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
margin-bottom: 18px;
}
.ph-titles h1 { font-size: 22px; font-weight: 800; letter-spacing: -0.02em; }
.ph-sub { color: var(--muted); font-size: 13px; margin-top: 3px; }
.ph-controls { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.live-pill {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 6px 12px;
border-radius: 999px;
background: rgba(47, 158, 111, 0.12);
color: var(--ok);
font-weight: 700;
font-size: 12px;
letter-spacing: 0.04em;
}
.lp-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok); }
.live-pill[data-state="live"] .lp-dot { animation: pulse 1.6s ease-in-out infinite; }
.live-pill[data-state="paused"] { background: var(--bg); color: var(--muted); }
.live-pill[data-state="paused"] .lp-dot { background: var(--muted); animation: none; }
.seg {
display: inline-flex;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-sm);
padding: 3px;
box-shadow: var(--sh-1);
}
.seg-btn {
border: none;
background: none;
padding: 5px 11px;
border-radius: 6px;
color: var(--muted);
font-weight: 600;
font-size: 12.5px;
transition: background 0.15s, color 0.15s;
}
.seg-btn:hover { color: var(--ink); }
.seg-btn.is-active { background: var(--brand); color: #fff; }
.btn {
border: 1px solid var(--line);
background: var(--white);
color: var(--ink);
border-radius: var(--r-sm);
padding: 8px 14px;
font-weight: 600;
font-size: 13px;
box-shadow: var(--sh-1);
transition: background 0.15s, border-color 0.15s, transform 0.05s;
display: inline-flex;
align-items: center;
gap: 7px;
}
.btn:hover { border-color: var(--line-2); }
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--brand); border-color: var(--brand); color: #fff; }
.btn-primary:hover { background: var(--brand-d); border-color: var(--brand-d); }
.btn-primary[aria-pressed="true"] { background: var(--warn); border-color: var(--warn); }
.btn-ghost { background: transparent; box-shadow: none; border-color: transparent; color: var(--muted); }
.btn-ghost:hover { background: var(--bg); color: var(--ink); border-color: var(--line); }
.btn-sm { padding: 5px 10px; font-size: 12px; }
/* ---------- Alert bar ---------- */
.alert-bar {
display: flex;
align-items: center;
gap: 12px;
background: rgba(212, 80, 62, 0.1);
border: 1px solid rgba(212, 80, 62, 0.4);
border-radius: var(--r-md);
padding: 11px 14px;
margin-bottom: 16px;
color: var(--danger);
font-weight: 600;
animation: flash-in 0.3s ease;
}
.ab-ico { font-size: 16px; }
.ab-text { flex: 1; font-size: 13.5px; }
.ab-dismiss { background: none; border: none; color: var(--danger); font-size: 14px; padding: 2px 6px; border-radius: 6px; }
.ab-dismiss:hover { background: rgba(212, 80, 62, 0.14); }
@keyframes flash-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
/* ---------- KPI row ---------- */
.kpi-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14px;
margin-bottom: 16px;
}
.kpi {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 14px 16px;
box-shadow: var(--sh-1);
transition: border-color 0.2s, box-shadow 0.2s;
}
.kpi:hover { box-shadow: var(--sh-2); }
.kpi.is-breach {
border-color: var(--danger);
background: rgba(212, 80, 62, 0.05);
animation: tile-flash 0.9s ease-in-out infinite;
}
@keyframes tile-flash {
0%, 100% { box-shadow: 0 0 0 0 rgba(212, 80, 62, 0); }
50% { box-shadow: 0 0 0 4px rgba(212, 80, 62, 0.18); }
}
.kpi-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.kpi-label { font-size: 12.5px; font-weight: 600; color: var(--muted); }
.card-menu {
background: none;
border: none;
color: var(--muted);
font-size: 16px;
line-height: 1;
padding: 0 4px;
border-radius: 5px;
}
.card-menu:hover { color: var(--ink); background: var(--bg); }
.kpi-value { font-size: 28px; font-weight: 800; letter-spacing: -0.02em; line-height: 1.1; }
.kpi-unit { font-size: 14px; font-weight: 600; color: var(--muted); margin-left: 3px; }
.kpi-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 8px; gap: 10px; }
.delta {
display: inline-flex;
align-items: center;
gap: 3px;
font-size: 12px;
font-weight: 700;
color: var(--ok);
}
.delta .d-arrow { font-size: 9px; }
.delta[data-dir="down"] { color: var(--danger); }
.delta[data-dir="down"] .d-arrow { transform: rotate(180deg); }
.spark { width: 96px; height: 28px; flex-shrink: 0; }
.spark path { fill: none; stroke: var(--brand); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.spark .spark-area { fill: var(--brand-50); stroke: none; }
/* ---------- Grid ---------- */
.grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
align-items: start;
}
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--sh-1);
padding: 16px 18px;
transition: box-shadow 0.2s, opacity 0.2s, transform 0.15s;
}
.card-stream { grid-column: 1 / -1; }
.card.dragging { opacity: 0.5; }
.card.drag-over { box-shadow: 0 0 0 2px var(--brand); }
.card-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 14px;
}
.card-title { font-size: 14.5px; font-weight: 700; letter-spacing: -0.01em; display: flex; align-items: center; gap: 8px; }
.card-meta { font-size: 12px; color: var(--muted); margin-top: 3px; }
.drag-hint { color: var(--line-2); cursor: grab; font-size: 13px; }
.card-stream:hover .drag-hint, .card:hover .drag-hint { color: var(--muted); }
/* ---------- Stream chart ---------- */
.stream-tabs { display: inline-flex; gap: 2px; background: var(--bg); border-radius: var(--r-sm); padding: 3px; }
.st-tab {
border: none;
background: none;
padding: 5px 12px;
border-radius: 6px;
font-weight: 600;
font-size: 12.5px;
color: var(--muted);
transition: background 0.15s, color 0.15s;
}
.st-tab:hover { color: var(--ink); }
.st-tab.is-active { background: var(--white); color: var(--brand-700); box-shadow: var(--sh-1); }
.chart-wrap { position: relative; }
.stream-chart {
width: 100%;
height: 220px;
display: block;
background: linear-gradient(180deg, rgba(91, 91, 240, 0.02), transparent);
border-radius: var(--r-md);
}
.grid-lines line { stroke: var(--line); stroke-width: 1; vector-effect: non-scaling-stroke; }
.stream-path { fill: none; stroke: var(--brand); stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.stream-fill { fill: url(#fillGrad); stroke: none; }
.stream-head { fill: var(--brand); stroke: #fff; stroke-width: 2; }
.threshold-line { stroke: var(--danger); stroke-width: 1.5; stroke-dasharray: 5 4; vector-effect: non-scaling-stroke; opacity: 0.65; }
.stream-chart.is-breach .stream-path { stroke: var(--danger); }
.stream-chart.is-breach .threshold-line { opacity: 1; animation: thresh-flash 0.7s ease-in-out infinite; }
@keyframes thresh-flash { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
.chart-axis {
display: flex;
justify-content: space-between;
margin-top: 4px;
font-size: 11px;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
.chart-readout {
margin-top: 10px;
font-size: 12.5px;
color: var(--muted);
font-weight: 500;
display: flex;
align-items: baseline;
gap: 5px;
flex-wrap: wrap;
font-variant-numeric: tabular-nums;
}
.cr-now { font-size: 18px; font-weight: 800; color: var(--ink); }
.cr-unit { font-weight: 600; color: var(--ink-2); }
.cr-sep { color: var(--line-2); }
/* ---------- Gauges ---------- */
.gauges { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.gauge { text-align: center; }
.gauge-svg { width: 100%; max-width: 130px; height: auto; }
.g-track { fill: none; stroke: var(--bg); stroke-width: 10; stroke-linecap: round; }
.g-fill {
fill: none;
stroke: var(--accent);
stroke-width: 10;
stroke-linecap: round;
stroke-dasharray: 144.5;
stroke-dashoffset: 144.5;
transition: stroke-dashoffset 0.4s ease, stroke 0.3s;
}
.gauge[data-level="warn"] .g-fill { stroke: var(--warn); }
.gauge[data-level="crit"] .g-fill { stroke: var(--danger); }
.g-needle line { stroke: var(--ink-2); stroke-width: 2.5; stroke-linecap: round; transform-origin: 60px 70px; transition: transform 0.4s ease; }
.g-needle circle { fill: var(--ink); }
.gauge-cap { margin-top: -10px; font-weight: 800; font-size: 19px; letter-spacing: -0.01em; }
.g-unit { font-size: 12px; color: var(--muted); font-weight: 600; margin-left: 1px; }
.gauge-name { display: block; font-size: 11.5px; color: var(--muted); font-weight: 600; margin-top: 2px; }
/* ---------- Node grid ---------- */
.node-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 8px; }
.node {
border: 1px solid var(--line);
border-radius: var(--r-sm);
padding: 9px 10px;
background: var(--white);
transition: border-color 0.2s, background 0.2s;
}
.node-top { display: flex; align-items: center; gap: 7px; margin-bottom: 6px; }
.node-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok); flex-shrink: 0; }
.node-name { font-weight: 600; font-size: 12.5px; font-variant-numeric: tabular-nums; }
.node-load {
height: 5px;
border-radius: 999px;
background: var(--bg);
overflow: hidden;
}
.node-load i { display: block; height: 100%; background: var(--accent); border-radius: 999px; transition: width 0.4s ease, background 0.3s; }
.node-pct { font-size: 11px; color: var(--muted); font-weight: 600; margin-top: 4px; font-variant-numeric: tabular-nums; }
.node[data-state="warn"] { border-color: rgba(217, 138, 43, 0.5); background: rgba(217, 138, 43, 0.05); }
.node[data-state="warn"] .node-dot { background: var(--warn); }
.node[data-state="warn"] .node-load i { background: var(--warn); }
.node[data-state="down"] { border-color: rgba(212, 80, 62, 0.5); background: rgba(212, 80, 62, 0.06); animation: tile-flash 0.9s ease-in-out infinite; }
.node[data-state="down"] .node-dot { background: var(--danger); }
.node[data-state="down"] .node-load i { background: var(--danger); }
/* ---------- Event log ---------- */
.log { display: flex; flex-direction: column; gap: 1px; max-height: 264px; overflow-y: auto; }
.log-item {
display: grid;
grid-template-columns: 64px 56px 1fr;
gap: 10px;
align-items: baseline;
padding: 7px 8px;
border-radius: 6px;
font-size: 12.5px;
animation: log-in 0.25s ease;
}
.log-item:hover { background: var(--bg); }
@keyframes log-in { from { opacity: 0; transform: translateX(-6px); } to { opacity: 1; transform: none; } }
.log-time { color: var(--muted); font-variant-numeric: tabular-nums; font-size: 11.5px; }
.log-level {
font-size: 10.5px;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
padding: 1px 6px;
border-radius: 999px;
text-align: center;
}
.log-level[data-lvl="info"] { background: var(--brand-50); color: var(--brand-700); }
.log-level[data-lvl="ok"] { background: rgba(47, 158, 111, 0.14); color: var(--ok); }
.log-level[data-lvl="warn"] { background: rgba(217, 138, 43, 0.16); color: var(--warn); }
.log-level[data-lvl="error"] { background: rgba(212, 80, 62, 0.14); color: var(--danger); }
.log-msg { color: var(--ink-2); }
.log-msg strong { color: var(--ink); font-weight: 600; }
.log-empty { color: var(--muted); padding: 14px 8px; font-size: 12.5px; }
/* ---------- Toast ---------- */
.toast-host {
position: fixed;
right: 18px;
bottom: 18px;
display: flex;
flex-direction: column;
gap: 8px;
z-index: 50;
}
.toast {
background: var(--ink);
color: #fff;
padding: 10px 14px;
border-radius: var(--r-sm);
font-size: 13px;
font-weight: 500;
box-shadow: var(--sh-2);
display: flex;
align-items: center;
gap: 9px;
animation: toast-in 0.25s ease;
max-width: 320px;
}
.toast[data-kind="error"] { background: var(--danger); }
.toast[data-kind="ok"] { background: var(--ok); }
.toast.out { animation: toast-out 0.25s ease forwards; }
@keyframes toast-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes toast-out { to { opacity: 0; transform: translateY(10px); } }
/* ---------- Responsive ---------- */
@media (max-width: 980px) {
.kpi-row { grid-template-columns: repeat(2, 1fr); }
.grid { grid-template-columns: 1fr; }
.card-stream { grid-column: auto; }
}
@media (max-width: 720px) {
.shell { grid-template-columns: 1fr; }
.sidebar {
position: sticky;
top: 0;
height: auto;
z-index: 30;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
gap: 10px;
padding: 12px 16px;
}
.brand { flex: 1; }
.nav-toggle { display: block; order: 2; }
.nav-list {
order: 3;
flex-basis: 100%;
display: none;
}
.nav-list.is-open { display: flex; }
.sidebar-foot { order: 4; flex-basis: 100%; flex-direction: row; align-items: center; gap: 14px; margin-top: 0; }
.sidebar-region { margin-left: auto; }
.main { padding: 16px 16px 32px; }
.page-head { align-items: flex-start; }
.gauges { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 480px) {
.kpi-row { grid-template-columns: 1fr 1fr; }
.kpi-value { font-size: 24px; }
.ph-controls { width: 100%; }
.seg { flex: 1; }
.seg-btn { flex: 1; }
.pauseBtn, #pauseBtn { flex: 1; justify-content: center; }
}
@media (max-width: 380px) {
.kpi-row { grid-template-columns: 1fr; }
.gauges { grid-template-columns: 1fr; }
.log-item { grid-template-columns: 52px 50px 1fr; gap: 7px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
}
}(function () {
"use strict";
/* ---------- Config ---------- */
var WINDOW = 60; // points held in the rolling window
var VW = 600, VH = 220, PAD = 6;
var METRICS = {
cpu: { label: "CPU", unit: "%", base: 42, min: 0, max: 100, thresh: 85, vola: 6, kpi: true },
ram: { label: "Memory", unit: "%", base: 61, min: 0, max: 100, thresh: 90, vola: 3.5, kpi: true },
req: { label: "Requests", unit: " rps", base: 1180, min: 0, max: 2600, thresh: 2200, vola: 140, kpi: true },
lat: { label: "p95 latency", unit: " ms", base: 138, min: 20, max: 600, thresh: 320, vola: 22, kpi: true },
disk: { label: "Disk I/O", unit: "%", base: 34, min: 0, max: 100, thresh: 95, vola: 8 }
};
// Seed each metric series with WINDOW points
var series = {};
Object.keys(METRICS).forEach(function (k) {
var m = METRICS[k];
var arr = [];
var v = m.base;
for (var i = 0; i < WINDOW; i++) {
v = step(v, m);
arr.push(v);
}
series[k] = arr;
});
function step(v, m) {
var nv = v + (Math.random() - 0.5) * m.vola * 2;
// gentle pull toward base so it doesn't drift forever
nv += (m.base - nv) * 0.05;
if (nv < m.min) nv = m.min;
if (nv > m.max) nv = m.max;
return Math.round(nv * 10) / 10;
}
/* ---------- DOM refs ---------- */
var $ = function (s, r) { return (r || document).querySelector(s); };
var $$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); };
var streamPath = $("#streamPath");
var streamFill = $("#streamFill");
var streamHead = $("#streamHead");
var streamChart = $("#streamChart");
var thresholdLine = $("#thresholdLine");
var gridLines = $("#gridLines");
var chartAxis = $("#chartAxis");
var state = {
speed: 1000,
paused: false,
activeStream: "cpu",
alerted: {},
breachKpis: {}
};
/* ---------- Grid lines + axis (static) ---------- */
(function buildGrid() {
var rows = 4, frag = "";
for (var i = 1; i < rows; i++) {
var y = (VH / rows) * i;
frag += '<line x1="0" y1="' + y + '" x2="' + VW + '" y2="' + y + '" />';
}
gridLines.innerHTML = frag;
chartAxis.innerHTML = "<span>-60s</span><span>-40s</span><span>-20s</span><span>now</span>";
})();
/* ---------- Stream chart render ---------- */
function renderStream() {
var key = state.activeStream;
var m = METRICS[key];
var data = series[key];
var lo = m.min, hi = m.max;
// dynamic top padding above max value for nicer framing
var dataMax = Math.max.apply(null, data);
if (dataMax * 1.12 > hi) hi = m.max; else hi = Math.max(m.thresh * 1.1, dataMax * 1.15);
var span = hi - lo || 1;
var n = data.length;
var stepX = (VW - PAD * 2) / (n - 1);
function x(i) { return PAD + i * stepX; }
function y(val) { return PAD + (1 - (val - lo) / span) * (VH - PAD * 2); }
var d = "", i;
for (i = 0; i < n; i++) {
d += (i === 0 ? "M" : "L") + x(i).toFixed(1) + " " + y(data[i]).toFixed(1) + " ";
}
streamPath.setAttribute("d", d.trim());
streamFill.setAttribute("d", d.trim() + "L" + x(n - 1).toFixed(1) + " " + (VH - PAD) + " L" + x(0).toFixed(1) + " " + (VH - PAD) + " Z");
var last = data[n - 1];
streamHead.setAttribute("cx", x(n - 1).toFixed(1));
streamHead.setAttribute("cy", y(last).toFixed(1));
var ty = y(m.thresh);
thresholdLine.setAttribute("y1", ty.toFixed(1));
thresholdLine.setAttribute("y2", ty.toFixed(1));
var breached = last > m.thresh;
streamChart.classList.toggle("is-breach", breached);
// readout
$("#streamNow").textContent = fmt(last, key);
$("#streamUnit").textContent = m.unit.trim();
$("#streamPeak").textContent = fmt(dataMax, key);
$("#streamPeakUnit").textContent = m.unit.trim();
$("#streamThresh").textContent = fmt(m.thresh, key);
$("#streamThreshUnit").textContent = m.unit.trim();
}
function fmt(v, key) {
if (key === "req") return Math.round(v).toLocaleString();
if (key === "lat") return Math.round(v);
return Math.round(v);
}
/* ---------- KPI cards ---------- */
function buildSpark(el, data) {
var n = data.length, w = 96, h = 28, p = 2;
var lo = Math.min.apply(null, data), hi = Math.max.apply(null, data);
var span = hi - lo || 1;
var sx = (w - p * 2) / (n - 1);
function x(i) { return p + i * sx; }
function y(v) { return p + (1 - (v - lo) / span) * (h - p * 2); }
var d = "", a = "M" + x(0).toFixed(1) + " " + (h - p);
for (var i = 0; i < n; i++) {
d += (i === 0 ? "M" : "L") + x(i).toFixed(1) + " " + y(data[i]).toFixed(1) + " ";
a += "L" + x(i).toFixed(1) + " " + y(data[i]).toFixed(1) + " ";
}
a += "L" + x(n - 1).toFixed(1) + " " + (h - p) + " Z";
el.innerHTML = '<path class="spark-area" d="' + a + '"/><path d="' + d.trim() + '"/>';
}
function renderKpis() {
$$(".kpi").forEach(function (card) {
var key = card.getAttribute("data-metric");
var m = METRICS[key];
var data = series[key];
var last = data[data.length - 1];
var prev = data[data.length - 6] || data[0];
var valEl = $("[data-kpi-value]", card);
valEl.textContent = fmt(last, key);
var pct = prev ? ((last - prev) / prev) * 100 : 0;
var deltaEl = $("[data-kpi-delta]", card);
deltaEl.setAttribute("data-dir", pct >= 0 ? "up" : "down");
$("[data-kpi-delta-num]", card).textContent = Math.abs(pct).toFixed(1);
buildSpark($("[data-kpi-spark]", card), data.slice(-24));
var breach = last > m.thresh;
card.classList.toggle("is-breach", breach);
if (breach && !state.breachKpis[key]) {
state.breachKpis[key] = true;
onBreach(key, last);
} else if (!breach) {
state.breachKpis[key] = false;
}
});
updateAlertBar();
}
/* ---------- Threshold / alert bar ---------- */
function onBreach(key, val) {
var m = METRICS[key];
pushLog("error", "<strong>" + m.label + "</strong> crossed threshold at " + fmt(val, key) + m.unit.trim());
toast(m.label + " threshold exceeded", "error");
}
function updateAlertBar() {
var bar = $("#alertBar");
var breached = Object.keys(state.breachKpis).filter(function (k) { return state.breachKpis[k]; });
if (breached.length) {
bar.hidden = false;
var names = breached.map(function (k) { return METRICS[k].label; }).join(", ");
$("#alertBarText").textContent = breached.length === 1
? names + " is above its threshold — investigate now."
: breached.length + " metrics above threshold: " + names + ".";
} else {
bar.hidden = true;
}
}
$("#alertDismiss").addEventListener("click", function () { $("#alertBar").hidden = true; });
/* ---------- Gauges ---------- */
var GAUGE_LEN = 144.5; // arc length of the half-circle path
function renderGauges() {
$$(".gauge").forEach(function (g) {
var key = g.getAttribute("data-gauge");
var m = METRICS[key];
var data = series[key];
var val = data[data.length - 1];
var frac = (val - m.min) / (m.max - m.min);
frac = Math.max(0, Math.min(1, frac));
var fill = $(".g-fill", g);
fill.style.strokeDashoffset = (GAUGE_LEN * (1 - frac)).toFixed(1);
var needle = $(".g-needle line", g);
needle.style.transform = "rotate(" + (-90 + frac * 180) + "deg)";
$(".g-val", g).textContent = Math.round(val);
var level = frac > 0.88 ? "crit" : frac > 0.7 ? "warn" : "ok";
g.setAttribute("data-level", level);
});
}
/* ---------- Nodes ---------- */
var nodes = [];
(function seedNodes() {
var zones = ["eu-w1", "eu-w2", "us-e1", "us-e2", "us-w1", "ap-s1", "ap-n1", "sa-e1"];
zones.forEach(function (z, i) {
nodes.push({ id: "node-" + z, name: z, load: 30 + Math.round(Math.random() * 35), state: "ok" });
});
})();
function renderNodes(rebuild) {
var grid = $("#nodeGrid");
var up = 0, warn = 0, down = 0;
if (rebuild) {
grid.innerHTML = nodes.map(function (n) {
return '<li class="node" data-id="' + n.id + '" data-state="' + n.state + '">' +
'<div class="node-top"><span class="node-dot"></span><span class="node-name">' + n.name + '</span></div>' +
'<div class="node-load"><i style="width:' + n.load + '%"></i></div>' +
'<div class="node-pct">' + n.load + '% load</div></li>';
}).join("");
}
nodes.forEach(function (n) {
if (n.state === "ok") up++; else if (n.state === "warn") warn++; else down++;
if (!rebuild) {
var li = grid.querySelector('[data-id="' + n.id + '"]');
if (li) {
li.setAttribute("data-state", n.state);
li.querySelector(".node-load i").style.width = n.load + "%";
li.querySelector(".node-pct").textContent = n.load + "% load";
}
}
});
$("#nodesUp").textContent = up;
$("#nodesWarn").textContent = warn;
$("#nodesDown").textContent = down;
}
function jitterNodes() {
nodes.forEach(function (n) {
n.load += (Math.random() - 0.5) * 10;
n.load = Math.max(5, Math.min(99, Math.round(n.load)));
var roll = Math.random();
var prev = n.state;
if (n.load > 90 || roll < 0.012) n.state = "down";
else if (n.load > 78 || roll < 0.05) n.state = "warn";
else n.state = "ok";
if (prev !== n.state) {
if (n.state === "down") { pushLog("error", "Node <strong>" + n.name + "</strong> went critical"); toast(n.name + " critical", "error"); }
else if (n.state === "warn" && prev === "ok") pushLog("warn", "Node <strong>" + n.name + "</strong> degraded (" + n.load + "%)");
else if (n.state === "ok" && prev !== "ok") { pushLog("ok", "Node <strong>" + n.name + "</strong> recovered"); }
}
});
renderNodes(false);
}
/* ---------- Event log ---------- */
var logTick = 0;
var INFO_MSGS = [
"Healthcheck batch completed",
"Autoscaler held at current capacity",
"Cache hit ratio 94.2%",
"TLS session resumed for edge-3",
"Config sync ok across fleet",
"Snapshot persisted to cold storage"
];
function pushLog(level, html) {
var ol = $("#logStream");
var empty = ol.querySelector(".log-empty");
if (empty) empty.remove();
var li = document.createElement("li");
li.className = "log-item";
li.innerHTML = '<span class="log-time">' + nowTime() + '</span>' +
'<span class="log-level" data-lvl="' + level + '">' + level + '</span>' +
'<span class="log-msg">' + html + '</span>';
ol.insertBefore(li, ol.firstChild);
while (ol.children.length > 40) ol.removeChild(ol.lastChild);
if (level === "error" || level === "warn") {
var b = $("#navAlertBadge");
var c = parseInt(b.textContent, 10) || 0;
b.textContent = c + 1;
b.setAttribute("data-zero", "false");
}
}
function nowTime() {
var d = new Date();
return String(d.getHours()).padStart(2, "0") + ":" +
String(d.getMinutes()).padStart(2, "0") + ":" +
String(d.getSeconds()).padStart(2, "0");
}
$("#clearLog").addEventListener("click", function () {
$("#logStream").innerHTML = '<li class="log-empty">No events. Waiting for stream…</li>';
$("#navAlertBadge").textContent = "0";
$("#navAlertBadge").setAttribute("data-zero", "true");
toast("Event log cleared");
});
/* ---------- Toast ---------- */
var toastTimers = [];
function toast(msg, kind) {
var host = $("#toastHost");
var el = document.createElement("div");
el.className = "toast";
if (kind) el.setAttribute("data-kind", kind);
el.textContent = msg;
host.appendChild(el);
var t = setTimeout(function () {
el.classList.add("out");
setTimeout(function () { el.remove(); }, 250);
}, 2600);
toastTimers.push(t);
}
/* ---------- Tick loop ---------- */
var timer = null;
function advance() {
Object.keys(METRICS).forEach(function (k) {
var m = METRICS[k];
var arr = series[k];
var next = step(arr[arr.length - 1], m);
// occasionally inject a spike toward/over threshold to demo the alert
if (Math.random() < 0.025) next = Math.min(m.max, m.thresh + Math.random() * (m.max - m.thresh) * 0.6 + 2);
arr.push(next);
if (arr.length > WINDOW) arr.shift();
});
renderStream();
renderKpis();
renderGauges();
if (Math.random() < 0.4) jitterNodes();
logTick++;
if (logTick % 4 === 0) {
pushLog("info", INFO_MSGS[Math.floor(Math.random() * INFO_MSGS.length)]);
}
}
function startLoop() {
stopLoop();
if (state.paused) return;
timer = setInterval(advance, state.speed);
}
function stopLoop() { if (timer) { clearInterval(timer); timer = null; } }
/* ---------- Pause / Resume ---------- */
var pauseBtn = $("#pauseBtn");
pauseBtn.addEventListener("click", function () {
state.paused = !state.paused;
pauseBtn.setAttribute("aria-pressed", String(state.paused));
$("#pauseIcon").textContent = state.paused ? "▶" : "❚❚";
$("#pauseLabel").textContent = state.paused ? "Resume" : "Pause";
setConn(state.paused ? "paused" : "live", state.paused ? "Stream paused" : "Connected");
var pill = $("#livePill");
pill.setAttribute("data-state", state.paused ? "paused" : "live");
$("#livePillText").textContent = state.paused ? "PAUSED" : "LIVE";
if (state.paused) { stopLoop(); toast("Stream paused"); }
else { startLoop(); toast("Stream resumed", "ok"); }
});
/* ---------- Speed control ---------- */
$$(".seg-btn").forEach(function (btn) {
btn.addEventListener("click", function () {
$$(".seg-btn").forEach(function (b) { b.classList.remove("is-active"); b.removeAttribute("aria-pressed"); });
btn.classList.add("is-active");
btn.setAttribute("aria-pressed", "true");
state.speed = parseInt(btn.getAttribute("data-speed"), 10);
$("#intervalLabel").textContent = (state.speed / 1000).toFixed(1) + "s";
if (!state.paused) startLoop();
toast("Speed set to " + btn.textContent.trim());
});
});
/* ---------- Stream tabs ---------- */
$$(".st-tab").forEach(function (tab) {
tab.addEventListener("click", function () {
$$(".st-tab").forEach(function (t) { t.classList.remove("is-active"); t.setAttribute("aria-selected", "false"); });
tab.classList.add("is-active");
tab.setAttribute("aria-selected", "true");
state.activeStream = tab.getAttribute("data-stream");
renderStream();
});
});
/* ---------- Connection indicator (simulated drops) ---------- */
function setConn(stateName, label) {
var el = $("#connStatus");
el.setAttribute("data-state", stateName);
$("#connLabel").textContent = label;
}
setInterval(function () {
if (state.paused) return;
if (Math.random() < 0.06) {
setConn("reconnecting", "Reconnecting…");
pushLog("warn", "Telemetry link unstable — reconnecting");
setTimeout(function () {
if (!state.paused) { setConn("live", "Connected"); pushLog("ok", "Telemetry link restored"); }
}, 1800);
}
}, 7000);
/* ---------- Drag to rearrange ---------- */
var dragged = null;
$$("[data-card]").forEach(function (card) {
card.addEventListener("dragstart", function (e) {
dragged = card;
card.classList.add("dragging");
e.dataTransfer.effectAllowed = "move";
});
card.addEventListener("dragend", function () {
card.classList.remove("dragging");
$$(".card").forEach(function (c) { c.classList.remove("drag-over"); });
dragged = null;
});
card.addEventListener("dragover", function (e) {
e.preventDefault();
if (dragged && dragged !== card) card.classList.add("drag-over");
});
card.addEventListener("dragleave", function () { card.classList.remove("drag-over"); });
card.addEventListener("drop", function (e) {
e.preventDefault();
card.classList.remove("drag-over");
if (!dragged || dragged === card) return;
var grid = $("#grid");
var cards = $$("[data-card]", grid);
var di = cards.indexOf(dragged), ti = cards.indexOf(card);
if (di < ti) grid.insertBefore(dragged, card.nextSibling);
else grid.insertBefore(dragged, card);
toast("Layout rearranged");
});
});
/* ---------- Mobile nav toggle ---------- */
var navToggle = $("#navToggle");
navToggle.addEventListener("click", function () {
var open = $("#navList").classList.toggle("is-open");
navToggle.setAttribute("aria-expanded", String(open));
});
$$(".nav-item").forEach(function (a) {
a.addEventListener("click", function (e) {
e.preventDefault();
$$(".nav-item").forEach(function (n) { n.classList.remove("is-active"); n.removeAttribute("aria-current"); });
a.classList.add("is-active");
a.setAttribute("aria-current", "page");
});
});
/* ---------- Init ---------- */
$("#logStream").innerHTML = '<li class="log-empty">No events. Waiting for stream…</li>';
$("#navAlertBadge").setAttribute("data-zero", "true");
renderStream();
renderKpis();
renderGauges();
renderNodes(true);
pushLog("info", "Monitoring session started — eu-west-1");
startLoop();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Pulsedeck — Real-time monitoring</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="shell">
<!-- Sidebar -->
<nav class="sidebar" aria-label="Primary">
<div class="brand">
<span class="brand-mark" aria-hidden="true">◈</span>
<span class="brand-name">Pulsedeck</span>
</div>
<button class="nav-toggle" id="navToggle" aria-expanded="false" aria-controls="navList" aria-label="Toggle navigation">☰</button>
<ul class="nav-list" id="navList">
<li><a href="#" class="nav-item is-active" aria-current="page"><span class="ni-ico" aria-hidden="true">◉</span> Live monitor</a></li>
<li><a href="#" class="nav-item"><span class="ni-ico" aria-hidden="true">▤</span> Services</a></li>
<li><a href="#" class="nav-item"><span class="ni-ico" aria-hidden="true">◴</span> Latency</a></li>
<li><a href="#" class="nav-item"><span class="ni-ico" aria-hidden="true">⚑</span> Alerts <span class="nav-badge" id="navAlertBadge">0</span></a></li>
<li><a href="#" class="nav-item"><span class="ni-ico" aria-hidden="true">▦</span> Logs</a></li>
<li><a href="#" class="nav-item"><span class="ni-ico" aria-hidden="true">⚙</span> Settings</a></li>
</ul>
<div class="sidebar-foot">
<div class="conn" id="connStatus" data-state="live" role="status" aria-live="polite">
<span class="conn-dot" aria-hidden="true"></span>
<span class="conn-label" id="connLabel">Connected</span>
</div>
<p class="sidebar-region">Region <strong>eu-west-1</strong></p>
</div>
</nav>
<!-- Main -->
<main class="main" role="main">
<header class="page-head">
<div class="ph-titles">
<h1>Real-time monitoring</h1>
<p class="ph-sub">Helios Grid · edge fleet telemetry · streaming every <span id="intervalLabel">1.0s</span></p>
</div>
<div class="ph-controls">
<span class="live-pill" id="livePill" data-state="live" aria-live="polite">
<span class="lp-dot" aria-hidden="true"></span><span id="livePillText">LIVE</span>
</span>
<div class="seg" role="group" aria-label="Stream speed">
<button type="button" class="seg-btn" data-speed="2000">0.5×</button>
<button type="button" class="seg-btn is-active" data-speed="1000" aria-pressed="true">1×</button>
<button type="button" class="seg-btn" data-speed="500">2×</button>
<button type="button" class="seg-btn" data-speed="250">4×</button>
</div>
<button type="button" class="btn btn-primary" id="pauseBtn" aria-pressed="false">
<span id="pauseIcon" aria-hidden="true">❚❚</span> <span id="pauseLabel">Pause</span>
</button>
</div>
</header>
<!-- Alert banner (threshold breach) -->
<div class="alert-bar" id="alertBar" role="alert" aria-live="assertive" hidden>
<span class="ab-ico" aria-hidden="true">⚠</span>
<span class="ab-text" id="alertBarText">Threshold exceeded.</span>
<button type="button" class="ab-dismiss" id="alertDismiss" aria-label="Dismiss alert">✕</button>
</div>
<!-- KPI row -->
<section class="kpi-row" aria-label="Key metrics">
<article class="kpi" data-metric="cpu">
<header class="kpi-head">
<span class="kpi-label">CPU load</span>
<button type="button" class="card-menu" aria-label="CPU options">⋯</button>
</header>
<div class="kpi-value"><span data-kpi-value>0</span><span class="kpi-unit">%</span></div>
<div class="kpi-foot">
<span class="delta" data-kpi-delta><span class="d-arrow" aria-hidden="true">▲</span><span data-kpi-delta-num>0.0</span>%</span>
<svg class="spark" viewBox="0 0 96 28" preserveAspectRatio="none" data-kpi-spark aria-hidden="true"></svg>
</div>
</article>
<article class="kpi" data-metric="ram">
<header class="kpi-head">
<span class="kpi-label">Memory</span>
<button type="button" class="card-menu" aria-label="Memory options">⋯</button>
</header>
<div class="kpi-value"><span data-kpi-value>0</span><span class="kpi-unit">%</span></div>
<div class="kpi-foot">
<span class="delta" data-kpi-delta><span class="d-arrow" aria-hidden="true">▲</span><span data-kpi-delta-num>0.0</span>%</span>
<svg class="spark" viewBox="0 0 96 28" preserveAspectRatio="none" data-kpi-spark aria-hidden="true"></svg>
</div>
</article>
<article class="kpi" data-metric="req">
<header class="kpi-head">
<span class="kpi-label">Requests / sec</span>
<button type="button" class="card-menu" aria-label="Requests options">⋯</button>
</header>
<div class="kpi-value"><span data-kpi-value>0</span><span class="kpi-unit">rps</span></div>
<div class="kpi-foot">
<span class="delta" data-kpi-delta><span class="d-arrow" aria-hidden="true">▲</span><span data-kpi-delta-num>0.0</span>%</span>
<svg class="spark" viewBox="0 0 96 28" preserveAspectRatio="none" data-kpi-spark aria-hidden="true"></svg>
</div>
</article>
<article class="kpi" data-metric="lat">
<header class="kpi-head">
<span class="kpi-label">p95 latency</span>
<button type="button" class="card-menu" aria-label="Latency options">⋯</button>
</header>
<div class="kpi-value"><span data-kpi-value>0</span><span class="kpi-unit">ms</span></div>
<div class="kpi-foot">
<span class="delta" data-kpi-delta><span class="d-arrow" aria-hidden="true">▲</span><span data-kpi-delta-num>0.0</span>%</span>
<svg class="spark" viewBox="0 0 96 28" preserveAspectRatio="none" data-kpi-spark aria-hidden="true"></svg>
</div>
</article>
</section>
<!-- Widget grid -->
<section class="grid" id="grid" aria-label="Monitoring widgets">
<!-- Streaming charts -->
<article class="card card-stream" draggable="true" data-card="stream">
<header class="card-head">
<div class="ch-titles">
<h2 class="card-title">Live streams <span class="drag-hint" aria-hidden="true">⠿</span></h2>
<p class="card-meta">60-second rolling window · threshold lines dashed</p>
</div>
<div class="stream-tabs" role="tablist" aria-label="Stream metric">
<button type="button" class="st-tab is-active" role="tab" aria-selected="true" data-stream="cpu">CPU</button>
<button type="button" class="st-tab" role="tab" aria-selected="false" data-stream="ram">RAM</button>
<button type="button" class="st-tab" role="tab" aria-selected="false" data-stream="req">Requests</button>
</div>
</header>
<div class="chart-wrap">
<svg class="stream-chart" id="streamChart" viewBox="0 0 600 220" preserveAspectRatio="none" role="img" aria-label="Streaming metric over time">
<defs>
<linearGradient id="fillGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="var(--brand)" stop-opacity="0.28" />
<stop offset="100%" stop-color="var(--brand)" stop-opacity="0" />
</linearGradient>
</defs>
<g class="grid-lines" id="gridLines"></g>
<line class="threshold-line" id="thresholdLine" x1="0" x2="600" y1="0" y2="0" />
<path class="stream-fill" id="streamFill" d="" />
<path class="stream-path" id="streamPath" d="" />
<circle class="stream-head" id="streamHead" r="4" cx="0" cy="0" />
</svg>
<div class="chart-axis" id="chartAxis" aria-hidden="true"></div>
</div>
<p class="chart-readout" aria-live="off">
<span class="cr-now" id="streamNow">0</span>
<span class="cr-unit" id="streamUnit">%</span>
<span class="cr-sep">·</span>
peak <span id="streamPeak">0</span><span id="streamPeakUnit">%</span>
<span class="cr-sep">·</span>
threshold <span id="streamThresh">0</span><span id="streamThreshUnit">%</span>
</p>
</article>
<!-- Gauges -->
<article class="card card-gauges" draggable="true" data-card="gauges">
<header class="card-head">
<h2 class="card-title">Live gauges <span class="drag-hint" aria-hidden="true">⠿</span></h2>
<button type="button" class="card-menu" aria-label="Gauge options">⋯</button>
</header>
<div class="gauges">
<div class="gauge" data-gauge="cpu">
<svg viewBox="0 0 120 76" class="gauge-svg" role="img" aria-label="CPU gauge">
<path class="g-track" d="M14 70 A46 46 0 0 1 106 70" />
<path class="g-fill" d="M14 70 A46 46 0 0 1 106 70" />
<g class="g-needle"><line x1="60" y1="70" x2="60" y2="30" /><circle cx="60" cy="70" r="4" /></g>
</svg>
<div class="gauge-cap"><span class="g-val">0</span><span class="g-unit">%</span></div>
<span class="gauge-name">CPU</span>
</div>
<div class="gauge" data-gauge="ram">
<svg viewBox="0 0 120 76" class="gauge-svg" role="img" aria-label="Memory gauge">
<path class="g-track" d="M14 70 A46 46 0 0 1 106 70" />
<path class="g-fill" d="M14 70 A46 46 0 0 1 106 70" />
<g class="g-needle"><line x1="60" y1="70" x2="60" y2="30" /><circle cx="60" cy="70" r="4" /></g>
</svg>
<div class="gauge-cap"><span class="g-val">0</span><span class="g-unit">%</span></div>
<span class="gauge-name">Memory</span>
</div>
<div class="gauge" data-gauge="disk">
<svg viewBox="0 0 120 76" class="gauge-svg" role="img" aria-label="Disk I/O gauge">
<path class="g-track" d="M14 70 A46 46 0 0 1 106 70" />
<path class="g-fill" d="M14 70 A46 46 0 0 1 106 70" />
<g class="g-needle"><line x1="60" y1="70" x2="60" y2="30" /><circle cx="60" cy="70" r="4" /></g>
</svg>
<div class="gauge-cap"><span class="g-val">0</span><span class="g-unit">%</span></div>
<span class="gauge-name">Disk I/O</span>
</div>
</div>
</article>
<!-- Node tiles -->
<article class="card card-nodes" draggable="true" data-card="nodes">
<header class="card-head">
<div class="ch-titles">
<h2 class="card-title">Node health <span class="drag-hint" aria-hidden="true">⠿</span></h2>
<p class="card-meta"><span id="nodesUp">0</span> healthy · <span id="nodesWarn">0</span> warning · <span id="nodesDown">0</span> critical</p>
</div>
</header>
<ul class="node-grid" id="nodeGrid" aria-label="Fleet nodes"></ul>
</article>
<!-- Event log -->
<article class="card card-log" draggable="true" data-card="log">
<header class="card-head">
<h2 class="card-title">Event stream <span class="drag-hint" aria-hidden="true">⠿</span></h2>
<button type="button" class="btn btn-ghost btn-sm" id="clearLog">Clear</button>
</header>
<ol class="log" id="logStream" aria-live="polite" aria-label="Live event log"></ol>
</article>
</section>
</main>
</div>
<div class="toast-host" id="toastHost" aria-live="polite" aria-atomic="true"></div>
<script src="script.js"></script>
</body>
</html>Real-time monitoring
A live monitoring console for the fictional Helios Grid edge fleet. Four KPI cards (CPU load, memory, requests per second, p95 latency) each pair a ticking value with an up or down delta colored against --ok / --danger and a tiny inline-SVG sparkline. Below them, a wide streaming chart redraws a 60-second rolling window every tick, scrolling old points off the left edge while a leading dot tracks the latest reading. Three half-circle gauges sweep their needles for CPU, memory and disk I/O, a node-health grid shows per-zone load bars, and an aria-live event stream logs every change. Everything is rendered with inline SVG and pure CSS — no chart libraries, no canvas, no images.
The dashboard is genuinely live. A setInterval loop pushes fresh data points into each series and re-renders the charts, gauges and KPIs smoothly. Each metric carries a dashed threshold line; when a value crosses it the chart path and the matching KPI tile flash red, a banner with role="alert" announces the breach, and a timestamped error lands in the log. The connection indicator occasionally drops to reconnecting and recovers, the nav alert badge counts unread incidents, and a toast confirms each notable event.
Every control works without a framework: a segmented speed selector rescales the stream from 0.5× to 4×, CPU / RAM / Requests tabs switch the active streaming chart, the Pause / Resume button freezes the entire loop and flips the LIVE pill, the log can be cleared, and widget cards can be dragged to rearrange the grid. The layout collapses to a single column with an off-canvas nav around 720px, stays usable down to ~360px, and honors prefers-reduced-motion.