/*
* Gemeinsamer Baustein „Sicherungen“.
*
* Jedes schreibende Plugin bekommt damit dieselbe Ansicht: welche Läufe gab
* es, was steckt drin, wie weit lässt sich das zurückdrehen. Der Text zur
* Rückspielbarkeit kommt aus dem Manifest — die Oberfläche behauptet also
* nichts, was das Werkzeug nicht auch kann.
*/
(function () {
"use strict";
const LEVEL = {
voll: { css: "ok", text: "vollständig rückspielbar" },
neue_id: { css: "info", text: "rückspielbar, neue ID" },
teilweise: { css: "warn", text: "teilweise rückspielbar" },
nein: { css: "err", text: "nicht rückspielbar" },
};
function renderRuns(runs, selected) {
if (!runs.length) {
return `
Noch keine Sicherungen. Es entsteht bei jedem Lauf eine, der wirklich
etwas schreibt — Trockenübungen zählen nicht.
`;
}
return runs.map((r) => {
const lv = LEVEL[r.restore_level] || LEVEL.nein;
const when = r.created_at ? new Date(r.created_at) : null;
return `
${BT.escape(r.label || r.id)}
${when && !isNaN(when) ? when.toLocaleString("de-DE") : BT.escape(r.id)}
· ${r.done} von ${r.steps} Schritten geschrieben
${lv.text}
${r.restored ? `bereits zurückgespielt ` : ""}
`;
}).join("");
}
function renderDetail(detail) {
const steps = (detail.manifest?.steps || []).filter((s) => s.status === "done");
if (!steps.length) return `In diesem Lauf wurde nichts geschrieben.
`;
return `${steps.map((s) => {
const lv = LEVEL[s.restore_level] || LEVEL.nein;
let was = "";
if (s.action === "properties") {
was = `${Object.keys(s.written || {}).length} Werte gesetzt`;
} else if (s.action === "merge") {
was = `zusammengeführt auf „${BT.escape(s.target_name)}“`
+ (s.references?.length ? ` · ${s.references.length} Verweise aufgezeichnet` : "")
+ (s.rescued?.length ? ` · ${s.rescued.length} Werte gerettet` : "");
} else if (s.action === "delete") {
was = "gelöscht";
} else if (s.action === "recipe") {
was = `${s.step_count} Schritte gesichert`;
}
return `
${lv.text}
${BT.escape(s.food_name || s.source_name || "?")}
— ${was}
`;
}).join("")}
`;
}
/**
* Baut den Reiter. `mount` ist das Zielelement, `runner` ein BT.Runner,
* der auf /api/run/restore zeigt.
*/
BT.Backups = function (options) {
const mount = options.mount;
const runner = options.runner;
let runs = [];
let selected = null;
async function load() {
const data = await BT.api("/api/backups");
runs = data.runs || [];
if (!runs.some((r) => r.id === selected)) selected = runs[0]?.id || null;
draw(data.dir);
if (selected) await detail();
}
async function detail() {
if (!selected || !options.detail) { paintDetail(""); return; }
try {
paintDetail(renderDetail(await BT.api(`/api/backups/${encodeURIComponent(selected)}`)));
} catch { paintDetail(""); }
}
function paintDetail(html) {
const box = mount.querySelector("#btDetail");
if (box) box.innerHTML = html;
}
function draw(dir) {
const current = runs.find((r) => r.id === selected);
const lv = LEVEL[current?.restore_level] || LEVEL.nein;
mount.innerHTML = `
Sicherungen
Neu laden
Jeder Lauf, der wirklich geschrieben hat, liegt unter
${BT.escape(dir || "")}. Der Zustand vorher ist dort
vollständig festgehalten.
${renderRuns(runs, selected)}
${runs.length ? `
Was passierte in diesem Lauf
${options.note || ""}
Trockenübung
Zurückspielen
auch wenn seither von Hand geändert wurde
Bereit.
` : ""}`;
mount.querySelectorAll('input[name="btrun"]').forEach((el) =>
el.addEventListener("change", () => { selected = el.value; draw(dir); detail(); }));
const byId = (id) => mount.querySelector("#" + id);
byId("btReload")?.addEventListener("click", load);
byId("btDry")?.addEventListener("click", () => go(false));
byId("btApply")?.addEventListener("click", () => go(true));
if (runner) runner.rebind({ log: byId("btLog"), status: byId("btStatus") });
}
function go(apply) {
const current = runs.find((r) => r.id === selected);
if (!current) return;
if (apply && !confirm(
`Lauf „${current.label || current.id}“ zurückspielen?\n\n` +
`${current.done} Schritte · ${(LEVEL[current.restore_level] || LEVEL.nein).text}\n\n` +
(current.restored ? "Achtung: Dieser Lauf wurde bereits zurückgespielt.\n\n" : "") +
`Erst die Trockenübung ansehen, wenn du unsicher bist.`)) return;
runner.start({ run: selected, apply, force: mount.querySelector("#btForce")?.checked || false });
}
return { load, reload: load };
};
})();