From 21cab463656b3e1068ce66b7a8e105d655680c3f Mon Sep 17 00:00:00 2001 From: Oliver Hofmann Date: Sun, 10 May 2026 11:44:10 +0200 Subject: [PATCH] Remove docs/ from tracking, add to gitignore --- .gitignore | 5 +- .../plans/2026-05-10-log-viewer-autoreload.md | 384 ------------------ ...2026-05-10-log-viewer-autoreload-design.md | 67 --- 3 files changed, 4 insertions(+), 452 deletions(-) delete mode 100644 docs/superpowers/plans/2026-05-10-log-viewer-autoreload.md delete mode 100644 docs/superpowers/specs/2026-05-10-log-viewer-autoreload-design.md diff --git a/.gitignore b/.gitignore index 62a3e64..8a9d64a 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,7 @@ config.json # Generated documents KURZANLEITUNG.tex -KURZANLEITUNG.pdf \ No newline at end of file +KURZANLEITUNG.pdf + +# Internal planning docs +docs/ \ No newline at end of file diff --git a/docs/superpowers/plans/2026-05-10-log-viewer-autoreload.md b/docs/superpowers/plans/2026-05-10-log-viewer-autoreload.md deleted file mode 100644 index 0d446b4..0000000 --- a/docs/superpowers/plans/2026-05-10-log-viewer-autoreload.md +++ /dev/null @@ -1,384 +0,0 @@ -# Log-Viewer und Auto-Reload Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Letzten 10 Zeilen von usage.log/error.log in der Settings-Sektion anzeigen; alle Daten alle 5 Minuten automatisch neu laden; Zeitstempel der letzten Aktualisierung im Header. - -**Architecture:** Neuer Backend-Endpunkt `GET /api/logs/{name}` in `admin.py` liest die letzten 10 Zeilen aus den Logdateien. `SettingsSection` bekommt einen `refreshKey`-Prop, den `App` alle 5 Minuten inkrementiert — das triggert einen neuen `useEffect`-Lauf. `App` hält den `lastUpdated`-Timestamp und zeigt ihn im Header. - -**Tech Stack:** FastAPI (Python), React 18, axios, CSS - ---- - -### Task 1: Backend-Endpunkt `GET /api/logs/{name}` - -**Files:** -- Modify: `backend/admin.py` -- Create: `backend/tests/test_admin_logs.py` - -- [ ] **Schritt 1: Test schreiben** - -Datei `backend/tests/test_admin_logs.py` anlegen: - -```python -import os -import pytest -from fastapi.testclient import TestClient - -os.environ.setdefault("ADMIN_PASSWORD", "test-admin-pw") -os.environ.setdefault("OLLAMA_URL", "http://127.0.0.1:9999") - - -@pytest.fixture -def client(tmp_path): - log_file = tmp_path / "usage.log" - log_file.write_text("\n".join(f"Zeile {i}" for i in range(1, 16)) + "\n") - (tmp_path / "error.log").write_text("Fehler A\nFehler B\n") - os.environ["LOG_FILE"] = str(log_file) - - from database import Base, engine - Base.metadata.drop_all(bind=engine) - Base.metadata.create_all(bind=engine) - - from admin import app - yield TestClient(app, raise_server_exceptions=False) - - Base.metadata.drop_all(bind=engine) - os.environ.pop("LOG_FILE", None) - - -AUTH = {"Authorization": "Bearer test-admin-pw"} - - -def test_logs_usage_returns_last_10_lines(client): - resp = client.get("/api/logs/usage", headers=AUTH) - assert resp.status_code == 200 - lines = resp.json()["lines"] - assert len(lines) == 10 - assert lines[-1] == "Zeile 15" - assert lines[0] == "Zeile 6" - - -def test_logs_error_returns_content(client): - resp = client.get("/api/logs/error", headers=AUTH) - assert resp.status_code == 200 - assert resp.json()["lines"] == ["Fehler A", "Fehler B"] - - -def test_logs_missing_file_returns_empty(client, tmp_path): - os.environ["LOG_FILE"] = str(tmp_path / "nonexistent.log") - resp = client.get("/api/logs/usage", headers=AUTH) - assert resp.status_code == 200 - assert resp.json()["lines"] == [] - - -def test_logs_invalid_name_returns_400(client): - resp = client.get("/api/logs/secret", headers=AUTH) - assert resp.status_code == 400 - - -def test_logs_requires_auth(client): - resp = client.get("/api/logs/usage") - assert resp.status_code == 401 -``` - -- [ ] **Schritt 2: Test zum Scheitern bringen** - -```bash -cd backend -/Users/oliver/Development/Technologien/llm_quota/.venv/bin/python3 -m pytest tests/test_admin_logs.py -v -``` - -Erwartung: alle Tests FAIL mit 404 oder AttributeError (Endpunkt existiert nicht). - -- [ ] **Schritt 3: Endpunkt in `admin.py` implementieren** - -Direkt vor der Static-File-Mount-Zeile (`_dist = ...`) einfügen: - -```python -@app.get("/api/logs/{name}") -async def get_log_lines(name: str, _ = Depends(require_admin_auth)): - if name not in ("usage", "error"): - raise HTTPException(status_code=400, detail="name must be 'usage' or 'error'") - log_file = Path(os.getenv("LOG_FILE", "logs/usage.log")) - path = log_file if name == "usage" else log_file.parent / "error.log" - try: - lines = path.read_text(encoding="utf-8").splitlines() - return {"lines": lines[-10:]} - except FileNotFoundError: - return {"lines": []} -``` - -- [ ] **Schritt 4: Tests laufen lassen** - -```bash -cd backend -/Users/oliver/Development/Technologien/llm_quota/.venv/bin/python3 -m pytest tests/test_admin_logs.py -v -``` - -Erwartung: alle 5 Tests PASS. - -- [ ] **Schritt 5: Gesamte Test-Suite prüfen** - -```bash -cd backend -/Users/oliver/Development/Technologien/llm_quota/.venv/bin/python3 -m pytest tests/ -q -``` - -Erwartung: alle Tests PASS, keine Regressionen. - -- [ ] **Schritt 6: Commit** - -```bash -git add backend/admin.py backend/tests/test_admin_logs.py -git commit -m "Add GET /api/logs/{name} endpoint to admin API" -``` - ---- - -### Task 2: Log-Anzeige in `SettingsSection` - -**Files:** -- Modify: `frontend/src/main.jsx` -- Modify: `frontend/src/styles.css` - -- [ ] **Schritt 1: `refreshKey`-Prop und Log-State in `SettingsSection` ergänzen** - -In `main.jsx`, `SettingsSection`-Signatur und State ändern: - -```jsx -function SettingsSection({ password, refreshKey }) { - // bestehende States beibehalten, neu hinzufügen: - const [usageLog, setUsageLog] = useState([]); - const [errorLog, setErrorLog] = useState([]); -``` - -- [ ] **Schritt 2: Log-Fetch in den bestehenden `useEffect` integrieren** - -Den bestehenden `useEffect` in `SettingsSection` (Zeilen 110–122) ersetzen: - -```jsx - useEffect(() => { - const headers = authHeaders(password); - Promise.all([ - axios.get('/api/settings', { headers }), - axios.get('/api/proxy-info', { headers }), - axios.get('/api/logs/usage', { headers }), - axios.get('/api/logs/error', { headers }), - ]).then(([settingsRes, proxyRes, usageRes, errorRes]) => { - const s = settingsRes.data; - setSettings(s); - setProxyEndpoint(proxyRes.data.endpoint); - setAppVersion(proxyRes.data.version); - setUsageLog(usageRes.data.lines); - setErrorLog(errorRes.data.lines); - fetchModels(s.ollama_url, s.force_model); - }).catch(() => setError('Einstellungen konnten nicht geladen werden.')); - }, [refreshKey]); -``` - -Wichtig: `[refreshKey]` statt `[]` als Dependency-Array, damit ein neuer `refreshKey` einen Re-Fetch auslöst. - -- [ ] **Schritt 3: Log-Anzeige am Ende von `SettingsSection` einfügen** - -Den `return`-Block von `SettingsSection` — direkt vor dem schließenden `` — erweitern: - -```jsx -
-

