Add a dashboard and backup download to the web UI

Metric history (5s to 1h tiers, persisted), uplink status, ping targets,
DHCP leases, and a tar.gz backup of the state directory.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
This commit is contained in:
2026-10-29 13:34:21 +01:00
co-authored by Claude Sonnet 5.5
parent e684ddb06c
commit 2a111d012b
13 changed files with 1835 additions and 37 deletions
+45
View File
@@ -0,0 +1,45 @@
// Live updates for the overview: refetches the dashboard part of the page
// (rendered by the server, like the page itself) and switches time ranges
// without a reload. Without JavaScript the page and its links still work.
(function () {
"use strict";
const root = document.getElementById("dashboard");
if (!root) return;
let range = root.dataset.range;
let interval = Number(root.dataset.interval) || 5;
let timer = null;
async function refresh() {
try {
const r = await fetch("/dashboard?range=" + encodeURIComponent(range), { credentials: "same-origin" });
if (r.redirected || r.status === 401 || r.status === 403) {
location.href = "/login"; // the session ended
return;
}
if (r.ok) root.innerHTML = await r.text();
} catch (e) {
// The router may be restarting the UI (an apply); try again later.
}
schedule();
}
function schedule() {
clearTimeout(timer);
if (!document.hidden) timer = setTimeout(refresh, interval * 1000);
}
root.addEventListener("click", function (ev) {
const a = ev.target.closest("a[data-range]");
if (!a) return;
ev.preventDefault();
range = a.dataset.range;
interval = range === "1h" ? 5 : 60;
history.replaceState(null, "", "/?range=" + encodeURIComponent(range));
refresh();
});
document.addEventListener("visibilitychange", function () {
if (document.hidden) clearTimeout(timer);
else refresh();
});
schedule();
})();