89 lines
18 KiB
Markdown
89 lines
18 KiB
Markdown
# SPEC.md
|
|
|
|
## Summary
|
|
|
|
Bouw een persoonlijke webapp die als standaard startpagina van de browser dient en een prachtig, professioneel en strak afgewerkt overzicht geeft van de diensten op de Unraid-server. De app ontdekt Docker-containers automatisch via Unraid en een beperkte read-only Docker-socketproxy, maar toont nieuwe containers niet automatisch op het hoofddashboard. In het instellingenmenu kan de gebruiker per container zichtbaarheid, categorie, sortering, icoon, naam en URL beheren. Voor Docker-apps wordt standaard de lokale WebUI-URL uit de Unraid-configuratie gebruikt. Daarnaast kunnen handmatig favoriete externe websites worden toegevoegd met naam, URL, icoon, categorie en sorteerpositie. Het dashboard bevat een centrale zoekbalk die zowel zichtbare Docker-apps als favorieten doorzoekt en, wanneer geen lokaal resultaat wordt gekozen, de invoer als Google-zoekopdracht in een nieuw tabblad opent. De gebruiker kan kiezen tussen één overzicht met categorieën onder elkaar en een weergave met afzonderlijke categoriepagina's of tabbladen. Thema's, weergavevoorkeuren en openingsgedrag worden via instellingen beheerd. Externe toegang kan optioneel worden ingeschakeld; wanneer die actief is, haalt een read-only koppeling met Nginx Proxy Manager beschikbare externe URL's op en koppelt die aan de juiste apps, met steeds een handmatige override als terugval. De UI moet aanvoelen als een hoogwaardige, moderne productinterface met sterke visuele hiërarchie, nette details, subtiele animaties en een verzorgde dashboardervaring.
|
|
|
|
## Architecture
|
|
|
|
- Project type: webapp
|
|
- Preferred stack: TypeScript als hoofdtaal; React met Vite voor de frontend; Node.js met Fastify voor de backend; SQLite met Drizzle ORM voor persistentie en migraties; gedeelde types en validaties met Zod; een eenvoudige REST-API tussen frontend en backend; een zorgvuldig opgebouwd design system met design tokens, CSS-variabelen en herbruikbare UI-componenten; een moderne componentbibliotheek of headless aanpak voor toegankelijke basiscomponenten; iconenset van hoge kwaliteit; subtiele motion via lichte animatielogica; Docker multi-stage build en Docker Compose voor Unraid; Vitest voor unit- en integratietests; Playwright voor browsertests. Gebruik een overzichtelijke repository met duidelijk gescheiden frontend-, backend- en shared-mappen. Richt de frontend expliciet in op een premium dashboardervaring met consistente spacing, kaartsystemen, typografie, kleurgebruik, statusbadges en instellingenpanelen.
|
|
- Runtime model: Interactieve app
|
|
- Update/sync strategy: Polling
|
|
- Persistence: SQLite
|
|
- Deployment target: Docker op Unraid
|
|
- Deployment profile: de standaard Compose-installatie maakt precies één branded container `DockDeck` op poort `1218`. Containerinventaris en CPU/geheugenstatus komen via GET van de bestaande AppOps-gateway; de applicatie mount nooit de Docker-socket. Een afzonderlijke beperkte socketproxy blijft alleen als optionele fallbackoverlay beschikbaar.
|
|
- Repository state: private Gitea-repository `NuklearRabbit/DockDeck`; `main` volgt de remote.
|
|
- Constraints: De MVP moet als Docker-container op de bestaande Unraid-server draaien en alleen vanaf het lokale netwerk bereikbaar zijn. De app moet zonder eigen login bruikbaar zijn, omdat netwerktoegang de primaire beveiligingsgrens vormt. Alle Docker-toegang moet read-only verlopen via een beperkte socketproxy; de hoofdcontainer krijgt geen directe onbeperkte Docker-socket. Unraid-templategegevens mogen alleen worden gelezen. Nginx Proxy Manager wordt uitsluitend read-only benaderd. De applicatie moet bruikbaar blijven wanneer Nginx Proxy Manager niet is geconfigureerd of tijdelijk niet bereikbaar is. Er mogen geen betaalde externe diensten nodig zijn. Alle configuratie moet persistent blijven bij herstart en eenvoudig te back-uppen zijn. Codex moet een nieuwe private repository in de persoonlijke Gitea-omgeving aanmaken en mag geen secrets hardcoderen. Visuele kwaliteit is belangrijk, maar de UI mag niet onnodig zwaar of traag worden; professionele polish moet samengaan met vlotte prestaties als browserstartpagina.
|
|
|
|
## Module Boundaries
|
|
|
|
| Module | Responsibility | Notes |
|
|
| -------------- | ------------------------------------------- | --------------------------- |
|
|
| UI / Interface | Gebruikersinteractie en invoer verzamelen. | Afstemmen op projecttype. |
|
|
| Domain | Businessregels en pure transformaties. | Houd afhankelijkheden laag. |
|
|
| Infrastructure | Data, integraties en runtime adapters. | Isoleer externe systemen. |
|
|
| Documentation | Projectdossier, beslissingen en overdracht. | Altijd actueel houden. |
|
|
|
|
## Dataflow
|
|
|
|
```text
|
|
Gebruiker -> Interface -> Domain logic -> Data/integraties -> Resultaat -> Documentatie/testlog
|
|
```
|
|
|
|
## Interfaces And Contracts
|
|
|
|
- Inputdata: Docker-apps met container-ID, technische naam, weergavenaam, afbeelding, ontdekt icoon, online- of offline-status, lokale WebUI-URL, optionele externe URL, zichtbaarheid, categorie, sorteerpositie, detectiebron en laatste statuscontrole; categorieën met naam, icoon, sorteerpositie en weergave-instellingen; favorieten met naam, URL, icoon, categorie en sorteerpositie; dashboardinstellingen met gekozen thema, weergavemodus, openingsgedrag, zoekprovider, pollinginterval en externe-toegangsschakelaar; integratie-instellingen zonder geheime waarden; import- en exportbestand met versieerbaar configuratieschema; synchronisatiestatus en beperkte foutinformatie voor Unraid, Docker-proxy en Nginx Proxy Manager.
|
|
- Integraties: Unraid Docker-templatebestanden of beschikbare Unraid-metadata voor containernaam, WebUI en iconen; een beperkte read-only Docker Socket Proxy voor containerinventaris en runtime-status; Nginx Proxy Manager API in read-only modus voor optionele externe hostnamen en URL's; Google Search via een gewone zoek-URL zonder API-key; Gitea API voor het aanmaken van een private repository en het configureren van de Git-remote.
|
|
- Update/sync: Polling
|
|
- Persistence: SQLite
|
|
- API/UI contracten: JSON REST onder `/api`: dashboardaggregaat, discovery-sync, gevalideerde PATCH voor apps/preferences, CRUD voor categorieën/favorieten, een same-origin credential-write-endpoint en versie 1 export/import. Zod valideert iedere mutatiegrens; credentialwrites antwoorden uitsluitend met `saved: true` en geen response bevat credentialwaarden.
|
|
- Categorie-icooncontract: één gedeelde frontendcatalogus voedt de renderer en de instellingenkeuzelijst. Alle canonieke categoriewaarden verwijzen naar een afzonderlijke Lucide-component; onbekende geïmporteerde waarden vallen terug op `Layers3` en nieuwe categorieën krijgen eerst een nog ongebruikt ondersteund pictogram.
|
|
- Visuele voorkeuren: `theme`, `accent`, `density`, `contentWidth`, `backgroundStyle`, `wallpaperFit`, `wallpaperPositionX`, `wallpaperPositionY`, `wallpaperZoom`, `showHero`, `showStatus`, `viewMode`, `pollingInterval` en `externalAccess` worden server-side gevalideerd en in SQLite bewaard. Oudere databases krijgen ontbrekende voorkeurkolommen idempotent; versie 1 exports blijven achterwaarts compatibel. De wallpaper schaalt tegen het volledige zichtbare platformviewport onder de topbalk, niet tegen de scrollhoogte of alleen de middenkolom, en vervangt daar de actieve achtergrondpreset.
|
|
- Responsive contract: het document krijgt alleen gevalideerde `data-*` attributen voor de visuele tokens. De grid gebruikt vloeiende kolommen en begrensde contentprofielen voor mobiel, tablet, desktop en ultrawide. DM Sans en Newsreader worden lokaal gebundeld.
|
|
- Widgetcontract: apps hebben persistente velden voor inschakeling, formaat, onafhankelijke volgorde, informatielagen, custom label, metricvolgorde, primaire metric, statlimiet, per-metric value/gauge/progress-presentatie, boven-/onderdrempel, grafiekreeks, lijn-/vlak-/staaftype, legenda, samplevenster en een begrensde verversingscadans. De discoverylaag leest voor maximaal acht geselecteerde containers alleen `GET /containers/{id}/stats`, houdt maximaal zestig punten uitsluitend in procesgeheugen bij en geeft freshness plus CPU-, geheugen-, netwerk-, proces- en uptimetelemetrie door. Een mislukte statsread blokkeert navigatie of discovery niet.
|
|
- Hostwidgetcontract: `showServerWidget`, formaat, volgorde, statselectie/-volgorde/-limiet, configureerbare grafiek en capaciteit-, detail-, voortgangs- en Unraid-native lagen staan standaard aan en worden persistent en exporteerbaar opgeslagen. De hostkaart combineert Docker `GET /info` met read-only mounts voor Unraid runtimebestanden en hwmon; er is geen shell-, socket- of beheeractie. Array-, parity-, opslag-, temperatuur- en optionele UPS-data worden tot een klein presentatiecontract en maximaal zestig geheugenpunten gereduceerd.
|
|
- Providerwidgetcontract: de adapterlaag ondersteunt AdGuard Home, Plex, Immich, Home Assistant, Sonarr/Radarr/Lidarr, Ollama, Glances, Tautulli, Paperless-ngx, Gitea, Jellyfin, Seerr, Prowlarr, Authentik, NPM, Portainer, Grafana, Prometheus, Nextcloud, Audiobookshelf, Netdata en PeerTube. Alle netwerkrequests zijn GET en time-bounded. Deluge, qBittorrent, JDownloader, Tdarr, Bazarr en Vaultwarden kunnen een door de gebruiker geconfigureerde GET-only metrics bridge lezen omdat hun native beheer-API geen zuivere GET-only grens biedt. Iedere overige ontdekte app krijgt een eigen allowlisted `CUSTOM_WIDGET_<APP>_URL` en optionele bearer-token voor een klein JSON-contract met `summary` en maximaal twaalf `stats`; waarden blijven server-side en worden begrensd. Laatst succesvolle data blijft bij fouten als stale cache beschikbaar; app-specifieke Dockerstats blijven de fallback.
|
|
- Widgetinhoudcontract: Widget Studio publiceert voor iedere app metriekdefinities met betekenis, groep, unit, grafiekgeschiktheid, standaardpresentatie en aanbevolen zichtbaarheid. De gebruiker kan tot 24 labels persistent verbergen, rangschikken en presenteren; runtime-metrics die een gekoppelde app aanvullend levert worden dynamisch toegevoegd. Zonder koppeling blijven dezelfde app-specifieke labels zichtbaar met een expliciete connectiestatus; Dockertelemetrie wordt niet als fictieve appstat vermomd. Layoutpresets en volledige back-ups bewaren deze keuzes legacy-compatibel.
|
|
- Serveridentiteitscontract: `serverName` en `serverUrl` staan in SQLite, export/import en Settings. Zonder opgeslagen URL wordt `UNRAID_URL` of `UNRAID_HOST` gebruikt en, wanneer geen poort is opgegeven, poort `5000` toegevoegd. Dezelfde actuele waarden voeden hostkaart en zoekresultaten.
|
|
- Single-containercontract: het AppOps-contract leest uitsluitend `GET /api/containers`, reduceert de response direct tot noodzakelijke inventaris- en metriekvelden en rapporteert uitval als degraded zonder opgeslagen navigatie te blokkeren. CPU- en geheugencapaciteit komen uit twee specifieke read-only `/proc`-bestandsmounts.
|
|
- Layoutcontract: `/api/widget-layout` bewaart de afzonderlijke widgetvolgorde. Layoutpresets hebben een eigen gevalideerd record en eigen import/export-API, onafhankelijk van de volledige versie 1-back-up.
|
|
- Operationscontract: `/api/diagnostics` retourneert uitsluitend health-, integratie-, discovery- en configuratiestatus zonder secrets of ruwe providerpayloads. De PWA-cache bevat alleen statische shellassets en sluit iedere `/api/`-request expliciet uit.
|
|
- Faviconcontract: een expliciete favorieticoon-URL blijft leidend. Zonder override probeert de browser alleen `<favoriet-origin>/favicon.ico`; een fout levert een lokaal monogram en veroorzaakt geen server-side fetch of third-party request.
|
|
- Appverwijdercontract: `DELETE /api/apps/:id` verwijdert uitsluitend het DockDeck-record en schrijft de technische containernaam naar `removed_apps`. Discovery slaat die naam voortaan over; Docker en Unraid worden nooit gemuteerd. Versie 1-back-ups mogen de optionele `removedApps`-lijst bevatten.
|
|
- Zoekcontract: de zoekindex wordt uitsluitend afgeleid van de actuele dashboardpayload en indexeert zowel zichtbare als verborgen apps, weergave- en technische namen, categorieën, images, URL-varianten, favorieten en de Unraid-host. Lokaal resultaat, toetsenbordselectie en expliciete Google-fallback blijven afzonderlijke acties.
|
|
- URL-resolutiecontract: `[IP]` gebruikt voor een expliciet `br0`-netwerk het LAN-adres van de container en anders `UNRAID_HOST`. Een exact gepubliceerde poort blijft leidend; bij een verouderde templatepoort mag één ondubbelzinnige gepubliceerde TCP-poort als fallback dienen. Overrides blijven altijd leidend.
|
|
|
|
## File Structure
|
|
|
|
- Broncode, tests en documentatie worden gescheiden per verantwoordelijkheid.
|
|
- Nieuwe modules krijgen colocated tests waar dat past bij de stack.
|
|
|
|
## Error Handling
|
|
|
|
- Valideer gebruikersinput dicht bij de grens van het systeem.
|
|
- Toon herstelbare fouten duidelijk en log technische details zonder secrets.
|
|
|
|
## Security And Privacy
|
|
|
|
- Credentials expected: Ja
|
|
- Security/privacy notes: Gebruik uitsluitend environmentvariabelen voor gevoelige configuratie. Voorzie minimaal variabelen zoals GITEA_URL, GITEA_TOKEN en GITEA_OWNER voor repository-initialisatie en optionele NPM_URL, NPM_USERNAME en NPM_PASSWORD of een ondersteund read-only token voor Nginx Proxy Manager. Gebruik voor de Docker-koppeling geen onbeperkte socketmount in de applicatiecontainer, maar een afzonderlijke proxy met alleen de minimaal noodzakelijke read-endpoints. Sla secrets nooit op in SQLite, exports, logs, frontendbundels, testfixtures of Git. Toon gevoelige integratiewaarden gemaskeerd in de UI. De app mag geen Docker-mutaties uitvoeren en mag nooit containers starten, stoppen, herstarten, verwijderen of aanpassen. De MVP heeft geen eigen authenticatie en moet daarom standaard alleen op een lokaal bindadres of afgeschermd Docker-netwerk worden gepubliceerd. Externe toegang via Nginx Proxy Manager wordt alleen voorbereid; correcte authenticatie en toegangscontrole via bijvoorbeeld Authentik blijven de verantwoordelijkheid van een latere deploymentstap.
|
|
|
|
## Test Strategy
|
|
|
|
- Testing expectations: Unit tests voor URL-normalisatie, categorie- en sorteervoorwaarden, zoekresultaten, configuratievalidatie, import- en exportversies, container-naar-Nginx-hostmatching en permissiegrenzen; backend-integratietests met gemockte Unraid-templategegevens, Docker Socket Proxy en Nginx Proxy Manager; SQLite-migratie- en persistentietests; tests die aantonen dat geen Docker-mutatie-endpoints of beheeracties worden gebruikt; browsertests voor eerste ingebruikname, containers zichtbaar maken, categorieën beheren, favorieten toevoegen, zoeken, Google-fallback, thema wisselen, weergavemodus wijzigen, URL openen en configuratie exporteren en importeren; visuele UI-smoketests voor kernschermen zoals dashboard, categorie-overzicht en instellingen; Docker-buildtest en Compose-smoketest; een live read-only validatie op Unraid waarbij discovery, status en lokale WebUI-URL's worden gecontroleerd zonder containers te wijzigen.
|
|
- Quality bar: De interface moet eruitzien als een professioneel premium product en niet als een simpel hobbydashboard. Verwacht een moderne, luxueuze en verzorgde UI met uitgebalanceerde witruimte, consistente kaartcomponenten, hoogwaardige iconografie, duidelijke typografie, nette schaduwen of diepte waar passend, subtiele animaties, verzorgde empty states, sterke visuele hiërarchie en een kalme maar rijke look-and-feel. De belangrijkste startpagina moet snel laden en direct bruikbaar zijn wanneer integraties traag of tijdelijk offline zijn. Fouten moeten begrijpelijk worden getoond zonder technische stacktraces. Ontbrekende iconen of URL's moeten nette fallbacks hebben. Instellingen moeten veilig opgeslagen worden en bij foutieve invoer gevalideerde feedback geven. De interface moet responsief zijn voor desktop, tablet en basisgebruik op mobiel. Toetsenbordnavigatie, zichtbare focus, semantische HTML en voldoende kleurcontrast zijn vereist. Statuspolling mag geen zichtbare flikkering veroorzaken en moet stoppen wanneer de pagina niet actief is of verstandig worden vertraagd. De code moet modulair, getypeerd, gedocumenteerd en zonder onnodige complexiteit zijn. Esthetische kwaliteit is voor deze MVP een kernvereiste en niet louter een extra.
|
|
|
|
## Deployment Design
|
|
|
|
- Runtime/stack: TypeScript als hoofdtaal; React met Vite voor de frontend; Node.js met Fastify voor de backend; SQLite met Drizzle ORM voor persistentie en migraties; gedeelde types en validaties met Zod; een eenvoudige REST-API tussen frontend en backend; een zorgvuldig opgebouwd design system met design tokens, CSS-variabelen en herbruikbare UI-componenten; een moderne componentbibliotheek of headless aanpak voor toegankelijke basiscomponenten; iconenset van hoge kwaliteit; subtiele motion via lichte animatielogica; Docker multi-stage build en Docker Compose voor Unraid; Vitest voor unit- en integratietests; Playwright voor browsertests. Gebruik een overzichtelijke repository met duidelijk gescheiden frontend-, backend- en shared-mappen. Richt de frontend expliciet in op een premium dashboardervaring met consistente spacing, kaartsystemen, typografie, kleurgebruik, statusbadges en instellingenpanelen.
|
|
- Deployment target: Docker op Unraid
|
|
- Repository state: private Gitea-repository `NuklearRabbit/DockDeck`, remote en deployment zijn operationeel.
|
|
- Build, release en rollback stappen: `npm run build`; lokaal `npm start`; op Unraid `docker compose up -d --build`. Rollback via vorige Git-commit/image en herstel van het `dockdeck_data` volume of een versie 1 export.
|
|
|
|
## Open Design Questions
|
|
|
|
- Opgelost: SQLite met named volume is voldoende voor de eerste release.
|
|
- Opgelost: Unraid, Docker Proxy en NPM hebben deterministische adapters/fixtures.
|
|
- Opgelost: gestructureerde Fastify-requestlogs plus niet-gevoelige integratiestatus en `/api/health` vormen de minimale observability.
|