dashboard/app/main.py

88 lines
2.7 KiB
Python

import asyncio
import socket
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from app.config import get_settings
from app.plugins import PLUGINS, ROUTERS as PLUGIN_ROUTERS
from app.render import BASE, render
from app.routers import disks, overview, plugins, processes, services
from app.routers import journal as journal_router
from app.sampling import sampler_loop
from app.state import HistoryStore
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Start shared state and run plugin lifecycle hooks around the app.
Startup: stores settings and the history ring buffer on `app.state`,
opens every plugin (a plugin `open()` failure is ignored, not fatal),
and spawns the background sampler task. Shutdown: cancels the
sampler task and closes every plugin.
Args:
app: the FastAPI instance.
Yields:
Control to the ASGI app for the server's lifetime.
"""
settings = get_settings()
app.state.settings = settings
app.state.store = HistoryStore(maxlen=settings.history_maxlen)
for p in PLUGINS:
try:
await p.open()
except Exception: # noqa
pass
task = asyncio.create_task(sampler_loop(app.state.store, settings.sample_interval))
yield
_ = task.cancel()
try:
await task
except asyncio.CancelledError:
pass
for p in PLUGINS:
try:
await p.close()
except Exception: # noqa
pass
async def index():
"""Serve the single-page dashboard shell at "/".
The shell only holds the tab bar and containers; each tab polls its
own `/api/*` endpoint for content, so this renders once and never again.
Returns:
The rendered `index.html` as an HTMLResponse.
"""
return HTMLResponse(render("index.html", hostname=socket.gethostname()))
def create_app() -> FastAPI:
"""Build the FastAPI application.
Wires up the `/static` mount, the six core routers (overview, disks,
processes, journal, services, plugins), and the routers contributed by
each plugin (see `app/plugins/__init__.py`).
Returns:
The configured FastAPI instance.
"""
app = FastAPI(title="Dashboard", lifespan=lifespan)
app.mount("/static", StaticFiles(directory=BASE / "static"), name="static")
for r in (overview.router, disks.router, processes.router, journal_router.router, services.router, plugins.router):
app.include_router(r)
for r in PLUGIN_ROUTERS:
app.include_router(r)
_ = app.get("/", include_in_schema=False)(index)
return app
app = create_app()