231bd5e47f
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
296 lines
16 KiB
Markdown
296 lines
16 KiB
Markdown
---
|
||
phase: quick-260923-dhh
|
||
plan: 01
|
||
subsystem: infrastructure
|
||
tags: [proxmox, pve, pbs, pmg, undici, scheduler, rls, module-registry, nestjs, next-intl]
|
||
dependency-graph:
|
||
requires: []
|
||
provides: [proxmox-module, proxmox-server-model, proxmox-background-poller]
|
||
affects: [apps/api/src/proxmox, apps/web/src/app/(portal)/modules/proxmox, apps/web/src/lib/proxmox-api.ts]
|
||
tech-stack:
|
||
added: []
|
||
patterns:
|
||
- "undiciFetch statt globalem fetch fuer einen bedingten TLS-Dispatcher (zweites, unabhaengiges Auftreten nach icon-discovery.service.ts)"
|
||
- "Nur-Lese-Riegel per Quelltext-Analyse (proxmox-nur-lesen.spec.ts), Vorbild rls-access-inventory.spec.ts"
|
||
- "Scheduler kombiniert DkvSchedulerService-Mandanten-Fan-out mit TenderSchedulerService-onApplicationBootstrap-Timing"
|
||
- "select ohne Geheimnisfelder statt nachtraeglicher Maskierung"
|
||
key-files:
|
||
created:
|
||
- apps/api/prisma/migrations/20260923140000_proxmox_server/migration.sql
|
||
- apps/api/src/proxmox/proxmox.types.ts
|
||
- apps/api/src/proxmox/proxmox-auth.ts
|
||
- apps/api/src/proxmox/proxmox-client.service.ts
|
||
- apps/api/src/proxmox/proxmox-normalize.ts
|
||
- apps/api/src/proxmox/proxmox.service.ts
|
||
- apps/api/src/proxmox/proxmox-scheduler.service.ts
|
||
- apps/api/src/proxmox/proxmox.controller.ts
|
||
- apps/api/src/proxmox/proxmox.module.ts
|
||
- apps/api/src/proxmox/proxmox.seed.ts
|
||
- apps/api/src/proxmox/dto/proxmox-server.dto.ts
|
||
- apps/api/src/proxmox/proxmox-nur-lesen.spec.ts
|
||
- apps/web/src/app/(portal)/modules/proxmox/page.tsx
|
||
- apps/web/src/app/(portal)/modules/proxmox/layout.tsx
|
||
- apps/web/src/app/(portal)/modules/proxmox/components/ServerCard.tsx
|
||
- apps/web/src/app/(portal)/modules/proxmox/settings/page.tsx
|
||
- apps/web/src/app/(portal)/modules/proxmox/settings/components/ServerForm.tsx
|
||
- apps/web/src/lib/proxmox-api.ts
|
||
modified:
|
||
- apps/api/prisma/schema.prisma
|
||
- apps/api/src/app.module.ts
|
||
- apps/api/src/prisma/rls-access-inventory.spec.ts
|
||
- apps/web/src/lib/module-loader.ts
|
||
- apps/web/src/messages/de.json
|
||
- apps/web/src/messages/en.json
|
||
- apps/web/src/messages/umlaut-dictionary.ts
|
||
- docs/mandantentrennung-zugriffsklassifikation.md
|
||
- docs/anleitung-entwicklung.md
|
||
- docs/anleitung-anwender.md
|
||
decisions:
|
||
- "D-01 bis D-11 aus dem Plan woertlich umgesetzt, keine Abweichung."
|
||
- "proxmoxGet uebergibt bewusst KEIN method-Feld an undiciFetch (GET ist der Grundwert) — dadurch ist loginTicket() in proxmox-auth.ts die einzige Stelle, die ein Anfrageverfahren explizit uebergibt, und proxmox-nur-lesen.spec.ts kann das maschinell auf genau EINS pruefen."
|
||
- "Ticket-Erneuerung sitzt je POLL-DURCHLAUF, nicht je Aufruf: ein PBS-Durchlauf mit mehreren Folgeabfragen (Belegung + je Datenspeicher Sicherungen) loggt sich bei 401 hoechstens einmal neu ein, nicht einmal je Anfrage."
|
||
- "proxmox.service.ts ist der EINZIGE forSystem()-Aufrufer des Moduls (loadActiveServersForScheduler) — in FORSYSTEM_ALLOWED_CALL_SITES eingetragen, Stand von ProxmoxServer auf system-gebunden gehoben."
|
||
- "docs/anwenderhandbuch.md aus dem Plan existiert nicht im Repo — der echte Dateiname ist docs/anleitung-anwender.md; dort den Proxmox-Abschnitt eingefuegt (Rule 3)."
|
||
metrics:
|
||
duration: "~5h (Session unterbrochen und fortgesetzt)"
|
||
completed: 2026-09-23
|
||
actuals:
|
||
tokens: 50829
|
||
tasks: 7
|
||
commits: 7
|
||
plan_head_before: ec9c779
|
||
status: complete
|
||
---
|
||
|
||
# Quick 260923-dhh: Proxmox-Modul (PVE/PBS/PMG) — nur beobachten Summary
|
||
|
||
Vollstaendiges Proxmox-Modul (Datenbank, Dienst, API, Hintergrundabfrage je Mandant, Einstellungsseite, Modulseite, Dokumentation) — PVE/PBS/PMG werden per API-Token (PVE/PBS) oder Ticket-Anmeldung (alle drei) nur gelesen, kein Weg im Modul veraendert je etwas bei Proxmox.
|
||
|
||
## Gemessene Torzahlen
|
||
|
||
| Tor | Ausgangswert (23.09., vor Beginn) | Endstand (nach Aufgabe 7) |
|
||
|---|---|---|
|
||
| `pnpm --filter @tessera/api test` | 1240 Tests, 77 Dateien | **1311 Tests, 82 Dateien** |
|
||
| `pnpm --filter @tessera/web test` | 693 Tests, 82 Dateien | **708 Tests, 84 Dateien** |
|
||
| `rls-coverage.spec.ts` / `rls-access-inventory.spec.ts` | 5 / 30 | **5 / 30** (unveraendert gruen) |
|
||
| `proxmox-nur-lesen.spec.ts` | (existierte nicht) | **2 Tests, gruen** |
|
||
| `pnpm type-check` | 4/4 | **4/4** |
|
||
| `pnpm lint` | 5/5 | **5/5** |
|
||
| Biome-Warnungen in `apps/web` | 53 | **53** (exakt unveraendert) |
|
||
|
||
## Performance
|
||
|
||
- **Duration:** ~5h (inklusive einer Unterbrechung durch Nutzungslimit, an derselben Stelle fortgesetzt)
|
||
- **Tasks:** 7/7
|
||
- **Files modified:** 34 (18 neu, 16 geaendert)
|
||
|
||
## Accomplishments
|
||
|
||
- `ProxmoxServer`/`ProxmoxServerStatus` mit RLS (`tenant_isolation_policy` auf beiden,
|
||
`system_read_policy` zusaetzlich auf `ProxmoxServer` fuer den Planer-Startpfad)
|
||
- `proxmox-auth.ts` als einzige Stelle, die Kopfzeilen/Cookies baut: Token-Schema je Produkt
|
||
(PVE `=`, PBS `:`, PMG lehnt ab) und Ticket-Anmeldung (die einzige nicht-lesende Anfrage
|
||
des Moduls)
|
||
- `proxmox-client.service.ts`/`proxmox-normalize.ts`: nachsichtige Fehler-/Feldbehandlung,
|
||
sieben stabile Fehlerschluessel, nie ein Wurf bei unerwarteter Form
|
||
- `proxmox-scheduler.service.ts`: ein Cron-Auftrag je Mandant (`proxmox-poll:<tenantId>`),
|
||
`onApplicationBootstrap`, Abfrageintervall = kleinstes `pollIntervalMin` der aktiven Server
|
||
- Einstellungsseite (anlegen/bearbeiten/loeschen/testen) und Modulseite (Serverliste mit
|
||
produktabhaengiger Auslastung, `null` immer als „unbekannt")
|
||
- Anwenderhandbuch- und Entwicklungsanleitung-Abschnitte, Zugriffsklassifikation vollstaendig
|
||
nachgezogen
|
||
|
||
## Task Commits
|
||
|
||
Jede Aufgabe wurde einzeln committet:
|
||
|
||
1. **Aufgabe 1: PVE per Token, Ende-zu-Ende** — `3a1bfd9` (feat)
|
||
2. **Aufgabe 2: Benutzer/Passwort, Fehlerklassen, Nur-Lesen-Riegel** — `4f8a368` (test)
|
||
3. **Aufgabe 3: PBS und PMG auswerten** — `998aba9` (feat)
|
||
4. **Aufgabe 4: Hintergrundabfrage je Mandant, Verbindungstest** — `fccaf8d` (feat)
|
||
5. **Aufgabe 5: Einstellungsseite (anlegen, bearbeiten, loeschen, testen)** — `723cf68` (feat)
|
||
6. **Aufgabe 6: Modulseite mit Auslastung** — `06fcdc0` (feat)
|
||
7. **Aufgabe 7: Dokumentation und Nachmessung aller Tore** — `3091b04` (docs)
|
||
|
||
_Kein separater Metadaten-Commit — STATE.md/SUMMARY.md werden laut Auftrag nicht committet._
|
||
|
||
## Files Created/Modified
|
||
|
||
Siehe `key-files` im Frontmatter — vollstaendige Liste, hier die wichtigsten:
|
||
|
||
- `apps/api/prisma/migrations/20260923140000_proxmox_server/migration.sql` — RLS-Migration,
|
||
von Hand geschrieben (Vorbild `20260923120000_dashboard_tabs`)
|
||
- `apps/api/src/proxmox/proxmox-client.service.ts` — `proxmoxGet`, `classifyFailure`,
|
||
`parseJsonLenient`
|
||
- `apps/api/src/proxmox/proxmox-auth.ts` — `buildTokenAuthHeader`, `loginTicket`,
|
||
`buildTicketCookieHeader`
|
||
- `apps/api/src/proxmox/proxmox-normalize.ts` — `normalizePve`/`normalizePbs`/`normalizePmg`
|
||
plus `readNumber`/`readText`/`readBool`/`readList`
|
||
- `apps/api/src/proxmox/proxmox.service.ts` — CRUD, Poll-Logik, Zehn-Sekunden-Sperre,
|
||
`loadActiveServersForScheduler` (einziger `forSystem()`-Aufruf)
|
||
- `apps/api/src/proxmox/proxmox-scheduler.service.ts` — Planer je Mandant
|
||
- `apps/web/src/app/(portal)/modules/proxmox/components/ServerCard.tsx` — einzige Stelle,
|
||
die einen Messwert in Text verwandelt
|
||
|
||
## Decisions Made
|
||
|
||
Siehe `decisions` im Frontmatter. Zusaetzlich zwei technische Entwurfsentscheidungen, die der
|
||
Plan nicht bis auf diese Ebene vorschrieb:
|
||
|
||
- **Signatur `proxmoxGet(target, path)`:** `target` traegt fertige Kopfzeilen
|
||
(`{ baseUrl, tlsRejectUnauthorized, headers }`), gebaut ausschliesslich von `proxmox-auth.ts`
|
||
— der Klient selbst kennt keine Anmeldeform, nur HTTP-Transport und Fehlerklassifikation.
|
||
- **`proxmox-nur-lesen.spec.ts` erkennt Aufrufformen ueber Klammertiefen-Bilanzierung**
|
||
(nicht per einfachem Zeilen-Regex), weil Proxmox-Pfade und `proxmoxGet(`/`getWithRetry(`-
|
||
Aufrufe im Quelltext ueber mehrere Zeilen verteilt sind.
|
||
|
||
## Deviations from Plan
|
||
|
||
### Auto-fixed Issues
|
||
|
||
**1. [Rule 3 - Blocking] `docs/anwenderhandbuch.md` existiert nicht im Repo**
|
||
- **Found during:** Aufgabe 7
|
||
- **Issue:** Das Plan-Frontmatter nennt `docs/anwenderhandbuch.md` als zu aendernde Datei; diese
|
||
Datei gibt es im Repository nicht. Der tatsaechliche Anwenderhandbuch-Dateiname ist
|
||
`docs/anleitung-anwender.md` (bestaetigt per `git log --diff-filter=A`).
|
||
- **Fix:** Den Proxmox-Abschnitt in `docs/anleitung-anwender.md` eingefuegt statt eine neue,
|
||
falsch benannte Datei anzulegen.
|
||
- **Files modified:** `docs/anleitung-anwender.md`
|
||
- **Verification:** Datei existiert, Abschnitt „Proxmox" lesbar, Modulzahl „vier" auf „fuenf"
|
||
korrigiert.
|
||
- **Committed in:** `3091b04` (Aufgabe-7-Commit)
|
||
|
||
**2. [Rule 3 - Blocking] Umlaut-Regressionswaechter (`umlaut-guard.spec.ts`) schlug fehl**
|
||
- **Found during:** Aufgabe 5 und erneut Aufgabe 6
|
||
- **Issue:** Neue, bereits korrekte deutsche Woerter mit „ss" (`bewusst`, `gemessene`,
|
||
`Messung`, `Prozessorlast`) in den neuen `de.json`-Texten wurden vom Waechter als
|
||
moegliche ae/oe/ue/ss-Ersatzschreibung markiert, weil sie noch nicht auf der Positivliste
|
||
standen.
|
||
- **Fix:** Alle vier Woerter zu `UMLAUT_ALLOWLIST` in `apps/web/src/messages/umlaut-dictionary.ts`
|
||
hinzugefuegt (kein Ersatzschreibung — bereits korrektes Deutsch).
|
||
- **Files modified:** `apps/web/src/messages/umlaut-dictionary.ts`
|
||
- **Verification:** `umlaut-guard.spec.ts` gruen, `pnpm --filter @tessera/web test` vollstaendig
|
||
gruen.
|
||
- **Committed in:** `723cf68` (Aufgabe 5), `06fcdc0` (Aufgabe 6)
|
||
|
||
**3. [Rule 3 - Blocking] `proxmox-nur-lesen.spec.ts` erkannte den `getWithRetry`-Umschlag nicht**
|
||
- **Found during:** Aufgabe 3 (beim Einbau der PBS-Mehrfachabfrage)
|
||
- **Issue:** Der urspruengliche Riegel erkannte Proxmox-Pfade nur innerhalb direkter
|
||
`proxmoxGet(...)`-Aufrufe; nach der Extraktion der Ticket-Erneuerung in einen privaten
|
||
Umschlag `getWithRetry()` (Aufgabe 2/3) lagen alle Pfade jetzt in dessen Argumenten, nicht
|
||
mehr direkt in `proxmoxGet(...)`.
|
||
- **Fix:** Die erlaubte Aufrufform-Liste um `getWithRetry` erweitert (dokumentierte Ausnahme,
|
||
selbst durch dieselbe erste Aussage des Riegels abgesichert: `getWithRetry` ruft
|
||
ausschliesslich `proxmoxGet`).
|
||
- **Files modified:** `apps/api/src/proxmox/proxmox-nur-lesen.spec.ts`
|
||
- **Verification:** Beide Aussagen des Riegels gruen, bewusster Test bestaetigt weiterhin genau
|
||
eine `undiciFetch`-Methodenstelle.
|
||
- **Committed in:** `998aba9` (Aufgabe 3)
|
||
|
||
---
|
||
|
||
**Total deviations:** 3 auto-fixed (alle Rule 3 — blockierende Fehler beim Ausfuehren, keine
|
||
davon eine architektonische Entscheidung)
|
||
**Impact on plan:** Keine Abweichung vom fachlichen Umfang des Plans; alle drei Korrekturen
|
||
waren notwendig, damit die vom Plan selbst verlangten Tore (Aufgabe 7: alle Testsuiten gruen)
|
||
ueberhaupt erreichbar waren.
|
||
|
||
## Issues Encountered
|
||
|
||
Die Ausfuehrung wurde durch ein Nutzungslimit mitten in Aufgabe 4 unterbrochen (nach dem
|
||
Schreiben von `proxmox-scheduler.service.ts` und dem Wiring in `proxmox.controller.ts`/
|
||
`proxmox.module.ts`, vor dem Schreiben der zugehoerigen Testdatei). Nach Fortsetzung wurde der
|
||
Stand anhand von `git status`/`git log` verifiziert und exakt an der protokollierten Stelle
|
||
weitergearbeitet — keine Wiederholung bereits committeter Aufgaben.
|
||
|
||
## User Setup Required
|
||
|
||
**Es gibt in dieser Umgebung keinen echten PVE-/PBS-/PMG-Server.** Alle Tests laufen gegen
|
||
erfundene Antworten in der von der Recherche dokumentierten Form (`vi.mock('undici', …)`).
|
||
Folgende Annahmen der Recherche sind vor dem ersten echten Test explizit zu bestaetigen bzw.
|
||
bei Abweichung an genau einer Stelle nachzuziehen:
|
||
|
||
- **Annahme A2 — Ticket-Cookie-Namen fuer PBS/PMG:** `PBSAuthCookie`/`PMGAuthCookie` sind aus
|
||
dem PVE-Muster ABGELEITET, nicht aus Primaerdoku bestaetigt. Nachzuziehende Stelle:
|
||
`TICKET_COOKIE_NAME` in `apps/api/src/proxmox/proxmox-auth.ts`.
|
||
- **Annahme A3 — PBS-Belegungs-/Snapshot-Feldnamen:** `store`/`total`/`used`/`avail` und
|
||
`backup-time`/`verification` sind aus Forenbelegen abgeleitet. Nachzuziehende Stelle:
|
||
`PBS_USAGE_FIELDS`/`PBS_SNAPSHOT_FIELDS` in `apps/api/src/proxmox/proxmox-normalize.ts`
|
||
(mehrere plausible Namen je Feld moeglich, der Leser nimmt den ersten vorhandenen).
|
||
- **Annahme A5 — PMG-Statistikfelder:** `count_in`/`count_out`/`spamcount_in`/`spamcount_out`/
|
||
`viruscount_in`/`viruscount_out` sind aus `pmgsh`-Community-Belegen abgeleitet.
|
||
Nachzuziehende Stelle: `PMG_STATS_FIELDS` in `apps/api/src/proxmox/proxmox-normalize.ts`.
|
||
- **NUR-LESE-Rollen am Proxmox-Server selbst anlegen** (aus dem Plan-Frontmatter
|
||
`user_setup`, unveraendert offen): PVE `PVEAuditor`, PBS `Audit`/`DatastoreAudit`,
|
||
PMG `Auditor` — je Produkt fuer den Zugang, den Tessera nutzt.
|
||
|
||
Weicht die Wirklichkeit an einer dieser Stellen ab, zeigt die Modulseite dank der
|
||
nachsichtigen Leser „unbekannt" statt eines Absturzes, und die gekuerzte Rohantwort bleibt im
|
||
Zwischenlager erhalten (`rawSample`, bis 20 000 Zeichen) — der Nutzer sieht darin, wie das
|
||
Feld tatsaechlich heisst.
|
||
|
||
## Known Stubs
|
||
|
||
Keine — jede in `<must_haves>` genannte Wahrheit ist durch mindestens einen automatisierten
|
||
Test belegt (siehe Aufgaben 1–6). Die drei oben genannten Annahmen sind keine Stubs, sondern
|
||
dokumentierte, noch nicht am echten Server bestaetigte Feldnamen — die Auswertung fuer sie ist
|
||
vollstaendig gebaut, nur ihre exakten externen Namen sind ungeprueft.
|
||
|
||
## Next Phase Readiness
|
||
|
||
- Das Modul ist vollstaendig gebaut und alle automatisierten Tore sind gruen; die
|
||
Dashboard-Kachel (D-11) ist bewusst nicht Teil dieses Auftrags und folgt separat
|
||
(`WIDGET_TYPES`/`WIDGET_MODULE_SLUGS`/`registerWidget`, siehe
|
||
`docs/anleitung-entwicklung.md`, Abschnitt „Eine Kachel zum Modul").
|
||
- **Blocker fuer den naechsten Schritt:** keiner auf Code-Ebene. Der Nutzer muss das Modul
|
||
gegen mindestens einen echten PVE-/PBS-/PMG-Server pruefen (siehe „User Setup Required"),
|
||
bevor die drei Annahmen als bestaetigt gelten koennen.
|
||
- Container wurden in dieser Ausfuehrung bewusst NICHT neu gebaut/neu gestartet und es wurde
|
||
keine Browser-Pruefung durchgefuehrt (Auftragsvorgabe) — das uebernimmt der Nutzer bzw. eine
|
||
spaetere Sitzung.
|
||
|
||
---
|
||
*Phase: quick-260923-dhh*
|
||
*Completed: 2026-09-23*
|
||
|
||
## Self-Check: PASSED
|
||
|
||
All 24 files listed under `key-files` (created + modified) verified present on disk. All 7
|
||
task commits (`3a1bfd9`, `4f8a368`, `998aba9`, `fccaf8d`, `723cf68`, `06fcdc0`, `3091b04`)
|
||
verified present in `git log`.
|
||
|
||
## Nachbesserungen aus dem Rundgang
|
||
|
||
Drei Befunde aus dem menschlichen Browser-Rundgang zu diesem Modul wurden behoben — Details,
|
||
Tasks und Tests in einem eigenen Quick-Task:
|
||
[260923-ku6-drei-nachbesserungen-aus-dem-browser-run](../260923-ku6-drei-nachbesserungen-aus-dem-browser-run/260923-ku6-SUMMARY.md)
|
||
(Commits `710034c`, `f1bb7f7`).
|
||
|
||
**Befund 1 (wichtig): „Verbindung testen" pruefte den gespeicherten Stand, nicht das
|
||
Formular.** Eine im Formular abgeschaltete Zertifikatspruefung oder ein neu eingetipptes
|
||
Token-/Passwort-Geheimnis wurden vom Test ignoriert und griffen erst nach „Speichern" — eine
|
||
Falle fuer den naheliegenden Ablauf (eintippen, testen, dann erst speichern). Behoben durch ein
|
||
neues `TestProxmoxServerDto` samt Merge-Baustein `resolveEffectiveTestServer` in
|
||
`ProxmoxService`: normale Formularfelder gewinnen immer (auch wenn absichtlich geleert),
|
||
Geheimnisfelder behalten die bestehende „leer gelassen -> gespeicherten Wert weiterverwenden"-
|
||
Regel aus `updateServer`, weil `ServerForm` sie beim Laden nie aus der Datenbank vorbefuellt.
|
||
Neue Route `POST servers/test` (ohne `:id`) deckt denselben Test waehrend der Neuanlage ab, wo
|
||
es noch keinen gespeicherten Server gibt; der Testen-Knopf steht jetzt immer zur Verfuegung,
|
||
nicht mehr erst nach dem ersten Speichern.
|
||
|
||
**Befund 2 (wichtig): falsche Meldung fuer „noch nie abgefragt".** Ein frisch angelegter
|
||
Server zeigte „Letzte Abfrage: unbekannt" UND faelschlich „Ein unerwarteter Fehler ist
|
||
aufgetreten" — die leere Zwischenlagerzeile aus `createServer` hat `reachable: false` und
|
||
`errorKind: null`, was bisher blind in die Fehleruebersetzung `unbekannt` lief. Behoben durch
|
||
einen eigenen, ruhigen Zustand fuer `status.lastPolledAt === null`, der auf „Jetzt
|
||
aktualisieren" verweist; die bestehenden Fehlermeldungen (inkl. `unbekannt` fuer echte
|
||
unbekannte Fehler) bleiben fuer bereits abgefragte, aber nicht erreichbare Server unveraendert.
|
||
|
||
**Befund 3 (kosmetisch): die Adresse wurde in Grossbuchstaben angezeigt.** Die Klasse
|
||
`uppercase` sass auf der ganzen Statuszeile statt nur auf dem Produktkuerzel und faerbte
|
||
dadurch auch die Adresse gross. Jetzt nur noch auf dem Produktkuerzel (`<span>`).
|
||
|
||
**Zahlen nach der Nachbesserung:** api 1311 → 1316 Tests, web 708 → 712 Tests, type-check
|
||
4/4, lint 5/5, Biome `apps/web` weiterhin exakt 53 Warnungen. Container wurden nicht neu
|
||
gebaut, keine Browser-Pruefung in diesem Lauf (macht der Orchestrator danach).
|