No description
  • HTML 68.5%
  • Go 31.3%
Find a file
thiel 3e8146df55
All checks were successful
CI / test (push) Successful in 18s
Container image / image (push) Successful in 1m19s
CI / release (push) Successful in 24s
Server-Referenzzeit fuer geraeteunabhaengige Uhr-/Timer-Sync
Manifest-Antworten tragen jetzt X-Server-Now (Server-Wanduhr, Epoch ms).
Der Player berechnet daraus einen geglaetteten Offset (RTT/2-korrigiert)
und zeigt via nowMs()=Date.now()+Offset in allen Sekunden-Widgets
(Uhr, Countdown, Analog, Weltuhr, Tage-seit) sowie in der Wanduhr-/
Video-Sync die identische Server-Zeit - unabhaengig vom NTP-Stand des
einzelnen Geraets. Ergaenzt clockAlign (Sekundengrenze) um die
gemeinsame Zeitbasis.

- api.go: writeManifest() stempelt X-Server-Now auf alle 4 Manifest-Ausgaben
- frontend/player.html: nowMs()/syncClockOffset(); alle Zeitlesestellen + clockAlign + syncClock-elapsed + Video-Sync auf nowMs(); poll liest Header
2026-07-14 04:43:39 +00:00
.forgejo/workflows CI: Image-Push via REGISTRY_TOKEN (Paket-Scope) statt Auto-Token 2026-07-05 07:08:24 +00:00
cmd/server Alarm: Empfaenger je Eskalationsstufe (ALERT_ESCALATION_EMAILS, additiv, Recipients-Helfer) 2026-07-13 15:19:41 +00:00
docs Alarm: Empfaenger je Eskalationsstufe (ALERT_ESCALATION_EMAILS, additiv, Recipients-Helfer) 2026-07-13 15:19:41 +00:00
frontend Server-Referenzzeit fuer geraeteunabhaengige Uhr-/Timer-Sync 2026-07-14 04:43:39 +00:00
internal Server-Referenzzeit fuer geraeteunabhaengige Uhr-/Timer-Sync 2026-07-14 04:43:39 +00:00
.dockerignore Server liefert Builder unter / (go:embed), Builder-API-Basis same-origin; .dockerignore/compose/Deploy-Doku fuer docker compose up 2026-07-05 07:21:55 +00:00
.env.example Admin-Authentifizierung (Passwort-Login + Session-Token) 2026-07-04 21:27:38 +00:00
.gitignore Go-Backend Slice 1: API + PostgreSQL + Docker + CI 2026-07-04 20:18:56 +00:00
docker-compose.yml Alarm: Empfaenger je Eskalationsstufe (ALERT_ESCALATION_EMAILS, additiv, Recipients-Helfer) 2026-07-13 15:19:41 +00:00
Dockerfile PDF-Slideshow-Widget (pdf): server-seitige Rasterisierung -> Bild-Slideshow 2026-07-11 17:00:13 +00:00
go.mod QR-Code-Widget: serverseitiges Encoding (go-qrcode), Matrix im Manifest, /api/qr-Vorschau, Builder-Editor+Live-Vorschau 2026-07-05 07:44:26 +00:00
go.sum QR-Code-Widget: serverseitiges Encoding (go-qrcode), Matrix im Manifest, /api/qr-Vorschau, Builder-Editor+Live-Vorschau 2026-07-05 07:44:26 +00:00
Makefile Go-Backend Slice 1: API + PostgreSQL + Docker + CI 2026-07-04 20:18:56 +00:00
README.md docs: Onboarding-Leitfaden, Monitoring-Handbuch, Widget-Katalog + README auf aktuellen Stand 2026-07-13 12:33:59 +00:00

signagetools

Werkzeug zur Vorbereitung und Ausrollung von Digital-Signage-Programmen auf ein oder mehrere Displays mit Ablaufplänen, zeitlicher Steuerung und zusammenhängenden Zeitplänen.

Repos

Das Projekt ist auf zwei Repos aufgeteilt:

  • signagetools (dieses Repo) Server (Go-API), Scheduling-Logik und Admin-Frontend inkl. Builder (frontend/builder.html). Der Builder ist Authoring/Admin und gehört zur Server-Seite.
  • signagetools-player der On-Device-Player (Go-Agent + Player-UI), der auf Pi/Mini-PC läuft. Eigenes Deployable, eigener Release-Zyklus.

Die Design-Dokumente (docs/) leben zentral hier und gelten für beide.

Status

