f846f832de
Co-authored-by: Cursor <cursoragent@cursor.com>
134 lines
4.4 KiB
JavaScript
134 lines
4.4 KiB
JavaScript
/**
|
|
* 系统设置 · 备份恢复
|
|
*/
|
|
(function (global) {
|
|
function statusEl() {
|
|
return document.getElementById("backup-status");
|
|
}
|
|
|
|
function setStatus(msg, isErr) {
|
|
const el = statusEl();
|
|
if (!el) return;
|
|
el.textContent = msg || "";
|
|
el.style.color = isErr ? "#fb7185" : "";
|
|
}
|
|
|
|
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 escapeHtml(s) {
|
|
return String(s)
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """);
|
|
}
|
|
|
|
async function loadList() {
|
|
const body = document.getElementById("backup-list-body");
|
|
if (!body) return;
|
|
body.innerHTML = '<tr><td colspan="4" class="muted">加载中…</td></tr>';
|
|
try {
|
|
const r = await fetch("/api/settings/backup/list", { credentials: "same-origin" });
|
|
const j = await r.json();
|
|
if (!j.ok) throw new Error(j.msg || "加载失败");
|
|
const dir = document.getElementById("backup-dir-label");
|
|
if (dir && j.backup_dir) dir.textContent = j.backup_dir;
|
|
const items = j.items || [];
|
|
if (!items.length) {
|
|
body.innerHTML = '<tr><td colspan="4" class="muted">暂无备份</td></tr>';
|
|
return;
|
|
}
|
|
body.innerHTML = items
|
|
.map(function (it) {
|
|
const name = escapeHtml(it.name || "");
|
|
return (
|
|
"<tr>" +
|
|
"<td><code style=\"font-size:.74rem\">" +
|
|
name +
|
|
"</code></td>" +
|
|
"<td>" +
|
|
escapeHtml(it.mtime_bj || "—") +
|
|
"</td>" +
|
|
"<td>" +
|
|
escapeHtml(fmtSize(it.size)) +
|
|
"</td>" +
|
|
"<td style=\"white-space:nowrap\">" +
|
|
'<a class="btn-secondary" style="padding:3px 8px;font-size:.74rem;text-decoration:none" href="/api/settings/backup/download/' +
|
|
encodeURIComponent(it.name) +
|
|
'">下载</a> ' +
|
|
'<button type="button" class="btn-del" style="padding:3px 8px;font-size:.74rem" data-backup-restore="' +
|
|
encodeURIComponent(it.name) +
|
|
'">恢复</button>' +
|
|
"</td>" +
|
|
"</tr>"
|
|
);
|
|
})
|
|
.join("");
|
|
} catch (e) {
|
|
body.innerHTML =
|
|
'<tr><td colspan="4" class="muted">' + escapeHtml(e.message || "加载失败") + "</td></tr>";
|
|
}
|
|
}
|
|
|
|
async function createBackup() {
|
|
setStatus("备份中…");
|
|
try {
|
|
const r = await fetch("/api/settings/backup/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 || "备份失败");
|
|
setStatus("已生成 " + (j.name || "备份"));
|
|
await loadList();
|
|
} catch (e) {
|
|
setStatus(e.message || "备份失败", true);
|
|
}
|
|
}
|
|
|
|
async function restoreBackup(name) {
|
|
if (!name) return;
|
|
if (!confirm("确定用该备份恢复?\n将覆盖 .env、数据库与复盘图片,建议恢复后重启。")) return;
|
|
setStatus("恢复中…");
|
|
try {
|
|
const r = await fetch("/api/settings/backup/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 || "恢复失败");
|
|
setStatus(j.msg || "恢复完成");
|
|
} catch (e) {
|
|
setStatus(e.message || "恢复失败", true);
|
|
}
|
|
}
|
|
|
|
function boot() {
|
|
const root = document.querySelector("[data-backup-root]");
|
|
if (!root || root.dataset.bound === "1") return;
|
|
root.dataset.bound = "1";
|
|
const createBtn = document.getElementById("backup-create-btn");
|
|
const refreshBtn = document.getElementById("backup-refresh-btn");
|
|
if (createBtn) createBtn.addEventListener("click", createBackup);
|
|
if (refreshBtn) refreshBtn.addEventListener("click", loadList);
|
|
root.addEventListener("click", function (ev) {
|
|
const btn = ev.target.closest("[data-backup-restore]");
|
|
if (!btn) return;
|
|
const name = decodeURIComponent(btn.getAttribute("data-backup-restore") || "");
|
|
void restoreBackup(name);
|
|
});
|
|
void loadList();
|
|
}
|
|
|
|
global.InstanceBackup = { boot: boot, loadList: loadList };
|
|
})(typeof window !== "undefined" ? window : globalThis);
|