Matthias Nott
2026-02-21 15c6d258137464aad5bbcc53f6044ad628849048
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import logging
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from app.routers import backups, restore, services, status, system
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
logger = logging.getLogger(__name__)
_STATIC_DIR = Path(__file__).parent.parent / "static"
@asynccontextmanager
async def lifespan(app: FastAPI):
    logger.info("Ops WebUI server is running")
    yield
app = FastAPI(
    title="Ops WebUI API",
    description="Backend API for the ops web dashboard",
    version="1.0.0",
    lifespan=lifespan,
)
# ---------------------------------------------------------------------------
# CORS – open for development; restrict in production via env/reverse proxy
# ---------------------------------------------------------------------------
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
# ---------------------------------------------------------------------------
# API routers
# ---------------------------------------------------------------------------
app.include_router(status.router, prefix="/api/status", tags=["status"])
app.include_router(backups.router, prefix="/api/backups", tags=["backups"])
app.include_router(restore.router, prefix="/api/restore", tags=["restore"])
app.include_router(services.router, prefix="/api/services", tags=["services"])
app.include_router(system.router, prefix="/api/system", tags=["system"])
# ---------------------------------------------------------------------------
# Static files – serve CSS/JS at /static and HTML at /
# Mount /static first for explicit asset paths, then / for SPA fallback
# ---------------------------------------------------------------------------
if _STATIC_DIR.exists():
    app.mount("/static", StaticFiles(directory=str(_STATIC_DIR)), name="static-assets")
    app.mount("/", StaticFiles(directory=str(_STATIC_DIR), html=True), name="static")
else:
    logger.warning("Static directory not found at %s – frontend will not be served", _STATIC_DIR)