--- phase: quick-261002-k67 plan: 01 subsystem: nextcloud-status tags: [nextcloud, ampel, modul, dashboard-kachel, scheduler, rls] status: complete requires: - module-registry (ModuleGuard, ModuleManage, seedModule) - Freigabestufe Verwalten (quick-261002-icv) provides: - Modul "nextcloud-status" (Kategorie infrastructure) mit Ampel-Kacheln je Cloud - Tabelle NextcloudInstance (RLS, system_read_policy) - stündlicher Prüfauftrag nextcloud-status-poll - Dashboard-Kachel "nextcloud-status" (zweite modulgebundene Kachel) affects: - WIDGET_TYPES / WIDGET_MODULE_SLUGS (@tessera/shared) - docs/mandantentrennung-zugriffsklassifikation.md, FORSYSTEM_ALLOWED_CALL_SITES tech-stack: added: [] patterns: - reine Bewertungsfunktion mit eingereichtem Datum - Stale-while-revalidate-Zwischenspeicher im Speicher (12 h, 15 min Pause nach Fehlschlag) - ein globaler Cron-Auftrag in onApplicationBootstrap ohne Datenbankzugriff bei der Registrierung key-files: created: - apps/api/prisma/migrations/20261002150000_nextcloud_status/migration.sql - apps/api/src/nextcloud-status/ (Rating, Fetch, Release, Service, Controller, Scheduler, Logo-Regeln, DTO, Seed, Modul, je mit Spec) - apps/web/src/lib/nextcloud-status-api.ts - apps/web/src/components/nextcloud-status/ (rating-display, sort-clouds + Test) - apps/web/src/app/(portal)/modules/nextcloud-status/ (layout, page, CloudTile, CloudForm + Tests) - apps/web/src/components/dashboard/widgets/nextcloud-status-widget.tsx (+ Test) modified: - apps/api/prisma/schema.prisma, apps/api/src/app.module.ts - apps/api/src/prisma/rls-access-inventory.spec.ts, docs/mandantentrennung-zugriffsklassifikation.md - apps/api/src/module-registry/module-manage-handlers.spec.ts - packages/shared/src/index.ts, apps/api/src/dashboard/widget-module-map(.spec).ts - Web-Registrierungen (module-loader, module-identity, nav-store, module-tile, module-layouts.test, widget-registry(+Tests), widget-icon, widget-wrapper, (portal)/page(+Test)), de.json, en.json, umlaut-dictionary.ts - CHANGELOG.md, docs/anleitung-anwender.md, docs/anleitung-administration.md decisions: - "Logo-Bytes als bytea an der Zeile; Listen-/Planerabfragen wählen sie nie aus, nur getLogo (D-A)" - "Bewertung beim Lesen in der API; Grundkennung plus Parameter, Übersetzung im Web (D-B)" - "Ein globaler Cron-Auftrag, beim Start ohne Datenbankzugriff registriert (D-C)" - "Zertifikate der Clouds werden geprüft, Fehler erscheint als Nicht erreichbar mit Fehlercode (D-H)" metrics: duration: etwa 1 h 15 min completed: 2026-10-02 actuals: tokens: 52000 tasks: 3 commits: 3 plan_head_before: 7188733f70f88ce7a61c53f76a614c476864bbfd plan_head_after: 87a7b7cbcd86756b1892c14abd77f66744edc969 --- # Phase quick-261002-k67 Plan 01: Modul Nextcloud-Status mit Ampel-Kacheln Neues Modul „Nextcloud-Status“ (Gruppe Infrastruktur): Verwalter tragen Nextcloud-Clouds ihrer Kunden ein, Tessera ruft stündlich `/status.php` ab, bewertet die Version gegen die Daten von endoflife.date (Ampel grün/gelb/rot/grau) und zeigt je Cloud eine Kachel sowie eine Übersichtskachel auf dem Dashboard. ## Was gebaut wurde **Aufgabe 1 (Tracer), Commit 5ef7b0c:** Tabelle `NextcloudInstance` samt Zeilenschutz (Migration 20261002150000, lokal angewendet, `migrate status` aktuell, Drift-Prüfung Exit 0). Reine Funktion `rateNextcloud` (alle Regeln aus L-05 einschließlich der Grenzen 90/91 Tage und Support-Ende-Tag gelb, Tag danach rot), `normalizeCloudUrl`, `parseNextcloudStatus`, `fetchNextcloudStatus` (nur GET `/status.php`, 10 s, 3 Weiterleitungen, 64 KiB, keine Zugangsdaten, nur eigene Fehlerkürzel). `NextcloudReleaseService` als Zwischenspeicher (12 h, veraltete Daten sofort, Erneuerung im Hintergrund, 15 min Pause nach Fehlschlag, geteilte Anfrage). Dienst, Controller (`GET instances`, `POST instances`, `POST instances/:id/check`), Seed, Modul, RLS-Inventar. Web: Modulseite mit Kacheln (Ampelleiste, Pille mit Klartext, Logo oder Initialen, Version, letzte Prüfung), Registrierungen in Loader, Identität (Wolkensymbol), Seitenleisten-Titel, Layout-Test, Texte de/en. **Aufgabe 2, Commit 6698ef1:** Ändern, Entfernen, Logo hochladen/abrufen/entfernen, „Jetzt prüfen“ (`POST instances/check`, statisch vor allen `:id`-Routen) und Einzelprüfung, alle mit `@ModuleManage('nextcloud-status')` und ohne Rollen-Decorator; Logo per Magic Bytes (PNG/JPEG/GIF/WebP, kein SVG, 1 MiB, multer plus Zweitprüfung). Stündlicher Auftrag `nextcloud-status-poll` (`0 * * * *`) in `onApplicationBootstrap` ohne Datenbankzugriff; je Durchlauf ein `forSystem`-Aufruf (nur `id`, `tenantId`), danach jede Cloud an ihren Mandanten gebunden, höchstens vier gleichzeitig, Überlappungsschutz. Web: Sortierung (Kundenname, Status, Version, Support-Ende; je Benutzer in localStorage), Formular (Anlegen/Bearbeiten/Löschen mit Bestätigung/Logo), Verwalten-Knöpfe nur mit `useCanManageModule`. `FORSYSTEM_ALLOWED_CALL_SITES` und die Zugriffsklassifikation nachgezogen. **Aufgabe 3, Commit 87a7b7c:** Dashboard-Kachel `nextcloud-status` (geteilte Typliste, Modulzuordnung `nextcloud-status` → `nextcloud-status`, Registry, Symbol, Rahmenkopf, Katalog nur mit Modulzugriff): Zähler grün/gelb/rot (und „ohne Bewertung“ nur wenn > 0), darunter rote, dann gelbe Clouds mit Grund; Klick öffnet das Modul, im Bearbeitungsmodus keine Links, ruft nie eine Prüfung auf. CHANGELOG, Anwender- und Administrationsanleitung. ## Testergebnisse (ehrlich) | Prüfung | Ergebnis | |---|---| | API-Tests gesamt (`pnpm --filter @tessera/api test`) | 118 Dateien, **2030 Tests grün** | | Web-Tests gesamt (`pnpm --filter @tessera/web test`) | 119 Dateien, **1276 Tests grün** | | `tsc --noEmit` api | grün | | `tsc --noEmit` web | grün | | Biome lint auf den neuen/geänderten Dateien | keine Meldungen auf neuen Dateien; die zehn verbleibenden Warnungen der Gesamtläufe stammen aus bestehenden Widget-Dateien (z. B. Stoppuhr) und wurden nicht angefasst | | Biome check --write | nur auf neuen Dateien angewendet | | Schlüsselabgleich de/en (`nextcloudStatus`, `widgets.nextcloudStatus`) und Prüfung auf Mandanten-Wörter | 74 Schlüssel je Sprache, deckungsgleich, kein Treffer | | Umlaut-Wächter | grün (Allowlist: Neueste, ausstehend, Statusseite, Bildadresse, aktuell) | | rls-coverage, rls-access-inventory | grün (93 Paare, Bereichszeile 0/14/1, Summe 61/264/8, 7 Dateien/8 `forSystem`-Aufrufe) | Neue Tests: Rating (22), Fetch/Parser/URL (35), Release-Cache (10), Service (18), Controller (11), Scheduler (5), Logo-Regeln (6), module-manage-handlers (+9), Web: Seite (13), CloudForm (9), sort-clouds (8), Widget (8) sowie angepasste Aufzählungstests der Widget-Typen. ## Lokaler Stand - Migration lokal angewendet (Container-IP 172.19.0.2), `docker compose up -d --build api web` gebaut, api healthy, web läuft. - API-Log: „Nextcloud-Status module seeded in registry“, „Nextcloud-Status cron job registered: 0 * * * *“, alle neun Routen gemappt (`instances/check` vor `instances/:id`), „No pending migrations to apply“. - Datenbank: `Module`-Zeile `nextcloud-status`, Kategorie `infrastructure`, `isSystem` true; Tabelle `NextcloudInstance` vorhanden. - Nicht gepusht. SUMMARY/STATE/PLAN nicht committiert (macht der Orchestrator). ## Abweichungen vom Plan Keine funktionalen Abweichungen. Drei kleine Anmerkungen: 1. **[Hinweis] Zugriffsklassifikation in zwei Schritten:** Task 1 trug `nextcloud-status.service.ts`/`nextcloudInstance` als `gebunden` ein (0/4/0), Task 2 stellte es wie geplant auf `system-gebunden` (0/14/1) um. Die Zahlen stammen aus der Gate-Schleife (`grep -cE 'tenantPrisma\.[a-zA-Z]*\.'` über die nicht-Spec-Dateien), nicht aus Annahmen. 2. **[Rule 1 - Fehler] Zählerstand im Registry-Test:** Die Erwartung `counted` (44) im bestehenden Registry-Test musste auf 48 steigen (zwölf Typen mal vier Felder); dazu mussten die Aufzählungen aus dem Plan angepasst werden (Katalog-, Registry-, Modulkarten-Tests). Beim ersten Voll-Lauf war zusätzlich `widget-module-map.spec.ts` noch auf einen Modulbezug eingestellt; im selben Task behoben, danach alle Tests grün. 3. **[Hinweis] Ledger für `commits:`:** Der Ledger-Eintrag wurde erst nach dem ersten Commit angelegt und aus dessen Elternkommit (7188733) gebildet; die Zählung (3) entspricht `git rev-list --count 7188733..HEAD`. ## Bekannte Stubs Keine. ## Bedrohungen (Threat Model) Alle als `mitigate` eingestuften Punkte sind umgesetzt und getestet: T-k67-02 (Logo: Magic Bytes, nosniff, Sandbox-CSP, 1 MiB), -03 (`@ModuleManage` ohne Rollen-Decorator, Metadaten-Specs), -04 (`forTenant`, `where { id, tenantId }`, ein einziger `forSystem`-Aufruf mit `select { id, tenantId }`), -05 (nur https, `referrerPolicy="no-referrer"`, API ruft die Logo-Adresse nie ab), -06 (Nebenläufigkeit 4, Überlappungsschutz, Zeitlimits), -07 (`PUBLIC_SELECT` ohne `logoData`, Spec prüft), -08 (statische Route vor `:id`, Reihenfolge-Test), -09 (`normalizeCloudUrl`). T-k67-01 (SSRF auf manuell eingegebene, auch interne Adressen) ist wie geplant akzeptiert und im Code und in der Administrationsanleitung dokumentiert. ## Threat Flags Keine neuen Angriffsflächen außerhalb des Plans. ## Für die Browser-Prüfung (Orchestrator) 1. Als Administrator im Marktplatz „Nextcloud-Status“ aktivieren (falls noch nicht), Freigabe erteilen; die Seite unter `/modules/infrastructure/nextcloud-status` und `/modules/nextcloud-status` öffnen (Seitenleiste, Gruppe Infrastruktur). 2. „Cloud hinzufügen“: eine öffentlich erreichbare Nextcloud eintragen (Kundenname + Adresse, z. B. eine echte Kunden-Cloud). Erwartung: Kachel mit Version, Ampel und Klartext, „Zuletzt geprüft vor …“; Kopfzeile nennt die neueste Nextcloud-Version. Außerdem eine nicht erreichbare Adresse (rot „Nicht erreichbar“) und eine Adresse ohne Nextcloud (rot „Keine gültige Nextcloud-Antwort“) probieren. 3. Logo: einmal PNG hochladen (Kachel zeigt es), einmal https-Bildadresse, „Logo entfernen“ (Initialen); eine Nicht-Bilddatei und eine Datei über 1 MB (Fehlermeldung im Formular). 4. Sortierung: alle vier Optionen, danach neu laden (Auswahl bleibt, im Dunkelmodus ebenfalls prüfen). 5. Verwalten-Stufe: Benutzer nur mit „Benutzen“ anmelden (sieht Kacheln und Sortierung, aber weder „Cloud hinzufügen“, „Jetzt prüfen“, Kachel-Knöpfe noch Bearbeiten), Benutzer mit „Verwalten“ (sieht alles). „Jetzt prüfen“ und Kachel-Prüfung ausprobieren. 6. Dashboard: im Bearbeitungsmodus „Widget hinzufügen“ → „Nextcloud-Status“ (nur mit Modulzugriff im Katalog). Zähler, rote/gelbe Liste mit Grund, Klick öffnet das Modul; kleine Kachel zeigt nur die Zähler. 7. Optional: `docker compose logs api` nach der vollen Stunde — Prüfläufe der Clouds laufen ohne Fehler. ## Self-Check: PASSED - Dateien vorhanden: Migration, `apps/api/src/nextcloud-status/*`, `apps/web/src/app/(portal)/modules/nextcloud-status/*`, Widget und Tests (alle per Commit nachgewiesen). - Commits vorhanden: 5ef7b0c, 6698ef1, 87a7b7c (`git log`), 3 Commits seit 7188733. - Laufender Stand: api healthy, Seed- und Planer-Logzeilen vorhanden, Modul-Zeile in der Datenbank.