Design-Fundament steht (Features, Architektur, Scheduling, Datenmodell). Server-MVP (Slice 1) läuft: Go-API mit PostgreSQL, Layouts speichern/laden, Medien-Upload, Manifest-Endpunkt, docker-compose und CI. Der interaktive Screen-Builder (frontend/builder.html) ist an die API angebunden (Speichern/Öffnen + Bild-Upload). Der Player-Agent (Repo signagetools-player) zieht das Manifest, cached Medien lokal und rendert im Kiosk — end-to-end getestet inkl. Offline-Weiterbetrieb. Damit steht die ganze Kette Builder → Server → Player.

Iteration 2 (Zeit-Scheduling) ist drin: Resolver mit Prioritäten, Overrides, Dayparting, Wochentagen und automatischem Rückfall auf den globalen Basisplan; Zeitplan-Editor im Builder; Player zieht ein zeitlich aufgelöstes Screen-Manifest.

Geräte-Enrollment + Player-Auth ist drin: neuer Player meldet sich an, zeigt einen Kopplungscode, wird im Builder unter „Geräte" freigegeben und erhält ein Token; Content-Pulls laufen ab dann Token-authentifiziert, Sperren wirkt sofort.

Admin-Authentifizierung ist drin: ist ADMIN_PASSWORD gesetzt, verlangen alle Verwaltungs-Endpunkte (Layouts, Zeitplan, Medien, Geräte) einen Login; der Builder zeigt dann ein Passwortfeld und schickt ein Session-Token mit. Ohne Passwort bleibt alles offen (Entwicklung) — der Server warnt beim Start. Player-Endpunkte (Enroll, Token, Manifest, Medien) bleiben bewusst außerhalb dieses Schutzes.

Bewusst noch offen: TLS (via Reverse-Proxy davor) und ein feineres Rollen-/Nutzer­modell statt eines gemeinsamen Passworts.

Builder-Ausbau ist drin: Mediathek (Uploads durchsuchen und wiederverwenden, inkl. Video), Layout-Verwaltung (Neu, Duplizieren, Löschen aus der Serverliste), Zonen duplizieren/ausrichten, Playlist-Elemente duplizieren, Text-Styling (Farbe, Größe, horizontale/vertikale Ausrichtung), Bild-/Video-Fit (cover/contain) und Video als Inhaltstyp — alle Stil-Optionen werden persistiert und vom Player umgesetzt.

Weiter offen: Zonen-Verteilen (Mehrfachauswahl) und Widget-Inhalte (Uhr, Wetter, RSS).

Kaskadierende Zeitplan-Auflösung ist drin: Standorte und Gerätegruppen, Geräte-Zuordnung (Standort + Gruppen) im Builder, und ein Resolver, der für ein Gerät alle Ebenen berücksichtigt (Gerät > Screen > Gruppe > Standort > global; Priorität schlägt Spezifität). Damit ist das Datenmodell aus datenmodell.md funktional vollständig.

Monitoring (Basis) ist drin: die Geräteliste zeigt Online-Status (aus last_seen, Fenster 90 s), „zuletzt gesehen", das gerade aufgelöste Layout („zeigt: …") und eine Online-Zusammenfassung; der Geräte-Dialog aktualisiert sich alle 10 s selbst.

Proof-of-Play (Basis) ist drin: der Server schreibt beim Manifest-Abruf intervallbasiert mit, welches Gerät wann welches Layout gezeigt hat (nur bei Layout-Wechsel eine neue Zeile); der Geräte-Dialog zeigt je Gerät ein „Protokoll" mit vonbis und Dauer. GET /api/playlog?device=&limit=.

Benutzer & Rollen sind drin: neben dem Bootstrap-Admin (ADMIN_PASSWORD) gibt es benannte Konten mit bcrypt-Hash und den Rollen viewer (nur ansehen), editor (Inhalte/Geräte) und admin (inkl. Benutzerverwaltung). Die Anmeldung wird aktiv, sobald ein Passwort gesetzt ist oder ein Konto existiert. Betrieb, TLS/Reverse-Proxy und Rollen sind in docs/deployment.md beschrieben.

Uhr-Widget ist drin: ein Element-Typ clock zeigt die lokale Player-Zeit, tickt sekündlich und lässt sich wie Text stylen (Farbe/Größe/Ausrichtung). Formate: Uhrzeit, Uhrzeit mit Sekunden, Datum, Uhrzeit + Datum, Wochentag + Datum.

Zonen anordnen ist drin: mehrere Zonen per Häkchen oder Shift-Klick wählen und gemeinsam ausrichten (Kanten/Mitte), gleichmäßig verteilen (ab 3 Zonen) oder auf gleiche Breite/Höhe bringen.

