Screenshot-Tools (scrot, imagemagick, x11-apps) und DISPLAY/XAUTHORITY
im systemd-Service in DEVELOPMENT.md ergänzt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
scrot braucht X11-Zugang für Screenshots. Der systemd-Service hatte
keine DISPLAY-Variable, weshalb alle Screenshot-Versuche fehlschlugen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
scrot, imagemagick und x11-apps werden jetzt automatisch auf allen
signage_players installiert. Außerdem MQTT_USER/PASS in compose auf
MQTT_USERNAME/PASSWORD korrigiert (passt zu den Backend-Env-Var-Namen).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Server gibt bei POST /api/v1/player/status jetzt mqtt-Block zurück (broker,
username, password) wenn MORZ_INFOBOARD_MQTT_BROKER gesetzt ist. Agents
parsen die Response und verbinden sich bei Config-Änderung automatisch neu
(applyMQTTConfig mit Reconnect-Logik, thread-safe via Mutex).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
go build hatte changed_when: true aber kein notify — Handler wurde
nie ausgelöst, neues Binary blieb ohne Dienst-Neustart wirkungslos.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GET /manage: neue Übersichtsseite mit Bulma-Karten für screen_user mit ≥2 Screens
- handleScreenUserRedirect leitet bei ≥2 Screens auf /manage statt auf ersten Screen
- On-Demand-Screenshot-Flow via MQTT:
- Backend publiziert signage/screen/{slug}/screenshot-request beim Seitenaufruf
- Player-Agent empfängt Topic, ruft TakeAndSendOnce() auf
- Player POST /api/v1/player/screenshot → Backend speichert in ScreenshotStore (RAM)
- GET /api/v1/screens/{screenId}/screenshot liefert gespeichertes Bild (authOnly)
- ScreenshotStore: In-Memory, thread-safe, kein Persistenz-Overhead
- JS-Retry nach 4s in Templates (Screenshot braucht 1-3s für MQTT-Roundtrip)
- manageTmpl zeigt Screenshot-Thumbnail beim Einzelscreen-Aufruf
- Doku: neue Endpoints, MQTT-Topics, Screenshot-Flow in SERVER-KONZEPT.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- HandleManageUI übergibt CSRFToken korrekt ans Template (leeres Hidden-Field
blockierte JS-Inject-Snippet)
- HandleTenantDashboard setzt CSRF-Cookie und befüllt CSRFToken in Template-Daten
- tenant/csrf_helpers.go: setCSRFCookie im tenant-Package (Import-Cycle-Isolation)
- Logout-Formular in tenantDashTmpl hat jetzt statisches CSRF-Hidden-Field
- Doku: POST /logout und POST /login mit CSRF-Anforderungen dokumentiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
printf "%q" im Go-Template erzeugte Go-quoted Strings ("..."), die als
Teil der screen_id an die DB übergeben wurden. FK-Constraint schlug fehl,
weil die ID mit eingebetteten Quotes keiner screens-Zeile entsprach.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fehlende slog.Error-Aufrufe in HandleAddUserToScreen, HandleCreateScreenUser,
HandleDeleteScreenUser und HandleRemoveUserFromScreen ergänzt — DB-Fehler
wurden bisher komplett geschluckt und waren nicht diagnostizierbar.
Tenant-Lookup in EnsureAdminUser und CreateScreenUser aus SQL-Subqueries
in eigene Queries extrahiert für bessere Fehlermeldungen bei fehlendem Tenant.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
HandleLoginPost renderte Fehlerseiten (falsches Passwort, leere Felder) ohne
CSRFToken in den Template-Daten. Das hidden field <csrf_token> war leer, sodass
jeder weitere Submit-Versuch mit "Ungültiger CSRF-Token" scheiterte.
Fix: setCSRFCookie am Anfang des Handlers aufrufen und das Token in allen
renderError-Pfaden an das Template übergeben.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Neue Rolle screen_user: User können sich einloggen und nur ihre
zugeordneten Bildschirme verwalten. Admins behalten vollen Zugriff.
- Migration 003: users.role-Spalte + user_screen_permissions (M:N)
- Store: CreateScreenUser, ListScreenUsers, DeleteUser,
GetAccessibleScreens, HasUserScreenAccess,
AddUserToScreen, RemoveUserFromScreen, GetScreenUsers
- Middleware: RequireScreenAccess enforces screen-level access
für alle /manage/{screenSlug}-Routen
- 4 neue Admin-Handler: CreateScreenUser, DeleteScreenUser,
AddUserToScreen, RemoveUserFromScreen (+4 Routes)
- Admin-UI: Tab "Benutzer" (anlegen/löschen) + Screen-User-Modal
(User zuordnen/entfernen) direkt in der Bildschirm-Tabelle
- Login: screen_user wird nach Login zum ersten zugänglichen Screen
weitergeleitet; kein Zugang zu /admin
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Admin users continue to redirect to /manage/ as before. Tenant users
now land on their own dashboard at /tenant/{slug}/dashboard instead of
the incorrect /manage/{slug} path. The fix applies to both the
already-logged-in check in HandleLoginUI and the post-login switch in
HandleLoginPost.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Neue Packages und Module:
- fileutil: Shared Upload-Logik mit Tenant-Isolation
- httpapi/csrf.go: Double-Submit-Cookie CSRF-Schutz
- httpapi/ratelimit.go: Rate-Limiting für /login
- httpapi/uploads.go: neuteredFileSystem (kein Directory-Listing)
- httpapi/manage/csrf_helpers.go: CSRF-Helpers für Templates
- player/agent/internal/screenshot/screenshot.go: Periodische Screenshot-Erfassung
Neue Umgebungsvariablen:
- MORZ_INFOBOARD_REGISTER_SECRET: Pre-Shared-Secret für Agent-Registrierung
- MORZ_INFOBOARD_SCREENSHOT_EVERY: Screenshot-Intervall im Player-Agent (Sekunden)
Dokumentation aktualisiert:
- server/backend/README.md: Neue Packages und Env-Variable REGISTER_SECRET
- DEVELOPMENT.md: Beide neuen Env-Variablen mit Erklärungen
- docs/API-ENDPOINTS.md: Screenshot-Endpoint als "In Vorbereitung" dokumentiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. [SQL] Fix username uniqueness constraint
- Changed from global unique to composite unique(tenant_id, username)
- Multi-tenant apps need same usernames across tenants (e.g., each tenant can have 'admin')
2. [Go] Fix inconsistent error handling in scanSession
- Now returns pgx.ErrNoRows when session not found (like scanUser)
- Allows proper 404 vs 500 error distinction in handlers
3. [Go] Add missing VerifyPassword function
- Implements bcrypt.CompareHashAndPassword for password verification
- Enables login flow with proper error handling for missing users
- Paired with existing GenerateFromPassword for secure password hashing
Security checks:
- SQL injection: All queries parameterized (no string interpolation)
- bcrypt: Cost factor 12 (production-recommended)
- Session tokens: PostgreSQL gen_random_uuid() (cryptographically secure)
- Password hashes: Protected with json:"-" tag (never exposed in responses)
- Error handling: Comprehensive, no silent failures
Build & Vet: All checks pass (go build ./..., go vet ./...)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PDF-URLs bekommen #toolbar=0&navpanes=0&scrollbar=0&view=Fit&page=1
angehängt, damit Chromium den PDF-Viewer ohne Sidebar und Toolbar
im Vollbild rendert. PDF.js als Folgeschritt in TODO dokumentiert.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Agent ergänzt relative src-Pfade (/uploads/...) mit ServerBaseURL
beim Playlist-Fetch, damit Chromium Medien direkt vom Backend lädt
statt 404 auf dem lokalen Agent-Server zu bekommen.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- hideAllContent() prüft opacity bevor display=none gesetzt wird
(verhindert Race mit displayItem)
- Neuer /api/startup-token Endpoint: Browser erkennt Agent-Neustart
und reloaded automatisch
- MQTT-Debounce von 3s auf 500ms, Browser-Poll von 30s auf 5s
reduziert für sub-sekunden Playlist-Updates
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend published auf signage/screen/{slug}/playlist-changed nach
Playlist-Mutationen (2s Debounce). Agent subscribed und fetcht
Playlist sofort (3s Debounce). 60s-Polling bleibt als Fallback.
Neue Packages: mqttnotifier (Backend), mqttsubscriber (Agent)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Die Transition-Logik in displayItem() setzte element.style.display = '',
wodurch die CSS-Klassen-Regel display:none wieder griff und alle
Content-Elemente (iframe, img, video) unsichtbar blieben.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Upload-Fortschrittsbalken per XHR mit Progress-Event
- Checkbox-Toggle statt Ja/Nein-Select für Enabled-Feld
- vars.yml Download-Button im Provisioning-Workflow
- Alle UX-Aufgaben in TODO.md abgehakt
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Flash-Messages nach allen Manage-Aktionen (Upload, Löschen, Speichern, Hinzufügen)
- Screen-Online/Offline-Status als farbiger Punkt in Admin-Tabelle
- overflow-x Wrapper für alle Tabellen (Admin, Playlist, Medienbibliothek)
- Navbar-Burger für mobile Viewports in Admin und Manage
- UX-Gestaltungsplan als Sektion in TODO.md eingetragen
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Player-UI: Content-Type-Handling (image/video/web statt alles-iframe),
Fast-Retry-Polling beim Start, Splash wird korrekt ausgeblendet,
Fallback-Anzeige bei X-Frame-Options-Blockade
- Dev-Display: Backend-URL auf 192.168.64.1 für Multipass-Netz korrigiert
- Media-Upload: Typ wird aus MIME-Type abgeleitet statt blind aus Formular
- TODO: Daten-Bug dokumentiert
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
README, DEVELOPMENT und TODO spiegelten noch den Stand vor Ebene 1+2 wider.
Checkboxen in TODO von ~18 auf ~70 aktualisiert, drei neue API-Dokumentationsdateien ergänzt.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- POST /admin/screens/provision: legt Screen in DB an (Upsert) und zeigt
eine 5-Schritt-Seite mit kopierbaren Code-Blöcken:
1. inventory.yml Eintrag
2. host_vars/{slug}/vars.yml Inhalt
3. ssh-copy-id Befehl
4. ansible-playbook Befehl (mit Vault-Passwort-Hinweis)
5. Link zur Playlist-Verwaltung
- Admin-Formular: IP-Adresse + SSH-User Felder ergänzt
- Altes "nur anlegen"-Formular als aufklappbaren Details-Block versteckt
- Clipboard-Copy-Buttons für jeden Code-Block
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Backend-Container mit Dockerfile aus server/backend/
- Postgres healthcheck damit Backend erst startet wenn DB bereit ist
- uploads-Volume für hochgeladene Dateien
- MORZ_INFOBOARD_DATABASE_URL zeigt auf postgres-Service
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Agent loggt im Normalfall nichts mehr (kein heartbeat_tick, kein
mqtt_heartbeat_sent, kein status_report_sent)
- nur noch Fehler und Zustandsaenderungen werden geloggt
- Ansible: journald auf Storage=volatile + RuntimeMaxUse=20M (RAM-only,
automatisches Verdraengen alter Eintraege bei vollem Puffer)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rolle signage_player: baut Binary lokal (linux/arm64), deployt es,
schreibt config.json per Template, installiert und aktiviert systemd-Unit
- inventory.yml mit Host info10 (10.0.0.200)
- group_vars/signage_players: getrennte vars.yml (oeffentlich) und
vault.yml (Secrets, gitignored) fuer MQTT-Credentials
- host_vars/info10: ansible_host, ansible_user, screen_id
- site.yml zeigt auf signage_players-Gruppe und signage_player-Rolle
- Binaries und vault.yml in .gitignore
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DELETE /api/v1/screens/{screenId}/status loescht einzelne Screen-Eintraege
- /api/v1/meta listet jetzt 5 Tools inkl. screen-status-delete und diagnostic_ui-Pfade
- filePlayerStatusStore persistiert den Status-Store atomar in einer JSON-Datei
- MORZ_INFOBOARD_STATUS_STORE_PATH aktiviert die Datei-Persistenz (leer = In-Memory)
- Integration-Test deckt den vollstaendigen Lifecycle: POST -> list -> HTML -> JSON -> DELETE -> 404 ab
- DEVELOPMENT.md beschreibt End-to-End-Entwicklungstest und neue Env-Variable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GET /api/v1/screens/status und GET /status akzeptieren jetzt
derived_state=online|degraded|offline zum direkten Filtern nach der
serverseitig abgeleiteten Diagnoseeinschaetzung. Erlaubte Werte sind
online, degraded und offline; unknown ist explizit nicht erlaubt, da
derived_state immer auf einen der drei Werte abgebildet wird.
Abgrenzung zu server_connectivity: derived_state filtert nach dem
zusammengefassten Zustand (stale + connectivity + status), waehrend
server_connectivity nur den gemeldeten Connectivity-Wert betrifft.
Beide Filter koennen kombiniert werden.
Tests: FiltersByDerivedState, RejectsInvalidDerivedState
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GET /api/v1/screens/status und GET /status akzeptieren jetzt q=<substring>
zum Filtern der Ergebnisliste nach ScreenID. Der Vergleich ist case-
insensitiv. Leerer Wert bedeutet kein Filter; jeder andere String ist gueltig
(keine Validierung noetig). Die Summary-Counts bleiben unveraendert und
beschreiben weiterhin den gesamten Store-Bestand.
Die Quick-Filter auf /status behalten den aktuellen q-Wert beim Klick, damit
der Textfilter nicht verloren geht wenn man z.B. von "All screens" auf
"Stale reports" wechselt.
Tests: FiltersByScreenIDSubstring, ScreenIDFilterIsCaseInsensitive
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>