Update GeoIntel project files
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s

This commit is contained in:
Jens
2026-07-25 23:43:08 +02:00
parent 9db6cca2b1
commit d5ea270329
155 changed files with 37970 additions and 825 deletions
+8 -22
View File
@@ -1,23 +1,9 @@
.git .git
.venv node_modules
venv dist
__pycache__ coverage
*.pyc playwright-report
.pytest_cache test-results
data
frontend/node_modules .env*
frontend/dist *.log
frontend/*.tsbuildinfo
backend/.pytest_cache
backend/**/*.pyc
backend/**/__pycache__
storage
postgres-data
datasets/raw
datasets/processed
datasets/cache
exports
models
.env
+12
View File
@@ -0,0 +1,12 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
+96 -149
View File
@@ -1,153 +1,100 @@
# Backend # Application (local-only by default)
GEOINTEL_ENV=development HOST=127.0.0.1
GEOINTEL_API_PREFIX=/api/v1 PORT=3000
DATABASE_URL=postgresql+psycopg://geointel:geointel@localhost:5432/geointel?connect_timeout=1 DATABASE_PATH=./data/dockdeck.db
STORAGE_ROOT=./storage INTEGRATION_ENV_PATH=./data/integrations.env
MAX_UPLOAD_MB=500 POLLING_INTERVAL_MS=30000
CORS_ORIGINS=http://localhost:1202,http://127.0.0.1:1202
ORTHOPHOTO_ENABLED=true
ORTHOPHOTO_WMS_URL=https://geo.api.vlaanderen.be/OMWRGBMRVL/wms
ORTHOPHOTO_WMS_LAYER=Ortho
ORTHOPHOTO_RESOLUTION_M=1.0
ORTHOPHOTO_MIN_SIDE_M=128
ORTHOPHOTO_MAX_SIDE_M=1024
ORTHOPHOTO_CACHE_TTL_HOURS=24
SOURCE_CATALOG_PROBE_ENABLED=true
SOURCE_CATALOG_GRB_WFS_URL=https://geo.api.vlaanderen.be/GRB/wfs
GRB_ENABLED=true
GRB_OGC_API_URL=https://geo.api.vlaanderen.be/GRB/ogc/features/v1
GRB_MIN_SIDE_M=10
GRB_MAX_SIDE_M=20000
GRB_PAGE_SIZE=1000
GRB_MAX_PAGES=200
GRB_MAX_FEATURES=150000
GRB_TIMEOUT_SECONDS=180
GRB_MAX_RESPONSE_MB=20
GRB_MAX_TOTAL_RESPONSE_MB=256
GRB_CACHE_TTL_HOURS=24
OFFICIAL_VECTOR_ENABLED=true
BWK_WFS_URL=https://geo.api.vlaanderen.be/BWK/wfs
DOV_SOIL_WFS_URL=https://www.dov.vlaanderen.be/geoserver/wfs
SPW_PICC_ENABLED=true
SPW_PICC_MAPSERVER_URL=https://geoservices.wallonie.be/arcgis/rest/services/TOPOGRAPHIE/PICC_VDIFF/MapServer
SPW_FLOOD_HAZARD_ENABLED=true
SPW_FLOOD_HAZARD_MAPSERVER_URL=https://geoservices.wallonie.be/arcgis/rest/services/EAU/ALEA_INOND/MapServer
URBIS_ENABLED=true
URBIS_WFS_URL=https://geoservices-vector.irisnet.be/geoserver/urbisvector/ows
OFFICIAL_VECTOR_MIN_SIDE_M=10
OFFICIAL_VECTOR_MAX_SIDE_M=20000
OFFICIAL_VECTOR_PAGE_SIZE=1000
OFFICIAL_VECTOR_MAX_PAGES=200
OFFICIAL_VECTOR_MAX_FEATURES=100000
OFFICIAL_VECTOR_TIMEOUT_SECONDS=180
OFFICIAL_VECTOR_MAX_RESPONSE_MB=20
OFFICIAL_VECTOR_MAX_TOTAL_RESPONSE_MB=256
OFFICIAL_VECTOR_CACHE_TTL_HOURS=24
SOURCE_CATALOG_STATBEL_DCAT_URL=https://doc.statbel.be/publications/DCAT/DCAT_opendata_datasets.ttl
SOURCE_CATALOG_STATBEL_MAX_RESPONSE_MB=5
SOURCE_CATALOG_ALZ_RELEASE_URL=https://landbouwcijfers.vlaanderen.be/open-geodata-landbouwgebruikspercelen
SOURCE_CATALOG_PROBE_TIMEOUT_SECONDS=10
SOURCE_CATALOG_PROBE_MAX_RESPONSE_MB=2
SOURCE_CATALOG_PROBE_CACHE_TTL_SECONDS=900
DHMV_ENABLED=true
DHMV_WCS_URL=https://geo.api.vlaanderen.be/DHMV/wcs
DHMV_RESOLUTION_M=5.0
DHMV_MIN_SIDE_M=10
DHMV_MAX_SIDE_M=20000
DHMV_MAX_PIXELS=12000000
DHMV_TIMEOUT_SECONDS=300
DHMV_MAX_RESPONSE_MB=160
FLOOD_HAZARD_ENABLED=true
FLOOD_HAZARD_WCS_URL=https://geoservice.waterinfo.be/OGRK/wcs
FLOOD_HAZARD_RESOLUTION_M=5.0
FLOOD_HAZARD_MIN_SIDE_M=10
FLOOD_HAZARD_MAX_SIDE_M=20000
FLOOD_HAZARD_MAX_PIXELS=12000000
FLOOD_HAZARD_TIMEOUT_SECONDS=300
FLOOD_HAZARD_MAX_RESPONSE_MB=160
BATHYMETRY_PROFILES_ENABLED=true
BATHYMETRY_PROFILES_LAYER_URL=https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/0
BATHYMETRY_WATERCOURSE_LAYER_URL=https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/1
BATHYMETRY_PROFILES_PAGE_SIZE=1000
BATHYMETRY_PROFILES_MAX_FEATURES=50000
BATHYMETRY_PROFILES_TIMEOUT_SECONDS=120
BATHYMETRY_PROFILES_MAX_RESPONSE_MB=32
MDK_BATHYMETRY_PROBE_ENABLED=true
MDK_BATHYMETRY_WCS_URL=https://bathy.agentschapmdk.be/spatialfusionserver/services/ows/wcs/EL_wcs
MDK_BATHYMETRY_PROBE_TIMEOUT_SECONDS=20
MDK_BATHYMETRY_PROBE_MAX_RESPONSE_MB=4
# Bounded MDK acquisition stays fail-closed until the readiness probe reports
# "reachable" and an advertised coverage id is configured explicitly.
MDK_BATHYMETRY_ACQUISITION_ENABLED=false
MDK_BATHYMETRY_COVERAGE_ID=
MDK_BATHYMETRY_REQUEST_CRS=EPSG:4326
MDK_BATHYMETRY_MAX_BBOX_DEG2=0.25
MDK_BATHYMETRY_ACQUISITION_TIMEOUT_SECONDS=120
MDK_BATHYMETRY_ACQUISITION_MAX_RESPONSE_MB=160
THEMATIC_RASTER_ENABLED=true
THEMATIC_RASTER_WCS_URL=https://www.mercator.vlaanderen.be/raadpleegdienstenmercatorpubliek/wcs
THEMATIC_RASTER_MIN_SIDE_M=100
THEMATIC_RASTER_MAX_SIDE_M=60000
THEMATIC_RASTER_MAX_PIXELS=30000000
THEMATIC_RASTER_TIMEOUT_SECONDS=300
THEMATIC_RASTER_MAX_RESPONSE_MB=160
WALOUS_ENABLED=true
WALOUS_SOURCE_DIR=/app/storage/source-cache/walous
WALOUS_ANALYSIS_RESOLUTION_M=10
WALOUS_MAX_SIDE_M=60000
WALOUS_MAX_PIXELS=36000000
YOLO_ENABLED=false
YOLO_MODELS_DIR=/app/models
YOLO_MODEL_PATH=
YOLO_MODEL_ID=yolo-configured
YOLO_MODEL_DISPLAY_NAME=Configured YOLO detector
YOLO_MODEL_VERSION=
YOLO_CONFIG_DIR=./storage/ultralytics
YOLO_DEVICE=cpu
YOLO_IMAGE_SIZE=640
YOLO_MAX_TILES=100
YOLO_MAX_DETECTIONS=1000
YOLO_DUPLICATE_IOU_THRESHOLD=0.5
YOLO_BATCH_SIZE=1
# Local segmentation models. GeoIntel never downloads model weights # Docker Compose publication (use the Unraid LAN IP for a trusted-LAN deployment)
# automatically; point these to existing local files to enable inference. DOCKDECK_BIND_ADDRESS=127.0.0.1
YOLO_SEG_ENABLED=false DOCKDECK_PORT=1218
YOLO_SEG_MODEL_PATH= DOCKDECK_INTERNAL_SUBNET=172.31.240.0/28
YOLO_SEG_MODEL_ID=yolo-seg-configured DOCKDECK_EGRESS_SUBNET=172.31.240.16/28
YOLO_SEG_MODEL_DISPLAY_NAME=Configured YOLO segmentation
YOLO_SEG_MODEL_VERSION=
SAM_ENABLED=false
SAM_MODEL_PATH=
SAM_MODEL_ID=sam-configured
SAM_MODEL_DISPLAY_NAME=Configured SAM segmentation
SAM_MODEL_VERSION=
SEGMENTATION_MAX_MASKS_PER_TILE=300
SEGMENTATION_DUPLICATE_IOU_THRESHOLD=0.5
ENABLE_GRB_WFS=false
GRB_WFS_URL=
OSM_OVERPASS_URL=https://overpass-api.de/api/interpreter
# Install backend raster dependencies when needed: # Discovery. MOCK_DISCOVERY is intended for local development and tests only.
# python -m pip install rasterio MOCK_DISCOVERY=true
APPOPS_URL=
DOCKER_PROXY_URL=
UNRAID_TEMPLATES_PATH=/unraid-templates
UNRAID_ICONS_SOURCE_PATH=/unraid-icons-source
UNRAID_ICONS_PATH=/unraid-icons-cache
UNRAID_ICONS_DIR=/boot/config/plugins/dockerMan/images
UNRAID_HOST=tower.local
UNRAID_URL=http://tower.local:5000
UNRAID_RUNTIME_PATH=/unraid-runtime
UNRAID_HWMON_PATH=/host-hwmon
UNRAID_CPUINFO_PATH=/host-proc/cpuinfo
UNRAID_MEMINFO_PATH=/host-proc/meminfo
UNRAID_UPS_STATUS_PATH=
# Frontend # Optional read-only Nginx Proxy Manager connection. Keep credentials server-side.
VITE_API_BASE_URL= NPM_URL=
VITE_API_PROXY_TARGET=http://localhost:8000 NPM_TOKEN=
# Leave empty to use the local/demo OpenStreetMap fallback with visible attribution. NPM_USERNAME=
# Set this to a managed MapLibre style URL for production or heavier tile traffic. NPM_PASSWORD=
VITE_MAP_STYLE_URL=
# Docker Compose / Unraid # Optional provider-aware widget APIs. Use dedicated read-only or minimum-scope
GEOINTEL_FRONTEND_PORT=1202 # credentials where the provider supports them. Values never reach the browser.
GEOINTEL_BACKEND_PORT=8000 ADGUARD_URL=
GEOINTEL_INSTALL_AI=false ADGUARD_USERNAME=
GEOINTEL_STORAGE_PATH=./storage ADGUARD_PASSWORD=
GEOINTEL_BACKUPS_PATH=./backups PLEX_URL=
GEOINTEL_MODELS_PATH=./models PLEX_TOKEN=
GEOINTEL_POSTGIS_DATA_PATH=./postgres-data IMMICH_URL=
GEOINTEL_POSTGRES_DB=geointel IMMICH_API_KEY=
GEOINTEL_POSTGRES_USER=geointel HOME_ASSISTANT_URL=
GEOINTEL_POSTGRES_PASSWORD=geointel HOME_ASSISTANT_TOKEN=
GEOINTEL_CORS_ORIGINS=http://localhost:1202,http://127.0.0.1:1202 OLLAMA_URL=
GEOINTEL_MAX_UPLOAD_MB=500 GLANCES_URL=
TAUTULLI_URL=
TAUTULLI_API_KEY=
PAPERLESS_URL=
PAPERLESS_TOKEN=
SONARR_URL=
SONARR_API_KEY=
RADARR_URL=
RADARR_API_KEY=
LIDARR_URL=
LIDARR_API_KEY=
GITEA_WIDGET_URL=
GITEA_WIDGET_TOKEN=
JELLYFIN_URL=
JELLYFIN_API_KEY=
SEERR_URL=
SEERR_API_KEY=
PROWLARR_URL=
PROWLARR_API_KEY=
AUTHENTIK_URL=
AUTHENTIK_TOKEN=
PORTAINER_URL=
PORTAINER_API_KEY=
GRAFANA_URL=
GRAFANA_TOKEN=
PROMETHEUS_URL=
NEXTCLOUD_URL=
NEXTCLOUD_USERNAME=
NEXTCLOUD_APP_PASSWORD=
AUDIOBOOKSHELF_URL=
AUDIOBOOKSHELF_TOKEN=
NETDATA_URL=
PEERTUBE_URL=
# Apps whose native authentication requires non-GET requests can use a tiny
# read-only bridge returning { "summary": "...", "stats": [{ "label": "...", "value": "..." }] }.
DELUGE_METRICS_URL=
DELUGE_METRICS_TOKEN=
QBITTORRENT_METRICS_URL=
QBITTORRENT_METRICS_TOKEN=
JDOWNLOADER_METRICS_URL=
JDOWNLOADER_METRICS_TOKEN=
TDARR_METRICS_URL=
TDARR_METRICS_TOKEN=
BAZARR_METRICS_URL=
BAZARR_METRICS_TOKEN=
VAULTWARDEN_METRICS_URL=
VAULTWARDEN_METRICS_TOKEN=
# Optional one-time private repository initialization
GITEA_URL=
GITEA_OWNER=
GITEA_TOKEN=
+5 -13
View File
@@ -1,13 +1,5 @@
*.sh text eol=lf * text=auto eol=lf
deploy/unraid/gosu-setpriv text eol=lf *.png binary
*.py text eol=lf *.jpg binary
*.yml text eol=lf *.jpeg binary
*.yaml text eol=lf *.webp binary
*.toml text eol=lf
*.ini text eol=lf
Dockerfile text eol=lf
*.md text eol=lf
*.tsx text eol=lf
*.ts text eol=lf
*.css text eol=lf
*.json text eol=lf
+11 -52
View File
@@ -1,56 +1,15 @@
# Python
__pycache__/
*.py[cod]
.venv/
venv/
.env
*.egg-info/
.pytest_cache/
.ruff_cache/
# Node
node_modules/ node_modules/
dist/ dist/
build/ coverage/
playwright-report/
test-results/
data/*.db
data/*.db-*
data/wallpaper.*
.env
.env.*
!.env.example
*.log
*.tsbuildinfo *.tsbuildinfo
# Large local data
/artifacts/
/.cache/
/datasets/raw/*
/datasets/processed/*
/datasets/cache/*
/storage/uploads/*
/storage/tiles/*
/storage/masks/*
/storage/reports/*
/storage/exports/*
/storage/rasters/*
/storage/models/*
/storage/operator-data/*
/storage/operator-evidence/*
/storage/release-evidence/*
/storage/previews/*
/storage/training/*
/storage/ultralytics/*
/exports/*
/models/*
/backend/storage/uploads/*
/backend/storage/tiles/*
/backend/storage/masks/*
/backend/storage/reports/*
/backend/storage/exports/*
/backups/*
/postgres-data/*
# Keep folder placeholders
!**/.gitkeep
!**/README.md
# Runtime-generated operator documentation is not repository documentation.
/storage/operator-data/README.md
# OS/editor
.DS_Store .DS_Store
.vscode/ .codex-input/
.idea/
+42 -27
View File
@@ -1,44 +1,59 @@
# AI Agent Instructions for GeoIntel # AGENTS.md
## Project identity Project: DockDeck
GeoIntel is a GeoAI Workbench for Belgium and the Belgian North Sea, not a Dit project gebruikt het Codex Project Operating System.
generic CRUD app and not a generic dashboard. Mol and the Kempen remain golden
regression areas, not the product boundary.
## Required behavior ## Werkmodus
- Read `docs/CODEX_BOOTSTRAP_PROMPT.md` first. Volg deze stappen bij elke wijziging: begrijpen, ontwerpen, plannen, bouwen, testen, verbeteren, documenteren, committen.
- Respect `docs/RC_SCOPE_FREEZE_BELGIUM_NORTH_SEA.md`.
- Use `docs/API_CONTRACTS.md` as source of truth for endpoints.
- Use `docs/DATABASE_IMPLEMENTATION_PLAN.md` as source of truth for persistence.
- Use `docs/DEFINITION_OF_DONE.md` to decide whether work is complete.
## Agent roles Startmodus: MVP bouwen
### Architecture Agent - Codex mag scaffolden, bouwen, testen, committen en pushen wanneer de repo en remote duidelijk zijn.
- Bouw de kleinste bruikbare verticale flow eerst.
- Vraag alleen om hulp bij ontbrekende credentials, onduidelijke productkeuzes of risicovolle externe acties.
Owns repository layout, API contracts, database migrations and service boundaries. ## Autonomie
### GIS Agent - Werk zelfstandig door waar de intentie duidelijk is.
- Stel alleen vragen wanneer een keuze het productgedrag, de architectuur of de veiligheid wezenlijk verandert.
- Houd wijzigingen klein, toetsbaar en passend bij het bestaande project.
- Gebruik de vastgelegde runtime-, sync-, persistentie-, deploy- en repo-keuzes als startpunt.
- Werk projectdocumentatie bij zodra besluiten, risico's of verificatie veranderen.
Owns GeoPandas, Shapely, Rasterio, CRS, clipping, buffering, spatial joins and metadata extraction. ## Autonomiegrenzen
### AI Agent - Commit en push alleen wanneer de repo en remote duidelijk zijn.
- Deploy alleen wanneer het deploymentdoel, poort en credentials expliciet bekend zijn.
- Vraag om input bij ontbrekende credentials, betaalde diensten of destructieve acties.
- Stop en vraag bevestiging wanneer een actie gebruikersdata kan wijzigen, verwijderen of publiceren.
Owns YOLO/SAM abstractions, inference contracts, model configuration, detection/segmentation persistence and `not_configured` behavior. ## Veiligheid
### QA Agent - Commit nooit secrets.
- Plaats geen API keys, tokens, wachtwoorden of klantdata in code, tests of logs.
- Verwacht credentials: Ja.
- Security/privacy notities: 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.
Owns tests, QA/QC metrics, regression checks and acceptance criteria. ## Quality Gate
### Frontend Agent - Run de relevante tests voor elke gedragswijziging.
- Run lint/typecheck voor overdracht of commit.
- Noteer testresultaten in TEST_LOG.md.
- Laat bekende risico's achter in RISKS.md en HANDOFF.md.
- Verplichte finale gate: `npm run format:check`, `npm run lint`, `npm run typecheck`, `npm test`, `npm run test:e2e`, `npm run test:security`, `npm run build` en `npm audit --audit-level=moderate`.
- Valideer `docker compose config` en de imagebuild zodra een Docker-runtime beschikbaar is.
Owns React, TypeScript, MapLibre, API client, UI states and workbench UX. ## Productgrenzen
## Never do this - Houd Docker-, Unraid- en NPM-integraties read-only.
- Voeg nooit Docker start/stop/restart/delete/exec- of andere beheeracties toe.
- Nieuwe discovery-records blijven standaard verborgen; integratiefouten mogen opgeslagen navigatie nooit blokkeren.
- Secrets blijven uitsluitend in environmentvariabelen en mogen niet in API-responses, SQLite, exports, logs, fixtures of browserbundels terechtkomen.
- De app heeft geen authenticatie en blijft standaard op `127.0.0.1` gebonden.
- Do not fake production AI outputs. ## Resume Instructions
- Do not silently skip geospatial validation.
- Do not add auth/multi-user/LiDAR/training before V1 foundation is stable. - Lees AGENTS.md, PROJECT_BRIEF.md, REQUIREMENTS.md, SPEC.md, PLAN.md, DECISIONS.md, TEST_LOG.md, RISKS.md en HANDOFF.md voordat je verder bouwt.
- Do not remove documentation to avoid conflicts. - Vat de huidige staat kort samen, bepaal de volgende stap uit PLAN.md en werk daarna de documentatie bij.
+65
View File
@@ -0,0 +1,65 @@
# DECISIONS.md
Project: DockDeck
## Decision Log
| ID | Decision | Rationale | Status |
| ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- |
| D-001 | DockDeck is een interactieve webapp. | Past bij browserstartpagina en lokaal beheer. | Accepted |
| D-002 | TypeScript, React/Vite, Fastify, Drizzle/SQLite, Zod, Vitest en Playwright in één repository. | Kleinste stack die de bindende brief volledig en testbaar dekt. | Accepted |
| D-003 | Fastify serveert in productie REST en de gebouwde React-client. | Eén container en één poort beperken Unraid-complexiteit. | Accepted |
| D-004 | Statuspolling is configureerbaar, standaard 30 seconden, en pauzeert op verborgen tabs. | Actuele status zonder flikkering of onnodige achtergrondbelasting. | Accepted |
| D-005 | SQLite staat in `/data/dockdeck.db`; een idempotente migratie draait vóór serverstart. | Eenvoudige persistente back-up zonder externe database. | Accepted |
| D-006 | De hoofdapp krijgt geen Docker-socket; discovery verloopt via een server-side GET-adapter en alleen de optionele fallbackproxy ziet de socket. | Handhaaft de expliciete veiligheidsgrens. | Accepted |
| D-007 | Git gebruikt lokaal `main`; private Gitea-initialisatie gebeurt via een apart environmentgestuurd script. | Credentials waren lokaal niet beschikbaar en worden nooit opgeslagen. | Accepted |
| D-008 | Integraties rapporteren `connected`, `degraded` of `disabled`; opgeslagen navigatie blijft beschikbaar. | Externe uitval mag het dashboard niet blokkeren. | Accepted |
| D-009 | Exportformaat versie 1 bevat alleen gebruikersconfiguratie, nooit secrets. | Veilige en toekomstig migreerbare back-up. | Accepted |
| D-010 | Visuele voorkeuren worden als uitbreidbare velden in SQLite opgeslagen en bij oudere databases idempotent toegevoegd. | Personalisatie blijft na updates behouden zonder een breaking exportwijziging. | Accepted |
| D-011 | DM Sans en Newsreader worden lokaal uit het applicatiepakket geladen. | De startpagina blijft visueel stabiel en heeft geen externe fontrequest nodig. | Accepted |
| D-012 | Servicewidgets gebruiken read-only runtimegegevens voor maximaal acht expliciet gekozen containers; standaard via AppOps en in de optionele fallback via Dockerstats. | Levert CPU-, geheugen- en statusinformatie zonder appcredentials of Dockerbeheerrechten. | Accepted |
| D-013 | Favorieten leiden zonder override hun icoon af als `<origin>/favicon.ico` en vallen bij fouten terug op een monogram. | Automatische first-party iconen zonder externe favicon- of trackingdienst. | Accepted |
| D-014 | De standaard Unraid-widget combineert AppOps-inventaris met minimale hostmounts; providers gebruiken expliciete server-side minimum-scope credentials en runtimefallbacks. | Levert echte dienstfunctionaliteit zonder container-environments te inspecteren, secrets op te slaan of de read-only grens te verruimen. | Accepted |
| D-015 | Providercredentials kunnen in Settings worden geschreven naar een atomisch vervangen `0600`-environmentbestand in het persistente volume; de API echoot waarden nooit terug. | Maakt app-specifieke widgets configureerbaar zonder secrets in SQLite, export, logs, frontendbundel of Git te plaatsen. | Accepted |
| D-016 | Widget Studio gebruikt niet-persistente capabilitymetadata; provideradapters mogen de ontdekte lokale app-origin als URL gebruiken en vragen alleen ontbrekende secrets. | Maakt rijke widgets vindbaar en vaak zero-config zonder dubbel URL-beheer of automatische wijziging van gebruikersvoorkeuren. | Accepted |
| D-017 | Een uit DockDeck verwijderde app wordt op technische containernaam persistent onderdrukt; de Docker-container zelf blijft onaangeraakt. | Geeft echte, restartbestendige opruiming binnen het platform zonder de read-only Dockergrens te doorbreken. | Accepted |
| D-018 | De zoekindex is volledig afgeleid uit de actuele dashboardpayload en bevat ook verborgen apps, aliases, URL's, categorieën, favorieten en de Unraid-host. | Hernoemen en configuratiewijzigingen worden direct vindbaar zonder een aparte stale zoekdatabase. | Accepted |
| D-019 | Widgetformaat en informatielagen zijn expliciete, persistente app-/hostvelden; providerdetectie gebruikt servicetokens en sluit supportcontainers uit. | Geeft echte inhoudscontrole en voorkomt dat brede substringmatches een verkeerde providerkaart opleveren. | Accepted |
| D-020 | Widgetvolgorde staat los van apptegelvolgorde; directe editbediening gebruikt semantische knoppen en blijft zonder drag-and-drop volledig bruikbaar. | Behoudt voorspelbare toetsenbord- en schermlezervolgorde in responsive dense grids. | Accepted |
| D-021 | App- en servergrafieken bewaren maximaal zestig punten alleen in serverprocesgeheugen; iedere widget heeft een begrensde refreshcadans, configureerbaar venster en expliciete freshness. | Geeft nuttige korte trends zonder een monitoringdatabase of onbegrensde hostbelasting. | Accepted |
| D-022 | Unraid-native status komt uit specifieke read-only runtime- en hwmonmounts, nooit uit een verruimde Dockerproxy of mutatie-API. | Levert array- en hostdiepgang met een minimale, controleerbare trust boundary. | Accepted |
| D-023 | Beveiligde applicaties zonder GET-only native API gebruiken optioneel een door de gebruiker beheerde metrics bridge. | Houdt DockDeck aantoonbaar GET-only en maakt toch app-specifieke widgets mogelijk. | Accepted |
| D-024 | Layoutpresets zijn afzonderlijke records en exports; toepassen wijzigt alleen widgetpresentatie, nooit appzichtbaarheid, credentials of containers. | Maakt contextwissels veilig en draagbaar zonder een volledige configuratierestore. | Accepted |
| D-025 | De PWA-serviceworker cachet uitsluitend de applicatieshell en weigert API-caching; diagnostics zijn secretvrij. | Voorkomt dat operationele data of credentials als offline autoriteit in de browser blijven staan. | Accepted |
| D-026 | Iedere ontdekte app krijgt capabilitymetadata en een optionele GET-only JSON-koppeling wanneer geen native adapter bestaat. | Dekt ook eigen en niche-apps af zonder onveilige beheer-API's, containerspecifieke secrets of generieke schijnmetrics. | Accepted |
| D-027 | Zichtbaarheid en volgorde van appmetrics worden per widget als labels opgeslagen en met live providerlabels samengevoegd. | Laat inhoud aanpassen zonder het opslagmodel aan iedere nieuwe provider of metric te koppelen. | Accepted |
| D-028 | Tower-identiteit is gebruikersconfiguratie; een ontbrekende poort in de Unraid-basis-URL wordt als `5000` geïnterpreteerd. | Herstelt de werkelijke lokale WebUI-link en houdt toekomstige hostnaam- of poortwijzigingen in de UI beheerbaar. | Accepted |
| D-029 | De standaard Unraid-deployment bestaat uit één branded DockDeck-container en gebruikt de bestaande AppOps GET-inventaris; een losse socketproxy is uitsluitend een optionele overlay. | Voldoet aan eenvoudig Unraid-beheer zonder de Docker-socket of mutatierechten aan DockDeck te geven. | Accepted |
| D-030 | Capabilitymetadata bepaalt ook de niet-gekoppelde kaartinhoud; ontbrekende appdata wordt expliciet als te koppelen getoond en nooit vervangen door generieke schijnmetrics. | Houdt iedere widget domeinspecifiek zonder waarden te verzinnen en scheidt echte Docker-infrastructuurtelemetrie duidelijk. | Accepted |
| D-031 | Unraid-listmetadata gebruikt een echt transparant PNG-bestand, ook al blijft de PWA vrij om het schaalbare SVG-asset te gebruiken. | Sluit aan op Unraids vaste `.png`-cache en voorkomt een onzichtbaar icoon door een fout MIME-/bestandsformaat. | Accepted |
| D-032 | De Unraid-deployment ververst na een icoonwijziging zowel Dockermans persistente als actieve RAM-cache en valideert vooraf het echte PNG-MIME-type. | DockerMan hergebruikt anders een bestaand actief cachebestand, waarna zijn `onerror` het bedoelde icoon stilzwijgend door `question.png` vervangt. | Accepted |
| D-033 | DockDeck Canon v1 is de bindende visuele bron; conflicterende Obsidian Control-, Core Modernist- en oudere Stitch-varianten worden niet gecombineerd. | Eén expliciet ontwerpsysteem voorkomt een hybride interface en houdt tokens, componentvormen en responsive gedrag onderhoudbaar. | Accepted |
| D-034 | Launcherinhoud en Quick Launch vormen de primaire dashboardhiërarchie; bestaande read-only widgets, presets en diagnostics blijven behouden onder een secundair uitklapbaar inzicht en Algemeen. | Behoudt alle operationele functionaliteit zonder de rustige dagelijkse startflow van Canon v1 te verdringen. | Accepted |
| D-035 | Geist wordt lokaal gebundeld en alle Canon-kleuren, spacing, radii en states lopen via semantische tokens voor dark, light en Harbor. | Voorkomt externe fontafhankelijkheden en maakt themagedrag consistent zonder Canon-waarden door component-CSS te verspreiden. | Accepted |
| D-036 | Playwright draait lokaal op de geïsoleerde API-/webpoorten 3101 en 5174, configureerbaar via environmentvariabelen. | Vermijdt botsing met de bestaande WSL-relay op poort 3000 zonder de productiestandaardpoort of runtimearchitectuur te wijzigen. | Accepted |
| D-037 | De Stitch-launcher combineert op desktop een permanente favorietenrail met zichtbare read-only servicepanelen; op tablet en mobiel blijven favorieten en apps vóór de panelen staan. | Herstelt de sterke dagelijkse navigatie en widgetmogelijkheden uit het oorspronkelijke DockDeck zonder de rustige mobiele Canon-hiërarchie te verliezen. | Accepted |
| D-038 | Appiconen gebruiken eerst expliciete Unraid-/gebruikersmetadata, daarna een lokaal gebundeld merkicoon en ten slotte een semantisch service-icoon; lettermonogrammen vervallen. | Geeft iedere app een herkenbare visuele identiteit zonder CDN-afhankelijkheid en houdt onbekende of eigen diensten toch betekenisvol. | Accepted |
| D-039 | Instellingen zijn centraal doorzoekbaar; verversingsinterval en verminderde beweging zijn expliciete, persistente voorkeuren die ook in versie 1-export/import meegaan. | Maakt de volledige instellingenoppervlakte vindbaar en voorkomt dat bereikbaarheid of polling alleen als verborgen backendcontract bestaan. | Accepted |
| D-040 | Nieuwe discovery-records krijgen een persistente `discoveredAt`-tijd en `isNew`-status; bestaande databases migreren records als erkend en een zichtbaarheidshandeling erkent een nieuw record. | Houdt de ontdekbanner en het filter “Nieuw ontdekt” waarheidsgetrouw zonder bewust verborgen bestaande apps als nieuw te presenteren. | Accepted |
| D-041 | Dashboard en instellingen delen voortaan dezelfde gecentreerde platformcanvas; vanaf 1900 px wordt de widgetlaag automatisch als rechterdock geopend en de appgrid vult ultrawide ruimte vloeiend. | Herstelt de visuele consistentie met de nette instellingenzijbalk, gebruikt brede schermen doelgericht en houdt widgets zichtbaar zonder de launcher te verdringen. | Accepted |
| D-042 | Een geslaagde Docker/AppOps-inventaris is autoritatief: ontbrekende apprecords worden automatisch uit DockDeck verwijderd en naamvergelijking is hoofdletterongevoelig; bij een degraded inventaris wordt niets opgeschoond. | Houdt Instellingen gelijk aan de werkelijk bestaande containers, herstelt actuele status zonder duplicaten en voorkomt dat tijdelijke integratie-uitval opgeslagen configuratie wist. | Accepted |
| D-043 | Unraid DockerMan-iconen worden uit een minimale read-only hostmount naar een tijdelijke containercache gekopieerd; daarna draait de applicatie als de niet-geprivilegieerde `dockdeck`-gebruiker en serveert alleen gekende afbeeldingsformaten. | De persistente Unraid-iconen zijn root-only en de runtimecache is niet volledig. Een begrensde initkopie levert alle echte iconen zonder hostpermissies te wijzigen of blijvende rootrechten te geven. | Accepted |
| D-044 | Slimme categorie-indeling is een expliciete gebruikersactie met geordende, geteste serviceregels; alleen bekende services worden verplaatst en onbekende containers en favorieten blijven ongewijzigd. | Corrigeert aantoonbare bulkfouten zonder automatische heartbeats of toekomstige discovery de bewuste persoonlijke indeling van onbekende apps te laten overschrijven. | Accepted |
| D-045 | Wallpapers worden als één gevalideerd lokaal PNG/JPEG/WebP-bestand naast de database opgeslagen; weerdata komt server-side en tien minuten gecachet van Open-Meteo. Publieke healthroutes leveren een zero-secret status, terwijl tokens alleen optionele detailstatistieken ontsluiten. | Houdt beelddata en integraties uit SQLite, exports en browserbundels, voorkomt secretlekken en levert toch een rijke dagelijkse startpagina met minimale externe belasting. | Accepted |
| D-046 | Een wallpaper wordt in een sticky stage ter hoogte van het zichtbare centrale viewport gerenderd. Passing, X-/Y-focus en zoom zijn afzonderlijke persistente voorkeuren met `contain` als veilige standaard. | Voorkomt dat lange dashboardinhoud de afbeelding sterk afsnijdt, houdt de volledige afbeelding standaard zichtbaar en geeft de gebruiker gecontroleerde compositievrijheid zonder het bronbestand te wijzigen. | Accepted |
| D-047 | Zodra een wallpaper bestaat, vervangt die de achtergrondpreset op het volledige platformvlak onder de topbalk. Een scherpe instelbare beeldlaag ligt boven een zachte `cover`-kopie; rails, widgets en tegels zijn leesbare glaslagen erboven. | Laat de afbeelding ononderbroken van de linker- tot de rechterkolom lopen en tegelijk volledig zichtbaar blijven bij `contain`, zonder Blauwdruk-, Aurora- of Minimaal-lagen door de wallpaper heen te mengen. | Accepted |
| D-048 | Canonieke categorie-iconen gebruiken één gedeelde catalogus voor keuzelijst én renderer; iedere huidige categorie resolveert naar een unieke Lucide-component en nieuwe categorieën kiezen eerst een ongebruikt icoon. | Voorkomt dat geldige opgeslagen waarden stil naar hetzelfde generieke `Layers3`-beeld terugvallen en houdt toekomstige categorieën visueel onderscheidbaar zonder data te herschrijven. | Accepted |
## Decision Rules
| Rule | Reason |
| --------------------------------------------------------------- | ------------------------------------- |
| Leg architectuurkeuzes vast voordat ze breed doorwerken. | Voorkomt impliciete afhankelijkheden. |
| Kies de kleinste oplossing die aan de must-haves voldoet. | Beschermt scope en snelheid. |
| Heropen beslissingen wanneer requirements of risico's wijzigen. | Houdt het dossier betrouwbaar. |
+29
View File
@@ -0,0 +1,29 @@
# syntax=docker/dockerfile:1.7
FROM node:22-alpine AS build
WORKDIR /app
RUN apk add --no-cache python3 make g++
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --omit=dev
FROM node:22-alpine AS runtime
ENV NODE_ENV=production \
HOST=0.0.0.0 \
PORT=3000 \
DATABASE_PATH=/data/dockdeck.db
WORKDIR /app
RUN apk add --no-cache su-exec && \
addgroup -S dockdeck && \
adduser -S -G dockdeck dockdeck && \
mkdir -p /data /host-proc /unraid-icons-cache && \
chown dockdeck:dockdeck /data /unraid-icons-cache
COPY --from=build --chown=dockdeck:dockdeck /app/package.json ./package.json
COPY --from=build --chown=dockdeck:dockdeck /app/node_modules ./node_modules
COPY --from=build --chown=dockdeck:dockdeck /app/dist ./dist
COPY docker-entrypoint.sh /usr/local/bin/dockdeck-entrypoint
RUN chmod 0755 /usr/local/bin/dockdeck-entrypoint
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 CMD wget -qO- http://127.0.0.1:3000/api/health >/dev/null || exit 1
ENTRYPOINT ["dockdeck-entrypoint"]
CMD ["node", "dist/server/index.js"]
+26
View File
@@ -0,0 +1,26 @@
# EXPORT_QUALITY.md
Project: DockDeck
Generated at: 2026-07-13T18:54:16.667Z
Exportprofiel: Codex volledig
Codex startmodus: MVP bouwen
## Status
- Resultaat: Klaar voor Codex
- Samenvatting: De huidige export bevat de verwachte bestanden, metadata en startinstructies.
## Checks
| Check | Status |
| ---------------------------------------- | ------ |
| Vereiste input ingevuld | Pass |
| Intake gereed voor autonome start | Pass |
| Bestandenset gecontroleerd | Pass |
| Metadata klopt met profiel en startmodus | Pass |
| Startprompt aanwezig | Pass |
| Autonomiegrenzen aanwezig | Pass |
## Blokkades
- Geen blokkades.
+173
View File
@@ -0,0 +1,173 @@
# HANDOFF.md
Project: DockDeck
Updated: 2026-07-22
## Current status
Release `9236094` draait vanaf `main` op `192.168.10.150:1218`. De bestaande `.env` bleef byte-identiek, het named volume, de configuratie en de geüploade wallpaper van 3.245.835 bytes zijn behouden. Image `1cf11fc02e7e…` is healthy met 0 herstarts. DockDeck bevat exact 90 bestaande containers, 48 zichtbare apps, 16 categorieën en 1 favoriet. De zeven livepanelen (zes apps plus Tower) zijn allemaal connected.
## Current update
De actuele pass corrigeert de resterende widescreenpunten uit een echte Chrome-audit op 3440×1215. De rechter widgetdock is 540 px breed, zichtbare widget- en instellingentekst blijft minimaal 1112 px, extreem lange beheeroppervlakken zijn begrensd en Quick Launch laadt het echte favoriete favicon. De expliciete actie `Slim herindelen` gebruikt geteste serviceregels en laat onbekende containers en favorieten ongemoeid. Live zijn 14 foutieve categorieën gecorrigeerd; onder meer Deluge/JDownloader2 staan bij Downloads, Glances/unraid-stats bij Monitoring en Nginx Proxy Manager bij Network & Admin.
De premium afwerkingspass is live voltooid en op 3440×1215 in Chrome gecontroleerd. App Beheer gebruikt een expliciete 52px-track met een knop die 24px tussen links en rechts beweegt. De vier genoemde beveiligings-/automatiseringsdiensten gebruiken compacte launchertegels. De topbalk toont gecachete actuele Open-Meteo-data voor Mol; Weergave bevat locatie-instellingen en een gevalideerde lokale wallpaper-upload. De placeholders DockDeck, GPU-Hot en Deluge zijn in het widgetdock vervangen door werkende compacte Authentik-, NPM- en Vaultwardenpanelen. Plex en Gitea hebben echte read-only providerdata; Home Assistant blijft correct offline zolang de container niet draait.
De daaropvolgende achtergrondfix herstelt de feitelijke rendering van Aurora, Blauwdruk, Minimaal en de geüploade wallpaper. De persistentie en wallpaper-API waren correct; een niet-bestaande CSS-token maakte de samengestelde achtergrondregels ongeldig en een te zware overlay hield de afbeelding vrijwel volledig tegen. De achtergrond wordt nu rechtstreeks op de centrale canvas samengesteld, met een afzonderlijke leesbare wallpaper-overlay per preset en expliciete E2E-dekking.
De live controle op 3440×1215 heeft alle drie presets via de instellingeninterface doorlopen. Hun berekende canvaslagen zijn onderling verschillend, de bestaande JPEG blijft in iedere preset aanwezig, er is geen horizontale overflow en Chrome rapporteert geen fouten. Blauwdruk is als actieve gebruikersvoorkeur hersteld.
De daaropvolgende wallpaperstudio corrigeert de resterende uitsnede: de afbeelding wordt voortaan tegen het zichtbare centrale viewport geschaald in plaats van tegen de volledige scrollhoogte. `Passend` is de veilige standaard; `Vullend`, horizontale/verticale focus en 80160% zoom zijn via een gecentreerde live preview instelbaar. Deze waarden zijn server-side gevalideerd, persistent, legacy-compatibel en exporteerbaar.
De live Chrome-hercontrole op 3440×1215 meet een wallpaperstage van 2381×1143 binnen de 3015 px hoge dashboardcanvas en een gecentreerde studiopreview van 1600×686. De afbeelding is volledig geladen met `Passend`, 50/50-focus en 100% zoom; er is geen horizontale overflow en Chrome rapporteert geen fouten. De draftflow `Vullend``Herstellen` is getest zonder de opgeslagen voorkeur of wallpaper te wijzigen.
De aansluitende platformpass haalt de wallpaper uit de middenkolom. De fixed stage begint exact onder de 72px-topbalk, bestrijkt de volledige viewportbreedte en -hoogte, blijft bij scrollen staan en schakelt de presetcompositie uit zolang een wallpaper bestaat. Een ambient `cover`-kopie vult ultrawide zijruimte achter het scherpe instelbare beeld; de linkerrail, apptegels en rechter widgetdock zijn doorlopende glaslagen erboven. De preview weerspiegelt dezelfde compositie.
De live Chrome-audit op 3440×1215 meet de stage van x=0/y=72 tot de volledige 3425×1143 browsercontentruimte. Bij 900 px scroll blijft de stage exact op y=72, de presetcanvas is transparant, beide zijpanelen gebruiken 62% glas en er is geen horizontale overflow. Chrome rapporteert geen applicatiefouten; alleen de geïnstalleerde WideFrog-extensie schreef één eigen debug-timeout.
De categorie-icooncorrectie vult de frontendcatalogus aan met de tien eerder ontbrekende geldige waarden. Daardoor tonen Media, Downloads, Books & Documents, Photos, Home & Automation, Security & Identity, Network & Admin, Monitoring, Development & Projects, AI & ML, Databases & Storage, Infrastructure, Games & Emulation, Tools & Productivity, Experiments & Archived en Favorites ieder een eigen passend pictogram. Nieuwe categorieën kiezen automatisch een nog ongebruikte optie.
De live Chrome-hercontrole bevestigt 16 categoriekaarten, 16 verschillende opgeslagen waarden en 16 verschillende gerenderde SVG-signaturen. De categorie-iconen zijn zowel in Instellingen als bij de dashboardsecties visueel onderscheidend; Chrome rapporteert 0 waarschuwingen of fouten.
## What works
- DockDeck Canon v1 als geïntegreerd productoppervlak, gebaseerd op de expliciete Canon-bronnen uit ZIP en Stitch MCP; oudere conflicterende designrichtingen zijn genegeerd.
- Lokale merkassets voor app/favicon/wordmark en lokaal gebundeld Geist; geen CDN- of tijdelijke beeldafhankelijkheid.
- Launcher-first dashboards met Canon-topbar, ontdekbanner, categoriechips, apptegels en states; desktop voegt een sticky favorietenrail met klok, deckstatus en widgetbeheer toe.
- Echte Unraid-/gebruikersiconen blijven eerste keuze; herkenbare lokale merkiconen en semantische service-iconen vervangen alle lettermonogrammen.
- Quick Launch als modal command palette met Apps, Favorieten en Google, toetsenbordnavigatie, Enter/Escape en focusstijl.
- Compact appsbeheer met filters, zichtbaarheid en paneelkeuze in de rij; naam/categorie/URL/icoon/sortering/verwijderen in een focus-trapped drawer met focusherstel.
- Geïsoleerde E2E-poorten 3101/5174 zodat lokale diensten op 3000 de browsertests niet blokkeren.
- Centrale instellingenzoeker met sectiegerichte trefwoorden en scrollherstel bij sectiewissel.
- Persistente verminderde beweging en configureerbare globale pollingcadans, inclusief legacy-databasemigratie en export/import.
- Canon-categoriekaarten met echte iconen/itemtellingen en veilige back-upimport met JSON-/groottevalidatie, dropzone en herstelbevestiging.
- Expliciete slimme categorieorganisatie voor bekende homelabservices, met behoud van onbekende persoonlijke toewijzingen en favorieten.
- Single-container read-only discovery via AppOps GET, met een gesaneerde Unraid XML/WebUI-metadataweergave en alleen voor andere hosts een optionele restricted-proxyoverlay.
- Nieuwe containers standaard verborgen; online/offline-status en 30-secondenpolling die pauzeert op verborgen tabs.
- Verdwenen containers worden na een geslaagde autoritatieve inventarislezing automatisch uit DockDeck verwijderd; degraded inventarischecks verwijderen niets. Terugkerende/actief geworden containers en hoofdlettervarianten krijgen bij iedere heartbeat hun actuele status zonder dubbele offline records.
- Volledig appbeheer: zichtbaarheid, naam, categorie, sortering, lokale/externe URL-override en icoonfallback.
- Apps kunnen permanent uit DockDeck worden verwijderd en blijven op technische containernaam onderdrukt bij volgende discovery; Docker blijft onaangeraakt.
- Categorieën, favorieten, totaaloverzicht en categorie-tabs.
- Zoekoverlay voor apps/favorieten plus Google-fallback; alle doelen in een nieuw tabblad.
- De zoekindex bevat actuele namen, technische namen, categorieën, images, URL's, verborgen apps, favorieten en Tower; hernoemen werkt zonder stale index.
- Porcelain, Midnight en Harbor, vier accenten, drie dichtheden, drie contentbreedtes en drie achtergronden; alle voorkeuren zijn persistent.
- Vloeiende mobiele, tablet-, desktop- en ultrawide-layouts met optionele hero/statusweergave en lokale fonts.
- Doorzoekbaar appbeheer met zichtbaarheidsfilters, volledig favorietenbeheer en configureerbare categorie-iconen.
- Configureerbare read-only servicewidgets voor maximaal acht containers met compact/standaard/breed formaat, app-specifieke informatieblokken en eerlijke provider-/infrastructuurstatus.
- Onafhankelijke widgetvolgorde, statselectie/-volgorde/-limiet, metricvolgorde, value/gauge/progress-presentatie, custom label, waarschuwing op iedere numerieke metric, refreshcadans, configureerbare lijn-/vlak-/staafgrafiek tot 60 punten en directe toetsenbordtoegankelijke dashboardeditmodus.
- Layoutpresets kunnen los van de volledige backup worden opgeslagen, toegepast, verwijderd, geëxporteerd en geïmporteerd.
- Een standaard inschakelbare Unraid-hostwidget met eigen formaat-/inhoudskeuzes, selecteerbare serverstats en configureerbare grafieken voor containers, images, geheugen, opslag, temperatuur, parity en UPS.
- Native Unraid array-, parity-, disk-/pool-, temperatuur- en optionele UPS-data via minimale read-only mounts.
- Provider-aware read-only verrijking voor AdGuard, Plex, Immich, Home Assistant en Sonarr/Radarr/Lidarr; zonder credential blijft iedere kaart werken met Dockerstats.
- Uitgebreide GET-only providers voor Gitea, Jellyfin, Seerr, Prowlarr, Authentik, NPM, Portainer, Grafana, Prometheus en Nextcloud, plus configureerbare GET-only bridges voor Deluge, qBittorrent, JDownloader, Tdarr, Bazarr en Vaultwarden.
- Widget Studio met zoeken, aanbevolen/alle/actieve filters, zichtbare metricfeatures, credentialstatus, acht-panelencapaciteit en directe activatie.
- Widget Studio dekt alle 97 opgeslagen apps met 97 expliciete live-inventarisprofielen voor applicaties, API's, frontends, workers, databases, caches, mail, GPU, queues en eigen projecten; er rest geen generiek profiel.
- Per app zijn metriclabels zichtbaar/verbergbaar, tot 24 labels rangschikbaar en persistent in SQLite, presets en exports.
- Apps zonder native provider ondersteunen een optionele GET-only JSON-URL en bearer-token rechtstreeks vanuit hun Widget Studio-kaart; secrets blijven server-side.
- Audiobookshelf, Netdata en PeerTube hebben aanvullende native GET-only adapters.
- Tower-naam en Unraid WebUI-URL zijn persistent instelbaar; een URL zonder poort krijgt standaard `5000`.
- Nieuwe providerwidgets voor Ollama en Glances (zero-config via ontdekte URL), plus Tautulli en Paperless-ngx met minimum-scope credential.
- Extra app-specifieke Dockerpresentaties voor AI-runtimes, documentworkflows en beveiligde diensten.
- Provider-specifieke, gemaskeerde credentialformulieren onder Settings → Integrations; writes zijn same-origin, worden nooit geëchood of geëxporteerd en testen de verbinding direct.
- App-specifieke fallbackkaarten: JDownloader toont actuele download-/uploadsnelheid en AdGuard DNS-verkeer plus een duidelijke API-lockstatus; andere servicetypen krijgen eigen labels en betekenisvolle workloadweergave.
- Automatische first-party favicons voor favorieten, met handmatige override en lokale merk-/semantische fallback.
- Informatief desktopzijpaneel met klok, deck health en snelle links; bredere content- en tegelverdeling op 2560/3440-schermen.
- Favorieten staan als faviconlinks in het scrollbare desktopzijpaneel en de Tower-titel opent rechtstreeks de geconfigureerde Unraid-URL.
- Dashboard en instellingen schalen op dezelfde platformbreedte tot ultrawide; vanaf 1900 px staat de widgetlaag als rechterdock open en gebruikt de appgrid de extra ruimte met begrensde kaartbreedtes.
- Quick Launch toont zonder zoekterm functionele snelkoppelingskaarten en gebruikt bij favorietresultaten dezelfde echte favicon-/override-/fallbackketen als het dashboard.
- Dashboardcategorieknoppen filteren in zowel overzichts- als categoriemodus; de geselecteerde categorie beperkt tegelijk apps en favorieten.
- De topbalk behoudt op Dashboard en Instellingen dezelfde merk-, zoek-, navigatie- en actiekolommen. Dashboard/Instellingen hebben zichtbare hover- en focusstates en Instellingen biedt ook handmatige statusverversing.
- Integraties gebruikt geen compacte kaartmatrix meer maar brede configuratieregels met leesbare URL-/credentialvelden. Paneelstudio toont een duidelijke kies-toevoegen-afstemmenflow en één brede service per rij.
- SQLite-persistentie, idempotente eerste migratie en versie 1 export/import zonder secrets.
- Optionele read-only NPM-hostlezing en ondubbelzinnige matching met veilige fallback.
- Productiebuild, health endpoint, Dockerfile, hardened Compose en veilige Gitea-init/pushscript.
- Installable PWA-shell zonder API-cache, freshness/stale-cache en secretvrije operationele diagnostics.
## Directe Stitch-pass
- Het dashboard volgt nu rechtstreeks de Canon-volgorde: compacte topbar, begroeting/ontdekking, categoriepilletjes, favorieten, standaard servicekaarten en brede featurekaarten.
- Quick Launch gebruikt de centrale Canon-sheet met gegroepeerde Apps, Favorieten en Google, inclusief toetsenbordselectie.
- App Beheer is de Canon-tabel met status, categorie, zichtbaarheid en actie; de editor is een vaste rechter drawer met zichtbaarheid, categorie en echte URL-contracten.
- Weergave zet de drie themapreviews en de Canon-keuzes voor dashboardmodus/grid-dichtheid in de eerste viewport; aanvullende bestaande voorkeuren blijven lager beschikbaar.
- De mobiele compositie gebruikt de Canon-ontdekkaart, aparte zoekbalk, ronde categorieknoppen, touchvriendelijke kaartfamilies en vaste ondernavigatie.
- Read-only servicepanelen zijn opnieuw een zichtbare Stitch-sectie op desktop en staan op compactere viewports na de launcherinhoud; de volledige Paneelstudio blijft rechtstreeks bereikbaar onder Instellingen → Algemeen.
- Finale screenshots staan in `docs/design/dockdeck-canon-v1/implementation/` en worden door Playwright op 390×844, 768×1024, 1440×900 en 3440×1440 vastgelegd.
## Run instructions
Lokaal:
```bash
cp .env.example .env
npm install
npm run dev
```
Open `http://127.0.0.1:5173`. Met `MOCK_DISCOVERY=true` worden deterministische voorbeeldcontainers gebruikt. Lokale data staat in `./data/dockdeck.db`.
Unraid gebruikt `deploy/unraid.env.example` als `.env`:
```dotenv
DOCKDECK_BIND_ADDRESS=192.168.10.150
DOCKDECK_PORT=1218
DOCKDECK_EGRESS_SUBNET=172.31.240.16/28
APPOPS_URL=http://192.168.10.150:1216
UNRAID_TEMPLATES_DIR=/mnt/user/appdata/dockdeck/unraid-templates
UNRAID_HOST=192.168.10.150
```
```bash
python3 deploy/sanitize-unraid-templates.py \
/boot/config/plugins/dockerMan/templates-user \
/mnt/user/appdata/dockdeck/unraid-templates
docker compose up -d --build
docker compose ps
sh deploy/refresh-unraid-icon.sh
```
De deployment staat in `/mnt/user/appdata/dockdeck`. De base Compose bevat één service/container met de naam `DockDeck`; `docker-compose.socket-proxy.yml` is alleen een fallback voor een host zonder AppOps. De standaardpoort is `1218` en bindt zonder override alleen op `127.0.0.1`. De huidige deployment bindt bewust aan `192.168.10.150` voor het vertrouwde LAN. Bescherm iedere reverse-proxyroute, want de MVP heeft geen login. Persistente data staat in named volume `dockdeck_dockdeck_data`: configuratie in `/data/dockdeck.db` en via Settings ingevoerde secrets in `/data/integrations.env` (`0600`). Bescherm ruwe volumeback-ups.
## Validation
```bash
npm run format:check
npm run lint
npm run typecheck
npm test
npm run test:security
npm run test:e2e
npm run build
npm audit --audit-level=moderate
```
Laatste gate: format, lint en typecheck groen; 48/48 unit-/integratietests; 5/5 securitytests; 12 uitgevoerde en 12 project-gededupliceerde Playwright-checks op vier viewports; production build; 0 kwetsbaarheden. Op Tower zijn `docker compose config --quiet`, imagebuild en `up -d` geslaagd. De live container is healthy met 0 herstarts, AppOps leest 90 containers GET-only en de browseraudit rapporteert 0 consolefouten/-warnings.
### Latest local gate
Format, lint en typecheck zijn groen; 48/48 unit-/integratietests; 5/5 securitytests; 12 uitgevoerde en 12 project-gededupliceerde Playwright-checks op vier viewports; production build; 0 kwetsbaarheden. De clientbuild is 364,55 kB JS (113,69 kB gzip) en 170,68 kB CSS (28,53 kB gzip); de serverbundle is 167,2 kB. De live browseraudit op 1440×900 mat op beide pagina's topbar `left=32`/`height=72`, settings-sidebar `left=0`, geen overflow en een werkende hoverstate. De schone live tab rapporteerde 0 consolefouten/-warnings; alle 5 lokaal ontdekte Unraid-iconen gaven HTTP 200.
## Environment
- App: `HOST`, `PORT`, `DATABASE_PATH`, `INTEGRATION_ENV_PATH`, `POLLING_INTERVAL_MS`.
- Deployment: `DOCKDECK_BIND_ADDRESS`, `DOCKDECK_PORT`, `DOCKDECK_EGRESS_SUBNET`.
- Discovery: standaard `APPOPS_URL`; optioneel `DOCKER_PROXY_URL` via de fallbackoverlay; daarnaast `UNRAID_TEMPLATES_PATH`, `UNRAID_ICONS_SOURCE_PATH`, `UNRAID_ICONS_PATH`, `UNRAID_HOST` en `UNRAID_URL`. `MOCK_DISCOVERY` is alleen voor dev/test.
- Optioneel NPM: aanbevolen `NPM_URL` + `NPM_TOKEN`, of `NPM_USERNAME` + `NPM_PASSWORD`.
- Optionele widgets: bestaande providercredentials, `AUDIOBOOKSHELF_TOKEN` en per-app `CUSTOM_WIDGET_<APP>_URL`/`_TOKEN`; alles is via Widget Studio instelbaar. URL's worden waar mogelijk uit discovery afgeleid. Ollama, Glances, Netdata en PeerTube hebben standaard geen credential nodig.
- Repository: `GITEA_URL`, `GITEA_OWNER`, `GITEA_TOKEN` voor `npm run gitea:init`.
## Repository status
- Lokale Git-branch: `codex/stitch-canon-ui-upgrade`.
- Remote: `origin` is geconfigureerd als `gitea-widefrog:NuklearRabbit/DockDeck.git`.
- Private repository: `NuklearRabbit/DockDeck`; SSH-auth werkt en `codex/stitch-canon-ui-upgrade` volgt de gelijknamige gepushte `origin`-branch.
## Open external checks
1. Indien gewenst: NPM-token instellen en één ondubbelzinnige externe host controleren.
2. Na wijzigingen aan Unraid-templates: de sanitizer opnieuw uitvoeren en DockDeck synchroniseren.
3. De huidige testrelease zelf beoordelen op `http://192.168.10.150:1218`. De live codevervanging naar commit `41942d5` is uitgevoerd via SSH-alias `unraid-widefrog`; image `af96b8ac7a04...` draait healthy met 0 restarts. De live configuratiecategorieën zijn herverdeeld over 16 categorieën; export-backup staat lokaal in `.codex-input/dockdeck-categories-before-20260722-052942.json`.
## Rollback
Gebruik de vorige stabiele Git-commit/image. Herstel `dockdeck_dockdeck_data` uit een beveiligde volumeback-up of importeer een versie 1 configuratie-export. De JSON-export bevat geen secrets; herstel die via Settings/deploymentenvironment of uit de apart beschermde volumeback-up.
+52
View File
@@ -0,0 +1,52 @@
# PLAN.md
Goal: De eerste bruikbare versie draait als Docker-container op Unraid en kan als browserstartpagina worden ingesteld. Na de eerste start ontdekt de app de beschikbare Docker-containers, toont ze in een instellingenoverzicht en laat de gebruiker selecteren welke apps op het dashboard verschijnen. Zichtbare apps staan netjes gegroepeerd in configureerbare categorieën, tonen naam, icoon en actuele online- of offline-status en openen hun lokale WebUI in een nieuw tabblad. Favoriete websites kunnen handmatig worden toegevoegd. De zoekbalk vindt apps en favorieten en kan rechtstreeks een Google-zoekopdracht starten. Alle instellingen blijven na een herstart behouden en kunnen worden geëxporteerd en opnieuw geïmporteerd. De totale ervaring moet eruitzien als een afgewerkt premium product dat de gebruiker met plezier dagelijks als startpagina gebruikt.
## Execution Rules
- Startmodus: MVP bouwen
- Codex mag scaffolden, bouwen, testen, committen en pushen wanneer de repo en remote duidelijk zijn.
- Bouw de kleinste bruikbare verticale flow eerst.
- Vraag alleen om hulp bij ontbrekende credentials, onduidelijke productkeuzes of risicovolle externe acties.
- Werk in kleine, verifieerbare stappen.
- Houd requirements, spec, beslissingen, risico's en handoff actueel.
- Respecteer runtime-, sync-, persistentie-, deploy- en repo-keuzes uit SPEC.md en DECISIONS.md.
- Test voor overdracht en commit.
- Commit pas wanneer de relevante quality gate is gehaald.
## Task Table
| Phase | Task | Done When | Status |
| -------- | ------------------------------------------------------------------- | ------------------------------------------ | ------ |
| Intake | Valideer briefing, aannames en open vragen. | PROJECT_BRIEF.md is actueel. | Done |
| Design | Werk requirements en SPEC.md bij. | Belangrijke keuzes staan in DECISIONS.md. | Done |
| Build | Implementeer de kleinste bruikbare verticale stap. | Must-haves worden aantoonbaar ondersteund. | Done |
| Test | Run unit, integratie en handmatige checks passend bij de wijziging. | TEST_LOG.md bevat resultaat en datum. | Done |
| Document | Werk handoff, risico's en gebruikersnotities bij. | Volgende stap is duidelijk. | Done |
| Commit | Review diff en commit zonder secrets. | Commit bevat alleen bedoelde wijzigingen. | Done |
## Current Phase
De volledige visuele audit van DockDeck Canon v1 is afgerond en releasecommit `5e411d1` draait op Unraid via `192.168.10.150:1218`. De dagelijkse launcher gebruikt de Stitch-hiërarchie met desktopfavorietenrail, echte appiconen, een compacte uitklapbare widgetlaag en een viewportbrede Quick Launch. Alle instellingen blijven beschikbaar, maar grote catalogi en geavanceerde personalisatie zijn progressief ontsloten; mobiel en tablet gebruiken een horizontale instellingenbalk. Lokale quality gate, Compose-config, imagebuild, persistentie, read-only integraties en finale live browseraudit zijn groen.
## Current Phase Update
De inventaris- en instellingenpass is live afgerond. Een verbonden AppOps/Docker-heartbeat heeft 139 historische records naar exact 90 bestaande containers gereconcilieerd: 0 stale namen, 0 ontbrekende namen, 0 dubbele namen en 0 statusafwijkingen. Dashboardfilters werken onafhankelijk van de weergavemodus. Dashboard en Instellingen delen exact dezelfde topbalkgeometrie en navigatiestates; Integraties gebruikt leesbare formulierregels en Paneelstudio een brede driestappenflow. Alle lokaal ontdekte Unraid-iconen geven live HTTP 200 vanuit de begrensde tijdelijke cache.
De daaropvolgende echte Chrome-audit op 3440×1215 vond geen overflow of browserfouten, maar wel resterende microtypografie van 68 px, te lange instellingenrijen en foutieve categorieën voor onder meer Deluge, JDownloader2, Glances en Nginx Proxy Manager. Release `16bd8c7` corrigeert dit live met een 540px-widgetdock, leesbare cascade guard, begrensde instellingenoppervlakken en expliciete veilige categorieorganisatie. De lokale gate, Tower Compose/imagebuild, read-only heartbeat en tweede live Chrome-audit zijn groen; de testtab blijft open voor gebruikerscontrole.
De huidige afwerkingspass voegt een echte links/rechts-schakelaar toe aan App Beheer, maakt Home Assistant, Authentik, Vaultwarden en Nginx Proxy Manager compact, en verrijkt de centrale launcher met subtiele omgevingsdiepte, een lokale wallpaper-upload en een configureerbare Open-Meteo-badge voor Mol. Publieke GET-healthchecks geven Authentik, NPM, Vaultwarden en Home Assistant zonder secret al een eerlijke status; minimum-scope tokens blijven optioneel voor detailstatistieken.
De achtergrondcorrectie maakt de drie opgeslagen achtergrondmodi daadwerkelijk zichtbaar en rendert de lokale wallpaper in de centrale dashboardcanvas. Een end-to-end browsertest wisselt alle presets, vergelijkt hun berekende achtergronden, uploadt via de echte instellingenflow en controleert vervolgens `has-wallpaper` en de server-URL in de zichtbare CSS-compositie.
De responsieve wallpaperpass koppelt het beeld los van de totale scrollhoogte en rendert het in een sticky viewportstage binnen het centrale vlak. Weergave bevat nu een begrensde live preview met Passend/Vullend, horizontale en verticale focus, zoom en herstel; alle keuzes migreren idempotent, blijven persistent en reizen mee in versie 1-back-ups.
De platformbrede wallpaperpass verplaatst die stage uit de middenkolom naar een fixed laag van direct onder de topbalk tot de viewportonderrand. Een wallpaper neutraliseert voortaan de actieve achtergrondpreset, loopt achter linkerrail, appcanvas en rechter widgetdock door en gebruikt bij `Passend` een zachte ambient-kopie om ultrawide zijruimte zonder harde kleurvlakken te vullen.
De categorie-icoonpass verbindt alle zestien live opgeslagen iconen met een eigen Lucide-component en dezelfde volledige keuzecatalogus in Instellingen. Nieuwe categorieën starten met een nog ongebruikt pictogram; onbekende geïmporteerde waarden behouden een veilige generieke fallback.
## Verification Rules
- 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.
- Noteer niet-geteste onderdelen expliciet in TEST_LOG.md.
+39
View File
@@ -0,0 +1,39 @@
# PROJECT_BRIEF.md
## Project
- Projectnaam: DockDeck
- Korte beschrijving: Een premium ogend persoonlijk startdashboard voor Unraid dat Docker-apps automatisch ontdekt, stijlvol groepeert en snelle navigatie biedt naar lokale diensten en eigen favorieten.
- Projectidee: 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.
- Probleem: De gebruiker heeft meerdere Docker-diensten op een Unraid-server en moet momenteel afzonderlijke URL's onthouden, bladwijzers beheren of via verschillende beheerinterfaces navigeren. Bestaande startpagina's sluiten niet volledig aan op de gewenste combinatie van automatische Unraid-detectie, configureerbare zichtbaarheid, categorie-indeling, statusweergave, lokale en externe URL's, favoriete websites, geïntegreerde zoekfunctie en een echt professioneel ogende premium UI.
- Doelgebruikers: Eén primaire gebruiker: de eigenaar en beheerder van de Unraid-server. Deze gebruiker verwacht zonder technische omwegen snel naar lokale Docker-diensten en favoriete websites te kunnen navigeren, zelf te bepalen welke apps zichtbaar zijn, categorieën en sortering te beheren, de startpagina visueel aan te passen en optioneel lokale of externe app-URL's te gebruiken. Er zijn in de MVP geen afzonderlijke gebruikersrollen, accounts of gedeelde profielen.
- Gewenste uitkomst: De eerste bruikbare versie draait als Docker-container op Unraid en kan als browserstartpagina worden ingesteld. Na de eerste start ontdekt de app de beschikbare Docker-containers, toont ze in een instellingenoverzicht en laat de gebruiker selecteren welke apps op het dashboard verschijnen. Zichtbare apps staan netjes gegroepeerd in configureerbare categorieën, tonen naam, icoon en actuele online- of offline-status en openen hun lokale WebUI in een nieuw tabblad. Favoriete websites kunnen handmatig worden toegevoegd. De zoekbalk vindt apps en favorieten en kan rechtstreeks een Google-zoekopdracht starten. Alle instellingen blijven na een herstart behouden en kunnen worden geëxporteerd en opnieuw geïmporteerd. De totale ervaring moet eruitzien als een afgewerkt premium product dat de gebruiker met plezier dagelijks als startpagina gebruikt.
- Succescriteria: De MVP is klaar wanneer: de applicatie via Docker Compose op Unraid kan worden gestart; beschikbare Docker-containers automatisch worden ontdekt zonder schrijf- of beheerrechten op Docker; nieuw ontdekte containers standaard verborgen blijven; de gebruiker containers zichtbaar of verborgen kan maken; naam, icoon, categorie, sortering en lokale URL per app kunnen worden aangepast; de lokale WebUI-URL waar mogelijk automatisch uit Unraid-templategegevens wordt afgeleid; zichtbare apps naam, icoon en correcte online- of offline-status tonen; status bij het openen en daarna ongeveer elke 30 seconden wordt vernieuwd; categorieën kunnen worden aangemaakt, hernoemd, gesorteerd en toegewezen; zowel een volledig overzicht als categoriegebaseerde navigatie configureerbaar is; favorieten met naam, URL, icoon, categorie en sortering kunnen worden beheerd; de zoekbalk apps en favorieten doorzoekt en overige invoer naar Google stuurt; alle links in een nieuw tabblad openen; licht, donker en minstens één aanvullend thema selecteerbaar zijn; configuratie persistent blijft na container- en serverherstart; back-up en herstel via export en import werken; de app uitsluitend voor lokaal gebruik is ingericht en geen container start-, stop-, restart- of wijzigingsacties bevat; een private Gitea-repository automatisch wordt aangemaakt, lokaal wordt geïnitialiseerd en een eerste bruikbare commit bevat; en de UI een consistent, professioneel en visueel hoogwaardig geheel vormt met nette kaartlay-outs, sterke typografie, duidelijke statusindicatoren, vloeiende maar subtiele interacties en een verzorgde instellingenervaring.
## Scope
- Must-haves: Automatische read-only ontdekking van Unraid Docker-containers; nieuwe containers standaard verborgen; instellingenpagina voor zichtbaarheid, naam, icoon, categorie, sortering en URL-overrides; automatische afleiding van lokale WebUI-URL's uit Unraid-templategegevens; read-only containerstatus met verversing bij laden en ongeveer elke 30 seconden; configureerbare categorieën; configureerbare keuze tussen één totaaloverzicht en categoriepagina's of tabbladen; handmatige favorieten voor externe websites; centrale zoekbalk voor apps, favorieten en Google; openen van alle doelen in een nieuw tabblad; licht, donker en aanvullende ingebouwde thema's; persistent bewaren van instellingen in SQLite; export en import van configuratie; Docker- en Docker Compose-configuratie voor Unraid; beperkte Docker-socketproxy zonder beheerrechten; optionele read-only Nginx Proxy Manager-koppeling voor externe URL's met handmatige fallback; duidelijke foutstatus wanneer Unraid, Docker-proxy of Nginx Proxy Manager tijdelijk onbereikbaar is; automatische initialisatie van een private Gitea-repository via environmentvariabelen; en een uitzonderlijk verzorgde professionele UI met premium dashboardlook, consistente componenten, strak kaartdesign, goede witruimte, duidelijke visuele hiërarchie, subtiele hover- en transition-effecten, nette iconografie en een afgewerkte instellingenomgeving.
- Nice-to-haves: Drag-and-drop sortering van apps en categorieën; automatisch herkennen van bekende applicatie-iconen; favicon-import voor favorieten; meerdere aanvullende kleurthema's; compacte en ruime tegelweergave; achtergrondafbeeldingen of subtiele dashboard-achtergronden; sneltoets om de zoekbalk te focussen; recente of meest gebruikte apps; optionele statuslatentie en responstijd; import van bestaande browserbladwijzers; meerdere dashboards of profielen; PWA-installatie; mobiele optimalisaties buiten de basisresponsiviteit; geavanceerde automatische matching tussen Nginx Proxy Manager-hosts en Docker-containers; handmatige statuscontrole voor favoriete websites; extra micro-interacties en visuele personalisatieopties zolang die de strakke premium uitstraling ondersteunen.
- Buiten scope: Containers starten, stoppen, herstarten, verwijderen of wijzigen; Docker Compose-stacks beheren; Unraid-systeembeheer; terminal- of shelltoegang; logviewer; resourcegrafieken en uitgebreide monitoring; notificaties en incidentbeheer; publieke internettoegang configureren; Authentik- of andere SSO-integratie; eigen gebruikersaccounts, rollen of multi-userondersteuning; opslag van echte API-tokens in de repository of browser; automatische wijzigingen aan Nginx Proxy Manager; cloudhosting; mobiele native apps; AI-functionaliteit; synchronisatie tussen meerdere Unraid-servers; automatische publicatie van diensten naar het internet.
- 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.
## Eerste Aannames
| Aanname | Status |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
| Het projecttype is webapp. | Te valideren |
| De voorkeursstack is 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.. | Te valideren |
| Runtime: Interactieve app. | Te valideren |
| Update/sync: Polling. | Te valideren |
| Persistentie: SQLite. | Te valideren |
| Deployment: Docker op Unraid. | Te valideren |
| Repository: Repo nog nodig. | Te valideren |
| De primaire gebruikers zijn Eén primaire gebruiker: de eigenaar en beheerder van de Unraid-server. Deze gebruiker verwacht zonder technische omwegen snel naar lokale Docker-diensten en favoriete websites te kunnen navigeren, zelf te bepalen welke apps zichtbaar zijn, categorieën en sortering te beheren, de startpagina visueel aan te passen en optioneel lokale of externe app-URL's te gebruiken. Er zijn in de MVP geen afzonderlijke gebruikersrollen, accounts of gedeelde profielen.. | Te valideren |
## Open Questions
| Vraag | Waarom dit nodig is |
| ----------------------------------------------------------------- | ------------------------------------ |
| Welke randgevallen moeten expliciet ondersteund worden? | Maakt requirements toetsbaar. |
| Welke data mag lokaal, extern of helemaal niet opgeslagen worden? | Bepaalt security en privacy ontwerp. |
| Welke definitie van klaar geldt voor de eerste release? | Houdt scope en planning scherp. |
+100 -253
View File
@@ -1,292 +1,139 @@
# GeoIntel Belgium and the Belgian North Sea # DockDeck
GeoIntel is a map-first GeoAI Workbench for Belgium and the Belgian North Sea. DockDeck is a calm, premium personal start dashboard for Unraid. Its normal Unraid profile is one branded application container, reads container inventory through the existing AppOps GET API and enriches it with sanitized local WebUI metadata.
It combines governed official-source coverage, raster/vector processing,
historical comparison, computer vision, QA/QC and geospatial exports.
Mol and the Kempen remain deep regression and model-validation references. The ## What the MVP includes
release scope is all of Belgium plus legally labelled Belgian maritime zones;
source coverage remains explicit per theme and jurisdiction.
GeoIntel is not a generic dashboard or chatbot. The core product is: - automatic read-only container discovery with new apps hidden by default;
- live online/offline status and 30-second visibility-aware polling;
- editable app name, icon fallback, category, order, local URL and external URL fallback;
- configurable categories, favorites and overview/category navigation;
- fast local search with a Google fallback, always opening in a new tab;
- Porcelain, Midnight and Harbor themes, four accents, three density and content-width profiles, and selectable backgrounds;
- responsive layouts from compact mobile screens through tablets, desktops and ultrawide displays;
- optional dashboard hero and status badges, plus editable category and favorite icons;
- up to eight configurable service widgets with independent order, compact/standard/wide layouts, custom labels, metric order, per-metric value/gauge/progress presentation, primary metric, any-signal warning thresholds and refresh cadence;
- line, area or bar charts with a selectable app/provider series, legend and 1060 in-memory samples;
- a default native Unraid widget with configurable statistics and charts for Docker inventory, array, parity, disk/pool, temperature and optional UPS status;
- rich GET-only providers for AdGuard, Plex, Immich, Home Assistant, Sonarr/Radarr/Lidarr, Ollama, Glances, Tautulli, Paperless, Gitea, Jellyfin, Seerr, Prowlarr, Authentik, NPM, Portainer, Grafana, Prometheus and Nextcloud;
- optional GET-only metrics bridges for Deluge, qBittorrent, JDownloader, Tdarr, Bazarr and Vaultwarden;
- a searchable Widget Studio that explains available metrics, recommends useful panels and activates up to eight widgets;
- persistent per-app metric visibility, order, presentation and card limits, with a tailored profile for every discovered app even before its optional data endpoint is connected;
- native Audiobookshelf, Netdata and PeerTube readers plus an optional GET-only JSON endpoint for every custom or niche app;
- an editable Tower name and Unraid WebUI URL that defaults to port `5000`;
- masked provider credential forms in Settings, with immediate read-only connection testing and no secret echo;
- automatic first-party favorite favicons with a resilient local monogram fallback;
- a useful desktop sidebar with time, deck health and quick service links;
- SQLite persistence plus versioned JSON export/import;
- reusable layout presets with independent import/export, direct dashboard edit mode and secret-free diagnostics;
- an installable PWA shell that explicitly excludes runtime API data from caching;
- optional read-only Nginx Proxy Manager host matching;
- one branded, read-only DockDeck container with a true transparent PNG Unraid-list icon, WebUI metadata and no Docker-socket mount.
> data → processing → geospatial output → QA/QC → export ## Local development
## Current milestone Requirements: Node.js 22 or newer and npm 10 or newer.
**v1.0.0 - Belgium/North Sea release**
The canonical release controls are:
- `docs/00-start/START_HERE.md`
- `docs/RC_SCOPE_FREEZE_BELGIUM_NORTH_SEA.md`
- `docs/RC_ROADMAP_BELGIUM_NORTH_SEA.md`
- `docs/RELEASE_RUNBOOK.md`
- `docs/KNOWN_LIMITATIONS.md`
- `docs/DEFINITION_OF_DONE.md`
Older milestone and sprint handoff files remain historical evidence. They do
not override the active national/maritime scope freeze or RC roadmap.
## Core V1 vertical slice
The first implementation target is:
1. Project + Area creation.
2. Dataset registration/upload and metadata extraction.
3. Reference building layer loading.
4. Predicted detection layer loading/import.
5. QA/QC matching against reference polygons.
6. Metrics and false positive/false negative outputs.
7. GeoJSON export.
8. Minimal map/workbench UI.
## Primary stack
- Frontend: React, TypeScript, MapLibre GL, Deck.gl, Tailwind.
- Backend: FastAPI, Python.
- Database: PostgreSQL + PostGIS.
- GIS processing: GeoPandas, Shapely, Rasterio, PyProj, GDAL.
- AI: PyTorch, Ultralytics YOLO, SAM-compatible architecture.
- Jobs: Redis + RQ.
- Storage: local filesystem first, MinIO-compatible later.
## Codex instructions
Codex must start with:
1. `docs/00-start/START_HERE.md`
2. `prompts/codex/M11_ARCHITECT_MASTER_PROMPT.md`
Then follow the build order in:
- `docs/build/BUILD_ORDER_DEPENDENCY_GRAPH.md`
- `docs/build/CODEX_OPERATING_SYSTEM.md`
Before every implementation pass, run available preflight/smoke scripts where applicable.
## Repo principle
This is a documentation-driven engineering repo. The documentation is not decorative; it is the control system for autonomous implementation.
## Fastest Day 1 command path
```bash ```bash
make readiness cp .env.example .env
npm install
npm run dev
``` ```
## Unraid / Tower deployment Open `http://127.0.0.1:5173`. The example environment enables deterministic discovery fixtures. Runtime data is written to `./data/dockdeck.db`.
GeoIntel runs on Unraid as an all-in-one DockerMan-native container. The container embeds PostGIS, runs the FastAPI backend internally, and serves the frontend through nginx on one editable web port. Quality commands:
Unraid template assets live in:
- `deploy/unraid/geointel.env.example`
- `deploy/unraid/geointel-unraid-template.xml`
- `deploy/unraid/geointel-icon.svg`
- `deploy/unraid/geointel-icon.png`
- `docker-compose.unraid.yml`
Copy the Unraid env template to `.env` in the checkout and edit ports/paths there:
```bash ```bash
cd /mnt/user/appdata/geointel npm run format:check
cp deploy/unraid/geointel.env.example .env npm run lint
nano .env
docker build -f deploy/unraid/Dockerfile.all-in-one -t geointel-all-in-one:latest .
bash deploy/unraid/run-dockerman-container.sh
```
Common editable values:
```env
GEOINTEL_FRONTEND_PORT=1202
GEOINTEL_STORAGE_PATH=/mnt/user/appdata/geointel/storage
GEOINTEL_POSTGIS_DATA_PATH=/mnt/user/appdata/geointel/postgres-data
```
The backend and PostGIS ports are intentionally not exposed to the LAN in the all-in-one runtime. See `deploy/unraid/README.md` for full setup, port-change and cleanup notes.
On Tower/Unraid, `scripts/deploy_tower.ps1` and `scripts/deploy_tower.sh` validate the Compose reference but build with plain `docker build`, then automatically install the editable DockerMan template as `/boot/config/plugins/dockerMan/templates-user/my-geointel.xml`, install the PNG icon as `/boot/config/plugins/dockerMan/images/geointel-icon.png`, remove any old Compose-owned `geointel` container and start the final container with DockerMan labels.
## Sprint 2 quick start
- Update dependencies:
```bash
python -m pip install -e backend/.[dev]
cd frontend && npm install
```
- Run full readiness checks (with no scope expansion):
```bash
python -m compileall backend/app
cd backend && python -m pytest
cd ../frontend && npm run typecheck && npm run build
bash scripts/run_readiness_check.sh
```
- Raster workflow validation command (backend only):
```bash
bash scripts/smoke_backend_import.sh
cd backend && python -c "from app.main import app; print(app.title)"
```
If `rasterio` is not installed, raster metadata endpoints return `RASTER_PROCESSING_UNAVAILABLE` and the frontend displays the
state as failed until the dependency is added.
## Sprint 4 raster foundation
- Raster operations now support:
- raster metadata extraction,
- raster preview generation,
- raster clip by area (with provenance on derived datasets),
- raster tile generation with manifest output.
- Raster services are dependency-aware:
- if `rasterio` is unavailable, endpoints return `RASTER_PROCESSING_UNAVAILABLE`.
- if preview dependencies (`numpy`, `pillow`) are unavailable, preview generation is unavailable with a clear error.
- Enable raster stack explicitly when needed:
```bash
cd backend && python -m pip install -e .[dev,raster]
```
## Sprint 5 raster analytics hardening
- Added raster band statistics (min/max/mean/std, nodata ratio/count, valid pixel count, dtype, optional histograms).
- Added raster reproject workflow with CRS validation and provenance persistence.
- Extended tile manifest expectations (`tile_set_id`, `tile_size`, `overlap`, `bounds`, `source_raster_id`, `tile_paths`, `tile_server`).
- Clarified raster operation availability in frontend/backend docs (`RASTER_PROCESSING_UNAVAILABLE` and invalid-CRS cases).
- Raster workflow command set (where available):
```bash
cd backend
python -m pip install -e .[dev,raster]
python -m pytest
cd ../frontend
npm run typecheck npm run typecheck
npm test
npm run test:e2e
npm run build npm run build
``` ```
Then give Codex the prompt in: ## Unraid / Docker Compose
- `prompts/codex/final/DAY_1_MASTER_PROMPT.md` Create an `.env` beside `docker-compose.yml`:
```dotenv
## M13 Codex optimization DOCKDECK_BIND_ADDRESS=192.168.10.150
DOCKDECK_PORT=1218
For the first serious Codex build run, use: DOCKDECK_EGRESS_SUBNET=172.31.240.16/28
APPOPS_URL=http://192.168.10.150:1216
- `prompts/codex/m13/DAY_1_OPTIMIZED_MASTER_PROMPT.md` UNRAID_TEMPLATES_DIR=/mnt/user/appdata/dockdeck/unraid-templates
UNRAID_HOST=192.168.10.150
Codex should also use the relevant reusable skill under `skills/` for each implementation pass. Validate the optimization assets with: UNRAID_URL=http://192.168.10.150:5000
```bash
make m13
``` ```
The full readiness path remains: For the current Unraid host, `deploy/unraid.env.example` contains this non-secret profile and can be copied to `.env`. It pins a small egress subnet because this Unraid host has exhausted its automatic address pools; change it if the host network layout changes.
Unraid keeps its source templates root-only and templates can contain sensitive values. Create DockDeck's sanitized metadata mirror before starting Compose; the helper copies only `Name`, `WebUI` and `Icon`:
```bash ```bash
make readiness python3 deploy/sanitize-unraid-templates.py \
/boot/config/plugins/dockerMan/templates-user \
/mnt/user/appdata/dockdeck/unraid-templates
``` ```
Then run. The base Compose file creates exactly one container named `DockDeck`:
## M14 Build Launch
For the first serious implementation run, use:
- `docs/40-build-launch/SPRINT_1_SCOPE_FREEZE.md`
- `docs/40-build-launch/BUILD_SUCCESS_DEFINITION.md`
- `docs/40-build-launch/CODEX_STOP_RULES.md`
- `prompts/codex/m14/CODEX_FIRST_DAY_MASTER_PROMPT.md`
Validate launch assets with:
```bash
make m14
```
Full readiness remains:
```bash
make readiness
```
## Sprint 1 execution (Sprint 1 only)
From a clean machine:
```bash
cd backend && python -m pip install -e .[dev]
cd ..
make backend-install
make frontend-install
make readiness
```
Copy `.env.example` to `.env` only when you want local overrides. Docker Compose has safe defaults for the local PostGIS/backend/frontend stack and does not require a root `.env` file to exist.
With Docker Compose, open the workbench at `http://localhost:1202`.
The Docker frontend is served by nginx and proxies `/api` and `/health` to the backend container, so browser clients should use the frontend URL only, for example `http://192.168.10.150:1202` on a LAN host.
Runtime containers include healthchecks for PostGIS, backend and frontend. After
startup, inspect them with:
```bash ```bash
docker compose up -d --build
docker compose ps docker compose ps
sudo sh deploy/refresh-unraid-icon.sh
``` ```
Verify the browser-facing API proxy after rebuilding Docker images: The final helper validates that the configured `<Icon>` URL returns a real PNG and replaces both DockDeck-specific caches used by Unraid DockerMan: the persistent cache and the active RAM cache. Run it after changing the icon or recreating the container. It never changes Docker state or touches another container's files. An explicit URL can be supplied as the second argument when no user template exists.
On a host without AppOps, use the optional hardened fallback overlay. Only that additional proxy sees the socket and it disables POST and all unused API families:
```bash ```bash
bash scripts/verify_browser_runtime.sh http://localhost:1202 http://localhost:8000/health docker compose -f docker-compose.yml -f docker-compose.socket-proxy.yml up -d --build
``` ```
Verify the Docker GIS runtime after rebuilding the backend image: By default Compose publishes DockDeck only on `127.0.0.1:1218`. For this Unraid host, set `DOCKDECK_BIND_ADDRESS=192.168.10.150` so the trusted LAN can reach `http://192.168.10.150:1218`. DockDeck has no built-in authentication; do not publish it directly to the internet.
```bash Persistent configuration lives in the named volume `dockdeck_data`. Normal preferences use `/data/dockdeck.db`; credentials entered through Settings use the permission-restricted `/data/integrations.env`. Protect volume backups because that environment file contains plaintext secrets. The JSON export never includes it.
bash scripts/verify_gis_runtime.sh http://localhost:1202
```
On the LAN host use the published browser URL, for example: ## Environment variables
```bash | Variable | Purpose |
bash scripts/verify_gis_runtime.sh http://192.168.10.150:1202 | ------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
``` | `DATABASE_PATH` | SQLite file path; defaults to `./data/dockdeck.db` locally and `/data/dockdeck.db` in Docker. |
| `INTEGRATION_ENV_PATH` | Permission-restricted environment file used for credentials entered in Settings. |
| `DOCKDECK_BIND_ADDRESS`, `DOCKDECK_PORT` | Host interface and port published by Compose; safely default to `127.0.0.1:1218`. |
| `DOCKDECK_EGRESS_SUBNET` | Explicit small subnet for the app's outbound network. |
| `APPOPS_URL` | Existing AppOps origin used for GET-only inventory and measured CPU/memory status. |
| `DOCKER_PROXY_URL` | Optional internal URL used only by the socket-proxy fallback overlay. |
| `UNRAID_TEMPLATES_PATH` | Read-only XML template mount inside the app. |
| `UNRAID_HOST` | Host used to resolve `[IP]` in WebUI templates; defaults to `tower.local`. |
| `UNRAID_URL` | Initial Tower/Unraid link; Settings can override it and a missing port defaults to `5000`. |
| `UNRAID_RUNTIME_PATH`, `UNRAID_HWMON_PATH` | Container paths for minimum read-only Unraid runtime and hardware-monitor mounts. |
| `UNRAID_UPS_STATUS_PATH` | Optional read-only APC/UPS status file inside the app container. |
| `MOCK_DISCOVERY` | Enables deterministic local/test fixtures; never enable for production discovery. |
| `NPM_URL`, `NPM_TOKEN` | Optional server-only Nginx Proxy Manager read credential. |
| `NPM_USERNAME`, `NPM_PASSWORD` | Optional server-only alternative for requesting an NPM session token. |
| `ADGUARD_URL`, `ADGUARD_USERNAME`, `ADGUARD_PASSWORD` | Optional AdGuard Home statistics for its selected widget. |
| `PLEX_URL`, `PLEX_TOKEN` | Optional Plex sessions, transcodes and library count. |
| `IMMICH_URL`, `IMMICH_API_KEY` | Optional Immich photo, video and storage totals. |
| `HOME_ASSISTANT_URL`, `HOME_ASSISTANT_TOKEN` | Optional Home Assistant automation, light and availability totals. |
| `OLLAMA_URL`, `GLANCES_URL` | Optional overrides; their discovered local app URLs provide zero-config read-only metrics. |
| `TAUTULLI_URL`, `TAUTULLI_API_KEY` | Optional Plex activity, transcode and bandwidth metrics. |
| `PAPERLESS_URL`, `PAPERLESS_TOKEN` | Optional Paperless document, inbox and archive-size metrics. |
| `AUDIOBOOKSHELF_URL`, `AUDIOBOOKSHELF_TOKEN` | Optional library, book, podcast and item totals. |
| `NETDATA_URL`, `PEERTUBE_URL` | Optional discovered-URL overrides for their native GET-only readers. |
| `SONARR_*`, `RADARR_*`, `LIDARR_*` | Optional provider URL and API key pairs for queue and version data. |
| `GITEA_WIDGET_*`, `JELLYFIN_*`, `SEERR_*`, `PROWLARR_*` | Optional server-side URL/token pairs for native GET-only metrics. |
| `AUTHENTIK_*`, `PORTAINER_*`, `GRAFANA_*` | Optional server-side URL/token pairs for administrative summary metrics. |
| `PROMETHEUS_URL`, `NEXTCLOUD_*`, `NPM_*` | Optional read-only monitoring, cloud and proxy metrics. |
| `*_METRICS_URL`, `*_METRICS_TOKEN` | Optional GET-only bridge for providers whose native API would require POST-based control. |
| `CUSTOM_WIDGET_<APP>_URL`, `CUSTOM_WIDGET_<APP>_TOKEN` | Settings-managed GET-only JSON metrics endpoint and optional bearer token for any other app. |
| `GITEA_URL`, `GITEA_OWNER`, `GITEA_TOKEN` | Used only by `npm run gitea:init` to create/push the private repository. |
Load the explicit offline demo workflow: Provider secrets are never stored in SQLite, JSON exports, logs or tracked files and are never returned to the browser. Values entered in Settings are persisted as environment assignments in `INTEGRATION_ENV_PATH` with mode `0600`; use DockDeck only on a trusted LAN or behind authenticated HTTPS, and protect raw volume backups.
```bash ## Repository initialization
curl -X POST http://192.168.10.150:1202/api/v1/demo/workflow
```
If `/api/v1/projects` returns frontend HTML instead of a JSON envelope, rebuild After a passing quality gate and with the three Gitea variables exported, run `npm run gitea:init`. The script refuses an unexpected existing remote, creates only a private repository, keeps the token out of Git configuration, and pushes `main` through a temporary authorization header.
and restart the frontend container.
Useful direct verification commands:
```bash
python -m compileall backend/app
cd backend && python -c "from app.main import app; print(app.title)"
python -m pytest
cd ../frontend && npm run typecheck
cd ../frontend && npm run build
docker compose config
bash scripts/run_readiness_check.sh
```
If `make` or `docker` are unavailable in your shell, run the equivalent script entrypoints directly:
```bash
bash scripts/backend_install.sh
bash scripts/backend_test.sh
bash scripts/frontend_install.sh
bash scripts/frontend_typecheck.sh
bash scripts/frontend_build.sh
bash scripts/run_readiness_check.sh
```
+88
View File
@@ -0,0 +1,88 @@
# REQUIREMENTS.md
Project: DockDeck
## Functional Requirements
Implementatiestatus 2026-07-14: FR-1 tot en met FR-19 zijn gebouwd en lokaal geverifieerd. De private Gitea-repository bestaat en productie draait op Unraid-poort `1218`. Alleen optionele integratiecredentials en een geauthenticeerde externe proxyroute blijven omgevingskeuzes; zie `TEST_LOG.md` en `docs/IMPLEMENTATION_STATUS.md`.
| ID | Requirement | Source | Status |
| ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
| FR-1 | Implementeer de must-haves: Automatische read-only ontdekking van Unraid Docker-containers; nieuwe containers standaard verborgen; instellingenpagina voor zichtbaarheid, naam, icoon, categorie, sortering en URL-overrides; automatische afleiding van lokale WebUI-URL's uit Unraid-templategegevens; read-only containerstatus met verversing bij laden en ongeveer elke 30 seconden; configureerbare categorieën; configureerbare keuze tussen één totaaloverzicht en categoriepagina's of tabbladen; handmatige favorieten voor externe websites; centrale zoekbalk voor apps, favorieten en Google; openen van alle doelen in een nieuw tabblad; licht, donker en aanvullende ingebouwde thema's; persistent bewaren van instellingen in SQLite; export en import van configuratie; Docker- en Docker Compose-configuratie voor Unraid; beperkte Docker-socketproxy zonder beheerrechten; optionele read-only Nginx Proxy Manager-koppeling voor externe URL's met handmatige fallback; duidelijke foutstatus wanneer Unraid, Docker-proxy of Nginx Proxy Manager tijdelijk onbereikbaar is; automatische initialisatie van een private Gitea-repository via environmentvariabelen; en een uitzonderlijk verzorgde professionele UI met premium dashboardlook, consistente componenten, strak kaartdesign, goede witruimte, duidelijke visuele hiërarchie, subtiele hover- en transition-effecten, nette iconografie en een afgewerkte instellingenomgeving. | Intake | Done |
| FR-2 | Ondersteun het kernidee: 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. | Intake | Done |
## Users And Roles
- Primaire gebruikers/rollen: Eén primaire gebruiker: de eigenaar en beheerder van de Unraid-server. Deze gebruiker verwacht zonder technische omwegen snel naar lokale Docker-diensten en favoriete websites te kunnen navigeren, zelf te bepalen welke apps zichtbaar zijn, categorieën en sortering te beheren, de startpagina visueel aan te passen en optioneel lokale of externe app-URL's te gebruiken. Er zijn in de MVP geen afzonderlijke gebruikersrollen, accounts of gedeelde profielen.
- Beheerrollen en permissies: Nog in te vullen.
## Core Workflows
1. Gebruiker start met: 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.
2. Gebruiker voert of beheert de belangrijkste gegevens: 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.
3. Systeem levert de gewenste uitkomst: De eerste bruikbare versie draait als Docker-container op Unraid en kan als browserstartpagina worden ingesteld. Na de eerste start ontdekt de app de beschikbare Docker-containers, toont ze in een instellingenoverzicht en laat de gebruiker selecteren welke apps op het dashboard verschijnen. Zichtbare apps staan netjes gegroepeerd in configureerbare categorieën, tonen naam, icoon en actuele online- of offline-status en openen hun lokale WebUI in een nieuw tabblad. Favoriete websites kunnen handmatig worden toegevoegd. De zoekbalk vindt apps en favorieten en kan rechtstreeks een Google-zoekopdracht starten. Alle instellingen blijven na een herstart behouden en kunnen worden geëxporteerd en opnieuw geïmporteerd. De totale ervaring moet eruitzien als een afgewerkt premium product dat de gebruiker met plezier dagelijks als startpagina gebruikt.
## Non-Functional Requirements
- 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.
- 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.
- Performance, toegankelijkheid en observability: Lokale fonts, een compacte productieclient, zichtbare toetsenbordfocus, semantische navigatiestatus, gereduceerde animatie waar gevraagd, visibility-aware polling, gestructureerde serverlogs en `/api/health`. De UI wordt zonder horizontale overflow getest op 390×844, 768×1024, 1440×900 en 3440×1440.
## UX Expectations
- UX verwachtingen: Een rustige premium startpagina die van smalle telefoon tot ultrawide proportioneel schaalt. De gebruiker kan thema, accentkleur, dichtheid, contentbreedte, achtergrond, wallpaperpassing/focus/zoom, dashboardhero, statusbadges en overzichtsmodus aanpassen; voorkeuren blijven persistent. De wallpapereditor toont vóór toepassen een realistische preview van het volledige platformvlak. Een geüploade wallpaper heeft voorrang op Aurora, Blauwdruk en Minimaal en loopt ononderbroken onder alle dashboardkolommen.
- Belangrijkste schermen en interacties: Dashboard met hero, zoekbalk, live overzicht, responsieve apptegels en maximaal acht configureerbare servicewidgets met read-only containerstatistieken; instellingen voor uiterlijk, apps, categorieën, favorieten, integraties en back-up. Appbeheer ondersteunt zoeken, zichtbaarheids- en widgetfilters; iedere canonieke categorie heeft een afzonderlijk semantisch pictogram, categorie-iconen zijn bewerkbaar en favorieticonen worden automatisch first-party geladen met een handmatige override.
## Data
- Data involved: 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.
- Integrations: 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.
## Deployment And Runtime
- Preferred stack/runtime: 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
- Repository state: private Gitea-repository `NuklearRabbit/DockDeck`; lokale `main` volgt de remote.
- Omgevingen: lokale development, geïsoleerde Vitest/Playwright-tests en productie op Unraid `192.168.10.150:1218`.
## Security And Privacy
- Credentials verwacht: 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.
- Secrets mogen nooit worden gecommit.
## Out Of Scope
Containers starten, stoppen, herstarten, verwijderen of wijzigen; Docker Compose-stacks beheren; Unraid-systeembeheer; terminal- of shelltoegang; logviewer; resourcegrafieken en uitgebreide monitoring; notificaties en incidentbeheer; publieke internettoegang configureren; Authentik- of andere SSO-integratie; eigen gebruikersaccounts, rollen of multi-userondersteuning; opslag van echte API-tokens in de repository of browser; automatische wijzigingen aan Nginx Proxy Manager; cloudhosting; mobiele native apps; AI-functionaliteit; synchronisatie tussen meerdere Unraid-servers; automatische publicatie van diensten naar het internet.
## Open Questions
- Welke requirements zijn release-blocking?
- Welke externe systemen moeten eerst gevalideerd worden?
## Widget Extension 2026-07-14
- FR-3 (Done): toon standaard een configureerbare Unraid-hostwidget met host-, CPU-, geheugen-, image- en containerinformatie uit Docker `/info`.
- FR-4 (Done): verrijk geselecteerde AdGuard-, Plex-, Immich-, Home Assistant-, Sonarr-, Radarr- en Lidarr-widgets met provider-specifieke read-only gegevens wanneer uitsluitend server-side environmentcredentials beschikbaar zijn.
- FR-5 (Done): een ontbrekende of falende provider-API valt altijd terug op generieke Dockerstats en blokkeert discovery of navigatie nooit.
- FR-6 (Done): apps kunnen uit DockDeck worden verwijderd zonder Docker te wijzigen; de technische containernaam blijft persistent onderdrukt bij discovery en wordt meegenomen in back-ups.
- FR-7 (Done): de dashboardzoekfunctie indexeert actuele appnamen, technische namen, categorieën, URL's, images, favorieten en de Unraid-host en beweegt direct mee met configuratiewijzigingen.
- FR-8 (Done): iedere servicewidget ondersteunt persistent een compact, standaard of breed formaat en afzonderlijke inhoudslagen voor samenvatting, appmetrics, Dockertelemetrie en voortgang.
- FR-9 (Done): de Unraid-hostwidget heeft eigen persistente formaat- en inhoudsinstellingen; alle widgetconfiguratie blijft exporteerbaar, legacy-compatibel en responsief.
- FR-10 (Done): widgets hebben een onafhankelijke volgorde met toetsenbordbediening en een directe dashboardbewerkingsmodus.
- FR-11 (Done): iedere widget ondersteunt metricvolgorde, primaire metric, aangepast label, waarschuwingsdrempel, sparkline en een veilige eigen verversingscadans.
- FR-12 (Done): de Unraid-hostwidget leest read-only array-, parity-, disk-/pool-, temperatuur- en optionele UPS-status uit minimaal gemounte hostbronnen.
- FR-13 (Done): app-specifieke providers zijn uitgebreid met Gitea, Jellyfin, Seerr, Prowlarr, Authentik, NPM, Portainer, Grafana, Prometheus en Nextcloud; beveiligde POST-gebaseerde API's gebruiken optionele GET-only metrics bridges.
- FR-14 (Done): widgets tonen expliciet `fresh`, `stale` of `unavailable` en behouden laatst succesvolle providergegevens bij tijdelijke uitval.
- FR-15 (Done): layoutpresets kunnen afzonderlijk worden opgeslagen, toegepast, verwijderd, geëxporteerd en geïmporteerd.
- FR-16 (Done): het platform biedt secretvrije diagnostics en een installable PWA-shell die nooit `/api`-responses cachet.
- FR-17 (Done): de Unraid-hostnaam en WebUI-URL zijn persistent instelbaar onder Widgets; Tower gebruikt standaard expliciet poort `5000` en de aangepaste waarde stuurt zowel de hostkaart als de dynamische zoekindex.
- FR-18 (Done): iedere ontdekte app staat in Widget Studio. Native providers krijgen gedocumenteerde read-only adapters; overige apps krijgen een optionele, server-side GET-only JSON-koppeling met configureerbare URL en bearer-token.
- FR-19 (Done): per widget kan de gebruiker afzonderlijke appmetrics tonen/verbergen, tot 24 metrics rangschikken, de primaire metric kiezen en alle keuzes meenemen in presets en back-ups. De live inventaris heeft specifieke profielen voor applicaties, workers, databases, caches, queues, mail, GPU, media, downloads en eigen projecten.
- FR-20 (Done): de standaard Unraid-installatie bestaat uit precies één container met de vaste naam `DockDeck`, eigen icoon en correcte WebUI op poort `1218`. Discovery gebruikt uitsluitend GET-verzoeken naar de bestaande AppOps-inventaris en mount nooit de Docker-socket; voor hosts zonder AppOps blijft een expliciete, afgescheiden socket-proxyoverlay beschikbaar.
- FR-21 (Done): iedere appwidget kan een echte provider- of infrastructuurreeks als lijn-, vlak- of staafgrafiek tonen, 1060 samples kiezen, de legenda schakelen, het aantal stats begrenzen, iedere metric als waarde/gauge/progress presenteren en op iedere numerieke metric boven of onder een configureerbare drempel waarschuwen.
- FR-22 (Done): de Tower-widget ondersteunt dezelfde diepe configuratie voor zichtbare serverstats, volgorde, statlimiet en grafieken van containers, images, opslag, temperatuur, parity en UPS. Provider- en serverhistoriek blijft maximaal 60 punten uitsluitend in procesgeheugen.
- FR-23 (Done): Unraid krijgt een werkelijk PNG-gecodeerd transparant DockDeck-icoon; de Compose-label en het gebruikerssjabloon verwijzen niet langer naar SVG-inhoud die Unraid foutief als `.png` cachet, en de deployment ververst zowel de persistente als actieve DockerMan-cache.
+75
View File
@@ -0,0 +1,75 @@
# RISKS.md
## Aanvulling 2026-07-22
- Open-Meteo is een externe, ongeauthenticeerde databron. DockDeck cachet tien minuten, gebruikt een korte timeout en laat alleen de weerbadge zonder actuele waarde bij uitval; navigatie, widgets en instellingen blijven volledig bruikbaar.
- Een wallpaper kan de leesbaarheid verminderen. DockDeck beperkt bestandstype en grootte, bewaart het bestand lokaal, gebruikt een rustige uniforme contrastlaag en maakt rails, widgets en tegels doorschijnende glasoppervlakken; de gebruiker kan de afbeelding direct verwijderen. Bij `Passend` vult een vervaagde kopie de ultrawide zijruimte, terwijl het scherpe hoofdbeeld volledig zichtbaar blijft.
- `Passend` behoudt de volledige afbeelding en kan bij een afwijkende beeldverhouding rustige zij- of bovenruimte tonen; `Vullend` benut het vlak volledig maar kan randen afsnijden. De preview, focusregelaars en begrensde zoom maken deze afweging expliciet zonder het originele bestand te bewerken.
Project: DockDeck
Updated: 2026-07-22
## Assumptions
- Eén lokale beheerder; geen accounts of multi-userrollen in de MVP.
- Docker- en Unraid-metadata zijn read-only en bereikbaar vanuit het afgeschermde Compose-netwerk.
- Nginx Proxy Manager is optioneel; een handmatige/lokale URL blijft altijd de fallback.
- SQLite is de persistente bron voor normale configuratie; providercredentials uit Settings staan afzonderlijk als environmentassignments in het named volume.
## Open risks
- Unraid WebUI-templatevelden verschillen per communitytemplate. De parser ondersteunt de gangbare `[IP]`, `[HOST]`, `[PORT]` en `[PORT:n]` vormen; onbekende vormen vragen een handmatige URL-override.
- Een container zonder WebUI of icoon gebruikt een nette fallback maar kan pas navigeren na een URL-override.
- Automatische NPM-matching accepteert alleen één ondubbelzinnige hostname; complexe namen blijven bewust handmatig.
- DockDeck heeft geen eigen authenticatie. De standaardbind is daarom `127.0.0.1`; een LAN-bind of reverse proxy vereist bewuste netwerktoegangscontrole.
- Credentialinvoer over de huidige LAN-HTTP-deployment is niet transportversleuteld. Gebruik dit scherm alleen op het vertrouwde LAN; zet voor toegang buiten dat segment een geauthenticeerde HTTPS-reverse proxy in.
- `/data/integrations.env` bevat plaintext providercredentials met bestandmodus `0600`. JSON-exports sluiten dit bestand uit, maar een ruwe volumeback-up bevat het wel en moet als secretmateriaal worden beschermd.
- De Unraid-host heeft zijn automatische Docker-address-pools opgebruikt. DockDeck gebruikt daarom twee expliciete, kleine subnetten (`172.31.240.0/28` en `172.31.240.16/28`); controleer deze opnieuw bij netwerkherconfiguratie.
- Unraid-templates kunnen secrets bevatten. De app krijgt uitsluitend een gegenereerde metadata-mirror met `Name`, `WebUI` en `Icon`; voer de sanitizer opnieuw uit nadat templates wijzigen.
- Niet iedere website publiceert een bruikbaar icoon op `/favicon.ico`; zulke favorieten en apps vallen terug op een lokaal merk- of semantisch service-icoon en kunnen in Settings nog steeds een expliciete icoon-URL krijgen.
- De standaard single-containerdeployment is voor discovery afhankelijk van de bestaande AppOps-gateway. Bij uitval blijven alle opgeslagen navigatie en instellingen bruikbaar en krijgt de integratie een degraded status; discovery hervat automatisch zodra AppOps terug is.
- AppOps levert actuele CPU- en geheugenwaarden, maar momenteel geen netwerkdoorvoer of PID-aantal. Native providers en GET-only metrics bridges blijven die app-specifieke lagen leveren; zonder zo'n koppeling toont DockDeck voor die velden een eerlijke unavailable-status.
- Provider-API's verschillen per versie en vereisen credentials met uiteenlopende scopes. DockDeck gebruikt alleen gedocumenteerde GET-routes en korte timeouts; zonder endpoint toont de kaart haar eigen capabilitylabels met een expliciete connectiestatus naast gescheiden echte Dockertelemetrie. Configureer waar mogelijk een afzonderlijke minimum-scope token en roteer die periodiek via Settings of de deploymentenvironment.
- Custom widgetendpoints zijn een expliciet lokaal vertrouwensbesluit: DockDeck doet alleen een GET met korte timeout, begrenst labels/waarden en echoot tokens niet, maar de gebruiker blijft verantwoordelijk voor een read-only endpoint en een minimum-scope token. Gebruik geen URL die bij een GET een mutatie veroorzaakt.
- App-specifieke profielen beschrijven mogelijke metriekvelden; eigen applicaties moeten het gedocumenteerde JSON-contract zelf aanbieden voordat deze waarden live worden. Zonder endpoint blijft de kaart volledig bruikbaar met read-only Dockertelemetrie.
- Grafiekhistoriek is bewust procesgeheugen: maximaal zestig samples per reeks, geen geheimen en geen langdurige opslag. Een containerrestart wist de trend zonder configuratie of navigatie te verliezen.
- Unraid cachet containericonen tweemaal onder een vaste `.png`-naam: persistent in `/var/lib/docker/unraid/images` en actief onder `/usr/local/emhttp/state`. Alleen de persistente cache vervangen is onvoldoende zolang de actieve cache bestaat; `deploy/refresh-unraid-icon.sh` valideert daarom het echte PNG-MIME-type en vervangt uitsluitend beide DockDeck-bestanden.
- Gemengde widgetformaten gebruiken CSS-grid-dense plaatsing. De onafhankelijke widgetvolgorde blijft gelijk aan de DOM-/toetsenbordvolgorde; directe editknoppen behouden deze toegankelijkheidsregel zonder drag-and-drop.
- Unraid hwmon-classlinks kunnen buiten de beperkte mount wijzen. DockDeck behandelt iedere onleesbare sensor afzonderlijk als optioneel en behoudt array-/disktemperaturen uit `disks.ini`; de Dockerproxy wordt niet verruimd.
- Ollama en Glances gebruiken standaard de ontdekte lokale app-origin zonder credential. Een reverse-proxysubpad of afwijkende API-root vraagt een expliciete URL-override in Settings; een fout valt terug op app-specifieke Dockertelemetrie.
- Tautulli plaatst zijn API-key volgens het officiële contract in de querystring van de uitgaande LAN-request. DockDeck logt die URL niet, maar de doelserver kan dat wel doen; gebruik daarom een afzonderlijke key en bescherm targetlogs.
- De huidige AdGuard API is met Basic Auth beveiligd en JDownloader publiceert alleen noVNC; zonder aanvullende credentials/API-activering tonen deze kaarten daarom veilige app-specifieke live Dockertelemetrie (waaronder transfersnelheid), maar geen querylog, blocktotalen of downloadqueue.
- Een verwijderde app blijft onderdrukt zolang de technische containernaam gelijk blijft. Als Docker de container later bewust onder een andere naam aanbiedt, behandelt DockDeck dit als een nieuw ontdekte app en blijft die standaard verborgen.
- LAN-adresdetectie gebruikt alleen expliciete `br0`-netwerken. Afwijkende macvlan-netwerknamen of meerdere gepubliceerde poorten blijven bewust handmatig overschrijfbaar om geen onzekere URL te kiezen.
- Nieuwe apps blijven als “nieuw ontdekt” gemarkeerd totdat hun zichtbaarheid bewust wordt gewijzigd. Dat is een eenvoudige erkenningshandeling zonder aparte inbox; een gebruiker die alleen andere metadata bewerkt, blijft de ontdekmarkering zien.
- De directe Stitch-pass ligt bewust als laatste, geïsoleerde presentatielaag in `stitch-direct.css` boven de oudere stylesheets. Dat houdt de gedragswijziging klein en reviewbaar, maar een toekomstige frontendopschoning kan de nu overschreven legacyregels veilig verwijderen zodra alle zeldzame settingsstates opnieuw visueel zijn geïnventariseerd.
- De herkenbare merkfallbacks komen uit de vastgepinde MIT-package `@icons-pack/react-simple-icons@13.8.0`. Expliciete appiconen uit Unraid of Settings blijven leidend; niet-afgedekte eigen diensten gebruiken bewust een semantisch Lucide-icoon.
- De lokale Windows-omgeving heeft geen `docker`-CLI/runtime. Compose-config en de klassieke imagebuild zijn daarom op Tower uitgevoerd en voor release `72879e1` groen; de bekende inconsistente BuildKit-cache blijft hostbrede technische schuld.
- De gedeelde ultrawide-canvas en rechter widgetdock zijn bewust in dezelfde presentatielaag geimplementeerd. De E2E-suite valideert geen horizontale overflow en open dock-state op 3440 px, maar zeldzame combinaties van veel brede widgets kunnen later nog een fijnere masonry-/virtualisatielaag vragen.
- Automatische inventarisopschoning verwijdert alleen DockDeck-records die ontbreken in een aantoonbaar verbonden AppOps/Docker-respons. Een foutief maar succesvol leeg antwoord van de externe inventarisbron zou daardoor als autoritatief gelden; maak voor deployment een secretvrije export en bewaak de gerapporteerde containertelling. De Docker-containers zelf worden nooit gewijzigd.
- De Unraid-iconcache wordt bij containerstart uit de root-only DockerMan-iconenmap gevuld en is bewust niet persistent. Een gewijzigd icoon wordt daarom na een containerrecreate zichtbaar; de hostbron blijft read-only en het blijvende applicatieproces draait als UID 100.
- De slimme categorieactie is bewust niet onderdeel van discovery of heartbeat. Nieuwe of hernoemde niche-services blijven in hun bestaande categorie tot de beheerder de actie opnieuw uitvoert of ze handmatig wijzigt; regels moeten bij nieuwe canonical servicetypen expliciet en getest worden uitgebreid.
- Een geïmporteerd of handmatig opgeslagen categorie-icoon buiten de ondersteunde catalogus gebruikt bewust `Layers3` als fallback. Het blijft in Instellingen als aangepast zichtbaar en kan daar zonder dataverlies naar een van de afzonderlijke ondersteunde pictogrammen worden omgezet.
## Blockers
- Geen releaseblockers. Live NPM/providerdiepgang en UPS-status wachten uitsluitend op optionele minimum-scope omgevingsconfiguratie.
## Technical debt
- De eerste migratie is idempotente SQL naast Drizzle-schema's; bij een volgende schemawijziging hoort een genummerde migratietabel/runner.
- NPM-auth met username/password vraagt tijdelijk een token op; een expliciet read-only `NPM_TOKEN` blijft de aanbevolen productieconfiguratie.
- Playwright gebruikt één sequentiële testdatabase. Een toekomstige grotere suite kan per worker een geïsoleerde databasefixture gebruiken.
- De voorkeurenmigratie voegt kolommen idempotent toe aan bestaande databases. Bij verdere schema-uitbreiding blijft een genummerde migratierunner de gewenste vervolgstap.
- De Unraid-host heeft een inconsistente BuildKit-cachelaag. DockDeck kon veilig met de klassieke builder worden gebouwd; herstel van de hostbrede BuildKit-cache valt buiten dit project.
## Security and privacy
- DockDeck leest nooit container-environments of applicatieconfiguratie om providercredentials automatisch te vinden; provider-URL's en tokens worden expliciet als server-side environmentvariabelen aangeleverd.
- Secrets bestaan uitsluitend als procesenvironment en, bij invoer via Settings, in het permission-restricted environmentbestand. Ze worden niet in SQLite of JSON-export opgeslagen en nooit via een API-response teruggegeven.
- De appcontainer krijgt geen Docker-socket. De standaardroute leest alleen AppOps `GET /api/containers`; de optionele fallbackproxy is niet gepubliceerd, heeft `POST=0`, minimale GET-families en een intern netwerk.
- De app definieert geen start-, stop-, restart-, delete-, exec- of andere Docker-mutaties.
- NPM wordt alleen gelezen; DockDeck wijzigt of publiceert geen proxyhosts.
- De sanitizer schrijft alleen toegestane metadata en verifieert de resulterende XML voordat DockDeck die leest.
+88
View File
@@ -0,0 +1,88 @@
# 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.
+24
View File
@@ -0,0 +1,24 @@
# START_PROMPT.md
Start dit project volgens mijn Codex Project Operating System.
Gebruik de bestanden in deze map als projectdossier.
Project: DockDeck
Projectidee: 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.
Startmodus: MVP bouwen
Belangrijke intakekeuzes:
- Runtime model: Interactieve app
- Update/sync strategy: Polling
- Persistence: SQLite
- Deployment target: Docker op Unraid
- Repository state: Repo nog nodig
Lees voordat je bouwt: AGENTS.md, PROJECT_BRIEF.md, REQUIREMENTS.md, SPEC.md, PLAN.md, DECISIONS.md, TEST_LOG.md, RISKS.md en HANDOFF.md.
- Codex mag scaffolden, bouwen, testen, committen en pushen wanneer de repo en remote duidelijk zijn.
- Start met de kleinste werkende MVP-flow en verifieer die end-to-end.
Stel alleen vragen wanneer dat nodig is om een wezenlijke product-, architectuur- of veiligheidskeuze te maken.
Werk daarna de docs bij, bouw de kleinste nuttige stap, test aantoonbaar, commit zonder secrets en rond af met een heldere handoff.
+365
View File
@@ -0,0 +1,365 @@
# TEST_LOG.md
## Unieke categorie-iconen - 2026-07-22
| Check | Resultaat |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| Live inventaris | Pass; 16 categorieën bevatten al 16 unieke semantische waarden, waarvan 10 eerder niet door de frontendrenderer werden herkend |
| Renderer en instellingen | Pass; één catalogus levert 19 opties en alle 16 canonieke categorieën resolven naar een afzonderlijke Lucide-component |
| Nieuwe categorie | Pass; kiest automatisch het eerste nog ongebruikte ondersteunde pictogram |
| Onbekende importwaarde | Pass; veilige `Layers3`-fallback blijft behouden en verschijnt als Aangepast in Instellingen |
| `npm run format:check` / lint / typecheck | Pass |
| Unit/integratie / security | Pass; 57/57 en 5/5 |
| Playwright | Pass; 13 uitgevoerd en 15 project-gededupliceerd op desktop, tablet, mobiel en ultrawide |
| Build / audit | Pass; JS 377,08 kB, CSS 187,64 kB, server 182,0 kB en 0 kwetsbaarheden |
| React-review | Pass; named components, stabiele catalogus, afgeleide icoonkeuze en behouden semantische SVG-presentatie |
De release draait live op Tower met image `sha256:1cf11fc02e7e…`, is healthy en heeft 0 herstarts. De `.env` en bestaande JPEG-wallpaper van 3.245.835 bytes bleven byte-identiek. Chrome bevestigt in Categorieën 16 opgeslagen icoonwaarden, 16 gerenderde SVG's en 16 unieke SVG-signaturen; het dashboard toont dezelfde semantische pictogrammen en rapporteert 0 browserwaarschuwingen of -fouten. Er was geen databasewijziging nodig: de bestaande unieke waarden worden uitsluitend voortaan correct gerenderd.
## Platformbrede wallpaperlaag - 2026-07-22
| Check | Resultaat |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Geometrie | Pass; stage start op 72 px, eindigt op de viewportonderrand en is exact viewportbreed vanaf x=0 |
| Presetprioriteit | Pass; canvas heeft bij een wallpaper `background-image: none` en een transparante achtergrond |
| Compositie | Pass; scherp instelbaar beeld plus zachte `cover`-ambientlaag, met glasoppervlakken voor linkerrail, tegels en rechter widgetdock |
| Scrollgedrag | Pass; fixed stage blijft onder de topbalk staan terwijl de dashboardinhoud onafhankelijk doorloopt |
| `npm run format:check` / lint / typecheck | Pass |
| Unit/integratie / security | Pass; 54/54 en 5/5 |
| Playwright | Pass; 13 uitgevoerd en 15 project-gededupliceerd op desktop, tablet, mobiel en ultrawide; geen horizontale overflow |
| Build / audit | Pass; JS 375,95 kB, CSS 187,64 kB, server 182,0 kB en 0 kwetsbaarheden |
| React-review | Pass; decoratieve ambientbeelden zijn verborgen, de bestaande semantiek en lokale draftstate blijven intact |
De live Tower-release draait op image `sha256:f5d62a9e30f6…`, is healthy en heeft 0 herstarts. De `.env` en bestaande JPEG-wallpaper van 3.245.835 bytes bleven byte-identiek; Unraid, AppOps en alle zeven livepanelen zijn connected. Chrome bevestigt op 3440×1215 een fixed stage van x=0/y=72 tot de volledige 3425×1143 browsercontentruimte. Na 900 px scroll blijft y=72 behouden, de presetcanvas blijft transparant, beide zijpanelen gebruiken 62% glas en er is geen horizontale overflow of applicatiefout.
## Responsieve wallpaperstudio - 2026-07-22
| Check | Resultaat |
| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Reproductie | Pass; `cover` schaalde tegen een 3077×3015 contentcanvas in plaats van het zichtbare centrale viewport van 3077×1143 |
| Viewportstage | Pass; wallpaper schaalt tegen `100svh - 72px`, blijft tijdens scrollen in het centrale vlak en verstoort het ultrawide dock niet |
| Preview en bediening | Pass; Passend/Vullend, X-/Y-focus, 80160% zoom, herstel en expliciet toepassen |
| Persistentie/migratie/export | Pass; vier nieuwe gevalideerde voorkeuren, legacydefaults en versie 1 round-trip |
| `npm run format:check` / lint / typecheck | Pass |
| Unit/integratie / security | Pass; 54/54 en 5/5 |
| Playwright | Pass; 13 uitgevoerd en 15 project-gededupliceerd op desktop, tablet, mobiel en ultrawide |
| Build / audit | Pass; JS 375,56 kB, CSS 185,13 kB, server 182,0 kB en 0 kwetsbaarheden |
| React-review | Pass; lokale draftstate, volledige dependencies, semantische controls, toetsenbordbediening en decoratieve alttekst |
De live Tower-release draait op image `sha256:606051e4f146…`, is healthy en heeft 0 herstarts. De `.env` en bestaande JPEG-wallpaper van 3.245.835 bytes bleven byte-identiek. Chrome bevestigt op 3440×1215 een wallpaperstage van 2381×1143 binnen een 3015 px hoge scrollcanvas: `Passend`, 50/50-focus en 100% zoom, zonder horizontale overflow of browserfouten. De gecentreerde studio-preview is 1600×686; `Vullend` wijzigt alleen de draft en `Herstellen` zet die terug zonder de live voorkeur te overschrijven.
## Achtergrond- en wallpaperherstel - 2026-07-22
| Check | Resultaat |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Reproductie live | Pass; voorkeur `grid` en JPEG-wallpaper van 3,2 MB waren opgeslagen, maar de CSS-compositie werd ongeldig verklaard door een ontbrekende semantische token |
| Achtergrondpresets | Pass; Aurora, Blauwdruk en Minimaal leveren drie verschillende berekende canvasachtergronden |
| Wallpaperflow | Pass; upload via Instellingen verschijnt als preview, zet `has-wallpaper` en wordt als `/api/wallpaper` in de zichtbare canvas gerenderd |
| `npm run format:check` / lint / typecheck | Pass |
| Unit/integratie / security | Pass; 54/54 en 5/5 |
| Playwright | Pass; 13 uitgevoerd en 15 project-gededupliceerd, inclusief de nieuwe end-to-end achtergrondflow |
| Build / audit | Pass; JS 371,81 kB, CSS 180,35 kB, server 180,6 kB en 0 kwetsbaarheden |
| Live Unraid-release | Pass; image `sha256:977791a84355…` healthy met 0 herstarts, `.env` byte-identiek en bestaande JPEG-wallpaper van 3.245.835 bytes behouden |
| Live Chrome-audit | Pass op 3440×1215; alle presets visueel en berekend verschillend, wallpaper in iedere compositie, 0 overflow en 0 browserfouten |
De audit heeft via de echte instellingeninterface Aurora, Minimaal en Blauwdruk geactiveerd. De wallpaper bleef tijdens iedere wissel zichtbaar; de canvascomposities gebruikten respectievelijk kleurlichten, een rustige overlay en een raster van 40 px. Blauwdruk is na de controle als actieve gebruikersvoorkeur hersteld.
## Premium wallpaper-, weer- en controlpass - 2026-07-22
| Check | Resultaat |
| ---------------------------- | ---------------------------------------------------------------------------------------------------- |
| Typecheck en unit/integratie | Pass; 54/54 tests inclusief weer-cache en wallpaperformaten |
| Playwright | Pass; 12 uitgevoerd, 12 project-gededupliceerd; desktop/tablet/mobile/3440px, 0 browserfouten |
| Toggle-geometrie | Pass; E2E bevestigt meer dan 18px verplaatsing tussen beide zichtbare posities |
| Visuele screenshots | Pass; weerbadge, compacte Home Assistant-tegel, consistente rails en geen horizontale overflow |
| React-review | Pass; effectcleanup, locatie-afhankelijke revalidatie, semantische switches en server-side datafetch |
De finale lint, typecheck, unit/integratie, Playwright, security, build en audit zijn groen. Build: client-JS 371,81 kB (115,70 kB gzip), CSS 178,48 kB (30,05 kB gzip), server 180,6 kB; `npm audit` meldt 0 kwetsbaarheden. Tower `docker compose config` en imagebuild zijn groen; image `a371cab602ce…` draait healthy met 0 restarts en een byte-identieke `.env`. Live wallpapercyclus: PUT 201, GET 200, DELETE 204. Open-Meteo gaf voor Mol 21,2 °C/bewolkt. Alle zes appwidgets zijn connected; de ultrawide Chrome-audit toont 0 functionele fouten.
Project: DockDeck
Laatste gate: 2026-07-22
## Autoritatieve inventarisheartbeat en instellingen-UX — 2026-07-22
| Check | Resultaat |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `npm run format:check` | Pass, alle bestanden Prettier-conform |
| `npm run lint` | Pass, 0 waarschuwingen |
| `npm run typecheck` | Pass |
| `npm test` | Pass, 48/48; inclusief connected-only opschoning, degraded behoud, deduplicatie en read-only/case-insensitieve Unraid-iconen |
| `npm run test:security` | Pass, 5/5 |
| `npm run test:e2e` | Pass, 12 uitgevoerd / 12 project-gededupliceerd op vier viewports; dashboardcategoriefilter en 0 console-/paginafouten |
| `npm run build` | Pass, JS 364,55 kB / 113,69 kB gzip; CSS 170,68 kB / 28,53 kB gzip; server 167,2 kB |
| `npm audit --audit-level=moderate` | Pass, 0 kwetsbaarheden |
| In-app browseraudit | Pass op 1440×900: topbars beide `left=32`/`height=72`, filter reduceert tot één groep, verticale Integraties, 14px kleine tekst, brede Paneelstudio |
| React-review | Pass: single-flight heartbeat, cleanup van timer/listeners, afgeleide filterstate, semantische knoppen, stabiele ids en lokaal begrensde state |
De browseraudit mat geen horizontale overflow en bevatte geen browserwarnings of -errors. De opschoonregel wijzigt uitsluitend DockDecks SQLite-records na een verbonden inventarisrespons; Docker blijft read-only en een degraded heartbeat verwijdert niets.
Live release `5e411d1`: Compose-config en imagebuild groen; image `sha256:2d320ff25b66eb84e3fdad27f5437ad8c9732e99891ceb14d714e95655c6db2c`; container healthy met 0 herstarts en PID 1 als UID 100. De secretvrije pre-sync-export bevat 139 apps, 16 categorieën en 1 favoriet (SHA-256 `35d1cf987511177a7246e906afd27f42f87f714f4b621358aaa21ab22b5873ea`). Na de verbonden heartbeat: 90 apps, 50 zichtbaar, 16 categorieën, 0 stale/ontbrekende/dubbele namen en 0 statusafwijkingen tegenover AppOps. Alle 5 lokale Unraid-iconen geven HTTP 200 met een afbeelding-MIME-type.
## Finale visuele audit — 2026-07-22
| Check | Resultaat |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `npm run format:check` | Pass, alle bestanden Prettier-conform |
| `npm run lint` | Pass, 0 waarschuwingen |
| `npm run typecheck` | Pass |
| `npm test` | Pass, 42/42; inclusief persistente echte ontdekstatus en legacy-migratie |
| `npm run test:security` | Pass, 5/5 |
| `npm run test:e2e` | Pass, 11 uitgevoerd / 9 project-gededupliceerd op vier viewports; 0 console-/paginafouten |
| `npm run build` | Pass, JS 361,49 kB / 113,01 kB gzip; CSS 147,26 kB / 25,19 kB gzip; server 164,6 kB |
| `npm audit --audit-level=moderate` | Pass, 0 kwetsbaarheden |
| In-app Chrome-audit | Pass: dashboard, Quick Launch en alle zeven instellingensecties; 390, 768, 1440 en 3440 px; geen horizontale overflow |
| React-review | Pass: afgeleide catalogusstate, begrensde renders, effect-cleanup, semantische controls, stabiele keys en portalfocus |
| Secret-/mutatiescan | Pass; geen toegevoegde secrets, externe UI-assets of Docker-mutaties |
| Lokale Docker-validatie | Niet beschikbaar: Windows-omgeving heeft geen Docker-CLI; Compose en imagebuild worden op Tower uitgevoerd |
De audit heeft de operationele servicepanelen onder een compacte maar zichtbare **Live inzicht**-rij geplaatst, zonder de Paneelstudio of configuratie te verwijderen. Appbeheer gebruikt maximaal twintig rijen per pagina en een echte `isNew`-status; Widget Studio toont twaalf mogelijkheden per tranche. Geavanceerde weergavekeuzes en favoriete-editorvelden blijven volledig werkend via expliciete uitklapsecties. De Quick Launch-portal vult op ultrawide het volledige viewport en herstelt de invoerfocus na de portalovergang.
### Live release `72879e1`
| Check | Resultaat |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Pre-deploybehoud | Secretvrije export opgeslagen; `.env`-SHA-256 vóór/staging/na identiek; named volume `dockdeck_dockdeck_data` behouden |
| `docker compose config --quiet` | Pass op huidige en gestagede bron |
| Klassieke Docker-imagebuild | Pass; image `sha256:5025a6599410…`, bundels identiek aan de lokale build |
| Compose release | Pass; één `DockDeck`-container, healthy, 0 herstarts op `192.168.10.150:1218` |
| Persistentie/schema | Pass; 138 apps, 55 zichtbaar, 1 favoriet, 3 categorieën, 6 appwidgets en dark theme; alle API-records hebben `isNew` |
| Read-only integraties | Pass; 233 templates, 3 array-/poolgroepen en 89 AppOps-containers connected; NPM bewust disabled |
| Live Chrome-audit | Pass op 3440×1271: favorietenrail, echte iconen, 0 monogrammen, Quick Launch 3440 px met focus, alle zeven instellingensecties en 0 overflow |
De vorige bronversie blijft recoverable onder `/mnt/user/appdata/dockdeck-prev-20260722-025420`; de secretvrije pre-release-export en behoudsmetingen staan afgeschermd onder `/mnt/user/appdata/dockdeck-release-backups/72879e1-20260722-025420`.
## Checks
| Check | Command / evidence | Result |
| -------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Dependency-installatie | `npm install` | Pass |
| Formatting | `npm run format:check` | Pass |
| Lint | `npm run lint` | Pass, 0 warnings |
| TypeScript | `npm run typecheck` | Pass |
| Unit/integratie/migratie | `npm test` | Pass, 42/42; inclusief app-/serverhistoriek, widgetpersistentie, capabilityprofielen en duale Unraid-icooncachehelper |
| Read-only security | `npm run test:security` | Pass, 5/5; inclusief GET-only providergrens, serviceworker zonder API-cache en cachehelper zonder Docker-mutaties |
| Browserflows | `npm run test:e2e` | Pass, 10 uitgevoerd / 18 bewust overgeslagen; vier viewports, advanced widgets, presets, diagnostics en editmodus |
| Production build | `npm run build` | Pass; client 305.49 kB / 91.03 kB gzip, CSS 69.77 kB / 12.85 kB gzip, serverbundle 163.0 kB |
| Dependency security | `npm audit --audit-level=moderate` | Pass, 0 vulnerabilities |
| Production smoke | `PORT=3100 npm start` + HTTP checks | Pass: health `ok`, HTML 200, 4 mockapps, alle nieuw verborgen |
| Visuele browseraudit | In-app Chromium, echte lokale UI | Pass: diepe widgetinstellingen op 375×812 en 2560×1440; mobiele codecontract-overflow hersteld, 0 consolefouten |
| Secret-/mutatiescan | `rg` plus securitytests | Pass; alleen verwachte envnamen en socketmount in de aparte proxy |
| Unraid Compose-config | `docker compose config --quiet` | Pass op Tower/Unraid |
| Unraid image/build | Klassieke Docker-builder | Pass; de BuildKit-cache op de host was inconsistent, de klassieke builder bouwde `dockdeck:local` |
| Unraid live smoke | Compose, health en HTTP API | Pass: healthy, 0 restarts, `192.168.10.150:1218`, persistent named volume |
| Live read-only discovery | Socketproxy + metadata-mirror | Pass: 77 containers, 228 bruikbare templates; 230 templates gesaneerd en geverifieerd tot Name/WebUI/Icon |
| Responsive live release | Unraid Compose + Chromium | Pass: `192.168.10.150:1218`, healthy, 0 restarts, 77 apps, nieuwe voorkeurvelden en geen browserconsolefouten |
| Live servicewidget | Docker stats + Chromium 2560×1080 | Pass: AdGuard gemigreerd, CPU/geheugen/netwerk/uptime gevuld, integraties connected, geen overflow/fouten |
| Providerwidget release | Vitest + in-app Chromium | Pass: Unraid-hostwidget en providerfallback op 390×844 en 2560×1440; geen overflow, overlay of consolefouten |
| React review | React best-practices checklist | Pass: named configurator, stabiele keys, geen extra effects, semantische buttons/switches en zichtbare focus |
| Live hostwidget release | Unraid Compose + API + Chromium | Pass: healthy op `192.168.10.150:1218`; Tower, 77 containers, 72 running, 20 cores, 62 GB, geen overflow/fouten |
| Live tailored widgets | API-delta + Chromium desktop/mobile | Pass: AdGuard DNS-layout/API-lock, JDownloader down-/uploadsnelheid, correcte links, 0 restarts en geen overflow/fouten |
| Live credential settings | API + in-app Chromium | Pass: AdGuard toont alleen ontbrekende username/password, password masking, geen overflow/consolefouten, geen dummywrite |
| Widget Studio lokaal | In-app Chromium + Playwright | Pass: catalogus/filter/search op desktop, responsive op vier viewports, gelijke kaarten, geen overflow/consolefouten |
| Widget Studio live | Unraid API + Compose health | Pass: 41 capabilities voor 82 apps, 2 bestaande widgets behouden, healthy met 0 restarts; credentialstore niet aangemaakt |
| Navigatie/beheer lokaal | Vitest + Playwright + Chromium | Pass: 30 tests, 8 browserchecks; remove-suppressie, rename-search, Tower, Google, sidebarfavorieten en 390px zonder overflow |
| Navigatie/beheer live | Unraid API + in-app Chromium | Pass: healthy/0 restarts; Tower-search/link, Tweakers-sidebar, gecorrigeerde AdGuard- en Immich-URL's, geen fouten/overflow |
| Widgetconfiguratie lokaal | Vitest + Playwright + agent-browser | Pass: drie formaten, inhoudslagen, import/export, 390/3440 px en geen overflow |
| Widgetconfiguratie live | Tower Compose + API + browser | Pass: healthy/0 restarts op `:1218`; 80 apps, 55 zichtbaar, 5 widgets; juiste AnythingLLM/Immich-attributie |
| Volledige widgetroadmap | Vitest + Playwright + API | Pass: onafhankelijke volgorde, metriccompositie, presets, freshness, cadence, warnings, sparklines, PWA en diagnostics |
| Native Unraid live | Read-only mounts + API | Pass: array STARTED, parity healthy/0 fouten, 3 opslaggroepen, 57 °C hoogste meting; integratie connected |
| Finale live responsiviteit | Chromium 390×844 en 3440×1440 | Pass: 7 live panelen incl. Tower, geen positieve overflow, geen Vite-overlay en PWA-manifest aanwezig |
| Persoonlijke widgetdiepte | API + Vitest + Chromium | Pass: 80/80 capabilities, 55 custom GET-profielen, 23 native providers en gescheiden app/worker/database-metrics |
| Tower poort/configuratie | SQLite + live API + Chromium | Pass: naam/URL zichtbaar in Settings; voorkeur en hostkaart wijzen naar `http://192.168.10.150:5000` |
| Finale Unraid release | Compose config/build/up + health | Pass: klassieke imagebuild, healthy, 0 restarts, 80 apps/55 zichtbaar/6 appwidgets; named-volumeconfiguratie behouden |
| Single-container release | AppOps + Compose + API + browser | Pass: exact één `DockDeck`, oude proxy verwijderd, 83 live containers, icoon/WebUI correct, data-ID en 55 zichtbare apps behouden |
| Diepe widgetconfiguratie | Vitest + Playwright + browser | Pass: statselectie/-limiet/-stijl, elke numerieke waarschuwing, 3 grafiektypes, 60-punt provider- en Towerhistoriek |
| App-specifieke fallback | Capabilityaudit + browser | Pass: kaarten behouden eigen metrics zonder endpoint en tonen eerlijke `Connect data`; alle geaudite generieke profielen weg |
| Unraid PNG-icoon | Asset, HTTP, beide caches en Chrome | Pass live: actieve SVG-cache vervangen; Unraid rendert nu de echte 512×512 PNG in DockDecks rij zonder `question.png`-fallback |
| Finale capabilityaudit | Live API + capabilityvergelijking | Pass: 97 opgeslagen apps, 97 capabilityprofielen, nul generieke profielen; BlockPilot en LumaOps expliciet gedekt |
| Diepe widgetrelease live | Compose + API + in-app Chromium | Pass: healthy/0 restarts, 74 ontdekt/69 actief, 55 zichtbaar, 6 appwidgets plus Tower, geen overflow of browserconsolefouten |
## Scenario coverage
- Deterministische discovery, Unraid XML/WebUI-portvervanging en NPM-hostmatching.
- Nieuwe containers verborgen; zichtbaarheid, naam, categorie, order en URL-override.
- Categorieën en favorieten aanmaken/bewerken/verwijderen.
- Volledige dynamische zoekindex voor zichtbare/verborgen apps, actuele namen, technische namen, categorieën, images, URL's, favorieten en Tower; expliciete Google-fallback in nieuw tabblad.
- Persistente DockDeck-only appverwijdering met rediscoveryonderdrukking en back-updekking; geen Docker-mutatie.
- Alle drie thema's, overzicht/categorienavigatie en mobiele navigatie.
- Persistente accent-, dichtheid-, contentbreedte-, achtergrond-, hero- en statusvoorkeuren.
- Responsieve grids en navigatie op mobiel, tablet, desktop en ultrawide; lokale fonts zonder externe request.
- Automatische first-party faviconafleiding met lokale merk-/semantische fallback en handmatige override; lettermonogrammen zijn niet meer nodig.
- Per-app widgetkeuze, limiet van acht, legacy-migratie en generieke read-only Dockerstats met degraded fallback.
- Persistente widgetformaten en inhoudslagen voor app- en Unraidpanelen, refreshmomenten en dichte responsieve plaatsing.
- Standaard Unraid-hostwidget via Docker `/info` en provider-aware AdGuard/Plex/Immich/Home Assistant/*arr-presentatie met server-only credentials.
- Gemaskeerde providercredentialinvoer via Settings, allowlist per provider, HTTP(S)-URL-validatie, same-origin write, atomische `0600`-environmentpersistentie en directe hertest zonder secret echo.
- Widget Studio capabilitycatalogus met aanbevolen/alle/actieve filters, acht-panelenlimiet, directe sync en vervangbare connected credentials.
- Volledige 80/80 capabilitycatalogus met app-specifieke metricdefinities, per-metric zichtbaarheid/volgorde en een begrensd custom GET-only JSON-contract.
- Persistente Tower-naam/URL met poort `5000` als standaard en dezelfde actuele waarde in kaart en zoekindex.
- Zero-config Ollama-/Glances-API's, Tautulli-/Paperless-adapters en app-specifieke AI/document/security-Dockerfallbacks.
- Per-servicetype fallbackpresentatie met berekende live netwerkdelta's; JDownloader gebruikt download-/uploadsnelheid en AdGuard DNS-traffic in plaats van generieke cumulatieve netwerkbytes.
- Zijpaneel met klok, deck health, favorieten en snelle servicelinks; gebalanceerde tegelbreedtes op 2560 en 3440 pixels.
- Statuspolling, offline container en integratie-degradatie.
- SQLite-migratie, restartpersistentie, container-ID-wijziging en export/import versie 1.
- Geen Docker-mutatie-endpoint of Docker-beheeractie.
## Open external checks
- Live Nginx Proxy Manager: credentials ontbreken; adapter en degradatie zijn wel gemockt/getest.
- Optionele providercredentials: alleen nodig voor de diepste API-metrics van beveiligde diensten; Dockerfallbacks blijven beschikbaar.
## Latest quality gate
De volledige lokale Canon v1-gate is groen: `format:check`, lint, typecheck, 42/42 unit- en integratietests, 5/5 afzonderlijke securitytests, 7 uitgevoerde en 9 doelbewust project-gededupliceerde Playwright-checks op desktop/tablet/mobile/ultrawide, production build en 0 kwetsbaarheden. De browserflows eindigen met 0 `console.error`- of ongehanteelde paginafouten. De clientbuild is 312,99 kB JS (93,53 kB gzip) en 94,91 kB CSS (17,51 kB gzip); de serverbundle is 163,9 kB. In-app Chromium verifieerde daarnaast eerste-use, gevuld dashboard, Quick Launch, apps-instellingen, mobiele drawer en dark/light op 390×844 en 1440×900. De vaste testpoortblokkade op 3000 is opgelost met geïsoleerde poorten 3101/5174 en een schone Canon-testdatabase. Docker Compose en imagebuild zijn niet opnieuw uitgevoerd omdat in deze Windows-omgeving geen `docker`-CLI/runtime beschikbaar is; de eerdere Tower-verificatie blijft ongewijzigd.
## Canon v1 gate — 2026-07-21
| Check | Resultaat |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `npm run format:check` | Pass, alle bestanden Prettier-conform |
| `npm run lint` | Pass, 0 waarschuwingen |
| `npm run typecheck` | Pass |
| `npm test` | Pass, 42/42 |
| `npm run test:security` | Pass, 5/5 |
| `npm run test:e2e` | Pass, 7 uitgevoerd / 9 gededupliceerd over vier viewports; 0 console-/paginafouten |
| `npm run build` | Pass, JS 312,99 kB / 93,53 kB gzip; CSS 94,91 kB / 17,51 kB gzip; server 163,9 kB |
| `npm audit --audit-level=moderate` | Pass, 0 kwetsbaarheden |
| In-app browserreview | Pass: 390×844 en 1440×900, dark/light, dashboard, Quick Launch, apps, drawer, geen horizontale overflow |
| Secret-/mutatiescan | Pass; geen toegevoegde secrets of Docker-mutaties |
| `docker compose config --quiet` / imagebuild | Niet uitgevoerd: `docker` is lokaal niet geïnstalleerd |
## Directe Stitch-pass — 2026-07-21
| Check | Resultaat |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `npm run format:check` | Pass, alle bestanden Prettier-conform |
| `npm run lint` | Pass, 0 waarschuwingen |
| `npm run typecheck` | Pass |
| `npm test` | Pass, 42/42 |
| `npm run test:security` | Pass, 5/5 |
| `npm run test:e2e` | Pass, 11 uitgevoerd / 9 project-gededupliceerd; 390×844, 768×1024, 1440×900 en 3440×1440; 0 console-/paginafouten |
| `npm run build` | Pass, JS 312,49 kB / 93,60 kB gzip; CSS 125,83 kB / 22,07 kB gzip; server 163,9 kB |
| `npm audit --audit-level=moderate` | Pass, 0 kwetsbaarheden |
| In-app browserreview | Pass: gevuld donker dashboard, Quick Launch, App Beheer, editordrawer en Weergave; muis/toetsenbord en focus bevestigd |
| React-review | Pass: afgeleide state, effect-cleanup, semantische links/buttons, stabiele sleutels en toegankelijke switches |
De CSS-toename is de expliciete directe Canon-compositielaag. Zij vervangt geen runtime- of backendlogica en bevat geen externe assets. De finale E2E-test schrijft de actuele referentiebeelden naar `docs/design/dockdeck-canon-v1/implementation/`.
## Stitch launcherrail, iconen en widgets — 2026-07-22
| Check | Resultaat |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `npm run format:check` | Pass, alle bestanden Prettier-conform |
| `npm run lint` | Pass, 0 waarschuwingen |
| `npm run typecheck` | Pass |
| `npm test` | Pass, 42/42 |
| `npm run test:security` | Pass, 5/5 |
| `npm run test:e2e` | Pass, 11 uitgevoerd / 9 project-gededupliceerd op 390×844, 768×1024, 1440×900 en 3440×1440; 0 console-/paginafouten |
| `npm run build` | Pass, JS 353,39 kB / 110,42 kB gzip; CSS 135,18 kB / 23,34 kB gzip; server 163,9 kB |
| `npm audit --audit-level=moderate` | Pass na transitieve `fast-uri`-lockfile-update, 0 kwetsbaarheden |
| In-app browserreview | Pass: desktopfavorietenrail, echte Google/YouTube/GitHub/Plex/Immich/Home Assistant-iconen, zichtbare widgets en mobiele volgorde |
| React-review | Pass: zelfstandig railcomponent, effect-cleanup, afgeleide state, semantische links/buttons en getypeerde instellingenroute |
| Docker Compose/imagebuild | Niet uitgevoerd: in de lokale Windows-omgeving is geen `docker`-CLI/runtime beschikbaar |
De browsertest valideert aanvullend dat de desktoprail en servicepanelen zichtbaar zijn, dat appkaarten geen `.app-monogram` meer renderen en dat de Paneelstudio via de rail rechtstreeks in `Instellingen → Algemeen` opent. De nieuwe merkiconen zijn lokaal gebundeld; expliciete Unraid-/gebruikersiconen blijven de eerste bron.
## Widescreen leesbaarheids- en categorieaudit — 2026-07-22
| Check | Resultaat |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `npm run format:check` | Pass, alle bestanden Prettier-conform |
| `npm run lint` | Pass, 0 waarschuwingen |
| `npm run typecheck` | Pass |
| `npm test` | Pass, 51/51; inclusief categorie-inferentie, behoud van onbekende toewijzingen en de expliciete organisatie-API |
| `npm run test:security` | Pass, 5/5 |
| `npm run test:e2e` | Pass, 12 uitgevoerd / 12 bewust overgeslagen over desktop, tablet, mobiel en ultrawide; 0 testfouten |
| `npm run build` | Pass, JS 365,35 kB / 113,90 kB gzip; CSS 173,48 kB / 29,03 kB gzip; server 170,8 kB |
| `npm audit --audit-level=moderate` | Pass, 0 kwetsbaarheden |
| React-review | Pass: afgeleide categorieanalyse, expliciete semantische actie, stabiele sleutels, geen extra effecten en ongewijzigde cleanup |
| Chrome audit vóór correctie | 3440×1215, 0 horizontale overflow en 0 consolefouten; wel widgetmetadata van 8 px, Paneelstudio tot 6 px en foutieve bulkcategorieën |
De pass vergroot de ultrawide widgetdock, maakt widget- en instellingenmicrotypografie leesbaar en begrenst extreem lange beheer-, integratie- en back-uprijen. De categorieactie gebruikt uitsluitend bestaande categorieën en laat onbekende services onaangeroerd.
### Live verificatie
| Check | Resultaat |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Secretvrij herstelpunt | `.codex-input/dockdeck-before-widescreen-audit-20260722-140834.json`, 80.809 bytes |
| `.env`-behoud | SHA-256 voor en na bronvervanging identiek: `e41e1b63…` |
| `docker compose config` | Pass vóór en na bronvervanging |
| Imagebuild en health | Pass, image `cc3a4b56fdfb…`, container healthy, 0 restarts |
| Read-only heartbeat | Pass, exact 90 bestaande containers en 50 zichtbare apps |
| Slimme categorieorganisatie | Pass, 14 gecorrigeerd, 76 behouden, 0 onbekend; 16 bestaande categorieën |
| Chrome 3440×1215 | Pass, dock 540 px, 0 horizontale overflow, 0 kapotte images, geen applicatieconsolefouten |
| Interacties | Pass: Downloads-filter toont exact Deluge/JDownloader2/slskd; instellingenzoeker, categoriebeheer en Quick Launch-favicon werken live |
De enige browserlogregel kwam van de WideFrog Chrome-extensie zelf (`keyflow timeout` op een titelupdate), niet van DockDeck. De live DockDeck-tab is als testdashboard open gebleven.
## Volledige instellingen- en releasepass — 2026-07-22
| Check | Resultaat |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `npm run format:check` | Pass, alle bestanden Prettier-conform |
| `npm run lint` | Pass, 0 waarschuwingen |
| `npm run typecheck` | Pass |
| `npm test` | Pass, 42/42; inclusief legacy-migratie en export/import van `reducedMotion` en `pollingIntervalMs` |
| `npm run test:security` | Pass, 5/5 |
| `npm run test:e2e` | Pass, 11 uitgevoerd / 9 project-gededupliceerd op vier viewports; instellingenzoeker en voorkeurpersistentie; 0 browserfouten |
| `npm run build` | Pass, JS 357,89 kB / 111,86 kB gzip; CSS 139,39 kB / 24,06 kB gzip; server 164,2 kB |
| `npm audit --audit-level=moderate` | Pass, 0 kwetsbaarheden |
| In-app browserreview | Pass: Apps, Categorieën, Weergave en Back-up; zoekfilter, kaartlayout, dropzone, scrollherstel en 0 horizontale overflow op 1280 |
| React-review | Pass: afgeleide zoekstate, correcte hookafhankelijkheden, getypeerde drag-events en semantische buttons/switches |
De instellingenmatrix dekt iedere persistente voorkeur, app-/favoriet-/categoriebeheer, widgets, integratiecredentials en back-up/export. Fictieve online- of back-uphistoriek is bewust niet toegevoegd; de UI toont alleen werkelijk beschikbare gegevens.
### Live Unraid-release
| Check | Resultaat |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Pre-deploybehoud | Secretvrije export gemaakt; `.env`-SHA-256 voor/na bronkopie identiek; named volume `dockdeck_dockdeck_data` behouden |
| `docker compose config` | Pass vóór en na bronvervanging |
| Klassieke Docker-imagebuild | Pass; image `14497e8a384c…`, client-/serverbundels identiek aan lokale build |
| Compose release | Pass; commit `3ffd39b`, één `DockDeck`-container, healthy, 0 restarts op `192.168.10.150:1218` |
| Persistentie na migratie | Pass; 55 zichtbare apps, 1 favoriet, 3 categorieën, 6 appwidgets en dark/30 s behouden; sync voegde 3 nieuwe records standaard verborgen toe |
| Read-only integraties | Pass; 233 templates, 3 array-/poolgroepen, 90 AppOps-containers GET-only en AdGuard connected; NPM bewust disabled |
| Live in-app browser | Pass; favorietenrail, echte iconen, 7 panelen, instellingenzoeker/controls, scrolltop 0 en geen horizontale overflow |
De enige foutregel in de eerste vijf minuten was een door de releasecheck zelf veroorzaakte lege POST zonder JSON-contenttype (verwachte HTTP 415); de onmiddellijk herhaalde geldige synchronisatie gaf HTTP 200 en alle daaropvolgende health-/dashboardchecks zijn groen.
## Stitch consistentie- en ultrawidepass - 2026-07-22
| Check | Resultaat |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `npm run format:check` | Pass, alle bestanden Prettier-conform |
| `npm run lint` | Pass, 0 waarschuwingen |
| `npm run typecheck` | Pass |
| `npm test` | Pass, 42/42 |
| `npm run test:security` | Pass, 5/5 |
| `npm run test:e2e` | Pass, 11 uitgevoerd / 9 project-gededupliceerd op desktop/tablet/mobile/ultrawide; 0 browserfouten |
| `npm run build` | Pass, JS 363,25 kB / 113,46 kB gzip; CSS 155,55 kB / 26,33 kB gzip; server 164,6 kB |
| `npm audit --audit-level=moderate` | Pass, 0 kwetsbaarheden |
| React-review | Pass: lokale state voor widgetdock, media-query cleanup, afgeleide Quick Launch-snelkoppelingen en semantische linkcontrols |
| Secret-/mutatiescan | Pass; geen toegevoegde secrets, externe UI-assets of Docker-mutaties |
Deze pass maakt de platformlayout consistent tussen dashboard en instellingen, vergroot de instellingen-typografie, gebruikt ultrawide-detectie om widgets als rechterdock te tonen, laat Quick Launch-favorieten met echte iconen renderen en vervangt de lege Quick Launch-uitleg door functionele snelkoppelingskaarten. Loopback-iconen uit discovery worden op een LAN-host naar de actuele DockDeck-host herschreven, zodat appiconen zoals Widefrog niet meer naar client-localhost wijzen. De E2E-suite valideert daarnaast dat de ultrawide-widgetdock automatisch open staat en rechts van de dashboardgroepen ligt zonder horizontale overflow.
## Edge-anchored Stitch shell en rijkere Dockerkaarten - 2026-07-22
| Check | Resultaat |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `npm run format:check` | Pass, alle bestanden Prettier-conform |
| `npm run lint` | Pass, 0 waarschuwingen |
| `npm run typecheck` | Pass |
| `npm test` | Pass, 42/42 |
| `npm run test:security` | Pass, 5/5 |
| `npm run test:e2e` | Pass, 11 uitgevoerd / 9 project-gededupliceerd op desktop/tablet/mobile/ultrawide |
| `npm run build` | Pass, JS 363,25 kB / 113,46 kB gzip; CSS 162,12 kB / 27,27 kB gzip; server 164,6 kB |
| `npm audit --audit-level=moderate` | Pass, 0 kwetsbaarheden |
| Chrome visuele audit | Pass: dashboardrail `left=0`, settings-sidebar `left=0`, ultrawide widgetdock `right=3440`, 0 horizontale overflow op 1920 en 3440 px |
Deze pass verwijdert de zwevende dashboard-shell op desktop: de linkerfavorietenrail staat tegen de viewportzijde en gebruikt dezelfde oppervlakte, padding en typografische taal als de instellingenrail. Op ultrawide staat het widgetpaneel als rechterdock tegen de rechter viewportzijde. Docker-snelkoppelingen zijn visueel zwaardere Stitch-servicekaarten met grotere iconen, statuschips, accentrail, arrow affordance, subtiel verloop en diepere hoverrespons.
### Live categorie-indeling - 2026-07-22
Voor de live DockDeck-config op `http://192.168.10.150:1218` is vooraf een secretvrije export gemaakt naar `.codex-input/dockdeck-categories-before-20260722-052942.json`. Daarna zijn 139 apps via de DockDeck-config-API over 16 categorieën verdeeld; verificatie: 0 apps zonder categorie. Categorieaantallen: Media 17, Downloads 2, Books & Documents 7, Photos 14, Home & Automation 2, Security & Identity 4, Network & Admin 4, Monitoring 7, Development & Projects 45, AI & ML 3, Databases & Storage 12, Infrastructure 16, Games & Emulation 5, Tools & Productivity 1, Experiments & Archived 0, Favorites 0.
Live codevervanging is daarna alsnog uitgevoerd via de bestaande SSH-alias `unraid-widefrog` (`root`, sleutel `widefrog_unraid_deploy`). De deploymentmap is een uitgepakte source-copy en geen git-worktree; daarom is commit `41942d5` via een checksum-gecontroleerd `git archive` naar Tower gekopieerd en over de source uitgepakt. `.env` bleef byte-identiek (`e41e1b63...` voor en na), `docker compose config --quiet` slaagde, `docker compose up -d --build` bouwde image `af96b8ac7a04...` en de `DockDeck`-container staat healthy met 0 restarts op `192.168.10.150:1218`. Na een geldige read-only sync zijn Unraid templates, Unraid runtime, AppOps inventory en AdGuard Home widget connected; dashboardverificatie: 139 apps, 55 zichtbaar, 16 categorieën, 0 zonder categorie.
Executable → Regular
View File
+31
View File
@@ -0,0 +1,31 @@
<?xml version="1.0"?>
<Container version="2">
<Name>DockDeck</Name>
<Repository>dockdeck:local</Repository>
<Network>bridge</Network>
<Shell>sh</Shell>
<Privileged>false</Privileged>
<Support>http://192.168.10.150:3000/NuklearRabbit/DockDeck</Support>
<Project>http://192.168.10.150:3000/NuklearRabbit/DockDeck</Project>
<Overview>DockDeck is a premium, read-only personal start dashboard for Unraid. This single-container profile uses the existing AppOps inventory API and never mounts the Docker socket.</Overview>
<Category>Tools:Status:</Category>
<WebUI>http://[IP]:[PORT:1218]</WebUI>
<Icon>http://192.168.10.150:1218/dockdeck-icon.png</Icon>
<ExtraParams>--read-only --security-opt=no-new-privileges:true</ExtraParams>
<PostArgs/>
<CPUset/>
<DateInstalled>1784060000</DateInstalled>
<DonateText/>
<DonateLink/>
<Requires>Unraid AppOps Gateway on port 1216</Requires>
<Config Name="WebUI" Target="3000" Default="1218" Mode="tcp" Description="DockDeck WebUI" Type="Port" Display="always" Required="true" Mask="false">1218</Config>
<Config Name="App data" Target="/data" Default="/mnt/user/appdata/dockdeck/data" Mode="rw" Description="Persistent database and integration settings" Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/dockdeck/data</Config>
<Config Name="Template metadata" Target="/unraid-templates" Default="/mnt/user/appdata/dockdeck/unraid-templates" Mode="ro" Description="Sanitized Unraid template metadata" Type="Path" Display="advanced" Required="true" Mask="false">/mnt/user/appdata/dockdeck/unraid-templates</Config>
<Config Name="Unraid runtime" Target="/unraid-runtime" Default="/var/local/emhttp" Mode="ro" Description="Read-only array and parity status" Type="Path" Display="advanced" Required="true" Mask="false">/var/local/emhttp</Config>
<Config Name="Hardware sensors" Target="/host-hwmon" Default="/sys/class/hwmon" Mode="ro" Description="Read-only host temperature sensors" Type="Path" Display="advanced" Required="true" Mask="false">/sys/class/hwmon</Config>
<Config Name="CPU information" Target="/host-proc/cpuinfo" Default="/proc/cpuinfo" Mode="ro" Description="Read-only host CPU capacity" Type="Path" Display="advanced" Required="true" Mask="false">/proc/cpuinfo</Config>
<Config Name="Memory information" Target="/host-proc/meminfo" Default="/proc/meminfo" Mode="ro" Description="Read-only host memory capacity" Type="Path" Display="advanced" Required="true" Mask="false">/proc/meminfo</Config>
<Config Name="AppOps URL" Target="APPOPS_URL" Default="http://192.168.10.150:1216" Mode="" Description="Existing read-only inventory API" Type="Variable" Display="always" Required="true" Mask="false">http://192.168.10.150:1216</Config>
<Config Name="Unraid host" Target="UNRAID_HOST" Default="192.168.10.150" Mode="" Description="Tower LAN address" Type="Variable" Display="always" Required="true" Mask="false">192.168.10.150</Config>
<Config Name="Unraid URL" Target="UNRAID_URL" Default="http://192.168.10.150:5000" Mode="" Description="Tower WebUI address" Type="Variable" Display="always" Required="true" Mask="false">http://192.168.10.150:5000</Config>
</Container>
+48
View File
@@ -0,0 +1,48 @@
#!/bin/sh
set -eu
container_name="${1:-DockDeck}"
icon_url="${2:-}"
template_path="/boot/config/plugins/dockerMan/templates-user/my-${container_name}.xml"
persistent_dir="/var/lib/docker/unraid/images"
runtime_dir="/usr/local/emhttp/state/plugins/dynamix.docker.manager/images"
fail() {
printf 'DockDeck icon refresh failed: %s\n' "$1" >&2
exit 1
}
[ "$(id -u)" -eq 0 ] || fail "run this helper as root on the Unraid host"
case "$container_name" in
"" | *[!A-Za-z0-9_.-]*) fail "invalid container name" ;;
esac
if [ -z "$icon_url" ] && [ -f "$template_path" ]; then
icon_url="$(sed -n 's#.*<Icon>\(.*\)</Icon>.*#\1#p' "$template_path" | head -n 1)"
fi
case "$icon_url" in
http://* | https://*) ;;
*) fail "provide an HTTP(S) icon URL or a user template containing <Icon>" ;;
esac
temporary_icon="$(mktemp /tmp/dockdeck-icon.XXXXXX)"
trap 'rm -f "$temporary_icon"' EXIT HUP INT TERM
curl --fail --silent --show-error --location \
--connect-timeout 5 --max-time 20 \
"$icon_url" --output "$temporary_icon"
[ "$(file -b --mime-type "$temporary_icon")" = "image/png" ] || \
fail "the downloaded asset is not a real PNG"
mkdir -p "$persistent_dir" "$runtime_dir"
install -m 0644 -o root -g root \
"$temporary_icon" "$persistent_dir/${container_name}-icon.png"
install -m 0644 -o root -g root \
"$temporary_icon" "$runtime_dir/${container_name}-icon.png"
printf 'Refreshed both Unraid icon caches for %s from %s\n' \
"$container_name" "$icon_url"
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""Copy only non-sensitive DockDeck metadata from Unraid templates."""
from __future__ import annotations
import argparse
from pathlib import Path
import xml.etree.ElementTree as ET
ALLOWED_FIELDS = ("Name", "WebUI", "Icon")
def sanitize(source_dir: Path, destination_dir: Path) -> int:
destination_dir.mkdir(parents=True, exist_ok=True)
written = 0
for source in sorted(source_dir.glob("*.xml")):
try:
root = ET.parse(source).getroot()
except (ET.ParseError, OSError):
continue
output = ET.Element("Container")
for field in ALLOWED_FIELDS:
value = root.findtext(field)
if value:
ET.SubElement(output, field).text = value.strip()
if output.findtext("Name"):
destination = destination_dir / source.name
ET.ElementTree(output).write(
destination,
encoding="utf-8",
xml_declaration=True,
)
destination.chmod(0o644)
written_tags = {
child.tag for child in ET.parse(destination).getroot()
}
if not written_tags.issubset(ALLOWED_FIELDS):
destination.unlink(missing_ok=True)
raise RuntimeError(
f"Unexpected field in sanitized template: {source.name}"
)
written += 1
return written
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("source", type=Path)
parser.add_argument("destination", type=Path)
args = parser.parse_args()
count = sanitize(args.source, args.destination)
print(f"Sanitized and verified {count} Unraid templates")
if __name__ == "__main__":
main()
+9
View File
@@ -0,0 +1,9 @@
DOCKDECK_BIND_ADDRESS=192.168.10.150
DOCKDECK_PORT=1218
DOCKDECK_EGRESS_SUBNET=172.31.240.16/28
UNRAID_TEMPLATES_DIR=/mnt/user/appdata/dockdeck/unraid-templates
UNRAID_HOST=192.168.10.150
UNRAID_URL=http://192.168.10.150:5000
UNRAID_RUNTIME_DIR=/var/local/emhttp
UNRAID_HWMON_DIR=/sys/class/hwmon
APPOPS_URL=http://192.168.10.150:1216
+56
View File
@@ -0,0 +1,56 @@
services:
dockdeck:
depends_on:
dockerproxy:
condition: service_started
environment:
APPOPS_URL: ""
DOCKER_PROXY_URL: http://dockerproxy:2375
networks:
- dockdeck_internal
dockerproxy:
image: ghcr.io/tecnativa/docker-socket-proxy:latest
restart: unless-stopped
environment:
LOG_LEVEL: warning
CONTAINERS: 1
INFO: 1
PING: 1
VERSION: 1
POST: 0
AUTH: 0
BUILD: 0
COMMIT: 0
CONFIGS: 0
DISTRIBUTION: 0
EVENTS: 0
EXEC: 0
IMAGES: 0
NETWORKS: 0
NODES: 0
PLUGINS: 0
SECRETS: 0
SERVICES: 0
SESSION: 0
SWARM: 0
SYSTEM: 0
TASKS: 0
VOLUMES: 0
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- dockdeck_internal
read_only: true
tmpfs:
- /run
- /tmp
security_opt:
- no-new-privileges:true
networks:
dockdeck_internal:
internal: true
ipam:
config:
- subnet: ${DOCKDECK_INTERNAL_SUBNET:-172.31.240.0/28}
+111 -175
View File
@@ -1,183 +1,119 @@
services: services:
db: dockdeck:
image: postgis/postgis:16-3.4 container_name: DockDeck
environment:
POSTGRES_DB: ${GEOINTEL_POSTGRES_DB:-geointel}
POSTGRES_USER: ${GEOINTEL_POSTGRES_USER:-geointel}
POSTGRES_PASSWORD: ${GEOINTEL_POSTGRES_PASSWORD:-geointel}
volumes:
- geointel_postgis:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${GEOINTEL_POSTGRES_USER:-geointel} -d ${GEOINTEL_POSTGRES_DB:-geointel}"]
interval: 5s
timeout: 5s
retries: 10
backend:
build: build:
context: ./backend context: .
args: image: dockdeck:local
GEOINTEL_INSTALL_AI: ${GEOINTEL_INSTALL_AI:-false} restart: unless-stopped
labels:
org.opencontainers.image.title: DockDeck
org.opencontainers.image.description: Premium read-only Unraid start dashboard
org.opencontainers.image.source: http://192.168.10.150:3000/NuklearRabbit/DockDeck
net.unraid.docker.managed: dockerman
net.unraid.docker.webui: "http://${UNRAID_HOST:-tower.local}:${DOCKDECK_PORT:-1218}"
net.unraid.docker.icon: "http://${UNRAID_HOST:-tower.local}:${DOCKDECK_PORT:-1218}/dockdeck-icon.png"
environment: environment:
DATABASE_URL: postgresql+psycopg://${GEOINTEL_POSTGRES_USER:-geointel}:${GEOINTEL_POSTGRES_PASSWORD:-geointel}@db:5432/${GEOINTEL_POSTGRES_DB:-geointel} HOST: 0.0.0.0
STORAGE_ROOT: /app/storage PORT: 3000
CORS_ORIGINS: ${GEOINTEL_CORS_ORIGINS:-http://localhost:1202,http://127.0.0.1:1202} DATABASE_PATH: /data/dockdeck.db
MAX_UPLOAD_MB: ${GEOINTEL_MAX_UPLOAD_MB:-500} INTEGRATION_ENV_PATH: /data/integrations.env
ORTHOPHOTO_ENABLED: ${ORTHOPHOTO_ENABLED:-true} APPOPS_URL: ${APPOPS_URL:-}
ORTHOPHOTO_WMS_URL: ${ORTHOPHOTO_WMS_URL:-https://geo.api.vlaanderen.be/OMWRGBMRVL/wms} DOCKER_PROXY_URL: ${DOCKER_PROXY_URL:-}
ORTHOPHOTO_WMS_LAYER: ${ORTHOPHOTO_WMS_LAYER:-Ortho} UNRAID_TEMPLATES_PATH: /unraid-templates
ORTHOPHOTO_RESOLUTION_M: ${ORTHOPHOTO_RESOLUTION_M:-1.0} UNRAID_ICONS_SOURCE_PATH: /unraid-icons-source
ORTHOPHOTO_MIN_SIDE_M: ${ORTHOPHOTO_MIN_SIDE_M:-128} UNRAID_ICONS_PATH: /unraid-icons-cache
ORTHOPHOTO_MAX_SIDE_M: ${ORTHOPHOTO_MAX_SIDE_M:-1024} UNRAID_RUNTIME_PATH: /unraid-runtime
ORTHOPHOTO_CACHE_TTL_HOURS: ${ORTHOPHOTO_CACHE_TTL_HOURS:-24} UNRAID_HWMON_PATH: /host-hwmon
SOURCE_CATALOG_PROBE_ENABLED: ${SOURCE_CATALOG_PROBE_ENABLED:-true} UNRAID_CPUINFO_PATH: /host-proc/cpuinfo
SOURCE_CATALOG_GRB_WFS_URL: ${SOURCE_CATALOG_GRB_WFS_URL:-https://geo.api.vlaanderen.be/GRB/wfs} UNRAID_MEMINFO_PATH: /host-proc/meminfo
GRB_ENABLED: ${GRB_ENABLED:-true} UNRAID_UPS_STATUS_PATH: ${UNRAID_UPS_STATUS_PATH:-}
GRB_OGC_API_URL: ${GRB_OGC_API_URL:-https://geo.api.vlaanderen.be/GRB/ogc/features/v1} UNRAID_HOST: ${UNRAID_HOST:-tower.local}
GRB_MIN_SIDE_M: ${GRB_MIN_SIDE_M:-10} UNRAID_URL: ${UNRAID_URL:-}
GRB_MAX_SIDE_M: ${GRB_MAX_SIDE_M:-20000} MOCK_DISCOVERY: "false"
GRB_PAGE_SIZE: ${GRB_PAGE_SIZE:-1000} POLLING_INTERVAL_MS: ${POLLING_INTERVAL_MS:-30000}
GRB_MAX_PAGES: ${GRB_MAX_PAGES:-200} NPM_URL: ${NPM_URL:-}
GRB_MAX_FEATURES: ${GRB_MAX_FEATURES:-150000} NPM_TOKEN: ${NPM_TOKEN:-}
GRB_TIMEOUT_SECONDS: ${GRB_TIMEOUT_SECONDS:-180} NPM_USERNAME: ${NPM_USERNAME:-}
GRB_MAX_RESPONSE_MB: ${GRB_MAX_RESPONSE_MB:-20} NPM_PASSWORD: ${NPM_PASSWORD:-}
GRB_MAX_TOTAL_RESPONSE_MB: ${GRB_MAX_TOTAL_RESPONSE_MB:-256} ADGUARD_URL: ${ADGUARD_URL:-}
GRB_CACHE_TTL_HOURS: ${GRB_CACHE_TTL_HOURS:-24} ADGUARD_USERNAME: ${ADGUARD_USERNAME:-}
OFFICIAL_VECTOR_ENABLED: ${OFFICIAL_VECTOR_ENABLED:-true} ADGUARD_PASSWORD: ${ADGUARD_PASSWORD:-}
BWK_WFS_URL: ${BWK_WFS_URL:-https://geo.api.vlaanderen.be/BWK/wfs} PLEX_URL: ${PLEX_URL:-}
DOV_SOIL_WFS_URL: ${DOV_SOIL_WFS_URL:-https://www.dov.vlaanderen.be/geoserver/wfs} PLEX_TOKEN: ${PLEX_TOKEN:-}
SPW_PICC_ENABLED: ${SPW_PICC_ENABLED:-true} IMMICH_URL: ${IMMICH_URL:-}
SPW_PICC_MAPSERVER_URL: ${SPW_PICC_MAPSERVER_URL:-https://geoservices.wallonie.be/arcgis/rest/services/TOPOGRAPHIE/PICC_VDIFF/MapServer} IMMICH_API_KEY: ${IMMICH_API_KEY:-}
SPW_FLOOD_HAZARD_ENABLED: ${SPW_FLOOD_HAZARD_ENABLED:-true} HOME_ASSISTANT_URL: ${HOME_ASSISTANT_URL:-}
SPW_FLOOD_HAZARD_MAPSERVER_URL: ${SPW_FLOOD_HAZARD_MAPSERVER_URL:-https://geoservices.wallonie.be/arcgis/rest/services/EAU/ALEA_INOND/MapServer} HOME_ASSISTANT_TOKEN: ${HOME_ASSISTANT_TOKEN:-}
URBIS_ENABLED: ${URBIS_ENABLED:-true} OLLAMA_URL: ${OLLAMA_URL:-}
URBIS_WFS_URL: ${URBIS_WFS_URL:-https://geoservices-vector.irisnet.be/geoserver/urbisvector/ows} GLANCES_URL: ${GLANCES_URL:-}
OFFICIAL_VECTOR_MIN_SIDE_M: ${OFFICIAL_VECTOR_MIN_SIDE_M:-10} TAUTULLI_URL: ${TAUTULLI_URL:-}
OFFICIAL_VECTOR_MAX_SIDE_M: ${OFFICIAL_VECTOR_MAX_SIDE_M:-20000} TAUTULLI_API_KEY: ${TAUTULLI_API_KEY:-}
OFFICIAL_VECTOR_PAGE_SIZE: ${OFFICIAL_VECTOR_PAGE_SIZE:-1000} PAPERLESS_URL: ${PAPERLESS_URL:-}
OFFICIAL_VECTOR_MAX_PAGES: ${OFFICIAL_VECTOR_MAX_PAGES:-200} PAPERLESS_TOKEN: ${PAPERLESS_TOKEN:-}
OFFICIAL_VECTOR_MAX_FEATURES: ${OFFICIAL_VECTOR_MAX_FEATURES:-100000} SONARR_URL: ${SONARR_URL:-}
OFFICIAL_VECTOR_TIMEOUT_SECONDS: ${OFFICIAL_VECTOR_TIMEOUT_SECONDS:-180} SONARR_API_KEY: ${SONARR_API_KEY:-}
OFFICIAL_VECTOR_MAX_RESPONSE_MB: ${OFFICIAL_VECTOR_MAX_RESPONSE_MB:-20} RADARR_URL: ${RADARR_URL:-}
OFFICIAL_VECTOR_MAX_TOTAL_RESPONSE_MB: ${OFFICIAL_VECTOR_MAX_TOTAL_RESPONSE_MB:-256} RADARR_API_KEY: ${RADARR_API_KEY:-}
OFFICIAL_VECTOR_CACHE_TTL_HOURS: ${OFFICIAL_VECTOR_CACHE_TTL_HOURS:-24} LIDARR_URL: ${LIDARR_URL:-}
SOURCE_CATALOG_STATBEL_DCAT_URL: ${SOURCE_CATALOG_STATBEL_DCAT_URL:-https://doc.statbel.be/publications/DCAT/DCAT_opendata_datasets.ttl} LIDARR_API_KEY: ${LIDARR_API_KEY:-}
SOURCE_CATALOG_STATBEL_MAX_RESPONSE_MB: ${SOURCE_CATALOG_STATBEL_MAX_RESPONSE_MB:-5} GITEA_WIDGET_URL: ${GITEA_WIDGET_URL:-}
SOURCE_CATALOG_ALZ_RELEASE_URL: ${SOURCE_CATALOG_ALZ_RELEASE_URL:-https://landbouwcijfers.vlaanderen.be/open-geodata-landbouwgebruikspercelen} GITEA_WIDGET_TOKEN: ${GITEA_WIDGET_TOKEN:-}
SOURCE_CATALOG_PROBE_TIMEOUT_SECONDS: ${SOURCE_CATALOG_PROBE_TIMEOUT_SECONDS:-10} JELLYFIN_URL: ${JELLYFIN_URL:-}
SOURCE_CATALOG_PROBE_MAX_RESPONSE_MB: ${SOURCE_CATALOG_PROBE_MAX_RESPONSE_MB:-2} JELLYFIN_API_KEY: ${JELLYFIN_API_KEY:-}
SOURCE_CATALOG_PROBE_CACHE_TTL_SECONDS: ${SOURCE_CATALOG_PROBE_CACHE_TTL_SECONDS:-900} SEERR_URL: ${SEERR_URL:-}
DHMV_ENABLED: ${DHMV_ENABLED:-true} SEERR_API_KEY: ${SEERR_API_KEY:-}
DHMV_WCS_URL: ${DHMV_WCS_URL:-https://geo.api.vlaanderen.be/DHMV/wcs} PROWLARR_URL: ${PROWLARR_URL:-}
DHMV_RESOLUTION_M: ${DHMV_RESOLUTION_M:-5.0} PROWLARR_API_KEY: ${PROWLARR_API_KEY:-}
DHMV_MIN_SIDE_M: ${DHMV_MIN_SIDE_M:-10} AUTHENTIK_URL: ${AUTHENTIK_URL:-}
DHMV_MAX_SIDE_M: ${DHMV_MAX_SIDE_M:-20000} AUTHENTIK_TOKEN: ${AUTHENTIK_TOKEN:-}
DHMV_MAX_PIXELS: ${DHMV_MAX_PIXELS:-12000000} PORTAINER_URL: ${PORTAINER_URL:-}
DHMV_TIMEOUT_SECONDS: ${DHMV_TIMEOUT_SECONDS:-300} PORTAINER_API_KEY: ${PORTAINER_API_KEY:-}
DHMV_MAX_RESPONSE_MB: ${DHMV_MAX_RESPONSE_MB:-160} GRAFANA_URL: ${GRAFANA_URL:-}
FLOOD_HAZARD_ENABLED: ${FLOOD_HAZARD_ENABLED:-true} GRAFANA_TOKEN: ${GRAFANA_TOKEN:-}
FLOOD_HAZARD_WCS_URL: ${FLOOD_HAZARD_WCS_URL:-https://geoservice.waterinfo.be/OGRK/wcs} PROMETHEUS_URL: ${PROMETHEUS_URL:-}
FLOOD_HAZARD_RESOLUTION_M: ${FLOOD_HAZARD_RESOLUTION_M:-5.0} NEXTCLOUD_URL: ${NEXTCLOUD_URL:-}
FLOOD_HAZARD_MIN_SIDE_M: ${FLOOD_HAZARD_MIN_SIDE_M:-10} NEXTCLOUD_USERNAME: ${NEXTCLOUD_USERNAME:-}
FLOOD_HAZARD_MAX_SIDE_M: ${FLOOD_HAZARD_MAX_SIDE_M:-20000} NEXTCLOUD_APP_PASSWORD: ${NEXTCLOUD_APP_PASSWORD:-}
FLOOD_HAZARD_MAX_PIXELS: ${FLOOD_HAZARD_MAX_PIXELS:-12000000} AUDIOBOOKSHELF_URL: ${AUDIOBOOKSHELF_URL:-}
FLOOD_HAZARD_TIMEOUT_SECONDS: ${FLOOD_HAZARD_TIMEOUT_SECONDS:-300} AUDIOBOOKSHELF_TOKEN: ${AUDIOBOOKSHELF_TOKEN:-}
FLOOD_HAZARD_MAX_RESPONSE_MB: ${FLOOD_HAZARD_MAX_RESPONSE_MB:-160} NETDATA_URL: ${NETDATA_URL:-}
BATHYMETRY_PROFILES_ENABLED: ${BATHYMETRY_PROFILES_ENABLED:-true} PEERTUBE_URL: ${PEERTUBE_URL:-}
BATHYMETRY_PROFILES_LAYER_URL: ${BATHYMETRY_PROFILES_LAYER_URL:-https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/0} DELUGE_METRICS_URL: ${DELUGE_METRICS_URL:-}
BATHYMETRY_WATERCOURSE_LAYER_URL: ${BATHYMETRY_WATERCOURSE_LAYER_URL:-https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/1} DELUGE_METRICS_TOKEN: ${DELUGE_METRICS_TOKEN:-}
BATHYMETRY_PROFILES_PAGE_SIZE: ${BATHYMETRY_PROFILES_PAGE_SIZE:-1000} QBITTORRENT_METRICS_URL: ${QBITTORRENT_METRICS_URL:-}
BATHYMETRY_PROFILES_MAX_FEATURES: ${BATHYMETRY_PROFILES_MAX_FEATURES:-50000} QBITTORRENT_METRICS_TOKEN: ${QBITTORRENT_METRICS_TOKEN:-}
BATHYMETRY_PROFILES_TIMEOUT_SECONDS: ${BATHYMETRY_PROFILES_TIMEOUT_SECONDS:-120} JDOWNLOADER_METRICS_URL: ${JDOWNLOADER_METRICS_URL:-}
BATHYMETRY_PROFILES_MAX_RESPONSE_MB: ${BATHYMETRY_PROFILES_MAX_RESPONSE_MB:-32} JDOWNLOADER_METRICS_TOKEN: ${JDOWNLOADER_METRICS_TOKEN:-}
MDK_BATHYMETRY_PROBE_ENABLED: ${MDK_BATHYMETRY_PROBE_ENABLED:-true} TDARR_METRICS_URL: ${TDARR_METRICS_URL:-}
MDK_BATHYMETRY_WCS_URL: ${MDK_BATHYMETRY_WCS_URL:-https://bathy.agentschapmdk.be/spatialfusionserver/services/ows/wcs/EL_wcs} TDARR_METRICS_TOKEN: ${TDARR_METRICS_TOKEN:-}
MDK_BATHYMETRY_PROBE_TIMEOUT_SECONDS: ${MDK_BATHYMETRY_PROBE_TIMEOUT_SECONDS:-20} BAZARR_METRICS_URL: ${BAZARR_METRICS_URL:-}
MDK_BATHYMETRY_PROBE_MAX_RESPONSE_MB: ${MDK_BATHYMETRY_PROBE_MAX_RESPONSE_MB:-4} BAZARR_METRICS_TOKEN: ${BAZARR_METRICS_TOKEN:-}
MDK_BATHYMETRY_ACQUISITION_ENABLED: ${MDK_BATHYMETRY_ACQUISITION_ENABLED:-false} VAULTWARDEN_METRICS_URL: ${VAULTWARDEN_METRICS_URL:-}
MDK_BATHYMETRY_COVERAGE_ID: ${MDK_BATHYMETRY_COVERAGE_ID:-} VAULTWARDEN_METRICS_TOKEN: ${VAULTWARDEN_METRICS_TOKEN:-}
MDK_BATHYMETRY_REQUEST_CRS: ${MDK_BATHYMETRY_REQUEST_CRS:-EPSG:4326}
MDK_BATHYMETRY_MAX_BBOX_DEG2: ${MDK_BATHYMETRY_MAX_BBOX_DEG2:-0.25}
MDK_BATHYMETRY_ACQUISITION_TIMEOUT_SECONDS: ${MDK_BATHYMETRY_ACQUISITION_TIMEOUT_SECONDS:-120}
MDK_BATHYMETRY_ACQUISITION_MAX_RESPONSE_MB: ${MDK_BATHYMETRY_ACQUISITION_MAX_RESPONSE_MB:-160}
THEMATIC_RASTER_ENABLED: ${THEMATIC_RASTER_ENABLED:-true}
THEMATIC_RASTER_WCS_URL: ${THEMATIC_RASTER_WCS_URL:-https://www.mercator.vlaanderen.be/raadpleegdienstenmercatorpubliek/wcs}
THEMATIC_RASTER_MIN_SIDE_M: ${THEMATIC_RASTER_MIN_SIDE_M:-100}
THEMATIC_RASTER_MAX_SIDE_M: ${THEMATIC_RASTER_MAX_SIDE_M:-60000}
THEMATIC_RASTER_MAX_PIXELS: ${THEMATIC_RASTER_MAX_PIXELS:-30000000}
THEMATIC_RASTER_TIMEOUT_SECONDS: ${THEMATIC_RASTER_TIMEOUT_SECONDS:-300}
THEMATIC_RASTER_MAX_RESPONSE_MB: ${THEMATIC_RASTER_MAX_RESPONSE_MB:-160}
WALOUS_ENABLED: ${WALOUS_ENABLED:-true}
WALOUS_SOURCE_DIR: ${WALOUS_SOURCE_DIR:-/app/storage/source-cache/walous}
WALOUS_ANALYSIS_RESOLUTION_M: ${WALOUS_ANALYSIS_RESOLUTION_M:-10}
WALOUS_MAX_SIDE_M: ${WALOUS_MAX_SIDE_M:-60000}
WALOUS_MAX_PIXELS: ${WALOUS_MAX_PIXELS:-36000000}
YOLO_ENABLED: ${YOLO_ENABLED:-false}
YOLO_MODELS_DIR: ${YOLO_MODELS_DIR:-/app/models}
YOLO_MODEL_PATH: ${YOLO_MODEL_PATH:-}
YOLO_MODEL_ID: ${YOLO_MODEL_ID:-yolo-configured}
YOLO_MODEL_DISPLAY_NAME: ${YOLO_MODEL_DISPLAY_NAME:-Configured YOLO detector}
YOLO_MODEL_VERSION: ${YOLO_MODEL_VERSION:-}
YOLO_CONFIG_DIR: ${YOLO_CONFIG_DIR:-/app/storage/ultralytics}
YOLO_DEVICE: ${YOLO_DEVICE:-cpu}
YOLO_IMAGE_SIZE: ${YOLO_IMAGE_SIZE:-640}
YOLO_MAX_TILES: ${YOLO_MAX_TILES:-100}
YOLO_MAX_DETECTIONS: ${YOLO_MAX_DETECTIONS:-1000}
YOLO_DUPLICATE_IOU_THRESHOLD: ${YOLO_DUPLICATE_IOU_THRESHOLD:-0.5}
YOLO_BATCH_SIZE: ${YOLO_BATCH_SIZE:-1}
YOLO_SEG_ENABLED: ${YOLO_SEG_ENABLED:-false}
YOLO_SEG_MODEL_PATH: ${YOLO_SEG_MODEL_PATH:-}
YOLO_SEG_MODEL_ID: ${YOLO_SEG_MODEL_ID:-yolo-seg-configured}
YOLO_SEG_MODEL_DISPLAY_NAME: ${YOLO_SEG_MODEL_DISPLAY_NAME:-Configured YOLO segmentation}
YOLO_SEG_MODEL_VERSION: ${YOLO_SEG_MODEL_VERSION:-}
SAM_ENABLED: ${SAM_ENABLED:-false}
SAM_MODEL_PATH: ${SAM_MODEL_PATH:-}
SAM_MODEL_ID: ${SAM_MODEL_ID:-sam-configured}
SAM_MODEL_DISPLAY_NAME: ${SAM_MODEL_DISPLAY_NAME:-Configured SAM segmentation}
SAM_MODEL_VERSION: ${SAM_MODEL_VERSION:-}
SEGMENTATION_MAX_MASKS_PER_TILE: ${SEGMENTATION_MAX_MASKS_PER_TILE:-300}
SEGMENTATION_DUPLICATE_IOU_THRESHOLD: ${SEGMENTATION_DUPLICATE_IOU_THRESHOLD:-0.5}
GEOINTEL_RECONCILE_INTERRUPTED_RUNS_ON_STARTUP: ${GEOINTEL_RECONCILE_INTERRUPTED_RUNS_ON_STARTUP:-true}
OLLAMA_ENABLED: ${OLLAMA_ENABLED:-false}
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://host.docker.internal:11434}
OLLAMA_DEFAULT_MODEL: ${OLLAMA_DEFAULT_MODEL:-qwen3.5:9b}
OLLAMA_TIMEOUT_SECONDS: ${OLLAMA_TIMEOUT_SECONDS:-120}
OLLAMA_MAX_OUTPUT_TOKENS: ${OLLAMA_MAX_OUTPUT_TOKENS:-1200}
OLLAMA_CONTEXT_TOKENS: ${OLLAMA_CONTEXT_TOKENS:-16384}
ports: ports:
- "${GEOINTEL_BACKEND_PORT:-8000}:8000" - "${DOCKDECK_BIND_ADDRESS:-127.0.0.1}:${DOCKDECK_PORT:-1218}:3000"
volumes: volumes:
- ${GEOINTEL_STORAGE_PATH:-./storage}:/app/storage - dockdeck_data:/data
- ${GEOINTEL_MODELS_PATH:-./models}:/app/models - ${UNRAID_TEMPLATES_DIR:-./fixtures/unraid-templates}:/unraid-templates:ro
- ${GEOINTEL_BACKUPS_PATH:-./backups}:/app/backups:ro - ${UNRAID_ICONS_DIR:-/boot/config/plugins/dockerMan/images}:/unraid-icons-source:ro
- ./fixtures:/app/fixtures:ro - ${UNRAID_RUNTIME_DIR:-/var/local/emhttp}:/unraid-runtime:ro
extra_hosts: - ${UNRAID_HWMON_DIR:-/sys/class/hwmon}:/host-hwmon:ro
- "host.docker.internal:host-gateway" - ${UNRAID_CPUINFO_FILE:-/proc/cpuinfo}:/host-proc/cpuinfo:ro
command: sh /app/docker_start.sh - ${UNRAID_MEMINFO_FILE:-/proc/meminfo}:/host-proc/meminfo:ro
depends_on: networks:
db: - dockdeck_egress
condition: service_healthy read_only: true
healthcheck: tmpfs:
test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health/ready', timeout=5).read()\""] - /tmp
interval: 10s - /unraid-icons-cache:size=64m,mode=0755
timeout: 5s security_opt:
retries: 12 - no-new-privileges:true
start_period: 30s
frontend: networks:
build: dockdeck_egress:
context: ./frontend ipam:
ports: config:
- "${GEOINTEL_FRONTEND_PORT:-1202}:80" - subnet: ${DOCKDECK_EGRESS_SUBNET:-172.31.240.16/28}
depends_on:
backend:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget -q -O - http://127.0.0.1/health/ready | grep -q '\"status\":\"ok\"'"]
interval: 10s
timeout: 5s
retries: 12
start_period: 10s
volumes: volumes:
geointel_postgis: dockdeck_data:
+17
View File
@@ -0,0 +1,17 @@
#!/bin/sh
set -eu
icon_source="${UNRAID_ICONS_SOURCE_PATH:-/unraid-icons-source}"
icon_cache="${UNRAID_ICONS_PATH:-/unraid-icons-cache}"
if [ -d "$icon_source" ] && [ -d "$icon_cache" ]; then
find "$icon_source" -maxdepth 1 -type f \( \
-iname '*.gif' -o -iname '*.ico' -o -iname '*.jpeg' -o \
-iname '*.jpg' -o -iname '*.png' -o -iname '*.svg' -o \
-iname '*.webp' \
\) -exec cp -f '{}' "$icon_cache/" \;
find "$icon_cache" -maxdepth 1 -type f -exec chmod 0644 '{}' \;
chown -R dockdeck:dockdeck "$icon_cache"
fi
exec su-exec dockdeck "$@"
+31 -134
View File
@@ -1,148 +1,45 @@
# Architecture # DockDeck architecture
## 1. Overzicht ## Runtime
GeoIntel bestaat uit: DockDeck is one deployable Node.js process. Fastify owns the REST API and serves the compiled React client in production. React/Vite provides the development UI. SQLite is mounted at `/data`; Drizzle ORM owns typed reads and writes while a small idempotent SQL migration creates the first schema before Fastify starts.
- React/TypeScript frontend
- FastAPI backend
- PostgreSQL/PostGIS database
- background job queue
- file/object storage
- GIS processing services
- AI inference services
## 2. Hoofdcomponenten
```text ```text
Frontend Browser → Fastify API → domain rules → Drizzle → SQLite
↓ REST/WebSocket ↘ discovery service → AppOps inventory (GET only)
FastAPI Backend → Unraid XML/runtime mounts (read only)
→ NPM API (read only, optional)
PostgreSQL + PostGIS
Storage: uploads, processed rasters, tiles, masks, exports
Workers: GIS processing, AI inference, QA/QC, export
``` ```
## 3. Frontend ## Trust boundaries
Aanbevolen stack: - The `dockdeck` container never receives `/var/run/docker.sock`.
- The standard deployment is exactly one `DockDeck` container. A separate `dockerproxy` exists only in the optional fallback overlay; there it is private, has `POST=0` and disables all mutation families.
- Unraid templates are mounted read-only.
- NPM, Gitea and provider credentials exist only as server-side process environment. Values entered in Settings are persisted in the permission-restricted environment file and remain excluded from API responses, SQLite and JSON exports.
- Fastify validates every user-controlled mutation with Zod. External integration errors are reduced to non-sensitive connection states.
- New discoveries are inserted with `visible=false`. Subsequent syncs update runtime fields but preserve user choices and overrides.
- A DockDeck-only app removal records the technical name in `removed_apps`; later discovery ignores that name and never calls a Docker mutation endpoint.
- React ## Modules
- TypeScript
- MapLibre GL
- Deck.gl
- Tailwind
- TanStack Query
- Zustand of vergelijkbare lichte state store
- Recharts voor eenvoudige grafieken
Belangrijke principes: - `src/shared`: Zod contracts and pure URL/search/matching/sorting rules.
- URL resolution prefers an explicit `br0` LAN address, then the configured Unraid host, and uses a single unambiguous published TCP port when template metadata is stale.
- Dashboard search is a derived client index over the latest payload, so renames, removals, category changes and URL changes require no separate search persistence.
- `src/server/database.ts`: persistence, migration, import/export and user preference invariants.
- Widget layout is stored as validated size/content, metric order/visibility/style, statistic limit, warning and chart fields on app and preference records. Legacy rows receive safe standard/all-content defaults; version 1 backups remain compatible because added backup fields are optional on import.
- `src/server/integrations`: read-only external adapters, provider capability metadata and deterministic mocks. Provider URLs default to an already discovered local app origin; explicit environment overrides remain available. Provider recognition is token-based and excludes known support-container roles.
- `src/server/integrations/unraid-status.ts`: parseert een minimale read-only view van Unraid runtime- en hwmondata naar array-, parity-, opslag-, temperatuur- en optionele UPS-metrics.
- Provideradapters bewaren alleen de laatst succesvolle gereduceerde metricresponse en een korte tijdreeks in procesgeheugen; geen van beide overleeft een procesrestart.
- `src/server/app.ts`: narrow REST surface; no Docker control routes.
- `src/client`: accessible responsive UI and design tokens.
- kaart centraal, maar analysepanelen even belangrijk ## Layout and offline model
- labs per workflow
- duidelijke jobstatus
- outputs altijd exporteerbaar
- geen verborgen mockgedrag
## 4. Backend Widgetvolgorde staat los van apptegelvolgorde. App- en hostrecords bewaren hun eigen volgorde; layoutpresets bevatten alleen presentatievelden, inclusief metricselectie, value/gauge/progress, waarschuwing en lijn-/vlak-/staafgrafiek. Provider- en hosthistoriek bevat maximaal zestig punten per numerieke reeks en blijft uitsluitend in procesgeheugen. Het toepassen van een preset kan geen appzichtbaarheid, navigatie-URL, credential of Dockerstaat wijzigen.
Aanbevolen stack: De serviceworker cachet alleen statische applicatieshellassets. Requests onder `/api/` worden volledig overgeslagen, zodat offline content nooit als actuele runtime- of providerdata wordt gepresenteerd.
- FastAPI ## Failure behavior
- SQLAlchemy 2.x
- GeoAlchemy2
- Alembic
- Pydantic
- RQ/Celery
- Rasterio
- GeoPandas
- Shapely
- PyProj
- NumPy
- OpenCV
- Ultralytics/PyTorch
## 5. Database Saved data is always returned independently of integration health. A failed AppOps inventory, missing Unraid mount or unavailable NPM instance produces a degraded integration card/banner without blocking navigation. Polling runs only while the browser tab is visible. AppOps inventory is read immediately; its slower all-container resource sample refreshes in the background so it cannot delay navigation.
PostgreSQL met PostGIS is verplicht voor:
- projectgebieden
- vectorfeatures
- detectiepolygonen
- segmentatiepolygonen
- spatial joins
- intersects
- IoU berekeningen
- bounds queries
## 6. Storage
Bewaar grote bestanden niet in de database.
Opslagcategorieën:
- originele uploads
- verwerkte rasters
- raster tiles
- masks
- model outputs
- exports
- rapporten
Database bewaart metadata en paden.
## 7. Jobs
Langlopende processen moeten via background jobs:
- raster metadata extraction
- raster clipping
- raster tiling
- vector import
- AI inference
- segmentation polygonize
- QA/QC
- change detection
- export generation
## 8. AI Inference
Inference pipeline:
```text
Raster dataset
→ clip to area
→ tile raster
→ normalize/preprocess
→ model inference
→ convert pixel coords to geospatial coords
→ merge/filter outputs
→ save detections/segmentations
→ expose as map layer
```
## 9. CRS-regels
- Alle interne geometrieën worden opgeslagen in PostGIS met bekende SRID.
- Voor metrische berekeningen wordt een geschikte projectie gebruikt.
- API-output naar frontend mag in EPSG:4326 of WebMercator-compatible formaat.
- Elke dataset zonder CRS krijgt status `needs_crs_review`.
## 10. Developmentstrategie
Bouwvolgorde:
1. backend foundation
2. database schema
3. project/area API
4. dataset upload en metadata
5. frontend workspace en kaart
6. vector import
7. raster import
8. processing jobs
9. detection lab
10. QA/QC
11. export
+29
View File
@@ -0,0 +1,29 @@
# Authenticated external access
DockDeck has no built-in authentication. Keep its Compose bind on `127.0.0.1` or a trusted LAN address unless an authenticated HTTPS reverse proxy protects every route.
## Recommended topology
```text
Internet -> HTTPS reverse proxy -> Authentik forward auth -> DockDeck :1218
Trusted LAN -----------------------------------------------> DockDeck :1218
```
## Minimum requirements
1. Terminate TLS with a valid certificate.
2. Require authentication before proxying `/`, static assets and every `/api/*` route.
3. Do not create an unauthenticated exception for `/api/diagnostics`, integration settings or exports.
4. Preserve the original host and forwarding headers.
5. Restrict the upstream to `http://192.168.10.150:1218`; never publish the internal Docker socket proxy.
6. Use a separate minimum-scope account/token for each provider and protect `/data/integrations.env` backups.
## Validation checklist
- An incognito request is redirected to authentication before DockDeck HTML or API JSON is returned.
- A signed-in user can load the dashboard, search, edit a harmless presentation preference and read diagnostics.
- Signing out invalidates both page and API access.
- The browser reports HTTPS without mixed content.
- The reverse proxy does not cache `/api/*` responses.
Nginx Proxy Manager discovery in DockDeck remains read-only and does not create or change proxy hosts. Proxy and Authentik configuration therefore stay an explicit deployment responsibility.
+37
View File
@@ -0,0 +1,37 @@
# DockDeck implementation status
Updated: 2026-07-22
| MVP criterion | Status | Evidence |
| ------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Read-only Docker/Unraid discovery | Live verified | AppOps GET adapter, optional restricted proxy overlay, mocks and adapter tests. |
| New containers hidden | Built | Repository insert invariant and API/database tests. |
| App visibility/name/category/order/URL management | Built | Settings UI, Zod API and persistence tests. |
| Persistent app removal | Verified | DockDeck-only delete endpoint, `removed_apps` suppression and rediscovery/export coverage. |
| Local WebUI detection | Built | Unraid XML parser, port substitution and unit tests. |
| Status and ~30 second polling | Built | Runtime state mapping and visibility-aware client interval. |
| Categories and both dashboard views | Built | CRUD API, settings and responsive overview/tab UI. |
| Favorites | Built | CRUD API, settings, dashboard tiles and search index. |
| Search and Google fallback | Verified | Dynamic full-payload index, keyboard selection, explicit Google action and browser flow. |
| Links open in new tabs | Built | All tiles/results use `_blank` with `noopener noreferrer`. |
| Three themes | Built | Porcelain, Midnight and Harbor tokens and controls. |
| Persistent appearance customization | Verified | Theme, mode, accent, density, width, background, hero, status, reduced motion and polling with migration/export coverage. |
| Searchable settings | Verified | Global section search, accessible controls and automatic scroll restoration on section changes. |
| Read-only service widgets | Verified | Per-app opt-in, eight-widget limit, domain data, three sizes, four layers and configurable presentation. |
| Configurable Tower identity | Live verified | Persistent server name/URL in Settings, search and host card; live URL is `192.168.10.150:5000`. |
| Automatic favorite favicons | Verified | First-party `/favicon.ico` derivation, explicit override and monogram fallback. |
| SQLite persistence | Built | Named Docker volume and restart persistence test. |
| Export/import | Built | Versioned Zod schema, API and settings flow; secrets excluded. |
| Read-only NPM integration | Built | Optional server-side adapter, unambiguous matching and fallback. |
| Degraded states | Built | Adapter isolation, health states, banner and integration panel. |
| Docker/Unraid packaging | Live verified | Commit `3ffd39b`, one healthy `DockDeck` on `192.168.10.150:1218`; 137 stored/55 visible apps and 90 GET-only containers. |
| Private Gitea repository | Verified | Private `NuklearRabbit/DockDeck`; local `main` tracks `origin/main`. |
| Professional responsive UI | Live verified | 390px through ultrawide plus live 1280×720; adaptive cards, persistent favorites sidebar and no overflow. |
| Independent widget composition | Verified | Order, size, content, labels, metric styles/limits, any-signal warnings, cadence, charts and edit mode. |
| Native Unraid health | Live verified | Array STARTED, parity healthy, three storage groups and temperatures through read-only mounts. |
| App-specific provider breadth | Live verified | 97/97 stored apps have an explicit domain profile; zero generic profiles remain in the live capability audit. |
| Per-app metric composition | Live verified | Labels can be selected, ordered, styled, limited, charted and monitored and survive presets/export/restart. |
| Configurable Unraid trends | Live verified | Container, image, storage, temperature, parity and UPS series with statistic selection and 60-point memory. |
| Unraid Docker-list icon | Live verified | DockerMan visibly renders the 512×512 PNG after both persistent and active caches are refreshed. |
| Layout presets | Verified | Create, apply, delete and independent import/export with database/API/browser coverage. |
| PWA and diagnostics | Live verified | Installable shell excludes `/api`; diagnostics are secret-free and report native/integration health. |
+75
View File
@@ -0,0 +1,75 @@
# DockDeck platform audit
Updated: 2026-07-15
## Executive assessment
DockDeck is now a complete daily-use dashboard rather than a launcher with generic telemetry. Navigation, app management, responsive presentation, widget composition, service-specific data, Unraid host depth, freshness, portability and diagnostics form one coherent read-only product. The P0, P1 and P2 roadmap from the previous audit is implemented.
## Current scorecard
| Area | Assessment | Evidence |
| --------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------- |
| Navigation and search | Complete | Dynamic index follows renames, removals, URLs, categories, favorites and Tower. |
| Responsive shell | Complete | Mobile, tablet, desktop and ultrawide layouts have no positive horizontal overflow. |
| App management | Complete | Rename, override, categorize, hide and persistent DockDeck-only removal. |
| Widget composition | Complete | Order, three sizes, content layers, per-metric visibility/order/style, limits, labels, warnings and edit mode. |
| Widget data depth | Complete framework | Live audit: 97/97 apps have a specific capability profile, zero generic profiles; 23 native providers. |
| Unraid host depth | Complete | AppOps plus array/parity/storage/temperature/UPS, selectable stats and configurable server trend charts. |
| Freshness and trends | Complete | Fresh/stale/unavailable semantics, cadence, stale cache and selectable line/area/bar charts up to sixty samples. |
| Portability | Complete | Full secret-free backup plus independent layout preset import/export. |
| Operations | Complete for LAN | Secret-free diagnostics, health endpoint, PWA shell and authenticated reverse-proxy guide. |
| Security | Strong for trusted LAN | Single socketless container, GET-only integrations, masked secrets and API-excluding service worker. |
## Delivered roadmap
### Personal widget depth
- Persistent Tower name and URL in Settings with port `5000` as the default Unraid WebUI route.
- Capability coverage expanded from a provider shortlist to every discovered app.
- Native Audiobookshelf, Netdata and PeerTube readers added to the existing provider set.
- Domain profiles for the actual live inventory, including app/API/frontend/worker/database/cache, mail, GPU, download, media, document and custom-project workloads.
- Per-app metric visibility and ordering, including dynamically returned labels and preset/export persistence.
- Per-app value/gauge/progress presentation, statistic limits, line/area/bar charts, legends and selectable 1060 sample windows.
- Warning rules can target any numeric infrastructure or provider metric and trigger above or below the configured value.
- Capability labels remain app-specific before an endpoint is connected; absent values say `Connect data` instead of showing unrelated generic Docker labels.
- BlockPilot root/runtime/API/web/Minecraft/ViaProxy, LumaOps, Chimera, OpenRGB, Porkfolio and unnamed legacy workloads now have explicit profiles rather than the last generic service profile.
- Tower exposes selectable/ordered server statistics and charts for containers, images, storage, temperature, parity and UPS.
- A constrained custom contract (`summary` plus at most twelve stats) for apps without a native read API; endpoint and optional bearer token are configured in Widget Studio and remain server-side.
### P0
- Independent widget order with keyboard-accessible move controls.
- Native Unraid adapter for array state, parity, disk/pool usage, temperatures and optional UPS data.
- App-specific Gitea adapter and GET-only metrics bridges for Deluge, JDownloader and Tdarr.
- Explicit freshness states with last-successful provider cache.
### P1
- Metric order, primary metric, custom labels and numeric provider/infrastructure warning thresholds.
- Saved, applicable, removable and portable layout presets.
- Safe per-widget refresh cadence between 15 seconds and 5 minutes.
- Direct dashboard edit mode with move, resize and hide controls.
- Provider expansion for Jellyfin, Seerr, Prowlarr, Authentik, NPM, Portainer, Grafana, Prometheus and Nextcloud, plus bridges for qBittorrent, Bazarr and Vaultwarden.
### P2
- Selectable line, area and bar charts with at most sixty in-memory points and no monitoring database.
- Installable PWA application shell; API responses are never cached.
- Independent preset export/import.
- Secret-free diagnostics and an authenticated external-access guide.
## Remaining environment-dependent opportunities
- Fill provider credentials or bridge URLs in Settings only for services whose protected API data is desired. Safe Docker fallback remains available without them.
- Configure `UNRAID_UPS_STATUS_PATH` only when a stable read-only APC/UPS status file is available on the host.
- Enable external access only behind authenticated HTTPS; DockDeck deliberately has no built-in user system.
- Persistent long-term history, alerts and notifications remain intentionally outside scope. They would turn DockDeck into a monitoring platform and need separate retention and delivery decisions.
## Guardrails
- A widget is app-specific only when its metrics describe the real workload of that app.
- Every external request remains GET-only, time-bounded and server-side.
- Provider failure never blocks navigation and always falls back to safe telemetry.
- Compact panels answer one question, standard panels a few, and wide panels may expose deeper context.
- Layout remains fully usable without drag-and-drop and is tested at both mobile and ultrawide sizes.
+9
View File
@@ -0,0 +1,9 @@
# DockDeck design system
De visuele basis is **DockDeck Canon v1**. Het onderhoudbare systeem, de bronselectie en de mapping naar echte productflows staan in:
- [`dockdeck-canon-v1/DESIGN_SYSTEM.md`](./dockdeck-canon-v1/DESIGN_SYSTEM.md)
- [`dockdeck-canon-v1/SOURCE_MANIFEST.md`](./dockdeck-canon-v1/SOURCE_MANIFEST.md)
- [`dockdeck-canon-v1/IMPLEMENTATION_MAPPING.md`](./dockdeck-canon-v1/IMPLEMENTATION_MAPPING.md)
Gebruik in productcode uitsluitend semantische tokens en lokale assets. Behoud dark en light themes, Nederlandse terminologie, echte API-data en de read-only productgrenzen.
@@ -0,0 +1,68 @@
# Stitch Canon UI-upgradeplan
Datum: 2026-07-21
Branch: `codex/stitch-canon-ui-upgrade`
## Nulmeting
DockDeck is een React/Vite-single-page-app met Fastify REST, SQLite en gedeelde Zod-contracten. `App.tsx` beheert dashboard/settings, polling, themadata-attributen en toasts. `Dashboard.tsx` groepeert echte apps en favorieten. `Settings.tsx` bevat de bestaande beheerflows; widgetconfiguratie blijft in afzonderlijke componenten. Er is geen router: schermwisseling is lokale React-state.
De bestaande functionaliteit is operationeel, maar de interface wijkt zichtbaar van Canon v1 af: Engelse copy, een permanente brede dashboardrail, een prominente monitoringlaag vóór de launcherinhoud en volledig uitgeklapte appformulieren. De baseline is opgeslagen onder `.codex-input/baseline/`.
Baselinechecks:
- format, lint en typecheck: geslaagd;
- unit/integratie: 42/42 geslaagd;
- security: 5/5 geslaagd;
- build en audit: geslaagd, 0 kwetsbaarheden;
- E2E-start: omgevingsblokkade doordat een WSL-relay poort 3000 bezet; de upgrade maakt de testpoort configureerbaar.
## Routes en workflows
| Oppervlak | Werkelijke workflow | Canonvertaling |
| ----------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
| Dashboard | apps/favorieten openen, categorieën, sync, widgets | compacte topbar, Quick Launch eerst, favorieten en apps als launcher; widgets lager en optioneel |
| Quick Launch | apps/favorieten/Tower/Google, toetsenbord | gegroepeerde command-palette met Apps, Favorieten en Google |
| Apps | zoeken, zichtbaarheid, widget, naam, categorie, URL's, icoon, sortering, verwijderen | compacte beheerregels met filters en een app-editor drawer |
| Favorieten | toevoegen, wijzigen, verwijderen | Canon-lijst plus rustig formulier/drawerpatroon |
| Categorieën | toevoegen, wijzigen, icoon, sortering, verwijderen | Canon-lijst met duidelijke aantallen en inline-acties |
| Weergave | thema, accent, dichtheid, breedte, achtergrond, hero/status, modus | visuele keuzevelden en previews binnen dezelfde settings-shell |
| Widgets/operaties | bestaande read-only panelen, presets en diagnostics | behouden onder Algemeen als secundaire geavanceerde functies |
| Integraties | status en gemaskeerde credentials | doelgerichte read-only statuskaarten zonder secret-echo |
| Back-up | export/import versie 1 | Canon back-up- en hersteloppervlak, zonder fictieve historie |
## Componentstrategie
Hergebruikt: API-client, contracten, URL-resolutie, iconpipeline, widgetconfigurators, server- en servicewidgets, persistence en alle backendvalidatie.
Herbouwd of toegevoegd: semantische tokenlaag, lokale merkassets, `DashboardTopBar`, dashboardcompositie, categorie-navigatie, app-/favoriettegels, Quick Launch-groepering, settings-shell, compacte appbeheerregels, app-editor drawer, banners, empty/loading/degraded states en mobiele navigatie.
## Tokens en responsiviteit
Canon v1 gebruikt een 8px-basis, diepe navy, tonale oppervlakken, lavendel voor actie/focus en groen uitsluitend voor positieve status. Dark, light en het bestaande aanvullende thema blijven functioneel via semantische CSS-variabelen. Bestaande accenten blijven beschikbaar maar worden op de Canon-tokenlaag geprojecteerd.
- 1920×1080 en 1440×900: max. 1280px launcherinhoud, compacte topbar, vier appkolommen waar passend.
- 7681024px: twee kolommen, compacte settingsnavigatie.
- 390×844: echte mobiele compositie, horizontaal scrollbare categorieën, één/twee kolommen naar inhoud, vaste ondernavigatie.
- Geen horizontale overflow; minimum klikdoel 44px; `prefers-reduced-motion` blijft leidend.
## Risico's en mitigatie
- De huidige widgetdiepgang is groter dan de statische Canon-scope. De functies blijven bestaan maar verdwijnen uit de primaire launcherhiërarchie.
- `Settings.tsx` is groot. De wijziging beperkt backend/API-impact en splitst nieuwe UI-primitives en drawerlogica waar dat reviewbaarheid verbetert.
- E2E gebruikt een vaste poort. De configuratie krijgt een geïsoleerde testpoort zonder productieruntime te wijzigen.
- Externe appiconen kunnen falen. De bestaande veilige monogramfallback blijft behouden.
## Validatie
Twee visuele rondes vergelijken dark/light dashboard, Quick Launch, settings/apps, editor en mobiel met de referenties. Daarna volgen alle verplichte scripts, browserconsole/netwerkcontrole, overflowchecks op 390×844, 768×1024, 1440×900 en 1920×1080, Compose-config en een Docker-imagebuild wanneer de lokale Docker-runtime beschikbaar is.
## Niet-doelen
Geen Docker-mutaties, containerbeheer, accounts, terminal/logviewer, nieuwe backendintegraties, fictieve data, automatische back-uphistorie of externe tijdelijke assets.
## Resultaat
Afgerond op 2026-07-21. De Canon-tokenlaag, lokale Geist-fonts, merkassets, topbar, launcherhiërarchie, Quick Launch, compacte settings-shell, app-editor drawer en responsive mobiele compositie zijn in de bestaande React/Fastify-app geïntegreerd. De E2E-runtime gebruikt geïsoleerde poorten 3101/5174 zodat een lokale WSL-relay op 3000 de suite niet meer blokkeert. De finale gates en eventuele resterende omgevingsbeperkingen staan in `TEST_LOG.md`, `RISKS.md` en `HANDOFF.md`.
Een aanvullende fidelity-pass heeft de eerder nog eigen DockDeck-interpretatie vervangen door de daadwerkelijke `canon_*`-compositie. Stitch bepaalt nu vrijwel volledig layout, density, kaartfamilies, navigatie, overlay, settings-tabel, drawer en mobiele hiërarchie; alleen echte data, bestaande validatie en operationele workflows wijken bewust af van de statische voorbeelden.
@@ -0,0 +1,30 @@
# DockDeck Canon v1
DockDeck is een rustige, premium digitale thuisbasis. De interface is een launcher, geen beheer- of monitoringconsole.
## Fundament
- 8px spacingbasis; 16px mobiel zijmarge, 24px desktopgutter, 1280px launchermaximum.
- Geist-achtige lokaal gebundelde sans-serif, sentence case en volledig Nederlandse copy met `je/jouw`.
- Dark-first diepe navy `#0b1326`; tonale oppervlakken en subtiele 1px-randen.
- Lavendel `#818cf8` voor merk, focus, selectie en primaire acties.
- Groen `#10b981` uitsluitend voor positieve status/bevestiging; rood voor offline/fout, slate voor onbekend.
- 8px radius voor controls, 1216px voor grotere oppervlakken, pills alleen waar de vorm betekenis heeft.
- Geen zware schaduwen of permanente pulserende statusanimaties.
## Semantische tokens
Productcode gebruikt tokens voor `background`, `surface`, `surface-elevated`, `border`, `text`, `text-muted`, `primary`, `focus`, `success`, `warning`, `error`, `unknown`, `hover`, `active` en `disabled`. Thema's wijzigen de tokenwaarden, niet de componentstructuur.
## Componentregels
- Apptegels tonen icoon, naam en `Online`, `Offline` of `Status onbekend`; geen poort, image, CPU, RAM of uptime.
- Favoriettegels delen dezelfde visuele familie zonder runtimestatus.
- Quick Launch groepeert Apps, Favorieten en Google en ondersteunt muis en toetsenbord.
- Settings gebruikt één shell met Algemeen, Apps, Favorieten, Categorieën, Weergave, Integraties en Back-up en herstel.
- Overlays hebben Escape, focusherstel en een zichtbaar sluitdoel; drawer op desktop, schermvullend op mobiel.
- Status wordt nooit uitsluitend met kleur gecommuniceerd.
## Assets
Alle merkassets, fonts en productie-iconen zijn lokaal. Stitch-screenshots zijn alleen ontwerpdocumentatie; HTML/CDN/Google-hosted exportassets worden niet uitgevoerd of gebundeld.
@@ -0,0 +1,37 @@
# Implementatiemapping — DockDeck Canon v1
| Canonreferentie | DockDeck-oppervlak | Implementatiekeuze |
| --------------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Dashboard 1440/1920 donker | primaire startpagina | compacte topbar, begroeting, categorieën, desktopfavorietenrail, apptegels en uitklapbaar live inzicht; echte data |
| Dashboard licht | light theme | dezelfde hiërarchie en componentvormen op lichte semantische tokens |
| Dashboard categorie-modus | bestaande `viewMode=categories` | categoriechips sturen de bestaande filterlogica |
| Quick Launch resultaten/geen lokaal resultaat | `SearchOverlay` | viewportbrede portal met Apps/Favorieten/Google, pijlen, Enter, Escape en herstelde toetsenbordfocus |
| Instellingen Apps | `Settings` / apps | compacte rijen, echte ontdekstatus, filters, paginering en status/zichtbaarheid afzonderlijk |
| App-editor drawer | app PATCH-contract | naam, zichtbaarheid/categorie, lokale/externe URL, icoon en sortering in toegankelijke drawer |
| Instellingen Favorieten | favoriete CRUD | Canon-lijst en formulier met bestaande validatie |
| Instellingen Categorieën | categorie CRUD | bestaande naam/icoon/sortering/verwijderen in rustige lijst |
| Instellingen Weergave | preferences PATCH | thema, modus en dichtheid eerst; accent, breedte, achtergrond en details progressief ontsloten |
| Instellingen Integraties | status/credential-API | uitsluitend bestaande read-only integraties; secrets gemaskeerd |
| Back-up en herstel | `/api/export` en `/api/import` | bestaande versie 1-flow, geen fictieve historie |
| Mobiel 390×844 | responsive dashboard/settings | mobiele topbar, touchcategorieën, compacte tegels en ondernavigatie |
## Bewuste afwijkingen
- Bestaande read-only widgets, presets en diagnostics blijven beschikbaar onder Algemeen omdat dit operationele productfunctionaliteit is; ze krijgen geen voorrang boven de launcher.
- De statische voorbeelden gebruiken andere apps en aantallen; DockDeck toont uitsluitend API-data.
- Canon-copy over Docker Proxy wordt niet letterlijk gebruikt omdat productie via AppOps kan ontdekken; integratietekst volgt de echte runtime.
- Het logo wordt vereenvoudigd voor 1648px en als lokaal SVG/PNG geleverd; de bronafbeelding wordt niet uitgesneden of gehotlinkt.
## Implementatiestatus
Alle bovenstaande kernoppervlakken zijn op 2026-07-21 geïmplementeerd. Het dashboard gebruikt de echte API-payload, Quick Launch groepeert server/apps/favorieten/Google, appbeheer gebruikt compacte regels plus een focus-trapped drawer en de bestaande geavanceerde widget- en diagnosticaflows staan onder **Algemeen**. De statische Canon-voorbeelden zijn dus niet als los prototype of hardcoded datalaag overgenomen.
Na de eerste review is een tweede, directere Stitch-pass uitgevoerd. `stitch-direct.css` is de finale compositielaag en neemt nu de daadwerkelijke Canon-hiërarchie over: 64px topbar, begroeting met compacte ontdekbanner, pilnavigatie, vaste favorietkaart, vierkoloms servicekaarten, tweekoloms featurekaarten, de volledige Quick Launch-sheet, App Beheer als vijfkolomstabel en de 570px app-editor. Op mobiel worden de ontdekkaart, zoekbalk, ronde categorieknoppen, favorietegels, compacte servicerijen en vaste ondernavigatie rechtstreeks uit `canon_mobiel_dashboard_390_844` gevolgd.
Het operationele widgetblok blijft in de normale launcher zichtbaar als compacte rij **Live inzicht** en klapt op verzoek open. De volledige Paneelstudio, presets en diagnostics blijven onder **Algemeen** beschikbaar, zodat de dagelijkse launcher rustig blijft zonder functionaliteit te verbergen.
De visuele eindcontrole staat in [`implementation/README.md`](./implementation/README.md). Daarbij is ook de eerste-use toestand met alle nieuw ontdekte apps verborgen gecontroleerd; de gevulde screenshots gebruiken uitsluitend de geïsoleerde mockdatabase onder `.codex-input/`.
## Consistentiepass 2026-07-22
Dashboard en instellingen delen nu dezelfde platformgeometrie tot 3040 px, met een schaalbare linkerrail en een rechter widgetdock dat vanaf 1900 px automatisch opent. De instellingenkopie en form controls zijn vergroot voor leesbaarheid, apptegels hebben sterkere iconografie en actieaffordances, Quick Launch gebruikt echte favorieticonen en de lege zoekstaat bevat directe snelkoppelingen in plaats van alleen uitlegtekst.
@@ -0,0 +1,23 @@
# Source manifest — DockDeck Canon v1
## ZIP
- Bron: `C:\Users\Jens\Downloads\stitch_dockdeck_premium_interface_system (2).zip`
- SHA-256: `C5A76D95680550A74024F967F86553A008E5657C05833EE13C042C4F493BD441`
- Tijdelijke extractie: `.codex-input/stitch-dockdeck-canon-v1/` (genegeerd door Git)
- Primair: `dockdeck_canon_v1/DESIGN.md`, `dockdeck_canon_logo/screen.png` en alle `canon_*`-schermen.
- Aanvullende controle: `design.md_dockdeck_canon_v1.md`.
- Bewust genegeerd: `core_modernist/`, `obsidian_control/`, niet-`canon_*` schermen, `*_definitief` varianten en conflicterende oudere designsystemen.
## Stitch MCP
- Beschikbaar op 2026-07-21.
- Project: `projects/11417268779858336248`**DockDeck Premium Interface System**.
- Project bijgewerkt: 2026-07-21T19:14:24.395101Z.
- Gebruikt design system: `assets/d3776abdbc9040bcac889ef9b20c3eb8`**DockDeck Canon v1**, versie 1.
- Gecontroleerde metadata: Geist, dark-first, 8px afronding, `#818cf8` primair, `#10b981` positief, `#0b1326` basis en 1280px contentmaximum.
- De actuele `CANON — ...` schermen zijn vergeleken; niet-canonieke oudere schermen en de systemen Obsidian Control/Core Modernist zijn niet als productbron gebruikt.
## Opgenomen referenties
De map `screens/` bevat alleen dashboard dark/light, Quick Launch-resultaten, instellingen Apps, mobiel dashboard en de logo-referentie. HTML-export en externe Google-assets zijn niet opgenomen of gebruikt als productieafhankelijkheid.
@@ -0,0 +1,20 @@
# Canon v1 implementatiebeelden
Deze beelden zijn op 2026-07-22 rechtstreeks uit de lokale applicatie vastgelegd met deterministische mockdiscovery. Ze bevatten geen credentials of productiegegevens. De actuele dashboards tonen de Stitch-launcherworkspace met desktopfavorietenrail, herkenbare appiconen en zichtbare read-only servicepanelen; de E2E-suite legt ze opnieuw vast.
| Oppervlak | Beeld |
| ----------------------------------------- | ---------------------------------------------------------------------------------------- |
| Dashboard licht, desktop 1440×900 | [`dashboard-light-desktop-1440x900.png`](./dashboard-light-desktop-1440x900.png) |
| Dashboard donker, desktop 1440×900 | [`dashboard-dark-desktop-1440x900.png`](./dashboard-dark-desktop-1440x900.png) |
| Quick Launch licht, desktop 1440×900 | [`quick-launch-light-desktop-1440x900.png`](./quick-launch-light-desktop-1440x900.png) |
| Apps-instellingen licht, desktop 1440×900 | [`settings-apps-light-desktop-1440x900.png`](./settings-apps-light-desktop-1440x900.png) |
| Dashboard licht, mobiel 390×844 | [`dashboard-light-mobile-390x844.png`](./dashboard-light-mobile-390x844.png) |
| App-editor drawer, mobiel 390×844 | [`app-drawer-mobile-390x844.png`](./app-drawer-mobile-390x844.png) |
| Dashboard donker, mobiel 390×844 | [`dashboard-dark-mobile-390x844.png`](./dashboard-dark-mobile-390x844.png) |
| Dashboard donker, tablet 768×1024 | [`dashboard-dark-tablet-768x1024.png`](./dashboard-dark-tablet-768x1024.png) |
| Dashboard donker, ultrawide 3440×1440 | [`dashboard-dark-ultrawide-3440x1440.png`](./dashboard-dark-ultrawide-3440x1440.png) |
| Quick Launch donker, desktop 1440×900 | [`quick-launch-dark-desktop-1440x900.png`](./quick-launch-dark-desktop-1440x900.png) |
| Apps-instellingen donker, desktop | [`settings-apps-dark-desktop-1440x900.png`](./settings-apps-dark-desktop-1440x900.png) |
| App-editor donker, desktop | [`app-drawer-dark-desktop-1440x900.png`](./app-drawer-dark-desktop-1440x900.png) |
De geïsoleerde E2E-database wordt alleen voor visuele verificatie aangepast om drie apps en drie favorieten zichtbaar te maken. De productregel blijft ongewijzigd: nieuw ontdekte apps zijn standaard verborgen. De finale audit controleert bovendien alle instellingensecties, de horizontale tablet-/mobiele navigatie, echte appiconen, de uitklapbare widgetlaag en een Quick Launch die ook op ultrawide het volledige viewport afdekt.
Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 831 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

+34
View File
@@ -0,0 +1,34 @@
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";
export default tseslint.config(
{ ignores: ["dist", "coverage", "playwright-report", "test-results"] },
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ["scripts/**/*.mjs"],
languageOptions: {
ecmaVersion: 2022,
globals: { ...globals.node, fetch: "readonly" },
},
},
{
files: ["**/*.{ts,tsx}"],
languageOptions: {
ecmaVersion: 2022,
globals: { ...globals.browser, ...globals.node },
},
plugins: { "react-hooks": reactHooks, "react-refresh": reactRefresh },
rules: {
...reactHooks.configs.recommended.rules,
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
"@typescript-eslint/no-explicit-any": "off",
},
},
);
+3
View File
@@ -0,0 +1,3 @@
# Unraid template fixture mount
This empty directory makes local Compose validation possible. On Unraid, set `UNRAID_TEMPLATES_DIR=/boot/config/plugins/dockerMan/templates-user` so DockDeck can read the real XML templates.
+19
View File
@@ -0,0 +1,19 @@
<!doctype html>
<html lang="nl">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0b1326" />
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" href="/dockdeck-mark.svg" type="image/svg+xml" />
<meta
name="description"
content="DockDeck — je rustige startpunt voor al je self-hosted apps."
/>
<title>DockDeck</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/client/main.tsx"></script>
</body>
</html>
+7340
View File
File diff suppressed because it is too large Load Diff
+62
View File
@@ -0,0 +1,62 @@
{
"name": "dockdeck",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "concurrently -k -n api,web -c cyan,magenta \"tsx watch src/server/index.ts\" \"vite --host 127.0.0.1\"",
"dev:server": "tsx watch src/server/index.ts",
"dev:web": "vite --host 127.0.0.1",
"build": "tsc -b && vite build && esbuild src/server/index.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/server/index.js",
"start": "node dist/server/index.js",
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "eslint . --max-warnings=0",
"typecheck": "tsc -b --pretty false",
"test": "vitest run",
"test:watch": "vitest",
"test:e2e": "playwright test",
"test:security": "vitest run src/tests/security.test.ts",
"gitea:init": "node scripts/init-gitea.mjs"
},
"dependencies": {
"@fastify/static": "^10.1.0",
"@fontsource-variable/geist": "^5.3.0",
"@fontsource/newsreader": "^5.2.10",
"@icons-pack/react-simple-icons": "13.8.0",
"better-sqlite3": "^12.2.0",
"drizzle-orm": "^0.45.2",
"fastify": "^5.6.1",
"lucide-react": "^0.468.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"zod": "^4.1.5"
},
"devDependencies": {
"@eslint/js": "^9.35.0",
"@playwright/test": "^1.55.0",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0",
"@types/better-sqlite3": "^7.6.13",
"@types/node": "^24.3.0",
"@types/react": "^19.1.12",
"@types/react-dom": "^19.1.9",
"@vitejs/plugin-react": "^5.0.2",
"concurrently": "^9.2.1",
"esbuild": "^0.25.9",
"eslint": "^9.35.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.3.0",
"jsdom": "^26.1.0",
"prettier": "^3.6.2",
"tsx": "^4.20.5",
"typescript": "~5.8.3",
"typescript-eslint": "^8.42.0",
"vite": "^7.1.4",
"vitest": "^3.2.4"
},
"engines": {
"node": ">=22"
}
}
+59
View File
@@ -0,0 +1,59 @@
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests",
fullyParallel: false,
workers: 1,
retries: 0,
reporter: "list",
use: {
baseURL: "http://127.0.0.1:5174",
trace: "retain-on-failure",
},
webServer: {
command: "npm run dev",
url: "http://127.0.0.1:3101/api/health",
reuseExistingServer: false,
timeout: 120_000,
env: {
MOCK_DISCOVERY: "true",
MOCK_WEATHER: "true",
DATABASE_PATH: "./data/e2e-canon-v1.db",
HOST: "127.0.0.1",
PORT: "3101",
VITE_API_PORT: "3101",
VITE_PORT: "5174",
},
},
projects: [
{
name: "desktop-chromium",
use: {
...devices["Desktop Chrome"],
viewport: { width: 1440, height: 900 },
},
},
{
name: "tablet-chromium",
use: {
...devices["Desktop Chrome"],
viewport: { width: 768, height: 1024 },
},
},
{
name: "mobile-chromium",
use: {
...devices["Pixel 7"],
viewport: { width: 390, height: 844 },
deviceScaleFactor: 1,
},
},
{
name: "ultrawide-chromium",
use: {
...devices["Desktop Chrome"],
viewport: { width: 3440, height: 1440 },
},
},
],
});
+40
View File
@@ -0,0 +1,40 @@
{
"schemaVersion": 1,
"generatedAt": "2026-07-13T18:54:16.667Z",
"rootFolderName": "dockdeck",
"exportProfile": {
"id": "codex-full",
"label": "Codex volledig"
},
"codexStartMode": {
"id": "build-mvp",
"label": "MVP bouwen"
},
"input": {
"projectName": "DockDeck",
"shortDescription": "Een premium ogend persoonlijk startdashboard voor Unraid dat Docker-apps automatisch ontdekt, stijlvol groepeert en snelle navigatie biedt naar lokale diensten en eigen favorieten.",
"projectIdea": "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.",
"targetUsers": "Eén primaire gebruiker: de eigenaar en beheerder van de Unraid-server. Deze gebruiker verwacht zonder technische omwegen snel naar lokale Docker-diensten en favoriete websites te kunnen navigeren, zelf te bepalen welke apps zichtbaar zijn, categorieën en sortering te beheren, de startpagina visueel aan te passen en optioneel lokale of externe app-URL's te gebruiken. Er zijn in de MVP geen afzonderlijke gebruikersrollen, accounts of gedeelde profielen.",
"problemToSolve": "De gebruiker heeft meerdere Docker-diensten op een Unraid-server en moet momenteel afzonderlijke URL's onthouden, bladwijzers beheren of via verschillende beheerinterfaces navigeren. Bestaande startpagina's sluiten niet volledig aan op de gewenste combinatie van automatische Unraid-detectie, configureerbare zichtbaarheid, categorie-indeling, statusweergave, lokale en externe URL's, favoriete websites, geïntegreerde zoekfunctie en een echt professioneel ogende premium UI.",
"desiredOutcome": "De eerste bruikbare versie draait als Docker-container op Unraid en kan als browserstartpagina worden ingesteld. Na de eerste start ontdekt de app de beschikbare Docker-containers, toont ze in een instellingenoverzicht en laat de gebruiker selecteren welke apps op het dashboard verschijnen. Zichtbare apps staan netjes gegroepeerd in configureerbare categorieën, tonen naam, icoon en actuele online- of offline-status en openen hun lokale WebUI in een nieuw tabblad. Favoriete websites kunnen handmatig worden toegevoegd. De zoekbalk vindt apps en favorieten en kan rechtstreeks een Google-zoekopdracht starten. Alle instellingen blijven na een herstart behouden en kunnen worden geëxporteerd en opnieuw geïmporteerd. De totale ervaring moet eruitzien als een afgewerkt premium product dat de gebruiker met plezier dagelijks als startpagina gebruikt.",
"successCriteria": "De MVP is klaar wanneer: de applicatie via Docker Compose op Unraid kan worden gestart; beschikbare Docker-containers automatisch worden ontdekt zonder schrijf- of beheerrechten op Docker; nieuw ontdekte containers standaard verborgen blijven; de gebruiker containers zichtbaar of verborgen kan maken; naam, icoon, categorie, sortering en lokale URL per app kunnen worden aangepast; de lokale WebUI-URL waar mogelijk automatisch uit Unraid-templategegevens wordt afgeleid; zichtbare apps naam, icoon en correcte online- of offline-status tonen; status bij het openen en daarna ongeveer elke 30 seconden wordt vernieuwd; categorieën kunnen worden aangemaakt, hernoemd, gesorteerd en toegewezen; zowel een volledig overzicht als categoriegebaseerde navigatie configureerbaar is; favorieten met naam, URL, icoon, categorie en sortering kunnen worden beheerd; de zoekbalk apps en favorieten doorzoekt en overige invoer naar Google stuurt; alle links in een nieuw tabblad openen; licht, donker en minstens één aanvullend thema selecteerbaar zijn; configuratie persistent blijft na container- en serverherstart; back-up en herstel via export en import werken; de app uitsluitend voor lokaal gebruik is ingericht en geen container start-, stop-, restart- of wijzigingsacties bevat; een private Gitea-repository automatisch wordt aangemaakt, lokaal wordt geïnitialiseerd en een eerste bruikbare commit bevat; en de UI een consistent, professioneel en visueel hoogwaardig geheel vormt met nette kaartlay-outs, sterke typografie, duidelijke statusindicatoren, vloeiende maar subtiele interacties en een verzorgde instellingenervaring.",
"mustHaves": "Automatische read-only ontdekking van Unraid Docker-containers; nieuwe containers standaard verborgen; instellingenpagina voor zichtbaarheid, naam, icoon, categorie, sortering en URL-overrides; automatische afleiding van lokale WebUI-URL's uit Unraid-templategegevens; read-only containerstatus met verversing bij laden en ongeveer elke 30 seconden; configureerbare categorieën; configureerbare keuze tussen één totaaloverzicht en categoriepagina's of tabbladen; handmatige favorieten voor externe websites; centrale zoekbalk voor apps, favorieten en Google; openen van alle doelen in een nieuw tabblad; licht, donker en aanvullende ingebouwde thema's; persistent bewaren van instellingen in SQLite; export en import van configuratie; Docker- en Docker Compose-configuratie voor Unraid; beperkte Docker-socketproxy zonder beheerrechten; optionele read-only Nginx Proxy Manager-koppeling voor externe URL's met handmatige fallback; duidelijke foutstatus wanneer Unraid, Docker-proxy of Nginx Proxy Manager tijdelijk onbereikbaar is; automatische initialisatie van een private Gitea-repository via environmentvariabelen; en een uitzonderlijk verzorgde professionele UI met premium dashboardlook, consistente componenten, strak kaartdesign, goede witruimte, duidelijke visuele hiërarchie, subtiele hover- en transition-effecten, nette iconografie en een afgewerkte instellingenomgeving.",
"niceToHaves": "Drag-and-drop sortering van apps en categorieën; automatisch herkennen van bekende applicatie-iconen; favicon-import voor favorieten; meerdere aanvullende kleurthema's; compacte en ruime tegelweergave; achtergrondafbeeldingen of subtiele dashboard-achtergronden; sneltoets om de zoekbalk te focussen; recente of meest gebruikte apps; optionele statuslatentie en responstijd; import van bestaande browserbladwijzers; meerdere dashboards of profielen; PWA-installatie; mobiele optimalisaties buiten de basisresponsiviteit; geavanceerde automatische matching tussen Nginx Proxy Manager-hosts en Docker-containers; handmatige statuscontrole voor favoriete websites; extra micro-interacties en visuele personalisatieopties zolang die de strakke premium uitstraling ondersteunen.",
"outOfScope": "Containers starten, stoppen, herstarten, verwijderen of wijzigen; Docker Compose-stacks beheren; Unraid-systeembeheer; terminal- of shelltoegang; logviewer; resourcegrafieken en uitgebreide monitoring; notificaties en incidentbeheer; publieke internettoegang configureren; Authentik- of andere SSO-integratie; eigen gebruikersaccounts, rollen of multi-userondersteuning; opslag van echte API-tokens in de repository of browser; automatische wijzigingen aan Nginx Proxy Manager; cloudhosting; mobiele native apps; AI-functionaliteit; synchronisatie tussen meerdere Unraid-servers; automatische publicatie van diensten naar het internet.",
"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.",
"projectType": "webapp",
"runtimeModel": "interactive-app",
"updateStrategy": "polling",
"persistenceModel": "sqlite",
"deploymentTarget": "docker-unraid",
"repositoryState": "repo-needed",
"preferredStack": "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.",
"dataInvolved": "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.",
"integrations": "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.",
"credentialsExpected": true,
"securityPrivacyNotes": "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.",
"qualityBar": "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.",
"testingExpectations": "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.",
"knownRisks": "Unraid-templatebestanden en WebUI-velden kunnen per container of template verschillen, waardoor URL-detectie fallbacks en handmatige overrides nodig zijn. Niet iedere container heeft een WebUI, correct icoon of eenvoudig herkenbare poort. Het automatisch koppelen van Nginx Proxy Manager-hosts aan containers kan dubbelzinnig zijn en moet daarom transparant, overschrijfbaar en niet-destructief blijven. Een lokale app zonder login is alleen veilig zolang netwerktoegang correct is afgeschermd. Toegang tot Docker-informatie blijft gevoelig, zelfs in read-only modus, waardoor de socketproxy strikt moet worden beperkt. Gitea-repositorycreatie kan mislukken door ontbrekende rechten, een bestaande naam of onbereikbaarheid; de lokale repository moet dan intact blijven en een duidelijke herstelstap tonen. Hoge UI-ambities kunnen de scope doen groeien door eindeloos finetunen van styling en details; daarom moet de premium uitstraling via een consistent design system en duidelijke acceptatiecriteria bewaakt worden zonder de kernfunctionaliteit te vertragen."
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

+21
View File
@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-labelledby="title">
<title id="title">DockDeck</title>
<defs>
<linearGradient id="shell" x1="64" y1="42" x2="448" y2="470" gradientUnits="userSpaceOnUse">
<stop stop-color="#243732"/>
<stop offset="1" stop-color="#111b19"/>
</linearGradient>
<linearGradient id="deck" x1="142" y1="134" x2="370" y2="372" gradientUnits="userSpaceOnUse">
<stop stop-color="#ff8b6e"/>
<stop offset="1" stop-color="#d9563d"/>
</linearGradient>
</defs>
<rect x="20" y="20" width="472" height="472" rx="122" fill="url(#shell)"/>
<rect x="42" y="42" width="428" height="428" rx="103" fill="none" stroke="#fff" stroke-opacity=".08" stroke-width="4"/>
<path d="M126 151c0-20 16-36 36-36h103c80 0 145 65 145 145v3c0 74-60 134-134 134H162c-20 0-36-16-36-36V151Z" fill="#f5f4ed"/>
<path d="M187 181h78c44 0 80 36 80 80s-36 80-80 80h-78V181Z" fill="url(#deck)"/>
<rect x="213" y="214" width="82" height="14" rx="7" fill="#fff" fill-opacity=".9"/>
<rect x="213" y="249" width="59" height="14" rx="7" fill="#fff" fill-opacity=".72"/>
<rect x="213" y="284" width="71" height="14" rx="7" fill="#fff" fill-opacity=".55"/>
<circle cx="378" cy="135" r="26" fill="#8bd5b5" stroke="#17221f" stroke-width="10"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" role="img" aria-labelledby="title">
<title id="title">DockDeck beeldmerk voor donkere achtergrond</title>
<path d="M9 7h13.5C33 7 41 15 41 25.5S33 44 22.5 44H9V7Z" fill="none" stroke="#818cf8" stroke-width="3.5" stroke-linejoin="round"/>
<path d="M7.5 27.5h33M11 27.5l9-11 18 11M20 17v17" fill="none" stroke="#818cf8" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 36c4-2.5 8-2.5 12 0s8 2.5 12 0 6-2.5 9-1" fill="none" stroke="#818cf8" stroke-width="3" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 584 B

+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" role="img" aria-labelledby="title">
<title id="title">DockDeck beeldmerk voor lichte achtergrond</title>
<path d="M9 7h13.5C33 7 41 15 41 25.5S33 44 22.5 44H9V7Z" fill="none" stroke="#4f56cf" stroke-width="3.5" stroke-linejoin="round"/>
<path d="M7.5 27.5h33M11 27.5l9-11 18 11M20 17v17" fill="none" stroke="#4f56cf" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 36c4-2.5 8-2.5 12 0s8 2.5 12 0 6-2.5 9-1" fill="none" stroke="#4f56cf" stroke-width="3" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 583 B

+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" role="img" aria-labelledby="title">
<title id="title">DockDeck monochroom beeldmerk</title>
<path d="M9 7h13.5C33 7 41 15 41 25.5S33 44 22.5 44H9V7Z" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linejoin="round"/>
<path d="M7.5 27.5h33M11 27.5l9-11 18 11M20 17v17" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 36c4-2.5 8-2.5 12 0s8 2.5 12 0 6-2.5 9-1" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 585 B

+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" role="img" aria-labelledby="title">
<title id="title">DockDeck</title>
<rect width="48" height="48" rx="12" fill="#818cf8"/>
<path d="M13 13h9.5C30 13 36 19 36 26.5S30 40 22.5 40H13V13Z" fill="none" stroke="#0b1326" stroke-width="3.4" stroke-linejoin="round"/>
<path d="M11.5 28.5h25M15 28.5l7-8 13 8M21 21v13" fill="none" stroke="#0b1326" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 35c3-2 6-2 9 0s6 2 9 0 5-2 7-.8" fill="none" stroke="#0b1326" stroke-width="2.8" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 602 B

+9
View File
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 236 48" role="img" aria-labelledby="title">
<title id="title">DockDeck woordmerk</title>
<g transform="translate(0)">
<path d="M9 7h13.5C33 7 41 15 41 25.5S33 44 22.5 44H9V7Z" fill="none" stroke="#818cf8" stroke-width="3.5" stroke-linejoin="round"/>
<path d="M7.5 27.5h33M11 27.5l9-11 18 11M20 17v17" fill="none" stroke="#818cf8" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 36c4-2.5 8-2.5 12 0s8 2.5 12 0 6-2.5 9-1" fill="none" stroke="#818cf8" stroke-width="3" stroke-linecap="round"/>
</g>
<text x="57" y="32" fill="#eef0ff" font-family="Geist, Inter, system-ui, sans-serif" font-size="26" font-weight="650" letter-spacing="-1">DockDeck</text>
</svg>

After

Width:  |  Height:  |  Size: 760 B

+17
View File
@@ -0,0 +1,17 @@
{
"name": "DockDeck",
"short_name": "DockDeck",
"description": "Je persoonlijke startpunt voor apps en favorieten.",
"start_url": "/",
"display": "standalone",
"background_color": "#0b1326",
"theme_color": "#818cf8",
"icons": [
{
"src": "/dockdeck-mark.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any maskable"
}
]
}
+58
View File
@@ -0,0 +1,58 @@
/* global self, caches, fetch, URL */
const CACHE = "dockdeck-shell-v1";
const SHELL = ["/", "/manifest.webmanifest", "/dockdeck-icon.svg"];
self.addEventListener("install", (event) => {
event.waitUntil(caches.open(CACHE).then((cache) => cache.addAll(SHELL)));
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
event.waitUntil(
caches
.keys()
.then((keys) =>
Promise.all(
keys.filter((key) => key !== CACHE).map((key) => caches.delete(key)),
),
),
);
self.clients.claim();
});
self.addEventListener("fetch", (event) => {
const request = event.request;
const url = new URL(request.url);
if (
request.method !== "GET" ||
url.origin !== self.location.origin ||
url.pathname.startsWith("/api/")
)
return;
if (request.mode === "navigate") {
event.respondWith(
fetch(request)
.then((response) => {
const copy = response.clone();
caches.open(CACHE).then((cache) => cache.put("/", copy));
return response;
})
.catch(() => caches.match("/")),
);
return;
}
event.respondWith(
caches.match(request).then(
(cached) =>
cached ||
fetch(request).then((response) => {
if (response.ok)
caches
.open(CACHE)
.then((cache) => cache.put(request, response.clone()));
return response;
}),
),
);
});
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
+86
View File
@@ -0,0 +1,86 @@
import { execFileSync } from "node:child_process";
const required = ["GITEA_URL", "GITEA_OWNER", "GITEA_TOKEN"];
const missing = required.filter((name) => !process.env[name]);
if (missing.length) {
console.error(
`Missing required environment variables: ${missing.join(", ")}`,
);
process.exit(2);
}
const base = process.env.GITEA_URL.replace(/\/$/, "");
const owner = process.env.GITEA_OWNER;
const token = process.env.GITEA_TOKEN;
const name = "DockDeck";
const headers = {
Authorization: `token ${token}`,
"content-type": "application/json",
};
let response = await fetch(
`${base}/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`,
{ headers },
);
if (response.status === 404) {
response = await fetch(`${base}/api/v1/user/repos`, {
method: "POST",
headers,
body: JSON.stringify({
name,
private: true,
auto_init: false,
description: "A premium personal launchpad for Unraid services.",
}),
});
}
if (!response.ok) {
console.error(
`Gitea repository initialization failed with HTTP ${response.status}.`,
);
process.exit(1);
}
const repository = await response.json();
if (!repository.private || repository.owner?.login !== owner) {
console.error(
"Gitea returned a repository with an unexpected owner or visibility. Aborting.",
);
process.exit(1);
}
const remoteUrl = `${base}/${encodeURIComponent(owner)}/${name}.git`;
let currentRemote = "";
try {
currentRemote = execFileSync("git", ["remote", "get-url", "origin"], {
encoding: "utf8",
}).trim();
} catch {
/* no origin yet */
}
if (currentRemote && currentRemote !== remoteUrl) {
console.error(
"An origin remote already exists with a different URL. No changes were made.",
);
process.exit(1);
}
if (!currentRemote)
execFileSync("git", ["remote", "add", "origin", remoteUrl], {
stdio: "inherit",
});
execFileSync(
"git",
[
"-c",
`http.extraHeader=Authorization: token ${token}`,
"push",
"-u",
"origin",
"main",
],
{
stdio: ["ignore", "inherit", "inherit"],
},
);
console.log(`Private repository ready at ${base}/${owner}/${name}`);
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
View File
View File
View File
View File
Executable → Regular
View File
+210
View File
@@ -0,0 +1,210 @@
import {
Check,
Grid2X2,
LayoutDashboard,
Settings as SettingsIcon,
} from "lucide-react";
import {
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState,
} from "react";
import type { DashboardPayload } from "../shared/contracts";
import { api } from "./api";
import { Dashboard, DashboardSkeleton } from "./components/Dashboard";
import { Settings, type SettingsSection } from "./components/Settings";
import { Brand } from "./components/Brand";
type Page = "dashboard" | "settings";
export function App() {
const [page, setPage] = useState<Page>("dashboard");
const [settingsSection, setSettingsSection] =
useState<SettingsSection>("apps");
const [data, setData] = useState<DashboardPayload | null>(null);
const [error, setError] = useState<string | null>(null);
const [syncing, setSyncing] = useState(false);
const [toast, setToast] = useState<string | null>(null);
const toastTimerRef = useRef<number | undefined>(undefined);
const syncInFlightRef = useRef<Promise<void> | null>(null);
const theme = data?.preferences.theme;
const accent = data?.preferences.accent;
const density = data?.preferences.density;
const contentWidth = data?.preferences.contentWidth;
const backgroundStyle = data?.preferences.backgroundStyle;
const reducedMotion = data?.preferences.reducedMotion;
const pollingIntervalMs = data
? Math.min(
data.preferences.pollingIntervalMs,
...data.apps
.filter((app) => app.widgetEnabled)
.map((app) => app.widgetRefreshIntervalMs),
)
: undefined;
const load = useCallback(async () => {
try {
const payload = await api.dashboard();
setData(payload);
setError(null);
} catch (loadError) {
setError(
loadError instanceof Error
? loadError.message
: "DockDeck is niet bereikbaar.",
);
}
}, []);
const sync = useCallback(async () => {
if (syncInFlightRef.current) return syncInFlightRef.current;
const request = (async () => {
setSyncing(true);
try {
await api.sync();
await load();
} catch (syncError) {
setError(
syncError instanceof Error
? syncError.message
: "De actuele status kon niet worden vernieuwd.",
);
} finally {
setSyncing(false);
syncInFlightRef.current = null;
}
})();
syncInFlightRef.current = request;
return request;
}, [load]);
useEffect(() => {
void load().then(sync);
}, [load, sync]);
useLayoutEffect(() => {
if (!theme || !accent || !density || !contentWidth || !backgroundStyle)
return;
const root = document.documentElement;
root.dataset.theme = theme;
root.dataset.accent = accent;
root.dataset.density = density;
root.dataset.contentWidth = contentWidth;
root.dataset.background = backgroundStyle;
root.dataset.reducedMotion = String(Boolean(reducedMotion));
}, [accent, backgroundStyle, contentWidth, density, reducedMotion, theme]);
useEffect(() => {
if (!pollingIntervalMs) return;
const refreshWhenVisible = () => {
if (document.visibilityState === "visible") void sync();
};
const timer = window.setInterval(refreshWhenVisible, pollingIntervalMs);
document.addEventListener("visibilitychange", refreshWhenVisible);
window.addEventListener("online", refreshWhenVisible);
return () => {
window.clearInterval(timer);
document.removeEventListener("visibilitychange", refreshWhenVisible);
window.removeEventListener("online", refreshWhenVisible);
};
}, [pollingIntervalMs, sync]);
const notify = (message: string) => {
window.clearTimeout(toastTimerRef.current);
setToast(message);
toastTimerRef.current = window.setTimeout(() => setToast(null), 2400);
};
useEffect(
() => () => {
window.clearTimeout(toastTimerRef.current);
},
[],
);
if (!data && !error) return <DashboardSkeleton />;
if (!data && error)
return (
<div className="fatal-state">
<Brand />
<p className="eyebrow">VERBINDING VERBROKEN</p>
<h1>DockDeck is even niet bereikbaar.</h1>
<p>{error}</p>
<button className="primary-button" onClick={() => void load()}>
Opnieuw proberen
</button>
</div>
);
if (!data) return null;
return (
<div className="app-shell">
<a className="skip-link" href="#main-content">
Naar inhoud
</a>
{page === "dashboard" ? (
<Dashboard
data={data}
syncing={syncing}
onSync={sync}
onSettings={(section = "apps") => {
setSettingsSection(section);
setPage("settings");
}}
/>
) : (
<Settings
data={data}
initialSection={settingsSection}
onBack={() => setPage("dashboard")}
syncing={syncing}
onSync={sync}
onChanged={load}
notify={notify}
/>
)}
{toast && (
<div className="toast" role="status">
<Check size={16} />
{toast}
</div>
)}
<nav className="mobile-nav" aria-label="Mobiele navigatie">
<button
className={page === "dashboard" ? "active" : ""}
aria-current={page === "dashboard" ? "page" : undefined}
onClick={() => setPage("dashboard")}
>
<LayoutDashboard size={20} />
<span>Dashboard</span>
</button>
<button
className=""
onClick={() => {
setPage("dashboard");
window.setTimeout(
() =>
document
.getElementById("category-navigation")
?.scrollIntoView({ behavior: "smooth" }),
0,
);
}}
>
<Grid2X2 size={20} />
<span>Categorieën</span>
</button>
<button
className={page === "settings" ? "active" : ""}
aria-current={page === "settings" ? "page" : undefined}
onClick={() => setPage("settings")}
>
<SettingsIcon size={20} />
<span>Instellingen</span>
</button>
</nav>
</div>
);
}
+126
View File
@@ -0,0 +1,126 @@
import type {
AppPatch,
Category,
CategoryOrganizationResult,
CategoryInput,
DashboardPayload,
Favorite,
FavoriteInput,
LayoutPreset,
LayoutPresetImport,
PreferencesPatch,
SystemDiagnostics,
WallpaperDescriptor,
WeatherMetric,
WidgetLayoutInput,
} from "../shared/contracts";
async function request<T>(path: string, init?: RequestInit): Promise<T> {
const response = await fetch(path, {
...init,
headers: init?.body
? { "content-type": "application/json", ...init.headers }
: init?.headers,
});
if (!response.ok) {
const body = (await response.json().catch(() => null)) as {
error?: string;
} | null;
throw new Error(body?.error ?? "DockDeck kon de wijziging niet opslaan.");
}
return response.status === 204
? (undefined as T)
: ((await response.json()) as T);
}
export const api = {
dashboard: () => request<DashboardPayload>("/api/dashboard"),
weather: () => request<WeatherMetric>("/api/weather"),
uploadWallpaper: async (file: File) => {
const response = await fetch("/api/wallpaper", {
method: "PUT",
headers: { "content-type": file.type },
body: file,
});
if (!response.ok) {
const body = (await response.json().catch(() => null)) as {
error?: string;
} | null;
throw new Error(body?.error ?? "Wallpaper kon niet worden opgeslagen.");
}
return (await response.json()) as WallpaperDescriptor;
},
removeWallpaper: () => request<void>("/api/wallpaper", { method: "DELETE" }),
sync: () => request("/api/discovery/sync", { method: "POST" }),
updateApp: (id: string, patch: AppPatch) =>
request(`/api/apps/${id}`, {
method: "PATCH",
body: JSON.stringify(patch),
}),
deleteApp: (id: string) => request(`/api/apps/${id}`, { method: "DELETE" }),
createCategory: (input: CategoryInput) =>
request<Category>("/api/categories", {
method: "POST",
body: JSON.stringify(input),
}),
updateCategory: (id: string, input: Partial<CategoryInput>) =>
request<Category>(`/api/categories/${id}`, {
method: "PATCH",
body: JSON.stringify(input),
}),
deleteCategory: (id: string) =>
request(`/api/categories/${id}`, { method: "DELETE" }),
organizeCategories: () =>
request<CategoryOrganizationResult>("/api/categories/organize", {
method: "POST",
}),
createFavorite: (input: FavoriteInput) =>
request<Favorite>("/api/favorites", {
method: "POST",
body: JSON.stringify(input),
}),
updateFavorite: (id: string, input: Partial<FavoriteInput>) =>
request<Favorite>(`/api/favorites/${id}`, {
method: "PATCH",
body: JSON.stringify(input),
}),
deleteFavorite: (id: string) =>
request(`/api/favorites/${id}`, { method: "DELETE" }),
updatePreferences: (patch: PreferencesPatch) =>
request("/api/preferences", {
method: "PATCH",
body: JSON.stringify(patch),
}),
updateWidgetLayout: (input: WidgetLayoutInput) =>
request<{ saved: true }>("/api/widget-layout", {
method: "PUT",
body: JSON.stringify(input),
}),
createLayoutPreset: (name: string) =>
request<LayoutPreset>("/api/layout-presets", {
method: "POST",
body: JSON.stringify({ name }),
}),
applyLayoutPreset: (id: string) =>
request<{ applied: true }>(`/api/layout-presets/${id}/apply`, {
method: "POST",
}),
deleteLayoutPreset: (id: string) =>
request(`/api/layout-presets/${id}`, { method: "DELETE" }),
importLayoutPreset: (payload: LayoutPresetImport) =>
request<LayoutPreset>("/api/layout-presets/import", {
method: "POST",
body: JSON.stringify(payload),
}),
diagnostics: () => request<SystemDiagnostics>("/api/diagnostics"),
updateIntegrationCredentials: (id: string, values: Record<string, string>) =>
request<{ saved: true }>(
`/api/integrations/${encodeURIComponent(id)}/credentials`,
{
method: "PUT",
body: JSON.stringify({ values }),
},
),
importBackup: (payload: unknown) =>
request("/api/import", { method: "POST", body: JSON.stringify(payload) }),
};
+1373
View File
File diff suppressed because it is too large Load Diff
+79
View File
@@ -0,0 +1,79 @@
import {
Activity,
Archive,
Bot,
Box,
BriefcaseBusiness,
Code2,
Database,
Download,
FileText,
Gamepad2,
Home,
Image as ImageIcon,
Layers3,
Network,
Play,
Server,
ShieldCheck,
Star,
Wrench,
} from "lucide-react";
export const categoryIconOptions = [
{ id: "Layers3", label: "Lagen" },
{ id: "Play", label: "Media" },
{ id: "Download", label: "Downloads" },
{ id: "FileText", label: "Documenten" },
{ id: "Image", label: "Foto's" },
{ id: "Home", label: "Thuis & automatisering" },
{ id: "ShieldCheck", label: "Beveiliging & identiteit" },
{ id: "Network", label: "Netwerk & beheer" },
{ id: "Activity", label: "Monitoring" },
{ id: "Code2", label: "Ontwikkeling" },
{ id: "Bot", label: "AI & machine learning" },
{ id: "Database", label: "Databases & opslag" },
{ id: "Server", label: "Infrastructuur" },
{ id: "Gamepad2", label: "Games & emulatie" },
{ id: "Wrench", label: "Gereedschap" },
{ id: "Archive", label: "Experimenten & archief" },
{ id: "Star", label: "Favorieten" },
{ id: "BriefcaseBusiness", label: "Werk" },
{ id: "Box", label: "Algemeen" },
] as const;
const categoryIconComponents = {
Layers3,
Play,
Download,
FileText,
Image: ImageIcon,
Home,
ShieldCheck,
Network,
Activity,
Code2,
Bot,
Database,
Server,
Gamepad2,
Wrench,
Archive,
Star,
BriefcaseBusiness,
Box,
} as const;
export function resolveCategoryIcon(name: string) {
return (
categoryIconComponents[name as keyof typeof categoryIconComponents] ??
Layers3
);
}
export function nextAvailableCategoryIcon(usedIcons: Iterable<string>) {
const used = new Set(usedIcons);
return (
categoryIconOptions.find((option) => !used.has(option.id))?.id ?? "Layers3"
);
}
+127
View File
@@ -0,0 +1,127 @@
import { ArrowUpRight, Box, Globe2 } from "lucide-react";
import type { DockApp, Favorite } from "../../shared/contracts";
import { effectiveAppUrl, favoriteIconUrl } from "../../shared/domain";
import { ServiceIcon } from "./ServiceIcon";
const serviceDescriptions: Array<[RegExp, string]> = [
[/plex/i, "Media server & streaming"],
[/jellyfin/i, "De vrije software mediaserver"],
[/sonarr/i, "PVR voor tv-series"],
[/radarr/i, "PVR voor films"],
[/home.?assistant|ha-core/i, "Centrale automatisering"],
[/adguard|pihole|pi-hole/i, "Netwerkbeveiliging & filter"],
[/nextcloud/i, "Bestandshosting"],
[/gitea|github|gitlab/i, "Code en samenwerking"],
[/immich/i, "Foto's en herinneringen"],
[/paperless/i, "Documenten en archief"],
];
function describeService(app: DockApp, categoryName: string) {
const haystack = `${app.displayName} ${app.technicalName}`;
return (
serviceDescriptions.find(([pattern]) => pattern.test(haystack))?.[1] ??
`${categoryName} service`
);
}
function statusLabel(status: DockApp["status"]) {
return status === "online"
? "Online"
: status === "offline"
? "Offline"
: "Status onbekend";
}
export function AppTile({
app,
externalAccess,
showStatus,
index,
categoryName,
variant = "standard",
}: {
app: DockApp;
externalAccess: boolean;
showStatus: boolean;
index: number;
categoryName: string;
variant?: "standard" | "feature";
}) {
const url = effectiveAppUrl(app, externalAccess);
const content = (
<>
<ServiceIcon
name={app.displayName}
icon={app.icon}
hint={`${app.technicalName} ${app.image ?? ""}`}
index={index}
/>
<span className="tile-copy">
<strong>{app.displayName}</strong>
<small>{describeService(app, categoryName)}</small>
</span>
{showStatus && (
<span className={`tile-status ${app.status}`}>
<span /> {statusLabel(app.status)}
</span>
)}
<ArrowUpRight className="tile-arrow" size={17} aria-hidden="true" />
</>
);
return url ? (
<a
className={`app-tile ${variant}`}
href={url}
target="_blank"
rel="noopener noreferrer"
data-testid="app-tile"
>
{content}
</a>
) : (
<div
className={`app-tile ${variant} disabled`}
aria-label={`${app.displayName} heeft geen URL`}
>
{content}
</div>
);
}
export function FavoriteTile({
favorite,
index,
}: {
favorite: Favorite;
index: number;
}) {
return (
<a
className="app-tile"
href={favorite.url}
target="_blank"
rel="noopener noreferrer"
data-testid="favorite-tile"
>
<ServiceIcon
name={favorite.name}
icon={favoriteIconUrl(favorite)}
index={index + 2}
/>
<span className="tile-copy">
<strong>{favorite.name}</strong>
<small>Favoriet</small>
</span>
<Globe2 className="status-icon" size={15} aria-hidden="true" />
<ArrowUpRight className="tile-arrow" size={17} aria-hidden="true" />
</a>
);
}
export function EmptyIcon() {
return (
<div className="empty-icon" aria-hidden="true">
<Box size={28} />
</div>
);
}

Some files were not shown because too many files have changed in this diff Show More