diff --git a/server/api.py b/server/api.py index 56c6173..050b7df 100644 --- a/server/api.py +++ b/server/api.py @@ -369,6 +369,37 @@ async def api_export_backup(): headers={"Content-Disposition": "attachment; filename=turbosu_backup.tsb"}) +@router.post("/backup/import") +async def api_import_backup(file: UploadFile = File(...)): + if not file.filename or not file.filename.endswith('.tsb'): + raise HTTPException(400, "Only .tsb files are accepted") + + import io, zipfile, shutil + zip_data = await file.read() + base = Path(__file__).resolve().parent.parent + + try: + with zipfile.ZipFile(io.BytesIO(zip_data), 'r') as zf: + for name in zf.namelist(): + if name.endswith('/') or '__pycache__' in name: + continue + dest = base / name + dest.parent.mkdir(parents=True, exist_ok=True) + dest.write_bytes(zf.read(name)) + + from config.settings import reload_config + reload_config() + from models.dashboard import dashboard_manager + import importlib + importlib.reload(__import__('models.dashboard', fromlist=['dashboard_manager'])) + from server.game_manager import game_plugin_manager + game_plugin_manager.reload() + + return {"ok": True, "message": "备份已恢复,请重启服务"} + except Exception as e: + return {"ok": False, "message": str(e)} + + # ---- Data Forwarding ---- @router.get("/forward") async def api_list_forward_targets(): diff --git a/static/js/pages/settings.js b/static/js/pages/settings.js index 0490eb3..b1043ce 100644 --- a/static/js/pages/settings.js +++ b/static/js/pages/settings.js @@ -100,6 +100,17 @@ const PageSettings = { a.click(); }); + document.getElementById('btn-import-backup')?.addEventListener('click', () => { + const input = document.createElement('input'); input.type = 'file'; input.accept = '.tsb'; + input.onchange = async (e) => { + const file = e.target.files[0]; if (!file) return; + const fd = new FormData(); fd.append('file', file); + const res = await fetch('/api/backup/import', { method: 'POST', body: fd }); + const data = await res.json(); + Toast.show(data.ok ? data.message || '恢复成功' : '恢复失败: ' + data.message, data.ok ? 'success' : 'error'); + }; input.click(); + }); + document.getElementById('settings-test-mode')?.addEventListener('change', async (e) => { const on = e.target.checked; await API.updateConfig({ ui_test_mode: on }); @@ -173,7 +184,7 @@ const PageSettings = { '

游戏插件管理

' + this._gamePluginListHtml(games) + '
' + - '

数据备份

导出全量备份
打包仪表盘+场景+配置为 .tsb 文件
' + + '

数据备份

导出全量备份
打包仪表盘+场景+配置为 .tsb
恢复备份
从 .tsb 文件恢复,需重启
' + '

开发工具

UI 测试模式
本地随机模拟遥测数据
' +