Fix all linter issues

This commit is contained in:
Johannes Schriewer 2026-08-30 22:49:19 +02:00
parent fbd4b6c5e8
commit d4a2ace913
10 changed files with 2989 additions and 28 deletions

View file

@ -60,7 +60,7 @@ agent's own shell command line and kills the session.
while the UI switch is on, reaps stale locks by `who` marker on startup) while the UI switch is on, reaps stale locks by `who` marker on startup)
+ lact plugin (shells out to `lact cli`: per-GPU profile dropdown with + lact plugin (shells out to `lact cli`: per-GPU profile dropdown with
set/reload, active profile polled every 5 s, GPU names shortened with set/reload, active profile polled every 5 s, GPU names shortened with
`app/collect/gpu.py:_shorten` like the overview card). `app/collect/gpu.py:shorten` like the overview card).
## Conventions ## Conventions

View file

@ -1 +0,0 @@
from app import routers # noqa: F401

View file

@ -16,7 +16,7 @@ def _read(path: str) -> str | None:
return None return None
def _shorten(name: str) -> str: def shorten(name: str) -> str:
name = re.sub(r"\s*\(rev.*\)$", "", name).strip() name = re.sub(r"\s*\(rev.*\)$", "", name).strip()
groups = re.findall(r"\[([^\]]+)\]", name) groups = re.findall(r"\[([^\]]+)\]", name)
if len(groups) >= 2: if len(groups) >= 2:
@ -42,7 +42,7 @@ def _gpu_name() -> str:
).stdout ).stdout
for line in out.splitlines(): for line in out.splitlines():
if "VGA" in line or "3D controller" in line: if "VGA" in line or "3D controller" in line:
_name_cache = _shorten(line.split(":", 2)[-1].strip()) _name_cache = shorten(line.split(":", 2)[-1].strip())
break break
except (OSError, subprocess.SubprocessError): except (OSError, subprocess.SubprocessError):
pass pass

View file

@ -1,10 +1,11 @@
from functools import lru_cache from functools import lru_cache
from typing import ClassVar
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings): class Settings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="DASH_", env_file=".env", extra="ignore") model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict(env_prefix="DASH_", env_file=".env", extra="ignore")
host: str = "127.0.0.1" host: str = "127.0.0.1"
port: int = 8501 port: int = 8501

View file

@ -39,6 +39,10 @@ async def lifespan(app: FastAPI):
pass pass
async def index():
return HTMLResponse(render("index.html", hostname=socket.gethostname()))
def create_app() -> FastAPI: def create_app() -> FastAPI:
app = FastAPI(title="Dashboard", lifespan=lifespan) app = FastAPI(title="Dashboard", lifespan=lifespan)
app.mount("/static", StaticFiles(directory=BASE / "static"), name="static") app.mount("/static", StaticFiles(directory=BASE / "static"), name="static")
@ -46,10 +50,7 @@ def create_app() -> FastAPI:
app.include_router(r) app.include_router(r)
for r in PLUGIN_ROUTERS: for r in PLUGIN_ROUTERS:
app.include_router(r) app.include_router(r)
_ = app.get("/", include_in_schema=False)(index)
@app.get("/", include_in_schema=False)
async def index():
return HTMLResponse(render("index.html", hostname=socket.gethostname()))
return app return app

View file

@ -1,12 +1,12 @@
import asyncio import asyncio
import re import re
import time import time
from typing import Any from typing import Annotated, Any
from fastapi import APIRouter, Form from fastapi import APIRouter, Form
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from app.collect.gpu import _shorten from app.collect.gpu import shorten
from app.plugins.base import Plugin from app.plugins.base import Plugin
from app.render import render from app.render import render
@ -55,7 +55,7 @@ def _parse_gpus(out: str) -> list[dict[str, str]]:
gpu_type = re.search(r"\[([^\]]*)\]\s*$", rest) gpu_type = re.search(r"\[([^\]]*)\]\s*$", rest)
gpus.append({ gpus.append({
"id": m.group(1), "id": m.group(1),
"name": _shorten(name.group(1)) if name else rest.strip(), "name": shorten(name.group(1)) if name else rest.strip(),
"type": gpu_type.group(1) if gpu_type else "", "type": gpu_type.group(1) if gpu_type else "",
}) })
return gpus return gpus
@ -126,7 +126,7 @@ async def state():
@router.post("/set") @router.post("/set")
async def set_profile(gpu_id: str = Form(...), profile: str = Form(...)): async def set_profile(gpu_id: Annotated[str, Form()], profile: Annotated[str, Form()]):
async with _set_lock: async with _set_lock:
data = await _gather(with_profiles=True, force_gpus=True) data = await _gather(with_profiles=True, force_gpus=True)
if data["error"]: if data["error"]:
@ -145,7 +145,7 @@ async def set_profile(gpu_id: str = Form(...), profile: str = Form(...)):
@router.post("/reload") @router.post("/reload")
async def reload(gpu_id: str | None = Form(None)): async def reload(gpu_id: Annotated[str | None, Form()] = None):
_ = gpu_id _ = gpu_id
return HTMLResponse(await _skeleton(message="profiles refreshed")) return HTMLResponse(await _skeleton(message="profiles refreshed"))

