97 lines
3.2 KiB
Python
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})
|