/** * 系统设置 · 备份与恢复 */ (function () { const page = document.getElementById("page-settings"); if (!page) return; const elAuto = document.getElementById("backup-auto-enabled"); const elHour = document.getElementById("backup-auto-hour"); const elRetention = document.getElementById("backup-retention-days"); const elIncludeEnv = document.getElementById("backup-include-env"); const elIncludeImages = document.getElementById("backup-include-images"); const elRoot = document.getElementById("backup-root"); const elStatus = document.getElementById("backup-status-line"); const elList = document.getElementById("backup-list"); const elRun = document.getElementById("backup-run-now"); const elRestoreFile = document.getElementById("backup-restore-file"); const elRestoreBtn = document.getElementById("backup-restore-upload-btn"); let settingsCache = null; let statusCache = null; function fmtBytes(n) { const v = Number(n); if (!Number.isFinite(v) || v < 0) return "—"; if (v < 1024) return v + " B"; if (v < 1024 * 1024) return (v / 1024).toFixed(1) + " KB"; return (v / (1024 * 1024)).toFixed(2) + " MB"; } function setStatus(msg, isErr) { if (!elStatus) return; elStatus.textContent = msg || ""; elStatus.className = "backup-status-line" + (isErr ? " err" : ""); } function collectBackupFromUI() { return { auto_enabled: !!(elAuto && elAuto.checked), auto_hour: Math.max(0, Math.min(23, parseInt(elHour && elHour.value, 10) || 0)), retention_days: Math.max(1, Math.min(365, parseInt(elRetention && elRetention.value, 10) || 30)), include_env: !!(elIncludeEnv && elIncludeEnv.checked), include_exchange_images: !!(elIncludeImages && elIncludeImages.checked), backup_root: (elRoot && elRoot.value || "").trim(), }; } function syncBackupUI(data) { const b = (data && data.backup) || {}; if (elAuto) elAuto.checked = b.auto_enabled !== false; if (elHour) elHour.value = b.auto_hour != null ? b.auto_hour : 0; if (elRetention) elRetention.value = b.retention_days != null ? b.retention_days : 30; if (elIncludeEnv) elIncludeEnv.checked = b.include_env !== false; if (elIncludeImages) elIncludeImages.checked = !!b.include_exchange_images; if (elRoot) elRoot.value = b.backup_root || ""; } function renderBackupList(status) { if (!elList) return; const rows = (status && status.backups) || []; const state = (status && status.state) || {}; const root = (status && status.backup_root) || ""; let html = '
'; html += '
目录:' + esc(root) + '
'; if (state.last_backup_at) { html += '
上次备份:' + esc(state.last_backup_at) + '(' + esc(state.last_trigger || "") + ")
"; } if (state.last_auto_at) { html += '
上次自动:' + esc(state.last_auto_at) + "
"; } if (state.last_restore_at) { html += '
上次恢复:' + esc(state.last_restore_at) + " ← " + esc(state.last_restore_from || "") + "
"; } html += "
"; if (!rows.length) { html += '

暂无备份文件

'; elList.innerHTML = html; return; } html += ''; rows.forEach(function (row) { html += "'; }); html += "
文件大小时间
" + esc(row.name) + "" + fmtBytes(row.size) + "" + esc(row.modified_at || "") + '' + '下载 ' + '
"; elList.innerHTML = html; elList.querySelectorAll(".backup-restore-local").forEach(function (btn) { btn.addEventListener("click", function () { restoreLocal(btn.getAttribute("data-name")); }); }); } function esc(s) { return String(s || "") .replace(/&/g, "&") .replace(/