feat(admin): LLM-/GPU-Status-Karte (read-only) — Plan-Schritt 2

- app/admin_llm.py: read-only Statusabfragen ohne sudo (docker ps, ollama ps,
  nvidia-smi, systemctl --user) mit sicheren Defaults bei fehlenden Tools.
- GET /api/admin/llm/status (require_admin): Backend, Modell, Backend-Status,
  geladene Ollama-Modelle, GPU-Auslastung, Gateway-Dienst-Status.
- Admin Status-Tab: LLM-Backend-Karte mit GPU-Balken.
- Tests: Auth-Gate + Antwortschema (160 grün).
- Doku §7.5: Status-Tab um LLM/GPU-Karte ergänzt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-20 18:33:06 +02:00
commit 97ae0a5d34
6 changed files with 216 additions and 3 deletions

View file

@ -0,0 +1,41 @@
"""Tests für den read-only LLM-/System-Status (Admin-Panel).
Getestet: GET /api/admin/llm/status Auth-Gate + Antwortschema. Die Status-Helfer
rufen externe Tools (docker/ollama/nvidia-smi/systemctl) auf; fehlen sie in der
Testumgebung, liefern sie sichere Defaults -> der Endpunkt bleibt 200.
"""
import pytest
from fastapi.testclient import TestClient
from app.main import app
from app.config import settings
client = TestClient(app)
ADMIN = "test-admin-key"
ADM_HDR = {"X-Admin-Key": ADMIN}
@pytest.fixture(autouse=True)
def _setup(monkeypatch):
monkeypatch.setattr(settings, "admin_api_key", ADMIN)
monkeypatch.setattr(settings, "auth_enabled", False)
yield
def test_status_requires_admin():
# Ohne Admin-Key (Auth aus -> anonym, kein Admin) -> 401.
assert client.get("/api/admin/llm/status").status_code == 401
def test_status_schema():
resp = client.get("/api/admin/llm/status", headers=ADM_HDR)
assert resp.status_code == 200
data = resp.json()
for key in ("backend", "model", "base_url", "llamacpp_running",
"ollama_reachable", "ollama_loaded", "gpus", "gateway_service_active"):
assert key in data, key
assert isinstance(data["ollama_loaded"], list)
assert isinstance(data["gpus"], list)
# backend wird aus der base_url abgeleitet.
assert data["backend"] in ("ollama", "llamacpp", "unknown")