Nutzungslog (letzte 10 Einträge)

-
-            {usageLog.length > 0 ? usageLog.join('\n') : '— keine Einträge —'}
-          
- {errorLog.length > 0 && ( - <> -

Fehlerlog

-
{errorLog.join('\n')}
- - )} -
-``` - -Der vollständige `return` von `SettingsSection` sieht dann so aus: - -```jsx - return ( -
-

Einstellungen

-
- {/* ... bestehende settings-rows ... */} - {error &&
{error}
} - {saved &&
Gespeichert.
} - -
-
-

Nutzungslog (letzte 10 Einträge)

-
-          {usageLog.length > 0 ? usageLog.join('\n') : '— keine Einträge —'}
-        
- {errorLog.length > 0 && ( - <> -

Fehlerlog

-
{errorLog.join('\n')}
- - )} -
-
- ); -``` - -- [ ] **Schritt 4: CSS ergänzen** - -Am Ende von `frontend/src/styles.css` anhängen: - -```css -.log-section { - margin-top: 24px; - border-top: 1px solid #eee; - padding-top: 16px; -} - -.log-section h3 { - font-size: 13px; - font-weight: 600; - color: #555; - margin: 0 0 6px; - text-transform: uppercase; - letter-spacing: 0.04em; -} - -.log-pre { - background: #1e2a35; - color: #c8d6df; - font-family: 'Menlo', 'Consolas', monospace; - font-size: 11px; - line-height: 1.6; - padding: 10px 14px; - border-radius: 4px; - margin: 0 0 14px; - overflow-x: auto; - white-space: pre; -} - -.log-pre-error { - background: #2d1b1b; - color: #f5a0a0; -} -``` - -- [ ] **Schritt 5: Manueller Browser-Test** - -Dev-Server starten (`./start.sh`), Admin-UI öffnen (`http://localhost:5173`), anmelden. Unter den Einstellungen müssen die letzten 10 Zeilen von `usage.log` erscheinen. Wenn `error.log` leer ist, kein Fehlerlog-Block sichtbar. - -- [ ] **Schritt 6: Commit** - -```bash -git add frontend/src/main.jsx frontend/src/styles.css -git commit -m "Show last 10 log lines in settings section" -``` - ---- - -### Task 3: Auto-Reload alle 5 Minuten + Zeitstempel - -**Files:** -- Modify: `frontend/src/main.jsx` -- Modify: `frontend/src/styles.css` - -- [ ] **Schritt 1: `refreshKey` und `lastUpdated` State in `App` ergänzen** - -Im `App`-Component direkt nach den bestehenden `useState`-Deklarationen: - -```jsx - const [refreshKey, setRefreshKey] = useState(0); - const [lastUpdated, setLastUpdated] = useState(null); -``` - -- [ ] **Schritt 2: `fetchApiKeys` so anpassen, dass `lastUpdated` gesetzt wird** - -Die bestehende `fetchApiKeys`-Funktion in `App` (Zeilen 215–221) ersetzen: - -```jsx - const fetchApiKeys = async () => { - try { - const res = await axios.get('/api/api-keys', { headers: authHeaders(password) }); - setApiKeys(res.data); - setLastUpdated(new Date()); - } catch { - setError('API-Keys konnten nicht geladen werden.'); - } - }; -``` - -- [ ] **Schritt 3: `setInterval` in `App` einbauen** - -Den bestehenden `useEffect` in `App` (Zeilen 210–213) ersetzen: - -```jsx - useEffect(() => { - if (!password) { setLoading(false); return; } - fetchApiKeys().finally(() => setLoading(false)); - - const timer = setInterval(() => { - fetchApiKeys(); - setRefreshKey(k => k + 1); - }, 5 * 60 * 1000); - - return () => clearInterval(timer); - }, [password]); -``` - -- [ ] **Schritt 4: `refreshKey` an `SettingsSection` übergeben** - -In der `return`-Anweisung von `App` den `SettingsSection`-Aufruf anpassen: - -```jsx - -``` - -- [ ] **Schritt 5: Zeitstempel im Header anzeigen** - -Den `header`-Block in `App` ersetzen: - -```jsx -
-

Ollama Proxy Admin

-
- {lastUpdated && ( - - Aktualisiert: {lastUpdated.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })} - - )} - -
-
-``` - -- [ ] **Schritt 6: CSS für Header-Right und Zeitstempel** - -Am Ende von `frontend/src/styles.css` anhängen: - -```css -.header-right { - display: flex; - align-items: center; - gap: 16px; -} - -.last-updated { - font-size: 12px; - color: #95a5a6; -} -``` - -- [ ] **Schritt 7: Manueller Browser-Test** - -Admin-UI öffnen. Nach dem Login: Zeitstempel erscheint oben rechts (HH:MM). Nach 5 Minuten müssen Keys und Logs automatisch aktualisiert werden — Timestamp springt auf neue Uhrzeit. - -Zum Schnelltest: Intervall testweise auf `5000` (5 Sekunden) setzen, prüfen, dann wieder auf `5 * 60 * 1000` zurücksetzen. - -- [ ] **Schritt 8: Commit** - -```bash -git add frontend/src/main.jsx frontend/src/styles.css -git commit -m "Add 5-minute auto-reload and last-updated timestamp to admin UI" -``` diff --git a/docs/superpowers/specs/2026-05-10-log-viewer-autoreload-design.md b/docs/superpowers/specs/2026-05-10-log-viewer-autoreload-design.md deleted file mode 100644 index 0d19cb2..0000000 --- a/docs/superpowers/specs/2026-05-10-log-viewer-autoreload-design.md +++ /dev/null @@ -1,67 +0,0 @@ -# Design: Log-Viewer und Auto-Reload in der Admin-Oberfläche - -## Kontext - -Die Admin-Oberfläche zeigt aktuell nur API-Keys und Einstellungen. Es gibt kein automatisches Aktualisieren und keinen Zugriff auf die Logdateien. Hauptbedürfnis: Das aktuell geladene Ollama-Modell schnell im Blick haben, ohne manuell neu laden zu müssen. - -## Scope - -- Letzten 10 Zeilen von `usage.log` und `error.log` in der Settings-Sektion anzeigen -- Alle Daten (Keys, Settings, Logs) alle 5 Minuten automatisch neu laden -- Zeitstempel der letzten Aktualisierung anzeigen - ---- - -## Backend - -### Neuer Endpunkt: `GET /api/logs/{name}` - -**Datei:** `backend/admin.py` - -**Parameter:** `name` — `usage` oder `error` (andere Werte → 400) - -**Verhalten:** -- Leitet den Pfad aus `LOG_FILE` (Env-Var, Default: `logs/usage.log`) ab -- `error` → selbes Verzeichnis, Dateiname `error.log` -- Liest die letzten 10 Zeilen der Datei -- Gibt `{"lines": ["...", ...]}` zurück -- Datei nicht vorhanden → leeres Array `{"lines": []}` -- Geschützt durch `require_admin_auth` - ---- - -## Frontend - -### Log-Anzeige in `SettingsSection` - -**Datei:** `frontend/src/main.jsx` - -- Neuer Unterabschnitt am Ende der Settings-Karte -- Zwei `
`-Blöcke: `usage.log` (immer) und `error.log` (nur wenn nicht leer)
-- Stil konsistent mit bestehenden Settings-Elementen (dunkler Hintergrund, Monospace-Font)
-- Daten werden beim Mount von `SettingsSection` zusammen mit den übrigen Settings geladen
-
-### Auto-Reload
-
-- `setInterval` im `App`-Component, Intervall: **5 Minuten (300 000 ms)**
-- Ruft dieselben Fetch-Funktionen auf wie beim Login: Keys + Settings + Logs
-- Timer wird beim Unmount (Logout) via `clearInterval` bereinigt
-- Oben rechts in der eingeloggten UI: kleiner Hinweis „Zuletzt aktualisiert: HH:MM"
-
----
-
-## Nicht im Scope
-
-- Live-Streaming / WebSocket
-- Filterung oder Suche in Logs
-- Konfigurierbare Zeilenzahl (fest: 10)
-- Automatisches Scrollen
-
----
-
-## Tests
-
-- `GET /api/logs/usage` gibt die letzten 10 Zeilen zurück
-- `GET /api/logs/error` gibt leeres Array zurück wenn Datei nicht existiert
-- `GET /api/logs/invalid` gibt 400 zurück
-- Manueller Test: Admin-UI öffnen, 5 Minuten warten, Timestamp prüfen