Dashboard
Homelab-landingsside der erstatter Heimdall: konfigurationsdrevet tile-grid med live status (online/degraderet/offline) for hver self-hosted service på LAN'et, plus en DeepSeek-drevet AI-chatbubble der kan svare på hvad der er nede. Egne adaptere til OctoPrint, TrueNAS, Proxmox, Gitea, Moonraker m.fl. — inklusiv Archive-suiten selv.
Selvinitieret, selvhostet projekt. Bygget og drevet af mig som arkitekt, product owner og reviewer, med AI som en overvåget "lærling" i udviklingsprocessen og i dele af selve produktet.
DEL 1: Resume
Dashboard er en homelab-startside, der viser live online/degraded/offline-status for hver selvhostet service på netværket, grupperet i et hierarki defineret i én YAML-fil. En chatboble svarer på "hvad er nede lige nu?" ud fra den samme live-tilstand. Den erstattede en bogmærke-agtig startside uden hierarki og næsten uden status.
Tech stack
- Backend: Python, FastAPI/uvicorn, Pydantic v2, almindelige
asyncio-opgaver (ca. 3.000 linjer) - Frontend: én HTML-fil, Alpine.js + Tailwind fra CDN, uden build-trin
- Konfiguration: YAML-hierarki, git-sporet, med en lokal tilsidesættelse, der flettes ind; legitimationsoplysninger som
${VAR}-referencer fra miljøet - Providers (adaptere): HTTP-ping, IPP-printere, OctoPrint, Moonraker, Proxmox, TrueNAS, Gitea, Jellyfin, Syncthing, Node-RED, Spoolman og hver af mine egne Archive*-apps
- AI: DeepSeek med SSE-streaming; kontekst bygget ud fra statustræet
- Drift: systemd user unit, installationsscript, "klonen er den kørende installation"
Største bedrift Et bevidst lille design, der skalerer ved at tilføje én fil. Hver servicetype er en provider-adapter, hierarkiet er data, status beregnes ved behov og gemmes aldrig, og fejl forbliver isolerede. En ny service er en YAML-post, og en ny slags service er én lille adapter.
DEL 2: Dybdegående baggrund
Problemet
Et homelab eller et lille serverlandskab vokser til dusinvis af services. En flad liste af bogmærker siger intet om, hvorvidt de virker, og den eneste måde at opdage, at noget er nede, er at åbne det. De fleste dashboards viser enten for lidt eller kræver en database, en admin-UI og en autentificeringsstak. Samme situation findes i små virksomheder: en håndfuld interne systemer, intet samlet overblik over sundhed, og det første tegn på problemer er en brugerhenvendelse.
Arkitektur og løsning
dashboard.yaml → config.py (+ lokal tilsidesættelse) → models.py (træ + rollup)
│
poller.py: én asyncio-opgave pr. bladfelt ────────────┤── providers/*.py (én adapter pr. `provider:`)
statuscache i hukommelsen (intet gemmes) │
api: GET /api/hierarchy, /api/tiles/{id}/detail, POST /api/chat (SSE)
static/index.html (Alpine.js + Tailwind, uden build)
Designvalg, der er værd at fremhæve
- Eksplicitte ikke-mål. Ingen database, ingen autentificering (kun LAN), ingen admin-UI, intet build-trin, intet scheduler-bibliotek. Specifikationen siger det med det samme, hvilket holdt scope småt og beslutningerne nemme at forsvare.
- Konfiguration som model. Hierarkiet er en håndredigeret, git-sporet YAML-fil uden hemmeligheder. Hemmeligheder ligger i miljøet og refereres som
${VAR}; en ikke-sat variabel bliver tom, og provideren falder tilbage til en almindelig tilgængelighedstjek i stedet for at fejle. - Én opgave pr. felt. Hvert bladfelt poller i sit eget interval i sin egen
asyncio-opgave, så én langsom eller ødelagt provider ikke kan blokere en anden. Resultater lægges i en almindelig ordbog i hukommelsen, fordi status er billig at udlede påny. - Meningsfuld rollup. En kategori viser den værste status blandt sine efterkommere (offline over degraded over ukendt over online), så øverste niveau besvarer "er noget galt?" med et blik.
- Provider-kontrakt. Hver adapter returnerer samme statusform og kan tilføje en lille widget (tonerbjælker, en fremdriftsbjælke), hvilket holder frontenden generisk.
- Genbrug hvor det betalte sig, og ærlighed hvor det ikke gjorde. Chattens streamingkode blev skrevet én gang her i stedet for kopieret en tredje gang. Specifikationen noterer også, at udtræk til en fælles pakke på tværs af projekter stadig er ugjort.
- Udrulning uden ceremoni. Git-klonen er installationen; opdateringer er
git pull,uv syncog en genstart. - Tests. Der er én enhedstestfil (IPP-printerparsing, 7 tests). Provider-adaptere er ellers verificeret live mod de rigtige services. Mock-baserede providertests ville være næste skridt i et team.
AI som lærling
AI i selve produktet. Chatboblen bygger et indrykket teksttræ af det levende hierarki med en "kræver opmærksomhed"-oversigt øverst og sender det til DeepSeek. Assistenten besvarer spørgsmål om den aktuelle tilstand; den kan ikke ændre noget. Uden en API-nøgle returnerer endpointet en eksplicit 503, og resten af dashboardet virker uændret.
AI i udviklingsprocessen (human-in-the-loop). Jeg ejer scope og listen over, hvad der er uden for grænserne; en AI-kodeassistent implementerer mod specifikationen. Afsnittet om kendte mangler holder udskudt arbejde synligt. Providers, der rører rigtige services (Proxmox, TrueNAS, OctoPrint), verificerer jeg selv mod de rigtige systemer.
Lederens refleksion
- Skriv ikke-mål ned. At sige "ingen database, ingen autentificering, intet build-trin" på første side forhindrer scopeglidning og er det billigste arkitekturreview, der findes.
- Gør udvidelse billig og fejl lokal. Adaptermønsteret og én opgave pr. felt betyder, at den almindelige ændring er lille, og en dårlig ændring ikke kan tage helheden ned.
- Hold hemmeligheder ude af det, du committer. Referencer i stedet for værdier gjorde konfigurationen sikker at versionere.
- Status er en rollup, ikke en liste. At præsentere værste tilfælde opad er det, der gør mange signaler til én beslutning.
- Vær åben om duplikering. At vide, at samme chatkode ligger tre steder, og skrive det ned, er bedre end at foregive, at den er delt.
Ærlige begrænsninger. Kun LAN uden autentificering, én instans, minimale automatiske tests, ingen CI. Bygget fra juli til oktober 2026.