locutory/static/health.js

145 lines
4.1 KiB
JavaScript
Raw Normal View History

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, names, 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, name] of names.entries()) {
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";