/* * 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 ` `; }).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

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

wird geladen …

${options.note || ""}
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 }; }; })();