feat: 2,3,7,8,9 - search/sort, Docker, log viewer, .tsb backup

- Dashboard browser: search box + sort dropdown (name/category/author)
- Dockerfile + docker-compose.yml
- Debug page: log viewer button (/api/logs)
- Settings: .tsb full backup export (dashboards+scenes+config)
- F1 composite dashboard previously included
This commit is contained in:
2026-07-26 17:32:09 +08:00
parent 9091f2133d
commit a08120c568
6 changed files with 104 additions and 0 deletions
+44
View File
@@ -313,6 +313,50 @@ async def api_reload_game_plugins():
return {"ok": True, "count": len(game_plugin_manager.list_all())}
# ---- Logs ----
@router.get("/logs")
async def api_get_logs(lines: int = 50):
from pathlib import Path
log_file = Path(__file__).resolve().parent.parent / "logs" / "turbosu.log"
if not log_file.exists():
return {"lines": []}
try:
with open(log_file, "r", encoding="utf-8", errors="replace") as f:
all_lines = f.readlines()
return {"lines": [l.rstrip() for l in all_lines[-lines:]]}
except Exception:
return {"lines": []}
# ---- Full Backup (.tsb) ----
@router.get("/backup/export")
async def api_export_backup():
import io, zipfile
from fastapi.responses import Response
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
base = Path(__file__).resolve().parent.parent
data_dir = base / "data"
if data_dir.exists():
for f in data_dir.rglob("*"):
if f.is_file() and "__pycache__" not in str(f):
zf.write(f, f.relative_to(base))
dash_dir = base / "dashboards"
if dash_dir.exists():
for f in dash_dir.rglob("*"):
if f.is_file():
zf.write(f, f.relative_to(base))
games_dir = base / "games" / "user"
if games_dir.exists():
for f in games_dir.rglob("*"):
if f.is_file():
zf.write(f, f.relative_to(base))
return Response(content=buf.getvalue(), media_type="application/zip",
headers={"Content-Disposition": "attachment; filename=turbosu_backup.tsb"})
# ---- Data Forwarding ----
@router.get("/forward")
async def api_list_forward_targets():