mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-13 22:26:49 +00:00
- py/http-response-splitting (server.py:87): _redirect percent-encodes CR and LF in the Location header - js/xss-through-dom (versions.js:82, languages.js:25): both switchers navigate to the URL they built instead of select.value The new cases in test_server.py, versions.test.js and languages.test.js fail without the fix and pass with it. 🎯 Fix: 92 % 💥 Break: 3 % 💸 Cost: – ⏱️ Time: – 🛡️ Security: 100 % 🏆 Score: 89 🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 % Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
93 lines
3.0 KiB
JavaScript
93 lines
3.0 KiB
JavaScript
(function () {
|
|
const LABELS = {
|
|
ready: "ready",
|
|
missing: "not built yet",
|
|
queued: "queued",
|
|
building: "building",
|
|
failed: "failed",
|
|
};
|
|
const POLL_MS = 2000;
|
|
|
|
function versionUrl(name, page) {
|
|
return page ? `/${name}/${page}.html` : `/${name}/`;
|
|
}
|
|
|
|
function describe(version) {
|
|
const label = LABELS[version.state];
|
|
return version.phase && version.state !== "ready" ? `${label}: ${version.phase}` : label;
|
|
}
|
|
|
|
function optionLabel(version) {
|
|
return version.built ? version.name : `${version.name} (${LABELS[version.state]})`;
|
|
}
|
|
|
|
function fillSwitcher(select, versions) {
|
|
const { current, page } = select.dataset;
|
|
const urls = versions.map((version) => versionUrl(version.name, page));
|
|
select.replaceChildren(
|
|
...versions.map((version, index) => {
|
|
const option = document.createElement("option");
|
|
option.value = urls[index];
|
|
option.textContent = optionLabel(version);
|
|
option.selected = version.name === current;
|
|
return option;
|
|
}),
|
|
);
|
|
select.onchange = () => {
|
|
window.location.href = urls[select.selectedIndex];
|
|
};
|
|
}
|
|
|
|
function fillOverview(tbody, versions) {
|
|
tbody.replaceChildren(
|
|
...versions.map((version) => {
|
|
const link = document.createElement("a");
|
|
link.href = versionUrl(version.name);
|
|
link.textContent = version.name;
|
|
const bar = document.createElement("progress");
|
|
bar.max = 100;
|
|
bar.value = version.progress;
|
|
const row = document.createElement("tr");
|
|
[link, document.createTextNode(describe(version)), bar].forEach((child) => {
|
|
const cell = document.createElement("td");
|
|
cell.append(child);
|
|
row.append(cell);
|
|
});
|
|
return row;
|
|
}),
|
|
);
|
|
}
|
|
|
|
function followBuild(section, versions) {
|
|
const version = versions.find((candidate) => candidate.name === section.dataset.build);
|
|
if (!version) {
|
|
return;
|
|
}
|
|
if (version.built) {
|
|
window.location.reload();
|
|
return;
|
|
}
|
|
section.querySelector("progress").value = version.progress;
|
|
section.querySelector("[data-phase]").textContent = describe(version);
|
|
section.querySelector("pre").textContent = version.log.join("\n");
|
|
}
|
|
|
|
async function refresh() {
|
|
const response = await fetch("/api/versions", { cache: "no-store" });
|
|
const versions = await response.json();
|
|
document.querySelectorAll("select.docs-version-switcher").forEach((select) => fillSwitcher(select, versions));
|
|
document.querySelectorAll("tbody[data-versions]").forEach((tbody) => fillOverview(tbody, versions));
|
|
document.querySelectorAll("section[data-build]").forEach((section) => followBuild(section, versions));
|
|
}
|
|
|
|
function start() {
|
|
refresh();
|
|
if (document.querySelector("tbody[data-versions], section[data-build]")) {
|
|
window.setInterval(refresh, POLL_MS);
|
|
}
|
|
}
|
|
|
|
window.docsVersions = { versionUrl, describe, optionLabel, fillSwitcher, fillOverview, followBuild, refresh };
|
|
document.addEventListener("DOMContentLoaded", start);
|
|
})();
|