fix: 版本更新 — 自动检查 + 手动备份 + 创建备份按钮
1. 设置页加载时自动触发版本检查 2. POST /api/updates/backup — 手动创建备份 3. 备份管理区新增「+ 创建备份」按钮 4. 顶栏版本按钮: checkTopUpdate 每10min轮询 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,7 @@ def setup_routes(app, prefix=''):
|
|||||||
app.router.add_get(f'{prefix}/api/updates', panel_auth(get_updates))
|
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/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/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_get(f'{prefix}/api/updates/backups', panel_auth(list_backups))
|
||||||
app.router.add_post(f'{prefix}/api/updates/rollback', panel_auth(do_rollback))
|
app.router.add_post(f'{prefix}/api/updates/rollback', panel_auth(do_rollback))
|
||||||
logger.info(f"📡 系统状态WS端点已注册: {prefix}/api/system/ws (已加认证)")
|
logger.info(f"📡 系统状态WS端点已注册: {prefix}/api/system/ws (已加认证)")
|
||||||
@@ -180,6 +181,33 @@ async def list_backups(req):
|
|||||||
return web.json_response({"backups": backups})
|
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):
|
async def do_rollback(req):
|
||||||
"""执行回滚"""
|
"""执行回滚"""
|
||||||
import glob, os, zipfile, shutil, tempfile
|
import glob, os, zipfile, shutil, tempfile
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
||||||
<h4 style="margin:0"><svg width="18" height="18" viewBox="0 0 24 24" style="vertical-align:-3px;margin-right:4px"><path fill="currentColor" d="M20 6h-8l-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V6h5.17l2 2H20v10z"/></svg>备份管理</h4>
|
<h4 style="margin:0"><svg width="18" height="18" viewBox="0 0 24 24" style="vertical-align:-3px;margin-right:4px"><path fill="currentColor" d="M20 6h-8l-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V6h5.17l2 2H20v10z"/></svg>备份管理</h4>
|
||||||
<button class="btn btn-sm btn-outlined" onclick="loadBackups()">刷新列表</button>
|
<button class="btn btn-sm btn-outlined" onclick="loadBackups()">刷新列表</button>
|
||||||
|
<button class="btn btn-sm btn-filled" onclick="createBackup()" style="margin-left:8px">+ 创建备份</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="backup-list"><span style="color:var(--text-dim);font-size:.85rem">加载中...</span></div>
|
<div id="backup-list"><span style="color:var(--text-dim);font-size:.85rem">加载中...</span></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -112,6 +113,15 @@
|
|||||||
} catch(e) {}
|
} 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() {
|
async function checkUpdate() {
|
||||||
document.getElementById("ver-badge").textContent = "⏳ 检查中...";
|
document.getElementById("ver-badge").textContent = "⏳ 检查中...";
|
||||||
await fetch("./api/updates/check", {method:"POST"});
|
await fetch("./api/updates/check", {method:"POST"});
|
||||||
@@ -127,7 +137,7 @@
|
|||||||
if (d.ok) alert("✓ " + d.msg);
|
if (d.ok) alert("✓ " + d.msg);
|
||||||
else alert("✗ " + (d.error||"失败"));
|
else alert("✗ " + (d.error||"失败"));
|
||||||
} catch(e) { alert("✗ " + e); }
|
} catch(e) { alert("✗ " + e); }
|
||||||
loadVersion();
|
loadVersion().then(function(){ checkUpdate(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 备份管理 ──
|
// ── 备份管理 ──
|
||||||
@@ -237,7 +247,7 @@
|
|||||||
|
|
||||||
// ── 初始加载 ──
|
// ── 初始加载 ──
|
||||||
loadKeys();
|
loadKeys();
|
||||||
loadVersion();
|
loadVersion().then(function(){ checkUpdate(); });
|
||||||
loadBackups();
|
loadBackups();
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user