| c26267b3 Andy McCall ↶ |
1 |
|
| 2 |
|
| 3 |
|
| 4 |
"use strict"; |
| 5 |
(() => { |
| 6 |
const root = document.getElementById("tab-runner"); |
| 7 |
if (!root) return; |
| 8 |
const jobs = JSON.parse(root.dataset.jobs); |
| 9 |
const status = document.getElementById("tab-status"); |
| 10 |
const list = document.getElementById("tab-jobs"); |
| 11 |
let archive = null; |
| 12 |
const fetchArchive = () => { |
| 13 |
archive ??= fetch(root.dataset.archive, { credentials: "same-origin" }).then((r) => { |
| 14 |
if (!r.ok) throw new Error(`the files did not come (HTTP ${r.status})`); |
| 15 |
return r.arrayBuffer(); |
| 16 |
}); |
| 17 |
return archive; |
| 18 |
}; |
| 19 |
const el = (tag, props, ...kids) => { |
| 20 |
const e = Object.assign(document.createElement(tag), props); |
| 21 |
e.append(...kids); |
| 22 |
return e; |
| 23 |
}; |
| 24 |
const rows = jobs.map((job) => { |
| 25 |
const state = el("span", { className: "small mute", textContent: "not run" }); |
| 26 |
const out = el("pre", { className: "snippet", hidden: true }); |
| 27 |
const run = el("button", { type: "button", textContent: "Run" }); |
| 28 |
const stop = el("button", { type: "button", textContent: "Stop", hidden: true }); |
| 29 |
const box = el( |
| 30 |
"div", |
| 31 |
{ className: "tab-job" }, |
| 32 |
el("h3", {}, job.name, " ", state), |
| 33 |
el("p", { className: "small mute" }, el("code", { textContent: job.exec.join(" ") })), |
| 34 |
el("p", {}, run, " ", stop), |
| 35 |
out, |
| 36 |
); |
| 37 |
list.append(box); |
| 38 |
let worker = null; |
| 39 |
const line = (cls, text) => { |
| 40 |
out.hidden = false; |
| 41 |
out.append(el("span", { className: cls, textContent: text + "\n" })); |
| 42 |
out.scrollTop = out.scrollHeight; |
| 43 |
}; |
| 44 |
const go = () => |
| 45 |
new Promise((resolve) => { |
| 46 |
out.replaceChildren(); |
| 47 |
state.textContent = "fetching the files…"; |
| 48 |
state.className = "small mute"; |
| 49 |
run.disabled = true; |
| 50 |
stop.hidden = false; |
| 51 |
const finish = (text, cls, ok) => { |
| 52 |
state.textContent = text; |
| 53 |
state.className = "small " + cls; |
| 54 |
run.disabled = false; |
| 55 |
stop.hidden = true; |
| 56 |
worker?.terminate(); |
| 57 |
worker = null; |
| 58 |
resolve(ok); |
| 59 |
}; |
| 60 |
stop.onclick = () => finish("stopped", "bad", false); |
| 61 |
fetchArchive() |
| 62 |
.then((buf) => { |
| 63 |
state.textContent = "running…"; |
| 64 |
worker = new Worker(root.dataset.worker); |
| 65 |
worker.onmessage = ({ data: m }) => { |
| 66 |
if (m.kind === "done") finish(m.code === 0 ? `passed in ${m.ms} ms` : `failed, exit ${m.code}, in ${m.ms} ms`, m.code === 0 ? "ok" : "bad", m.code === 0); |
| 67 |
else if (m.kind === "failed") finish(`could not run: ${m.text}`, "bad", false); |
| 68 |
else line(m.kind === "out" ? "" : m.kind === "err" ? "bad" : "mute", m.text); |
| 69 |
}; |
| 70 |
worker.onerror = (e) => finish(`the worker stopped: ${e.message}`, "bad", false); |
| 71 |
worker.postMessage({ archive: buf.slice(0), exec: job.exec, env: job.env }); |
| 72 |
}) |
| 73 |
.catch((e) => finish(e.message, "bad", false)); |
| 74 |
}); |
| 75 |
run.onclick = () => go(); |
| 76 |
return go; |
| 77 |
}); |
| 78 |
document.getElementById("tab-run-all").onclick = async (e) => { |
| 79 |
e.target.disabled = true; |
| 80 |
let passed = 0; |
| 81 |
for (const go of rows) if (await go()) passed += 1; |
| 82 |
status.textContent = `${passed} of ${rows.length} passed`; |
| 83 |
e.target.disabled = false; |
| 84 |
}; |
| 85 |
})(); |