# Roadmap Geplante Reihenfolge der nächsten Commits, aufbauend auf dem lauffähigen Grundgerüst aus Commit 1. ## ✅ Commit 2 — Geräte- & Dienste-API (erledigt) - REST-Endpunkte (Fastify) für `GET/POST/PATCH/DELETE` auf `devices` und `services` - Zod-Validierung der Request-Bodies (`packages/shared/src/schemas.ts`) - Repository-Layer über Drizzle (`apps/backend/src/db/repositories`) ## ✅ Commit 3 — Suche im Frontend (erledigt) - Anbindung von `rankServices` aus `packages/shared` an echte Backend-Daten - TanStack Query für Datenhaltung (`apps/frontend/src/hooks/useServices.ts`) - Tastatur-Navigation (Pfeiltasten wählen, Enter öffnet Dienst in neuem Tab, Escape leert Suche) - Neue UI-Komponente `ResultsList` in `packages/ui` > TanStack Router wird bewusst erst mit dem Adminbereich (Commit 6) eingeführt, > da es vorher keine zweite Route gibt, die er sinnvoll verwalten könnte. ## ✅ Commit 4 — Kategorien & Favoriten (erledigt) - Kategorien-API: Erstellen, Umbenennen, Löschen, Bulk-Reorder (`PATCH /api/categories/reorder`) - Löschen einer Kategorie setzt `category` bei betroffenen Diensten auf `null`, löscht die Dienste aber nicht - Favoriten-Toggle direkt in der Trefferliste des Frontends (Stern anklicken, per `useMutation` + Cache-Invalidierung) > Die eigentliche Kategorien-*Verwaltungsoberfläche* (Erstellen/Umbenennen/ > Drag & Drop in der UI) wandert in Commit 6 (Adminbereich) – die Startseite > bleibt bewusst die minimalistische Suche, kein Verwaltungs-UI dort. ## ✅ Commit 5 — Scanner (erledigt) - `apps/backend/src/scanner/`: DNS-Kandidaten-Auflösung, TCP-Portscan (80, 443 + typische Ports), HTTP-Titel-/Favicon-Extraktion, Softwareerkennung per Signatur-Liste, FritzBox-TR-064-Client (inkl. selbst implementierter HTTP-Digest-Authentifizierung) - `POST /api/scan/devices/:id` und `POST /api/scan/fritzbox` – ausschließlich manuell auslösbar, kein automatischer/zeitgesteuerter Scan - `upsertDeviceFromScan` / `upsertServiceFromScan` in den Repositories: garantiert, dass Benutzerfelder (displayName, category, favorite, order, alias, icon) bei erneuten Scans nie überschrieben werden – End-to-End getestet (Service manuell umbenannt/kategorisiert/favorisiert, erneut gescannt, Werte blieben erhalten) > Alle Module wurden gegen echte, lokal gestartete Test-Server verifiziert > (Port-Erkennung, Titel/Favicon-Parsing, Softwareerkennung, kompletter > FritzBox-SOAP-/Digest-Auth-Ablauf inkl. Fehlerfall bei falschem Passwort). > Ein Test gegen eine echte FritzBox war in dieser Umgebung nicht möglich. ## ✅ Commit 6 — Adminbereich (erledigt) - TanStack Router (code-basiert): `/` (Startseite) + `/admin/*` - Menüpunkte wie spezifiziert: Dashboard, Geräte, Dienste, Scanner, Kategorien, Plugins, Einstellungen, Logs - Geräte-Seite: anlegen, löschen, **„Jetzt scannen"-Button** pro Gerät - Dienste-Seite: Inline-Bearbeitung von Name/Kategorie/Alias/Reihenfolge, Favoriten-Toggle - Kategorien-Seite: anlegen, umbenennen, löschen, **natives Drag & Drop** (kein zusätzliches Package nötig) - Scanner-Seite: FritzBox-Trigger + Sammel-Scan aller Geräte - Logs-Seite: Scan-Historie (neue `scan_logs`-Tabelle, jeder Scan-Versuch wird protokolliert) - Einstellungen: Live-Backend-Status + Theme-Umschalter - Plugins: ehrlicher Hinweis, dass das Plugin-System noch nicht existiert (statt einer vorgetäuschten Funktion) > Verifiziert per Docker-Build-Simulation (Frontend + Backend inkl. > `pnpm deploy --prod`) und Backend-Smoke-Test aller von den Admin-Seiten > verwendeten Endpunkte. Die UI selbst konnte in dieser Umgebung nicht in > einem echten Browser durchgeklickt werden (kein Browser-Tool verfügbar) – > Verifikation stützt sich auf erfolgreichen TypeScript-/Vite-Build und > Codeprüfung. ## ✅ Commit 7 — PWA (erledigt) - `vite-plugin-pwa` mit generiertem Manifest (`manifest.webmanifest`) und Service Worker (Workbox, `generateSW`-Modus) - Echte Icons (192/512, jeweils normal + maskable, `apple-touch-icon.png`, `favicon.svg`) unter `apps/frontend/public/` - App-Shell wird precached; `/api/*` läuft über `NetworkFirst` mit 3s-Timeout und Cache-Fallback – Suche funktioniert damit auch offline mit dem zuletzt geladenen Datenstand - `nginx.conf`: `sw.js`/`manifest.webmanifest` explizit von der 7-Tage-Cache-Regel ausgenommen (sonst kommen Updates bei installierten Nutzern nie an), korrekter MIME-Type für das Manifest gesetzt > Verifiziert: Manifest gegen Installierbarkeits-Anforderungen geprüft (512er-Icon, > maskable Icon, alle Pflichtfelder vorhanden), alle Assets über einen echten > HTTP-Server abgerufen (200 OK, korrekte Content-Types), generierter Service-Worker- > Code inspiziert (Precache-Liste + NetworkFirst-Route bestätigt), vollständige > Docker-Build-Simulation durchlaufen. Die tatsächliche "Zum Homescreen hinzufügen"- > Installation konnte mangels Browser in dieser Umgebung nicht getestet werden. ## ✅ Commit 8 — Plugin-System (erledigt) - Plugins liegen als Ordner unter `apps/backend/plugins/*`, jeweils mit einer `index.js` (reines ESM, kein Build-Schritt). Werden beim Backend-Start geladen (`apps/backend/src/plugins/loader.ts`) und per Docker-Volume eingebunden – neue Plugins brauchen nur einen Container-Neustart, kein Image-Rebuild - Plugin-Vertrag (`apps/backend/src/plugins/types.ts`): `setup(ctx)` zum Registrieren zusätzlicher Softwareerkennung (inkl. Icon), `importDevices()` für eigene Geräte-Importquellen - Zwei funktionierende Beispiel-Plugins: `example-signatures` (Homebridge, Uptime Kuma) und `static-import` (Geräte aus lokaler `devices.json`) - `GET /api/plugins`, `POST /api/plugins/:name/import` – Import läuft wie Scans ausschließlich manuell per Knopfdruck - Frontend-Plugins-Seite zeigt echte geladene Plugins mit Capabilities und Import-Button (ersetzt den ehrlichen Platzhalter aus Commit 6) - Von neuen Plugins erkannte Software liefert ein Icon, das – wie `category` seit Commit 5 – nur beim erstmaligen Anlegen eines Dienstes als Startwert übernommen wird, nie nachträglich überschrieben > Verifiziert: beide Plugins laden nachweislich beim Start; ein echter Scan > gegen einen Test-Server mit "Homebridge" im Response-Body wurde über die > Plugin-Signatur erkannt, inkl. korrekt übernommenem Icon; Geräte-Import per > Plugin getestet (`source: "plugin"`); Fehlerfälle (unbekanntes Plugin → 404, > Plugin ohne Import-Fähigkeit → 400) geprüft; Docker-Build-Simulation > bestanden, dabei auch verifiziert, dass ein fehlendes Plugin-Verzeichnis > nicht zum Absturz führt, sondern nur zu einer leeren Liste. > **Bewusst nicht umgesetzt:** eigene Admin-Routen/Menüpunkte pro Plugin > (alle Plugins erscheinen gesammelt auf einer Seite) und Sandboxing > (Plugin-Code läuft mit vollem Zugriff im Backend-Prozess – nur Plugins aus > vertrauenswürdiger Quelle einbinden, siehe `apps/backend/plugins/README.md`). Damit ist die komplette in der ursprünglichen Projektübergabe beschriebene Funktionalität umgesetzt. ## Bugfixes aus dem ersten Praxistest (nach Commit 8) Basierend auf echtem Testen gegen ein reales Homelab (FritzBox + gescannte Geräte): - **Neues Feld `visible` auf Diensten.** Scan-Treffer mit Fehlerantworten (404, 400, 302 als Titel) oder generischen Ladebildschirm-Titeln ("Loading...", der Scanner führt kein JavaScript aus) werden beim erstmaligen Anlegen automatisch als `visible: false` markiert – sie erscheinen nicht mehr in der Suche, bleiben aber unter Admin -> Dienste sichtbar, umschaltbar (👁️/🙈) und löschbar. Bestehende Datenbanken werden beim Start automatisch migriert (`ALTER TABLE ... ADD COLUMN`), keine Daten gehen verloren. - **Hostname/URL/HTTPS sind jetzt im Adminbereich editierbar** (Dienste -> Bearbeiten) und werden ab sofort bei einem erneuten Scan eines bereits bekannten Dienstes **nicht mehr überschrieben** (vorher wurden diese Felder bei jedem Scan aufgefrischt). Nur `favicon`/`description` werden weiterhin aktualisiert. Achtung: Wird dabei der **Port** geändert, entsteht beim nächsten Scan ein neuer Eintrag für den ursprünglichen Port, da die Zuordnung über `deviceId + port` läuft. - **Suchergebnisliste erscheint nur noch bei aktiver Eingabe** – vorher war sie auch bei leerem Suchfeld dauerhaft aufgeklappt. - **Port wird jetzt überall mit angezeigt** (`hostname:port` statt nur `hostname`), in der Suche wie im Adminbereich. - **Admin-Sidebar ist jetzt echt fixiert** (`fixed` statt `static`) – vorher scrollte "Zurück zur Suche" bei langem Seiteninhalt aus dem Sichtfeld. ## Weitere Fixes (zweite Testrunde) - **Reset-Button** unter Admin -> Einstellungen -> Gefahrenzone: löscht alle Geräte und (per Cascade) alle Dienste unwiderruflich. Doppelte Bestätigung im Frontend (`window.confirm`) plus eine Pflicht-Bestätigung auf API-Ebene (`POST /api/reset` verlangt `{"confirm": true}`, sonst `400`). Kategorien und Logs bleiben erhalten. - **Port und Protokoll (http/https) als eigene Spalten** in der Dienste-Tabelle im Adminbereich, nicht mehr nur implizit in der URL. - **Favoriten-Leiste über der Suchleiste** auf der Startseite – immer sichtbar, unabhängig vom Suchfeld, sortiert nach der einstellbaren Reihenfolge. - **Dienste per Drag & Drop sortierbar** im Adminbereich (`PATCH /api/services/reorder`), bestimmt sowohl die Reihenfolge in der Favoriten-Leiste als auch bei gleichrangigen Suchtreffern. - **Healthcheck durchgängig in der Admin-Sidebar sichtbar** (vorher nur auf der Startseite und versteckt unter Einstellungen). - **Kategorien-Synchronisierung:** Die `categories`-Tabelle war leer, obwohl Dienste bereits Kategorien-Text trugen (z. B. aus der automatischen Softwareerkennung) – `services.category` ist reiner Freitext, kein Fremdschlüssel. Jetzt: (1) beim Start werden alle bereits auf Diensten vorhandenen Kategorienamen automatisch in die `categories`-Tabelle nachgetragen, (2) jeder Scan legt neu erkannte Kategorien automatisch an, (3) Umbenennen einer Kategorie im Adminbereich aktualisiert automatisch alle Dienste mit dem alten Namen. Einschränkung: manuell per API angelegte Dienste (nicht über einen Scan) lösen diesen Sync aktuell nicht aus. ## HTTPS im lokalen Netz - Frontend-Container liefert jetzt zusätzlich zu HTTP (Port 8080) auch HTTPS auf Port 8443 aus, kein erzwungener Redirect. - Entrypoint-Skript (`apps/frontend/docker-entrypoint.sh`) erzeugt beim Containerstart automatisch ein selbstsigniertes Zertifikat für die in `.env` hinterlegte `LAUNCHPAD_HOST` (IP-SAN bei IP-Adressen, DNS-SAN bei Hostnamen) – übernimmt nichts, falls unter `/etc/nginx/certs/` bereits eigene Zertifikate eingebunden sind (siehe README, z. B. per mkcert für eine Variante ohne Browser-Warnung). - `nginx.conf` in HTTP- und HTTPS-Serverblock aufgeteilt, gemeinsame Location-Regeln in `nginx-locations.conf` ausgelagert (kein doppelt gepflegter Proxy-/Cache-Code). > Verifiziert: Zertifikatserzeugung für IP-, Hostname- und Fallback-Fall > getestet, komplette nginx-Konfiguration mit einem echten nginx-Binary > validiert (`nginx -t` erfolgreich), echter TLS-Handshake gegen den > laufenden Server durchgeführt und geprüft, dass das ausgelieferte > Zertifikat die korrekte IP als Subject Alternative Name trägt. ## HTTPS-Revision: eigene lokale Root-CA statt DuckDNS Nutzer-Feedback: "Will nix externes, soll auch nur lokal laufen" – die DuckDNS/Let's-Encrypt-Lösung wurde daher wieder entfernt und durch eine komplett offline funktionierende Variante ersetzt. - `apps/frontend/docker-entrypoint.sh` erzeugt beim ersten Containerstart eine eigene Root-CA (`LaunchPad Local CA`, `CA:TRUE`, 10 Jahre gültig) und signiert damit ein Server-Zertifikat (`CA:FALSE`, korrekte SAN je nach `LAUNCHPAD_HOST`) – keinerlei externe Dienste beteiligt - CA + Server-Zertifikat liegen im neuen Docker-Volume `launchpad-certs` (statt im Container-Dateisystem) – bleiben dadurch über Neustarts und `--build` stabil. Ändert sich nur `LAUNCHPAD_HOST`, wird ausschließlich das Server-Zertifikat neu ausgestellt, die CA (und damit bereits erteiltes Gerätevertrauen) bleibt unangetastet - Root-CA wird unter `http://:8080/ca.crt` zum Download bereitgestellt (`nginx-locations.conf`, korrekter MIME-Type `application/x-x509-ca-cert` löst auf Android/iOS automatisch den nativen "Zertifikat installieren"-Dialog aus) – einziger verbleibender manueller Schritt ist der einmalige Import pro Gerät, dafür gibt es ohne externe CA keine Alternative - DuckDNS/`acme`-Service und zugehörige `.env`-Variablen vollständig entfernt > Verifiziert: CA- und Leaf-Zertifikat-Erzeugung lokal mit echtem `openssl` > durchgespielt (Zertifikatskette validiert erfolgreich gegen die CA, > `CA:FALSE`/`CA:TRUE` korrekt gesetzt, SAN korrekt), Idempotenz geprüft > (zweiter Lauf mit gleichem Host verändert nichts, geänderter Host erzeugt > nur ein neues Server-Zertifikat, CA bleibt stabil), kompletter End-to-End- > Test mit echtem nginx: `/ca.crt` per HTTP heruntergeladen und **damit ohne > `-k`-Flag** eine echte HTTPS-Verbindung erfolgreich validiert – simuliert > exakt das Verhalten eines Geräts nach CA-Import.