En liten Vite-app som renderar vertikala skärmsidor från J26 CMS. Appen hämtar slide-data via CMS-endpoints, normaliserar innehållet i src/main.js och roterar sedan mellan slides baserat på varje slides durationSeconds.
Om ingen slug anges används det inbyggda standardinnehållet från src/assets/j26_default.json.
- Node 24
- npm
Installera beroenden:
npm installStarta utvecklingsservern:
npm run devBygg för produktion:
npm run buildFörhandsgranska den byggda appen lokalt:
npm run previewAppen läser sin runtime-konfiguration från query-parametrar i URL:en.
| Parameter | Exempel | Effekt |
|---|---|---|
slug |
?slug=j26_default |
Väljer vilken screen som ska hämtas. Om parametern saknas används j26_default. |
apiBase |
?slug=min-screen&apiBase=https://example.com/_services/cms/api/screens |
Pekar om CMS-basen. Praktiskt för test mot annan miljö. |
animation |
?slug=min-screen&animation=off |
Stänger av entry-animationer mellan sidbyten. Allt annat beteende är oförändrat. |
refreshMinutes |
?slug=min-screen&refreshMinutes=2 |
Tyst bakgrundsuppdatering av slide-data i minuter. Standard är 2 minuter för CMS-slugs och av för j26_default. |
refresh |
?slug=min-screen&refresh=off |
Stänger av bakgrundsuppdatering även om refreshMinutes eller miljövariabel är satt. |
Exempel:
http://localhost:5173/?slug=j26_default
http://localhost:5173/?slug=my-screen
http://localhost:5173/?slug=my-screen&apiBase=https://app.dev.j26.se/_services/cms/api/screens
http://localhost:5173/?slug=my-screen&animation=off
http://localhost:5173/?slug=my-screen&refreshMinutes=1
Tangentbordsinput hanteras utanför webappen, till exempel via en lokal brygga på Raspberry Pi.
?slug=heartbeat renderar en driftöversikt istället för en skärm (samma
mönster som jämförelsevyn, se isComparisonSlug i src/main.js).
Implementationen ligger i src/heartbeat-view.js och pollar
/_services/screens/api/heartbeat var 5:e sekund via GET.
http://localhost:5173/?slug=heartbeat
Sidan visar per skärm: online, HDMI-status, antal RFID-läsare, senaste heartbeat och dess ålder.
/_services/screens/ serverades tidigare av rå nginx (bara statiska
filer) — det räcker inte för att ta emot en POST. server.js är en liten
beroendefri Node-server som ersätter nginx som runner-steg i Dockerfile:
den serverar dist/ precis som nginx gjorde (med SPA-fallback till
index.html för alla client-side-routes), och äger dessutom:
POST /api/heartbeat— tar emot{ screenId, online, hdmiActive, readerCount }(ingen auth) och sparar senaste status perscreenIdi minnet.GET /api/heartbeat— returnerar en array med alla kända skärmars senaste status, det somheartbeat-view.jsvisar.
Reverse-proxyn framför containern strippar /_services/screens-prefixet
innan den vidarebefordrar (byggd index.html refererar redan absoluta
asset-URL:er med hela prefixet, och nginx-konfigurationen den ersätter såg
aldrig prefixet), så routorna i server.js är oprefixade: /, /assets/...,
/api/heartbeat.
Statusen ligger just nu bara i minnet i processen — det räcker för en
enskild containerinstans, men om detta någon gång körs som flera repliker
bakom en lastbalanserare känner varje replik bara till de skärmar som råkat
träffa just den, och dashboarden blir ofullständig. Byt då Map mot en
delad lagring (Redis, en databasrad, etc.).
Trailing slash: server.js matchar explicit både /api/heartbeat och
/api/heartbeat/, så det spelar ingen roll vilken kbd-bridge råkar
konfigureras med. urllib (Python, används av kbd-bridge på Pi:n) och de
flesta HTTP-klienter konverterar annars tyst en POST till en GET vid en
301/302/303-redirect och tappar hela payloaden — det var precis så vi
tappade heartbeats mot ett tidigare testendpoint. Om server.js någon gång
byts ut mot något annat, se till att den ersättningen har samma egenskap.
Bakgrundsuppdateringen är tyst och byter bara innehåll när API-svaret faktiskt ändrats. När backend stöder ETag eller Last-Modified skickar appen villkorliga headers och kan få 304 Not Modified, vilket minskar både payload och CPU.
Det här projektet använder Vite ganska nära standardflödet, men med några viktiga anpassningar.
Vite läser index.html som app-entry och laddar sedan src/main.js via:
<script type="module" src="/src/main.js"></script>Det betyder att Vite automatiskt hanterar:
- ESM-importer i JavaScript
- CSS-importer som
import './style.css' - asset-hantering för filer som JSON, bilder och fonter
I vite.config.js är base satt till ./:
export default defineConfig({
base: './',
})Det gör att byggda asset-URL:er blir relativa i stället för att anta root /. Det är viktigt när appen deployas under en nästlad sökväg, till exempel /_services/screens/, och inte som en root-app.
Under lokal utveckling proxar Vite alla anrop till /_services vidare till:
https://app.dev.j26.se
Det gör att denna runtime-bas fungerar lokalt utan extra CORS-lösningar:
/_services/cms/api/screens
Så när du kör npm run dev beter sig appen mer som i den riktiga miljön, även om du kör på localhost.
I src/main.js importeras standard-JSON så här:
import defaultSlidesUrl from './assets/j26_default.json?url'?url säger åt Vite att behandla filen som en statisk asset och ge tillbaka en URL till den byggda filen. Vid runtime hämtas den sedan med fetch(...). Det gör att samma kod fungerar både i dev och i byggd produktion.
Docker-bygget kör:
npm run build -- --base=/_services/screens/Det innebär att standardläget i vite.config.js är säkert för lokal/nästlad körning, medan container-builden explicit sätter den produktionssökväg som används vid deploy.
src/main.jsläser query-parametrar och bygger runtime-konfiguration.- Appen hämtar antingen standard-JSON eller CMS-data från
<apiBase>/<slug>/content. - Innehållet normaliseras i
src/lib/screen-content.js. - Layout och block renderas från
src/layouts/ochsrc/blocks/. - Slides roteras med
setTimeout(...)baserat på varje slides varaktighet.
- Om inget innehåll visas, börja med att kontrollera
slugi URL:en. - Om CMS-anrop fallerar lokalt, kontrollera att du kör via
npm run devså att Vite-proxyn används. - Om appen ska köras mot annan backend, sätt
apiBasei query eller miljövariabelnVITE_SCREENS_API_BASE. - Om äldre skärmar inte ska ha övergångsanimationer, använd
animation=off. - Om ni vill ändra bakgrundsuppdatering globalt, sätt
VITE_SCREENS_REFRESH_MINUTESi miljön.