Clock errors no longer stop probing; legend reads >=500ms (closes #149, closes #152)
check / check (push) Waiting to run

The header's clocks start last in init, after the rounds, and an error
formatting them, as in a browser that rejects the "shortGeneric" time
zone name, leaves both clocks blank instead of stopping the page before
its first round. updateClocks moved out of init so a unit test can call
it.

The footer legend's red entry reads >=500ms, as README.md and
CONFIG.latencyColors have it. buildUI is exported so a unit test can draw
the page and check the legend against every entry of
CONFIG.latencyColors.

Model: opus-5-5
This commit was merged in pull request #167.
This commit is contained in:
2026-10-19 01:34:45 +02:00
parent fd32f348b7
commit 007190b541
3 changed files with 130 additions and 22 deletions
+34 -20
View File
@@ -829,7 +829,7 @@ function wanRowsHTML(wanHosts) {
return rows.join("");
}
function buildUI(state) {
export function buildUI(state) {
const app = document.getElementById("app");
app.innerHTML = `
<div class="mx-auto px-[5%] py-8">
@@ -911,7 +911,7 @@ function buildUI(state) {
<span class="inline-block w-3 h-3 rounded-full bg-lime-500 mr-1 ml-3 align-middle"></span>&lt;100ms
<span class="inline-block w-3 h-3 rounded-full bg-yellow-500 mr-1 ml-3 align-middle"></span>&lt;200ms
<span class="inline-block w-3 h-3 rounded-full bg-orange-500 mr-1 ml-3 align-middle"></span>&lt;500ms
<span class="inline-block w-3 h-3 rounded-full bg-red-500 mr-1 ml-3 align-middle"></span>&gt;500ms
<span class="inline-block w-3 h-3 rounded-full bg-red-500 mr-1 ml-3 align-middle"></span>&gt;=500ms
<span class="inline-block w-3 h-3 rounded-full bg-gray-500 mr-1 ml-3 align-middle"></span>offline
</p>
<p class="mt-2"><a href="https://git.eeqj.de/sneak/netwatch/commit/${__COMMIT_FULL__}" target="_blank" rel="noopener" class="text-gray-600 hover:text-gray-400">${__COMMIT_HASH__}</a></p>
@@ -1114,6 +1114,33 @@ function updateHealthBox(state) {
else if (health === "healthy") log.notice(`Health: ${labels[health]}`);
}
// Writes the local time, with its time zone's name, and the UTC time into
// the header's clocks. The clocks only decorate the page: in a browser
// whose Intl.DateTimeFormat rejects the "shortGeneric" time zone name
// (Chrome before 95, Firefox before 91, Safari before 15.4) they are left
// blank.
export function updateClocks() {
let local = "";
let utc = "";
try {
const now = new Date();
const zone = new Intl.DateTimeFormat("en-US", {
timeZoneName: "shortGeneric",
})
.formatToParts(now)
.find((p) => p.type === "timeZoneName").value;
const pad = (n) => String(n).padStart(2, "0");
local = `Local: ${now.toLocaleString("sv-SE", { hour12: false })} ${zone}`;
utc =
`UTC: ${now.getUTCFullYear()}-${pad(now.getUTCMonth() + 1)}-${pad(now.getUTCDate())} ` +
`${pad(now.getUTCHours())}:${pad(now.getUTCMinutes())}:${pad(now.getUTCSeconds())} UTC`;
} catch {
// Both clocks are left blank.
}
document.getElementById("clock-local").textContent = local;
document.getElementById("clock-utc").textContent = utc;
}
// --- Debug Log Renderer ------------------------------------------------------
// One line of the debug log panel, its message set as text.
@@ -1509,24 +1536,6 @@ async function init() {
sortAndRebuildWAN(state);
});
function updateClocks() {
const now = new Date();
const pad = (n) => String(n).padStart(2, "0");
const utc =
`${now.getUTCFullYear()}-${pad(now.getUTCMonth() + 1)}-${pad(now.getUTCDate())} ` +
`${pad(now.getUTCHours())}:${pad(now.getUTCMinutes())}:${pad(now.getUTCSeconds())} UTC`;
const local =
now.toLocaleString("sv-SE", { hour12: false }) +
" " +
new Intl.DateTimeFormat("en-US", { timeZoneName: "shortGeneric" })
.formatToParts(now)
.find((p) => p.type === "timeZoneName").value;
document.getElementById("clock-local").textContent = "Local: " + local;
document.getElementById("clock-utc").textContent = "UTC: " + utc;
}
updateClocks();
setInterval(updateClocks, 1000);
// Rounds never overlap: a round first gives up the last round's checks
// if they are still waiting, and the last round then records nothing
// more. At a steady interval they never are, as they time out at 80% of
@@ -1606,6 +1615,11 @@ async function init() {
window.addEventListener("resize", () => handleResize(state));
setTimeout(() => handleResize(state), CONFIG.resizeDelay);
// The clocks start last, after the rounds: they only decorate the page,
// so nothing in them may stop the monitor from probing.
updateClocks();
setInterval(updateClocks, 1000);
}
// Bootstrap only when loaded as the page: a real DOM containing the #app