locutory/static/health.js

145 lines
4.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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";