Wetter-Widget ist drin: ein Element-Typ weather zeigt das aktuelle Wetter (Open-Meteo, ohne API-Key) für einen Ort (Suche oder Koordinaten im Builder). Der Player holt und cached die Daten selbst (offline-resilient, Aktualisierung alle 10 min), gestylt wie Text.

RSS-Ticker ist drin: ein Element-Typ ticker zeigt ein Laufband mit Schlagzeilen aus einem RSS/Atom-Feed. Der Player holt, parst und cached die Schlagzeilen selbst (offline-resilient, Aktualisierung alle paar Minuten) und scrollt sie flüssig.

Status-Einblendung ist drin: war der Server länger nicht erreichbar, zeigt der Player ein dezentes Eck-Badge („Server nicht erreichbar · zeige Cache · vor X"), sonst nichts. So sieht man im Feld sofort, ob ein Bildschirm auf altem Cache läuft. Schwelle: max. 90 s bzw. das Dreifache des Poll-Intervalls.

Aktueller Stand: Der Funktionsumfang ist seither stark gewachsen. Es gibt über 30 Element-Typen (u. a. Wetter + Vorhersage mit Regen/Wind, Analoguhr, Kalender/Raumstatus mit Feiertagen, Tabelle mit Live-Daten und Zellen-Ampel, Fortschritt, Countdown, QR, Feiertage-Widget) — der vollständige Überblick in docs/widgets.md. Dazu Betriebszeiten/Standby je Layout und Zeit-Synchronisation mehrerer Player im Gleichtakt. Der Player-Agent hat eine Browser-Ersteinrichtung (Doppelklick → koppeln, kein Kommandozeilen- Flag nötig) und wird als fertige Binaries (inkl. Windows-.exe) über die Releases des Player-Repos verteilt.

Dokumentation

Schnellstart (Server)

docker compose up --build   # startet Server (:8080) + PostgreSQL
curl localhost:8080/healthz

Lokal ohne Container: PostgreSQL bereitstellen, .env.example nach .env kopieren, dann make run.

API (Slice 1)

  • GET /healthz, GET /api/version
  • GET /api/layouts, POST /api/layouts, GET|DELETE /api/layouts/{id}
  • GET /api/layouts/{id}/manifest — Manifest für ein festes Layout
  • GET /api/schedule, POST /api/schedule, DELETE /api/schedule/{id} — Zeitplan
  • GET /api/screens/{target}/manifestzeitlich aufgelöstes Manifest (was ein Player zieht)
  • GET /api/resolve?target=&at= — Debug: welches Layout gälte wann
  • GET /api/media, POST /api/media (multipart file), GET /media/{id}/{name}

Admin-Auth (aktiv nur mit gesetztem ADMIN_PASSWORD)

  • GET /api/auth/status{authRequired, authenticated}
  • POST /api/auth/login ({password}) → {token}; POST /api/auth/logout
  • Verwaltungs-Endpunkte (Layouts/Zeitplan/Medien/Geräte) verlangen dann Authorization: Bearer <token>.

Geräte / Enrollment

  • POST /api/devices/enroll{deviceId, pairingCode} (Player-Bootstrap)
  • GET /api/devices/token?deviceId=&code= — Player holt sein Token nach Freigabe
  • GET /api/devices/manifestToken-authentifiziert (Bearer), zeitlich aufgelöst
  • GET /api/devices, POST /api/devices/{id}/approve ({name,target,locationId,groupIds}), POST /api/devices/{id}/assign, POST /api/devices/{id}/revoke, DELETE /api/devices/{id}

Standorte & Gruppen

  • GET/POST /api/locations, DELETE /api/locations/{id}
  • GET/POST /api/groups, DELETE /api/groups/{id}
  • Zeitplan-Einträge zielen per scope auf global | location | group | device | screen; das Gerät-Manifest löst kaskadierend auf: Gerät > Screen > Gruppe > Standort > global (bei Gleichstand höhere Priorität, dann neuer). Debug: GET /api/resolve?device=<id>.

Das POST /api/layouts-Format entspricht exakt dem JSON aus dem Builder.

Dokumente

Priorisierung (Legende)

  • [M] Muss Kernfunktion, ohne die das Tool seinen Zweck nicht erfüllt.
  • [S] Soll wichtig, macht den Unterschied zwischen „solide" und „richtig gut".
  • [K] Kann nice-to-have, spätere Ausbaustufe.