Add Zola templates and browser-tested site scaffold
This commit is contained in:
parent
fd5fc944e6
commit
a33673243e
1 changed files with 2114 additions and 0 deletions
149
static/health.js
Normal file
149
static/health.js
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
import { decode } from "/vendor/msgpack/index.mjs";
|
||||
|
||||
const target = document.querySelector("#target");
|
||||
const day = document.querySelector("#day");
|
||||
const detail = document.querySelector("#detail");
|
||||
const observed = document.querySelector("#observed");
|
||||
|
||||
const percent = (value) =>
|
||||
value === null ? "Unknown" : `${(value * 100).toFixed(2)}% healthy`;
|
||||
|
||||
function state(value) {
|
||||
switch (value) {
|
||||
case null:
|
||||
return "unknown";
|
||||
case 1:
|
||||
return "good";
|
||||
case 0:
|
||||
return "down";
|
||||
default:
|
||||
return "mixed";
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
const response = await fetch(
|
||||
document.querySelector("script[data-api]").dataset.api,
|
||||
);
|
||||
|
||||
return response.ok ? decode(await response.arrayBuffer()) : null;
|
||||
}
|
||||
|
||||
function renderRow(record, recordIndex, dates, select) {
|
||||
const row = document.createElement("article");
|
||||
row.className = "health-row";
|
||||
|
||||
const title = document.createElement("h3");
|
||||
title.textContent = record.name;
|
||||
|
||||
const bars = document.createElement("div");
|
||||
bars.className = "bars";
|
||||
bars.setAttribute("role", "group");
|
||||
bars.setAttribute("aria-label", `${record.name}, daily history`);
|
||||
|
||||
for (const [dayIndex, value] of record.values.entries()) {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = `bar ${state(value)}`;
|
||||
button.title = `${dates[dayIndex]}: ${percent(value)}`;
|
||||
button.setAttribute("aria-label", `${record.name} · ${button.title}`);
|
||||
button.setAttribute("aria-pressed", "false");
|
||||
button.dataset.target = String(recordIndex);
|
||||
button.dataset.day = String(dayIndex);
|
||||
|
||||
button.addEventListener("click", () => {
|
||||
target.value = String(recordIndex);
|
||||
day.value = String(dayIndex);
|
||||
select();
|
||||
});
|
||||
|
||||
bars.append(button);
|
||||
}
|
||||
|
||||
row.append(title, bars);
|
||||
return row;
|
||||
}
|
||||
|
||||
function renderGroup(group, groupIndex, records, dates, select) {
|
||||
const [header, labels, values] = group;
|
||||
const section = document.createElement("section");
|
||||
section.className = "health-group";
|
||||
|
||||
const heading = document.createElement("h2");
|
||||
heading.id = `group-${groupIndex}`;
|
||||
heading.textContent = `${String(groupIndex + 1).padStart(2, "0")} ${header}`;
|
||||
section.setAttribute("aria-labelledby", heading.id);
|
||||
section.append(heading);
|
||||
|
||||
for (const [seriesIndex, metric] of labels.entries()) {
|
||||
const name =
|
||||
Object.entries(metric)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join(", ") || "{}";
|
||||
const record = { name, values: values[seriesIndex] };
|
||||
const recordIndex = records.length;
|
||||
|
||||
records.push(record);
|
||||
target.add(new Option(`${header} · ${name}`, String(recordIndex)));
|
||||
section.append(renderRow(record, recordIndex, dates, select));
|
||||
}
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
function render([start, groups]) {
|
||||
const dates = Array.from({ length: 30 }, (_, index) =>
|
||||
new Date((start + index * 86400) * 1000).toISOString().slice(0, 10),
|
||||
);
|
||||
observed.textContent = `${dates[0]} – ${dates.at(-1)} · UTC`;
|
||||
|
||||
const records = [];
|
||||
const select = () => {
|
||||
const record = records[Number(target.value)];
|
||||
const dayIndex = Number(day.value);
|
||||
detail.textContent = `${record.name} · ${dates[dayIndex]}: ${percent(record.values[dayIndex])}.`;
|
||||
|
||||
for (const bar of document.querySelectorAll(".bar")) {
|
||||
const selected =
|
||||
bar.dataset.target === target.value && bar.dataset.day === day.value;
|
||||
bar.setAttribute("aria-pressed", String(selected));
|
||||
}
|
||||
};
|
||||
|
||||
for (const [index, date] of dates.entries()) {
|
||||
day.add(new Option(date, String(index)));
|
||||
}
|
||||
day.value = "29";
|
||||
|
||||
groups.sort((left, right) => left[1].length - right[1].length);
|
||||
const container = document.querySelector("#groups");
|
||||
|
||||
for (const [groupIndex, group] of groups.entries()) {
|
||||
container.append(renderGroup(group, groupIndex, records, dates, select));
|
||||
}
|
||||
|
||||
if (records.length) {
|
||||
document.querySelector(".controls").hidden = false;
|
||||
target.addEventListener("change", select);
|
||||
day.addEventListener("change", select);
|
||||
select();
|
||||
} else {
|
||||
observed.textContent = "No data";
|
||||
}
|
||||
}
|
||||
|
||||
let snapshot;
|
||||
|
||||
try {
|
||||
snapshot = await load();
|
||||
} catch {
|
||||
snapshot = null;
|
||||
}
|
||||
|
||||
if (snapshot === null) {
|
||||
observed.textContent = "No data";
|
||||
} else {
|
||||
render(snapshot);
|
||||
}
|
||||
|
||||
document.documentElement.dataset.ready = "true";
|
||||
Loading…
Reference in a new issue