Newer
Older
hard-panel / panel / backend / app / main.py
from contextlib import asynccontextmanager
from pathlib import Path

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles

from app.api import ingest, servers
from app.db import close_db, init_db

# Собранный Vue-фронт (panel/frontend/dist) попадает сюда в docker-образе;
# в dev фронт крутится отдельно (vite, proxy /api → localhost:8000)
STATIC_DIR = Path(__file__).resolve().parent.parent / "static"


@asynccontextmanager
async def lifespan(_: FastAPI):
    await init_db()
    yield
    await close_db()


app = FastAPI(title="GHard Monitor API", version="0.1.0", lifespan=lifespan)

# В проде фронт раздаётся тем же origin; CORS нужен для vite dev-сервера (этап 3)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(ingest.router)
app.include_router(servers.router)


@app.get("/api/v1/health")
async def health() -> dict:
    return {"status": "ok"}


# --- Статика фронтенда: SPA с fallback на index.html -------------------------

if STATIC_DIR.is_dir():
    assets = STATIC_DIR / "assets"
    if assets.is_dir():
        app.mount("/assets", StaticFiles(directory=assets), name="assets")

    @app.get("/{full_path:path}", include_in_schema=False)
    async def spa(full_path: str) -> FileResponse:
        """Роуты Vue отдаём из dist, всё непонятное — index.html (SPA history)."""
        candidate = STATIC_DIR / full_path
        if full_path and candidate.is_file():
            return FileResponse(candidate)
        return FileResponse(STATIC_DIR / "index.html")