129 lines
4.2 KiB
JavaScript
129 lines
4.2 KiB
JavaScript
/* ==========================================================================
|
|
boehmitools · Runner
|
|
Gemeinsame Oberflächenlogik für Plugins, die ein Kommandozeilen-Tool
|
|
kapseln: Lauf starten, Ausgabe live streamen, Status anzeigen, abbrechen.
|
|
|
|
const runner = BT.Runner({ log: el, status: el, onFinish: fn });
|
|
runner.start({ apply: false }); // POST <mount>/api/run
|
|
runner.attach(jobId); // an laufenden Lauf andocken
|
|
|
|
Setzt boehmi.js voraus.
|
|
========================================================================== */
|
|
(function () {
|
|
"use strict";
|
|
|
|
const LABEL = {
|
|
running: ["busy", "läuft …"],
|
|
done: ["ok", "erfolgreich abgeschlossen"],
|
|
failed: ["error", "fehlgeschlagen"],
|
|
cancelled: ["warn", "abgebrochen"],
|
|
};
|
|
|
|
function classify(entry) {
|
|
if (entry.stream === "meta") return "l-meta";
|
|
if (entry.stream === "stderr") return "l-err";
|
|
const t = entry.text;
|
|
if (/^(FEHLER|Abbruch|Fehlgeschlagen)|✗|Traceback/i.test(t)) return "l-err";
|
|
if (/^(Warnung|Hinweis)|⚠/i.test(t)) return "l-warn";
|
|
if (/(erfolgreich|✓|OK\b|angelegt|aktualisiert)/i.test(t)) return "l-ok";
|
|
return "";
|
|
}
|
|
|
|
function Runner(cfg) {
|
|
// Nicht const: Oberflächen, die ihre Karten neu zeichnen, hängen den
|
|
// Runner per rebind() wieder an die frischen Elemente.
|
|
let logEl = cfg.log;
|
|
let statusEl = cfg.status;
|
|
// Plugins mit mehreren Läufen (prüfen / vorschlagen / übernehmen) geben
|
|
// ihren eigenen Endpunkt an. Vorgabe bleibt der bisherige.
|
|
const endpoint = cfg.endpoint || "/api/run";
|
|
let source = null;
|
|
let job = null;
|
|
|
|
function setStatus(kind, text, extra) {
|
|
if (!statusEl) return;
|
|
statusEl.className = "bt-status " + (kind || "");
|
|
statusEl.innerHTML = `<span class="bt-dot"></span><b>${BT.escape(text)}</b>` +
|
|
(extra ? ` <span class="bt-muted">${BT.escape(extra)}</span>` : "");
|
|
}
|
|
|
|
function clear() {
|
|
if (logEl) logEl.textContent = "";
|
|
}
|
|
|
|
function append(entry) {
|
|
if (!logEl) return;
|
|
const atBottom = logEl.scrollHeight - logEl.scrollTop - logEl.clientHeight < 40;
|
|
const span = document.createElement("span");
|
|
const cls = classify(entry);
|
|
if (cls) span.className = cls;
|
|
span.textContent = entry.text + "\n";
|
|
logEl.appendChild(span);
|
|
if (atBottom) logEl.scrollTop = logEl.scrollHeight;
|
|
}
|
|
|
|
function close() {
|
|
if (source) { source.close(); source = null; }
|
|
}
|
|
|
|
function attach(id) {
|
|
close();
|
|
source = new EventSource(BT.url(`/api/jobs/${id}/events`));
|
|
setStatus("busy", "läuft …");
|
|
source.addEventListener("line", (e) => append(JSON.parse(e.data)));
|
|
source.addEventListener("end", (e) => {
|
|
const info = JSON.parse(e.data);
|
|
job = info;
|
|
const [kind, text] = LABEL[info.status] || ["", info.status];
|
|
setStatus(kind, text, `${info.duration}s · Exit-Code ${info.returncode}`);
|
|
close();
|
|
if (cfg.onFinish) cfg.onFinish(info);
|
|
});
|
|
source.onerror = () => {
|
|
if (source && source.readyState === EventSource.CLOSED) {
|
|
close();
|
|
setStatus("warn", "Verbindung zum Protokoll unterbrochen");
|
|
}
|
|
};
|
|
}
|
|
|
|
async function start(body) {
|
|
clear();
|
|
setStatus("busy", "wird gestartet …");
|
|
try {
|
|
job = await BT.api(endpoint, { method: "POST", body: JSON.stringify(body || {}) });
|
|
attach(job.id);
|
|
if (cfg.onStart) cfg.onStart(job);
|
|
return job;
|
|
} catch (err) {
|
|
setStatus("error", "Start fehlgeschlagen", err.message);
|
|
BT.toast(err.message, "err");
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
async function cancel() {
|
|
if (!job) return;
|
|
try {
|
|
await BT.api(`/api/jobs/${job.id}/cancel`, { method: "POST" });
|
|
BT.toast("Abbruch angefordert.");
|
|
} catch (err) {
|
|
BT.toast(err.message, "err");
|
|
}
|
|
}
|
|
|
|
function rebind(next) {
|
|
if (next.log) logEl = next.log;
|
|
if (next.status) statusEl = next.status;
|
|
}
|
|
|
|
return {
|
|
start, attach, cancel, clear, setStatus, rebind,
|
|
get job() { return job; },
|
|
get busy() { return Boolean(source); },
|
|
};
|
|
}
|
|
|
|
window.BT.Runner = Runner;
|
|
})();
|