dashboard/app/routers/overview.py
2026-08-31 00:47:02 +02:00

97 lines
3.2 KiB
Python

import asyncio
import socket
import time
from typing import Any
import psutil
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse, JSONResponse
from app.collect import net as net_col
from app.config import get_settings
from app.render import render, uptime_str
from app.sample import Sample
from app.utils.window import window
router = APIRouter(prefix="/api", tags=["overview"])
@router.get("/overview")
async def overview(request: Request):
"""Render the Overview tab fragment: current system state card.
Takes the latest sample from the history store (an empty Sample when
none exists yet), derives vram_pct when the collector left it unset,
and adds interface / wifi data and uptime.
Args:
request: FastAPI request (app.state.store).
Returns:
The rendered overview.html as an HTMLResponse.
"""
store = request.app.state.store
s = store.latest() or Sample()
mem_total = s.mem_total or 0
vram_total = s.vram_total or 0
vram_used = s.vram_used
c = {
"cpu": s.cpu,
"cpu_temp": s.cpu_temp,
"load1": s.load1,
"load5": s.load5,
"load15": s.load15,
"mem_used": s.mem_used,
"mem_total": mem_total,
"mem_pct": s.mem_pct,
"swap_used": s.swap_used,
"swap_total": s.swap_total or 0,
"swap_pct": s.swap_pct,
"gpu": s.gpu,
"gpu_name": s.gpu_name,
"gpu_temp": s.gpu_temp,
"vram_used": vram_used,
"vram_total": vram_total,
"vram_pct": s.vram_pct
or ((vram_used / vram_total * 100) if (vram_total and vram_used is not None) else None),
"battery": s.battery,
"battery_status": s.battery_status,
"ac_online": s.ac_online,
"uptime": uptime_str(time.time() - psutil.boot_time()),
"hostname": socket.gethostname(),
"cores": psutil.cpu_count(logical=True) or 1,
**await asyncio.to_thread(net_col.sample),
}
return HTMLResponse(render("overview.html", c=c))
@router.get("/history")
async def history(request: Request):
"""Serve the ring buffer as chart data (JSON).
The buffer is window-averaged via app.utils.window.window() down to
at most `chart_max_points` points. Every key seen in any window gets
avg/min/max arrays, and each array is padded with None for windows
that lack the key (e.g. the GPU fields before a GPU is detected) so
the arrays stay aligned with the ts array — the charts rely on that.
Args:
request: FastAPI request (app.state.store).
Returns:
JSON with ts (unix seconds) and series: key to {avg, min, max}.
"""
snap = window(request.app.state.store.snapshot(), get_settings().chart_max_points)
ts = [round(t, 1) for t, _ in snap]
keys: set[str] = set()
for _, row in snap:
keys.update(row)
series: dict[str, dict[str, list[Any]]] = {
k: {"avg": [], "min": [], "max": []} for k in keys
}
for _, row in snap:
for k in keys:
agg = row.get(k)
for m in ("avg", "min", "max"):
series[k][m].append(agg[m] if agg else None)
return JSONResponse({"ts": ts, "series": series})