From 67d0f00525de476b41710fd7d40206c47e00ef7f Mon Sep 17 00:00:00 2001 From: qinglong Date: Sat, 13 Jun 2026 21:08:54 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0=20?= =?UTF-8?q?=E2=80=94=20=E8=87=AA=E5=8A=A8=E6=A3=80=E6=9F=A5=20+=20?= =?UTF-8?q?=E6=89=8B=E5=8A=A8=E5=A4=87=E4=BB=BD=20+=20=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E5=A4=87=E4=BB=BD=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 设置页加载时自动触发版本检查 2. POST /api/updates/backup — 手动创建备份 3. 备份管理区新增「+ 创建备份」按钮 4. 顶栏版本按钮: checkTopUpdate 每10min轮询 Co-Authored-By: Claude --- services/web_panel/routes/status.py | 28 ++++++++++++++++++++++++++++ static/web_panel/pages/settings.html | 14 ++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/services/web_panel/routes/status.py b/services/web_panel/routes/status.py index 941deab..a374ea3 100644 --- a/services/web_panel/routes/status.py +++ b/services/web_panel/routes/status.py @@ -43,6 +43,7 @@ def setup_routes(app, prefix=''): app.router.add_get(f'{prefix}/api/updates', panel_auth(get_updates)) app.router.add_post(f'{prefix}/api/updates/check', panel_auth(check_now)) app.router.add_post(f'{prefix}/api/updates/apply', panel_auth(apply_update)) + app.router.add_post(f'{prefix}/api/updates/backup', panel_auth(create_backup)) app.router.add_get(f'{prefix}/api/updates/backups', panel_auth(list_backups)) app.router.add_post(f'{prefix}/api/updates/rollback', panel_auth(do_rollback)) logger.info(f"📡 系统状态WS端点已注册: {prefix}/api/system/ws (已加认证)") @@ -180,6 +181,33 @@ async def list_backups(req): return web.json_response({"backups": backups}) +async def create_backup(req): + """手动创建备份""" + import time as _time, os as _os + from services.update_service import _backup_current, _PROJECT_ROOT + backup_dir = _os.path.join(_PROJECT_ROOT, "data", "backups") + _os.makedirs(backup_dir, exist_ok=True) + try: + sm = req.app.get("service_manager") + ver = "unknown" + if sm: + try: + init = sm.get_service("init") + ver = init.get_config("base").get("framework", {}).get("version", "0.0.0") + except: pass + name = f"SenSu_backup_{ver}_{_time.strftime('%Y%m%d_%H%M%S')}.zip" + dest = _os.path.join(backup_dir, name) + loop = asyncio.get_event_loop() + await loop.run_in_executor(None, _backup_current, dest) + # 清理旧备份 + backups = sorted(glob.glob(_os.path.join(backup_dir, "SenSu_backup_*.zip")), key=_os.path.getmtime, reverse=True) + for old_b in backups[5:]: + _os.unlink(old_b) + return web.json_response({"ok": True, "name": name}) + except Exception as e: + return web.json_response({"ok": False, "error": str(e)}, status=500) + + async def do_rollback(req): """执行回滚""" import glob, os, zipfile, shutil, tempfile diff --git a/static/web_panel/pages/settings.html b/static/web_panel/pages/settings.html index e754404..5d72ca1 100644 --- a/static/web_panel/pages/settings.html +++ b/static/web_panel/pages/settings.html @@ -19,6 +19,7 @@

备份管理

+
加载中...
@@ -112,6 +113,15 @@ } catch(e) {} } + async function createBackup() { + try { + var r = await fetch("./api/updates/backup", {method:"POST"}), d = await r.json(); + if (d.ok) alert("✓ 备份已创建: " + d.name); + else alert("✗ " + (d.error||"失败")); + loadBackups(); + } catch(e) { alert("✗ " + e); } + } + async function checkUpdate() { document.getElementById("ver-badge").textContent = "⏳ 检查中..."; await fetch("./api/updates/check", {method:"POST"}); @@ -127,7 +137,7 @@ if (d.ok) alert("✓ " + d.msg); else alert("✗ " + (d.error||"失败")); } catch(e) { alert("✗ " + e); } - loadVersion(); + loadVersion().then(function(){ checkUpdate(); }); } // ── 备份管理 ── @@ -237,7 +247,7 @@ // ── 初始加载 ── loadKeys(); - loadVersion(); + loadVersion().then(function(){ checkUpdate(); }); loadBackups();