View file

@ -1,4 +1,4 @@
from typing import Any from typing import Annotated, Any
import httpx import httpx
from fastapi import APIRouter, Form from fastapi import APIRouter, Form
@ -12,7 +12,7 @@ router = APIRouter(prefix="/api/plugins/llamacpp", tags=["plugins"])
def _headers(settings: Settings) -> dict[str, str]: def _headers(settings: Settings) -> dict[str, str]:
h = {} h: dict[str, str] = {}
if settings.llama_api_key: if settings.llama_api_key:
h["Authorization"] = f"Bearer {settings.llama_api_key}" h["Authorization"] = f"Bearer {settings.llama_api_key}"
return h return h
@ -37,6 +37,7 @@ async def gather_status() -> dict[str, Any]:
"models": [], "models": [],
"error": None, "error": None,
} }
models: list[dict[str, str | bool | float]] = []
try: try:
async with _client() as client: async with _client() as client:
try: try:
@ -62,12 +63,13 @@ async def gather_status() -> dict[str, Any]:
done = sum(p.get("done", 0) for p in prog.values()) done = sum(p.get("done", 0) for p in prog.values())
total = sum(p.get("total", 1) for p in prog.values()) total = sum(p.get("total", 1) for p in prog.values())
item["progress"] = round(done / total * 100, 1) if total else 0.0 item["progress"] = round(done / total * 100, 1) if total else 0.0
status["models"].append(item) models.append(item)
status["models"].sort(key=lambda m: m["id"]) models.sort(key=lambda m: m["id"])
except httpx.HTTPError as e: except httpx.HTTPError as e:
status["error"] = f"unreachable: {e.__class__.__name__}" status["error"] = f"unreachable: {e.__class__.__name__}"
except Exception as e: # noqa except Exception as e: # noqa
status["error"] = str(e)[:200] status["error"] = str(e)[:200]
status["models"] = models
return status return status
@ -117,7 +119,7 @@ async def state():
@router.post("/load") @router.post("/load")
async def load(model: str = Form(...)): async def load(model: Annotated[str, Form()]):
ok, err = await _action("/models/load", model) ok, err = await _action("/models/load", model)
return HTMLResponse( return HTMLResponse(
await _skeleton( await _skeleton(
@ -128,7 +130,7 @@ async def load(model: str = Form(...)):
@router.post("/unload") @router.post("/unload")
async def unload(model: str = Form(...)): async def unload(model: Annotated[str, Form()]):
ok, err = await _action("/models/unload", model) ok, err = await _action("/models/unload", model)
return HTMLResponse( return HTMLResponse(
await _skeleton( await _skeleton(

View file

@ -2,7 +2,7 @@ import asyncio
import json import json
import os import os
import signal import signal
from typing import Any from typing import Annotated, Any, cast
from fastapi import APIRouter, Form from fastapi import APIRouter, Form
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
@ -45,7 +45,7 @@ async def _list() -> tuple[list[dict[str, Any]], str]:
return [], "could not parse systemd-inhibit output" return [], "could not parse systemd-inhibit output"
if not isinstance(data, list): if not isinstance(data, list):
return [], "unexpected systemd-inhibit output" return [], "unexpected systemd-inhibit output"
items: list[dict[str, Any]] = [e for e in data if isinstance(e, dict)] items: list[dict[str, Any]] = [e for e in cast("list[Any]", data) if isinstance(e, dict)]
return items, "" return items, ""
@ -155,7 +155,7 @@ async def state():
@router.post("/toggle") @router.post("/toggle")
async def toggle(on: str | None = Form(None)): async def toggle(on: Annotated[str | None, Form()] = None):
async with _toggle_lock: async with _toggle_lock:
if on and _holder is None: if on and _holder is None:
err = await _acquire() err = await _acquire()

View file

@ -1,4 +1,4 @@
from typing import Any from typing import Annotated, Any
from fastapi import APIRouter, Form from fastapi import APIRouter, Form
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
@ -95,10 +95,10 @@ async def service_detail(unit: str):
@router.post("/{unit}/action") @router.post("/{unit}/action")
async def service_action( async def service_action(
unit: str, unit: str,
action: str = Form(...), action: Annotated[str, Form()],
q: str = Form(""), q: Annotated[str, Form()] = "",
sort: str = Form("name"), sort: Annotated[str, Form()] = "name",
order: str = Form("asc"), order: Annotated[str, Form()] = "asc",
): ):
error = None error = None
try: try:

File diff suppressed because it is too large Load diff