(function (global) { function escapeHtml(s) { return String(s) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function fmtSize(n) { const v = Number(n) || 0; if (v < 1024) return v + " B"; if (v < 1024 * 1024) return (v / 1024).toFixed(1) + " KB"; return (v / (1024 * 1024)).toFixed(2) + " MB"; } function setText(id, msg, isErr) { const el = document.getElementById(id); if (!el) return; el.textContent = msg || ""; el.style.color = isErr ? "var(--bad)" : ""; } function renderRows(tbodyId, items, downloadHrefFn, restoreAttr) { const body = document.getElementById(tbodyId); if (!body) return; if (!items || !items.length) { body.innerHTML = '暂无备份'; return; } body.innerHTML = items .map(function (it) { const name = it.name || ""; const ops = '下载' + (restoreAttr ? ' ' : ""); return ( "" + "" + escapeHtml(name) + "" + "" + escapeHtml(it.mtime_bj || "—") + "" + "" + escapeHtml(fmtSize(it.size)) + "" + "" + ops + "" + "" ); }) .join(""); } async function loadHubList() { const body = document.getElementById("hub-backup-body"); if (body) body.innerHTML = '加载中…'; try { const r = await fetch("/api/settings/backup/hub/list", { credentials: "same-origin" }); const j = await r.json(); if (!j.ok) throw new Error(j.msg || "加载失败"); const dir = document.getElementById("hub-backup-dir"); if (dir && j.backup_dir) dir.textContent = j.backup_dir; renderRows( "hub-backup-body", j.items || [], function (name) { return "/api/settings/backup/hub/download/" + encodeURIComponent(name); }, true ); } catch (e) { if (body) { body.innerHTML = '' + escapeHtml(e.message || "加载失败") + ""; } } } async function createHubBackup() { setText("hub-backup-status", "备份中…"); try { const r = await fetch("/api/settings/backup/hub/create", { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: "{}", }); const j = await r.json(); if (!j.ok) throw new Error(j.msg || "备份失败"); setText("hub-backup-status", "已生成 " + (j.name || "")); await loadHubList(); } catch (e) { setText("hub-backup-status", e.message || "备份失败", true); } } async function restoreHub(name) { if (!name) return; if (!confirm("确定恢复该中控备份?\n将覆盖 .env 与 hub.db,建议恢复后重启。")) return; setText("hub-backup-status", "恢复中…"); try { const r = await fetch("/api/settings/backup/hub/restore", { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: name }), }); const j = await r.json(); if (!j.ok) throw new Error(j.msg || "恢复失败"); setText("hub-backup-status", j.msg || "恢复完成"); } catch (e) { setText("hub-backup-status", e.message || "恢复失败", true); } } async function loadInstances() { const sel = document.getElementById("client-backup-instance"); if (!sel) return; try { const r = await fetch("/api/settings/backup/instances", { credentials: "same-origin" }); const j = await r.json(); if (!j.ok) throw new Error(j.msg || "加载实例失败"); const items = j.items || []; sel.innerHTML = '' + items .map(function (it) { return ( '" ); }) .join(""); } catch (e) { setText("client-backup-status", e.message || "加载实例失败", true); } } async function loadClientList() { const sel = document.getElementById("client-backup-instance"); const body = document.getElementById("client-backup-body"); const iid = sel && sel.value; if (!iid) { if (body) body.innerHTML = '请选择客户端'; return; } if (body) body.innerHTML = '加载中…'; setText("client-backup-status", ""); try { const r = await fetch("/api/settings/backup/client/" + encodeURIComponent(iid) + "/list", { credentials: "same-origin", }); const j = await r.json(); if (!j.ok) throw new Error(j.msg || "加载失败"); renderRows( "client-backup-body", j.items || [], function (name) { return ( "/api/settings/backup/client/" + encodeURIComponent(iid) + "/download/" + encodeURIComponent(name) ); }, false ); } catch (e) { if (body) { body.innerHTML = '' + escapeHtml(e.message || "加载失败") + ""; } setText("client-backup-status", e.message || "加载失败", true); } } async function uploadHubBackup(file) { if (!file) return; setText("hub-backup-status", "上传中…"); try { const fd = new FormData(); fd.append("file", file, file.name); const r = await fetch("/api/settings/backup/hub/upload", { method: "POST", credentials: "same-origin", body: fd, }); const j = await r.json(); if (!j.ok) throw new Error(j.msg || "上传失败"); setText("hub-backup-status", j.msg || ("已上传 " + (j.name || ""))); await loadHubList(); } catch (e) { setText("hub-backup-status", e.message || "上传失败", true); } } function boot() { const createBtn = document.getElementById("hub-backup-create"); const hubRefresh = document.getElementById("hub-backup-refresh"); const uploadInput = document.getElementById("hub-backup-upload"); const clientRefresh = document.getElementById("client-backup-refresh"); const sel = document.getElementById("client-backup-instance"); if (createBtn) createBtn.addEventListener("click", createHubBackup); if (hubRefresh) hubRefresh.addEventListener("click", loadHubList); if (uploadInput) { uploadInput.addEventListener("change", function () { const f = uploadInput.files && uploadInput.files[0]; uploadInput.value = ""; void uploadHubBackup(f); }); } if (clientRefresh) clientRefresh.addEventListener("click", loadClientList); if (sel) sel.addEventListener("change", loadClientList); const hubBody = document.getElementById("hub-backup-body"); if (hubBody) { hubBody.addEventListener("click", function (ev) { const btn = ev.target.closest("[data-restore-name]"); if (!btn) return; void restoreHub(decodeURIComponent(btn.getAttribute("data-restore-name") || "")); }); } void loadHubList(); void loadInstances(); } global.HubSettingsBackup = { boot: boot }; })(window);