feat: land dashboard groundwork

This commit is contained in:
truetype committed 2026-02-05 10:34:34 +01:00
commit d381c02946
12 files changed
+987

No files matched your search

+89
View File
@@ -0,0 +1,89 @@
from __future__ import annotations
import dataclasses
import subprocess
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel
from app.services import PortScanner
from app.tunnel_manager import TunnelManager
app = FastAPI(title="Gradio Tunnel Dashboard")
manager = TunnelManager()
scanner = PortScanner()
app.mount("/static", StaticFiles(directory="app/static"), name="static")
templates = Jinja2Templates(directory="app/templates")
class TunnelRequest(BaseModel):
port: int
@app.get("/", response_class=HTMLResponse)
async def index(request: Request) -> HTMLResponse:
return templates.TemplateResponse("index.html", {"request": request})
@app.get("/api/ports")
async def list_ports() -> list[dict[str, str | int | None]]:
records = scanner.scan()
return [dataclasses.asdict(record) for record in sorted(records, key=lambda item: item.port)]
@app.get("/api/tunnels")
async def list_tunnels() -> list[dict[str, str | int | None]]:
tunnels = manager.list_tunnels()
return [
{
"port": info.port,
"command": info.command,
"pid": info.pid,
"url": info.url,
"status": info.status,
"exit_code": info.exit_code,
"last_output": info.last_output,
}
for info in tunnels.values()
]
@app.get("/api/tunnels/{port}/logs")
async def tunnel_logs(port: int) -> dict[str, list[str]]:
logs = manager.get_logs(port)
if logs is None:
raise HTTPException(status_code=404, detail="Tunnel not running.")
return {"logs": logs}
@app.post("/api/tunnels")
async def start_tunnel(payload: TunnelRequest) -> dict[str, str | int | None]:
if payload.port <= 0 or payload.port > 65535:
raise HTTPException(status_code=400, detail="Invalid port.")
info = manager.start_tunnel(payload.port)
return {
"port": info.port,
"command": info.command,
"pid": info.pid,
"url": info.url,
"status": info.status,
"exit_code": info.exit_code,
"last_output": info.last_output,
}
@app.delete("/api/tunnels/{port}")
async def stop_tunnel(port: int) -> dict[str, bool]:
stopped = manager.stop_tunnel(port)
if not stopped:
raise HTTPException(status_code=404, detail="Tunnel not running.")
return {"stopped": True}
@app.on_event("shutdown")
def shutdown_event() -> None:
manager.shutdown()