88 lines
2.7 KiB
Python
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()
|