better dashboard

This commit is contained in:
KingKDot
2026-05-31 15:53:50 -05:00
parent b31901e62a
commit a8b8b6369a
4 changed files with 465 additions and 51 deletions
+279 -11
View File
@@ -3,6 +3,14 @@ let metricsTimer = null;
let onlineChart = null;
let osChart = null;
const STATS_COLLAPSED_KEY = "overlord_dashboard_stats_collapsed";
const STATS_HEIGHT_KEY = "overlord_dashboard_stats_height";
const STATS_ORDER_KEY = "overlord_dashboard_stats_order";
const PULSE_ORDER_KEY = "overlord_dashboard_pulse_order";
const DEFAULT_STATS_HEIGHT = 142;
const MIN_STATS_HEIGHT = 112;
const MAX_STATS_HEIGHT = 280;
const DEFAULT_CARD_ORDER = ["online", "sessions", "trend", "fleet", "pulse"];
const DEFAULT_PULSE_ORDER = ["api", "memory", "ping"];
const palette = {
text: "#cbd5e1",
@@ -10,7 +18,7 @@ const palette = {
border: "rgba(100, 116, 139, 0.18)",
panel: "rgba(15, 23, 42, 0.96)",
cyan: "#22d3ee",
emerald: "#34d399",
emerald: "#22c55e",
sky: "#38bdf8",
indigo: "#818cf8",
amber: "#fbbf24",
@@ -166,8 +174,8 @@ function makeCharts() {
datasets: [{
label: "Online",
data: [],
borderColor: palette.cyan,
backgroundColor: createGradient(onlineCtx, palette.cyan),
borderColor: palette.emerald,
backgroundColor: createGradient(onlineCtx, palette.emerald),
borderWidth: 2,
fill: true,
tension: 0.38,
@@ -184,7 +192,7 @@ function makeCharts() {
legend: { display: false },
tooltip: {
backgroundColor: palette.panel,
borderColor: "rgba(56, 189, 248, 0.32)",
borderColor: "rgba(34, 197, 94, 0.34)",
borderWidth: 1,
titleColor: "#e2e8f0",
bodyColor: palette.text,
@@ -225,6 +233,9 @@ function makeCharts() {
maintainAspectRatio: false,
animation: { duration: 260 },
cutout: "66%",
layout: {
padding: { top: 2, right: 4, bottom: 10, left: 4 },
},
plugins: {
legend: {
position: "right",
@@ -249,6 +260,37 @@ function makeCharts() {
return true;
}
function getSavedHeight() {
return normalizeHeight(localStorage.getItem(STATS_HEIGHT_KEY) || DEFAULT_STATS_HEIGHT);
}
function normalizeHeight(value) {
const n = Math.round(Number(value));
if (!Number.isFinite(n)) return DEFAULT_STATS_HEIGHT;
return Math.min(MAX_STATS_HEIGHT, Math.max(MIN_STATS_HEIGHT, n));
}
function applyHeightMode(height) {
const shell = $("dashboard-stats");
if (!shell) return;
shell.classList.toggle("is-compact", height < 142);
shell.classList.toggle("is-tiny", height < 122);
const healthRows = Array.from(shell.querySelectorAll(".dashboard-health-row"));
const visibleCount = height < 122 ? 1 : height < 146 ? 2 : healthRows.length;
healthRows.forEach((row, index) => {
row.classList.toggle("is-hidden", index >= visibleCount);
});
if (osChart) {
const nextDisplay = height >= 172;
if (osChart.options.plugins.legend.display !== nextDisplay) {
osChart.options.plugins.legend.display = nextDisplay;
osChart.update("none");
}
}
}
function updateOnlineChart(history, snapshot) {
if (!onlineChart) return;
const rows = aggregateOnlineHistory(history, snapshot);
@@ -345,7 +387,9 @@ export function initDashboardStats() {
if (initialized) return;
initialized = true;
if (!makeCharts()) {
setTimeout(makeCharts, 150);
setTimeout(() => {
if (makeCharts()) applyHeightMode(getSavedHeight());
}, 150);
}
initStatsToggle();
fetchDashboardMetrics();
@@ -355,8 +399,26 @@ export function initDashboardStats() {
function initStatsToggle() {
const shell = $("dashboard-stats");
const button = $("dashboard-stats-toggle");
const resetButton = $("dashboard-stats-reset");
const resizer = $("dashboard-stats-resizer");
if (!shell || !button) return;
const resizeCharts = () => {
requestAnimationFrame(() => {
onlineChart?.resize();
osChart?.resize();
});
};
const setHeight = (value, persist = true) => {
const height = normalizeHeight(value);
shell.style.setProperty("--dashboard-stats-height", `${height}px`);
resizer?.setAttribute("aria-valuenow", String(height));
if (persist) localStorage.setItem(STATS_HEIGHT_KEY, String(height));
applyHeightMode(height);
resizeCharts();
};
const setCollapsed = (collapsed) => {
shell.classList.toggle("is-collapsed", collapsed);
button.setAttribute("aria-expanded", collapsed ? "false" : "true");
@@ -367,16 +429,222 @@ function initStatsToggle() {
localStorage.setItem(STATS_COLLAPSED_KEY, collapsed ? "true" : "false");
};
initCardReorder();
initPulseReorder();
setHeight(getSavedHeight(), false);
setCollapsed(localStorage.getItem(STATS_COLLAPSED_KEY) === "true");
let dragStartY = 0;
let dragStartHeight = DEFAULT_STATS_HEIGHT;
const stopResize = () => {
shell.classList.remove("is-resizing");
resizer?.releasePointerCapture?.(resizer._activePointerId);
resizer._activePointerId = null;
};
resizer?.setAttribute("aria-valuemin", String(MIN_STATS_HEIGHT));
resizer?.setAttribute("aria-valuemax", String(MAX_STATS_HEIGHT));
resizer?.addEventListener("pointerdown", (e) => {
if (shell.classList.contains("is-collapsed")) return;
dragStartY = e.clientY;
dragStartHeight = getSavedHeight();
resizer._activePointerId = e.pointerId;
resizer.setPointerCapture?.(e.pointerId);
shell.classList.add("is-resizing");
e.preventDefault();
});
resizer?.addEventListener("pointermove", (e) => {
if (!shell.classList.contains("is-resizing")) return;
setHeight(dragStartHeight + (e.clientY - dragStartY));
});
resizer?.addEventListener("pointerup", stopResize);
resizer?.addEventListener("pointercancel", stopResize);
resizer?.addEventListener("keydown", (e) => {
if (shell.classList.contains("is-collapsed")) return;
const current = getSavedHeight();
if (e.key === "ArrowUp") {
e.preventDefault();
setHeight(current - 10);
} else if (e.key === "ArrowDown") {
e.preventDefault();
setHeight(current + 10);
} else if (e.key === "Home") {
e.preventDefault();
setHeight(MIN_STATS_HEIGHT);
} else if (e.key === "End") {
e.preventDefault();
setHeight(MAX_STATS_HEIGHT);
}
});
resetButton?.addEventListener("click", () => {
setHeight(DEFAULT_STATS_HEIGHT);
setCollapsed(false);
localStorage.removeItem(STATS_ORDER_KEY);
localStorage.removeItem(PULSE_ORDER_KEY);
const content = $("dashboard-stats-content");
if (content) applySavedOrder(content);
const pulseList = document.querySelector(".dashboard-health-list");
if (pulseList) applyPulseOrder(pulseList);
});
button.addEventListener("click", () => {
const nextCollapsed = !shell.classList.contains("is-collapsed");
setCollapsed(nextCollapsed);
if (!nextCollapsed) {
requestAnimationFrame(() => {
onlineChart?.resize();
osChart?.resize();
});
}
if (!nextCollapsed) resizeCharts();
});
}
function readSavedPulseOrder() {
try {
const parsed = JSON.parse(localStorage.getItem(PULSE_ORDER_KEY) || "[]");
if (!Array.isArray(parsed)) return DEFAULT_PULSE_ORDER;
const cleaned = parsed.filter((id) => DEFAULT_PULSE_ORDER.includes(id));
return [...cleaned, ...DEFAULT_PULSE_ORDER.filter((id) => !cleaned.includes(id))];
} catch {
return DEFAULT_PULSE_ORDER;
}
}
function savePulseOrder(list) {
const order = Array.from(list.querySelectorAll("[data-pulse-row]"))
.map((row) => row.dataset.pulseRow)
.filter(Boolean);
localStorage.setItem(PULSE_ORDER_KEY, JSON.stringify(order));
}
function applyPulseOrder(list) {
const byId = new Map();
list.querySelectorAll("[data-pulse-row]").forEach((row) => {
byId.set(row.dataset.pulseRow, row);
});
for (const id of readSavedPulseOrder()) {
const row = byId.get(id);
if (row) list.appendChild(row);
}
applyHeightMode(getSavedHeight());
}
function initPulseReorder() {
const list = document.querySelector(".dashboard-health-list");
if (!list || list.dataset.reorderBound === "true") return;
list.dataset.reorderBound = "true";
applyPulseOrder(list);
let dragged = null;
list.addEventListener("dragstart", (e) => {
const row = e.target.closest("[data-pulse-row]");
if (!row) return;
e.stopPropagation();
dragged = row;
row.classList.add("is-dragging");
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("text/plain", row.dataset.pulseRow || "");
});
list.addEventListener("dragover", (e) => {
if (!dragged) return;
const target = e.target.closest("[data-pulse-row]");
if (!target || target === dragged) return;
e.preventDefault();
e.stopPropagation();
const rect = target.getBoundingClientRect();
const after = e.clientY > rect.top + rect.height / 2;
list.insertBefore(dragged, after ? target.nextSibling : target);
applyHeightMode(getSavedHeight());
});
list.addEventListener("drop", (e) => {
if (!dragged) return;
e.preventDefault();
e.stopPropagation();
savePulseOrder(list);
applyHeightMode(getSavedHeight());
});
list.addEventListener("dragend", (e) => {
e.stopPropagation();
dragged?.classList.remove("is-dragging");
dragged = null;
savePulseOrder(list);
applyHeightMode(getSavedHeight());
});
}
function readSavedOrder() {
try {
const parsed = JSON.parse(localStorage.getItem(STATS_ORDER_KEY) || "[]");
if (!Array.isArray(parsed)) return DEFAULT_CARD_ORDER;
const cleaned = parsed.filter((id) => DEFAULT_CARD_ORDER.includes(id));
return [...cleaned, ...DEFAULT_CARD_ORDER.filter((id) => !cleaned.includes(id))];
} catch {
return DEFAULT_CARD_ORDER;
}
}
function saveCurrentOrder(content) {
const order = Array.from(content.querySelectorAll("[data-dashboard-card]"))
.map((card) => card.dataset.dashboardCard)
.filter(Boolean);
localStorage.setItem(STATS_ORDER_KEY, JSON.stringify(order));
}
function applySavedOrder(content) {
const byId = new Map();
content.querySelectorAll("[data-dashboard-card]").forEach((card) => {
byId.set(card.dataset.dashboardCard, card);
});
for (const id of readSavedOrder()) {
const card = byId.get(id);
if (card) content.appendChild(card);
}
}
function initCardReorder() {
const content = $("dashboard-stats-content");
if (!content || content.dataset.reorderBound === "true") return;
content.dataset.reorderBound = "true";
applySavedOrder(content);
let dragged = null;
content.addEventListener("dragstart", (e) => {
const card = e.target.closest("[data-dashboard-card]");
if (!card) return;
dragged = card;
card.classList.add("is-dragging");
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("text/plain", card.dataset.dashboardCard || "");
});
content.addEventListener("dragover", (e) => {
if (!dragged) return;
const target = e.target.closest("[data-dashboard-card]");
if (!target || target === dragged) return;
e.preventDefault();
const rect = target.getBoundingClientRect();
const after = e.clientY > rect.top + rect.height / 2 || e.clientX > rect.left + rect.width / 2;
content.insertBefore(dragged, after ? target.nextSibling : target);
});
content.addEventListener("drop", (e) => {
if (!dragged) return;
e.preventDefault();
saveCurrentOrder(content);
});
content.addEventListener("dragend", () => {
dragged?.classList.remove("is-dragging");
dragged = null;
saveCurrentOrder(content);
requestAnimationFrame(() => {
onlineChart?.resize();
osChart?.resize();
});
});
}
+149 -10
View File
@@ -4378,9 +4378,10 @@ body.sb-ready #top-nav {
}
.dashboard-stats-shell {
--dashboard-stats-height: 142px;
display: flex;
flex-direction: column;
gap: 10px;
gap: 8px;
}
.dashboard-stats-bar {
@@ -4405,6 +4406,46 @@ body.sb-ready #top-nav {
color: #38bdf8;
}
.dashboard-version-text {
display: inline-flex;
align-items: center;
min-height: 22px;
border-left: 1px solid rgba(100, 116, 139, 0.42);
padding-left: 10px;
color: #64748b;
font-size: 0.74rem;
font-weight: 600;
letter-spacing: 0;
text-transform: none;
}
.dashboard-version-text .server-version-number {
color: #22c55e;
margin-left: 4px;
font-variant-numeric: tabular-nums;
font-weight: 800;
}
.dashboard-version-text .server-version-number-warning {
color: #fbbf24;
margin-left: 4px;
font-variant-numeric: tabular-nums;
font-weight: 800;
}
.dashboard-version-text .server-version-number-mismatch {
color: #f87171;
margin-left: 4px;
font-variant-numeric: tabular-nums;
font-weight: 800;
}
.dashboard-stats-controls {
display: inline-flex;
align-items: center;
gap: 8px;
}
.dashboard-stats-toggle {
display: inline-flex;
align-items: center;
@@ -4432,10 +4473,41 @@ body.sb-ready #top-nav {
align-items: stretch;
}
.dashboard-stats-shell.is-collapsed .dashboard-stats-content {
.dashboard-stats-shell.is-collapsed .dashboard-stats-content,
.dashboard-stats-shell.is-collapsed .dashboard-stats-resizer {
display: none;
}
.dashboard-stats-shell.is-resizing {
user-select: none;
}
.dashboard-stats-resizer {
display: flex;
align-items: center;
justify-content: center;
height: 14px;
border-radius: 8px;
cursor: ns-resize;
touch-action: none;
color: #64748b;
}
.dashboard-stats-resizer span {
width: 92px;
height: 4px;
border-radius: 999px;
background: rgba(71, 85, 105, 0.78);
transition: background 140ms ease, width 140ms ease;
}
.dashboard-stats-resizer:hover span,
.dashboard-stats-resizer:focus-visible span,
.dashboard-stats-shell.is-resizing .dashboard-stats-resizer span {
width: 132px;
background: rgba(56, 189, 248, 0.78);
}
.dashboard-stat-card,
.dashboard-chart-card,
.dashboard-health-card {
@@ -4446,6 +4518,7 @@ body.sb-ready #top-nav {
background: rgba(15, 23, 42, 0.72);
box-shadow: none;
overflow: hidden;
cursor: grab;
}
.dashboard-stat-card::before,
@@ -4454,11 +4527,24 @@ body.sb-ready #top-nav {
display: none;
}
.dashboard-stat-card:active,
.dashboard-chart-card:active,
.dashboard-health-card:active {
cursor: grabbing;
}
.dashboard-stat-card.is-dragging,
.dashboard-chart-card.is-dragging,
.dashboard-health-card.is-dragging {
opacity: 0.52;
border-color: rgba(56, 189, 248, 0.62);
}
.dashboard-stat-card {
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: 142px;
height: var(--dashboard-stats-height);
padding: 14px;
}
@@ -4466,6 +4552,15 @@ body.sb-ready #top-nav {
border-color: rgba(34, 211, 238, 0.22);
}
.dashboard-stat-card--online .dashboard-stat-value {
color: #f8fafc;
text-shadow: none;
}
.dashboard-stat-card--online .dashboard-stat-subline span:last-child {
color: #86efac;
}
.dashboard-stat-card--sessions {
border-color: rgba(129, 140, 248, 0.24);
}
@@ -4489,7 +4584,7 @@ body.sb-ready #top-nav {
.dashboard-stat-value {
margin-top: 10px;
color: #f8fafc;
font-size: 2.4rem;
font-size: clamp(1.8rem, calc(var(--dashboard-stats-height) / 3.2), 3.8rem);
line-height: 1;
font-weight: 800;
font-variant-numeric: tabular-nums;
@@ -4508,7 +4603,7 @@ body.sb-ready #top-nav {
.dashboard-chart-card,
.dashboard-health-card {
min-height: 142px;
height: var(--dashboard-stats-height);
padding: 12px 14px 10px;
}
@@ -4549,12 +4644,13 @@ body.sb-ready #top-nav {
.dashboard-chart-wrap {
position: relative;
z-index: 1;
height: 88px;
height: max(56px, calc(var(--dashboard-stats-height) - 54px));
min-width: 0;
}
.dashboard-chart-wrap--donut {
height: 92px;
height: max(54px, calc(var(--dashboard-stats-height) - 62px));
padding-bottom: 8px;
}
.dashboard-health-list {
@@ -4562,6 +4658,9 @@ body.sb-ready #top-nav {
z-index: 1;
display: grid;
gap: 7px;
align-content: start;
max-height: calc(var(--dashboard-stats-height) - 52px);
overflow: hidden;
}
.dashboard-health-row {
@@ -4576,6 +4675,16 @@ body.sb-ready #top-nav {
padding: 5px 8px;
color: #9db1cc;
font-size: 0.78rem;
cursor: grab;
}
.dashboard-health-row.is-hidden {
display: none;
}
.dashboard-health-row.is-dragging {
opacity: 0.5;
border-color: rgba(56, 189, 248, 0.58);
}
.dashboard-health-row strong {
@@ -4584,6 +4693,26 @@ body.sb-ready #top-nav {
font-variant-numeric: tabular-nums;
}
.dashboard-stats-shell.is-compact .dashboard-chart-kicker {
display: none;
}
.dashboard-stats-shell.is-compact .dashboard-chart-head {
margin-bottom: 5px;
}
.dashboard-stats-shell.is-compact .dashboard-chart-pill {
max-width: 7rem;
}
.dashboard-stats-shell.is-tiny .dashboard-stat-subline {
display: none;
}
.dashboard-stats-shell.is-tiny .dashboard-chart-pill {
display: none;
}
.dashboard-live-dot {
width: 8px;
height: 8px;
@@ -4593,8 +4722,8 @@ body.sb-ready #top-nav {
}
.dashboard-live-dot.is-live {
background: #34d399;
box-shadow: 0 0 0 4px rgba(52, 211, 153, 0.12), 0 0 16px rgba(52, 211, 153, 0.34);
background: #22c55e;
box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.13), 0 0 16px rgba(34, 197, 94, 0.36);
}
.dashboard-live-dot.is-warn {
@@ -4644,8 +4773,18 @@ body.sb-ready #top-nav {
}
.dashboard-stat-value {
font-size: 2rem;
font-size: clamp(1.65rem, calc(var(--dashboard-stats-height) / 3.4), 3rem);
}
.dashboard-stats-bar {
align-items: flex-start;
}
.dashboard-stats-controls {
flex-wrap: wrap;
justify-content: flex-end;
}
}
.dashboard-menu-panel,
+9 -5
View File
@@ -89,11 +89,15 @@ window.setDashboardPageBounds = (currentPage, totalPages) => {
}
};
function setServerVersionLabel(version) {
function setServerVersionLabel(version, tone = "ok") {
if (!serverVersionText) return;
serverVersionText.textContent = "Server version: ";
const value = document.createElement("span");
value.className = "server-version-number";
value.className = tone === "bad"
? "server-version-number-mismatch"
: tone === "warn"
? "server-version-number-warning"
: "server-version-number";
value.textContent = version;
serverVersionText.appendChild(value);
}
@@ -103,16 +107,16 @@ async function loadServerVersion() {
try {
const res = await fetch("/api/version", { credentials: "include" });
if (!res.ok) {
serverVersionText.textContent = "Server version: unavailable";
setServerVersionLabel("unavailable", "bad");
return;
}
const payload = await res.json();
const version = typeof payload?.version === "string" && payload.version.trim()
? payload.version.trim()
: "unknown";
setServerVersionLabel(version);
setServerVersionLabel(version, version === "unknown" ? "warn" : "ok");
} catch {
serverVersionText.textContent = "Server version: unavailable";
setServerVersionLabel("unavailable", "bad");
}
}
+28 -25
View File
@@ -25,32 +25,25 @@
<header id="top-nav"></header>
<main class="px-5 py-6">
<div id="clients-shell" class="w-full flex flex-col gap-5">
<div class="flex items-center justify-between gap-3 flex-wrap">
<div>
<h1 class="text-2xl font-semibold">Clients</h1>
<p class="text-slate-400">KDOT wants YOU to have a good day!</p>
<p id="server-version-text" class="text-xs text-slate-500 mt-1">
Server version: loading...
</p>
</div>
<span
class="inline-flex items-center gap-2 px-3 py-2 rounded-full bg-slate-800/70 text-slate-300 border border-slate-600/60"
id="total-pill"
>0 online</span
>
</div>
<span id="total-pill" class="hidden">0 online</span>
<section id="dashboard-stats" class="dashboard-stats-shell" aria-label="Dashboard status">
<div class="dashboard-stats-bar">
<div class="dashboard-stats-title">
<i class="fa-solid fa-chart-simple"></i>
<span>Status overview</span>
<span id="server-version-text" class="dashboard-version-text">Server version: loading...</span>
</div>
<div class="dashboard-stats-controls" aria-label="Status overview controls">
<button id="dashboard-stats-reset" class="dashboard-stats-toggle" type="button" title="Reset status overview">
<i class="fa-solid fa-arrow-rotate-left"></i>
</button>
<button id="dashboard-stats-toggle" class="dashboard-stats-toggle" type="button" aria-expanded="true" title="Hide status overview">
<i class="fa-solid fa-eye-slash"></i>
</button>
</div>
<button id="dashboard-stats-toggle" class="dashboard-stats-toggle" type="button" aria-expanded="true" title="Hide status overview">
<i class="fa-solid fa-eye-slash"></i>
</button>
</div>
<div id="dashboard-stats-content" class="dashboard-stats-content">
<div class="dashboard-stat-card dashboard-stat-card--online">
<div class="dashboard-stat-card dashboard-stat-card--online" data-dashboard-card="online" draggable="true">
<div class="dashboard-stat-topline">
<span class="dashboard-live-dot is-live"></span>
<span>Online</span>
@@ -61,7 +54,7 @@
<span id="dash-online-ratio">0%</span>
</div>
</div>
<div class="dashboard-stat-card dashboard-stat-card--sessions">
<div class="dashboard-stat-card dashboard-stat-card--sessions" data-dashboard-card="sessions" draggable="true">
<div class="dashboard-stat-topline">
<i class="fa-solid fa-wave-square"></i>
<span>Sessions</span>
@@ -72,7 +65,7 @@
<span id="dash-http-errors">0 errors</span>
</div>
</div>
<div class="dashboard-chart-card">
<div class="dashboard-chart-card" data-dashboard-card="trend" draggable="true">
<div class="dashboard-chart-head">
<div>
<span class="dashboard-chart-kicker">Past Week</span>
@@ -84,7 +77,7 @@
<canvas id="dash-online-chart"></canvas>
</div>
</div>
<div class="dashboard-chart-card">
<div class="dashboard-chart-card" data-dashboard-card="fleet" draggable="true">
<div class="dashboard-chart-head">
<div>
<span class="dashboard-chart-kicker">Fleet Mix</span>
@@ -96,7 +89,7 @@
<canvas id="dash-os-chart"></canvas>
</div>
</div>
<div class="dashboard-health-card">
<div class="dashboard-health-card" data-dashboard-card="pulse" draggable="true">
<div class="dashboard-chart-head">
<div>
<span class="dashboard-chart-kicker">Status</span>
@@ -105,17 +98,17 @@
<span id="dash-last-refresh" class="dashboard-chart-pill">Waiting</span>
</div>
<div class="dashboard-health-list">
<div class="dashboard-health-row">
<div class="dashboard-health-row" data-pulse-row="api" draggable="true">
<span class="dashboard-live-dot is-live"></span>
<span>API</span>
<strong id="dash-api-status">Live</strong>
</div>
<div class="dashboard-health-row">
<div class="dashboard-health-row" data-pulse-row="memory" draggable="true">
<span id="dash-memory-dot" class="dashboard-live-dot is-live"></span>
<span>Memory</span>
<strong id="dash-memory-status">0%</strong>
</div>
<div class="dashboard-health-row">
<div class="dashboard-health-row" data-pulse-row="ping" draggable="true">
<span id="dash-ping-dot" class="dashboard-live-dot is-muted"></span>
<span>Ping Avg</span>
<strong id="dash-ping-status">-</strong>
@@ -123,6 +116,16 @@
</div>
</div>
</div>
<div
id="dashboard-stats-resizer"
class="dashboard-stats-resizer"
role="separator"
aria-orientation="horizontal"
tabindex="0"
title="Drag to resize status overview"
>
<span></span>
</div>
</section>
<div class="dashboard-toolbar">
<label