feat: TurboSu initial release - racing telemetry dashboard

This commit is contained in:
Yei.J. (AskaEth)
2026-07-25 15:23:40 +08:00
commit d61bb61a83
61 changed files with 5270 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
from __future__ import annotations
import json
import os
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse, FileResponse
from config.settings import get_config
from server.api import router as api_router
from server.websocket import ws_manager
from server.telemetry.listener import telemetry_listener
from utils.logger import get_logger
logger = get_logger(__name__)
BASE_DIR = Path(__file__).resolve().parent
STATIC_DIR = BASE_DIR / "static"
TEMPLATES_DIR = BASE_DIR / "templates"
os.makedirs(BASE_DIR / "logs", exist_ok=True)
@asynccontextmanager
async def lifespan(app: FastAPI):
logger.info("=" * 50)
logger.info(" TurboSu - Racing Telemetry Dashboard")
logger.info("=" * 50)
cfg = get_config()
logger.info("Server config: %s:%d", cfg.get("server_host", "0.0.0.0"), cfg.get("server_port", 9527))
await telemetry_listener.start()
logger.info("Telemetry listener auto-started")
yield
telemetry_listener.stop()
logger.info("TurboSu shutdown complete")
app = FastAPI(title="TurboSu", version="1.0.0", lifespan=lifespan)
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
templates = Jinja2Templates(directory=str(TEMPLATES_DIR))
app.include_router(api_router)
@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
@app.get("/dashboard/{theme_id}", response_class=HTMLResponse)
async def dashboard_render(request: Request, theme_id: str):
from models.dashboard import dashboard_manager
theme = dashboard_manager.get(theme_id)
template_html = dashboard_manager.get_template(theme_id)
return templates.TemplateResponse("dashboard.html", {
"request": request,
"theme": theme,
"template_html": template_html,
})
@app.get("/scene/{scene_id}", response_class=HTMLResponse)
async def scene_render(request: Request, scene_id: str):
from models.scene import scene_manager
scene = scene_manager.get(scene_id)
return templates.TemplateResponse("scene.html", {
"request": request,
"scene": scene,
})
@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
cid = await ws_manager.connect(ws)
try:
await ws.send_json({"type": "connected", "client_id": cid})
while True:
msg = await ws.receive_json()
if msg.get("type") == "ping":
await ws.send_json({"type": "pong"})
except WebSocketDisconnect:
pass
except Exception as e:
logger.error("WS error: %s", e)
finally:
ws_manager.disconnect(cid)
@app.get("/health")
async def health():
return {"status": "ok"}
if __name__ == "__main__":
import uvicorn
cfg = get_config()
uvicorn.run(
"app:app",
host=cfg.get("server_host", "0.0.0.0"),
port=cfg.get("server_port", 9527),
reload=False,
log_level="info",
)