### Security-Fixes (K1–K6, W1–W4, W7, N1, N5–N6, V1, V5–V7)
- K1: CSRF-Schutz via Double-Submit-Cookie (httpapi/csrf.go + csrf_helpers.go)
- K2: requireScreenAccess() in allen manage-Handlern (Tenant-Isolation)
- K3: Tenant-Check bei DELETE /api/v1/media/{id}
- K4: requirePlaylistAccess() + GetByItemID() für JSON-API Playlist-Routen
- K5: Admin-Passwort nur noch als [gesetzt] geloggt
- K6: POST /api/v1/screens/register mit Pre-Shared-Secret (MORZ_INFOBOARD_REGISTER_SECRET)
- W1: Race Condition bei order_index behoben (atomare Subquery in AddItem)
- W2: Graceful Shutdown mit 15s Timeout auf SIGTERM/SIGINT
- W3: http.MaxBytesReader (512 MB) in allen Upload-Handlern
- W4: err.Error() nicht mehr an den Client
- W7: Template-Execution via bytes.Buffer (kein partial write bei Fehler)
- N1: Rate-Limiting auf /login (5 Versuche/Minute pro IP, httpapi/ratelimit.go)
- N5: Directory-Listing auf /uploads/ deaktiviert (neuteredFileSystem)
- N6: Uploads nach Tenant getrennt (uploads/{tenantSlug}/)
- V1: Upload-Logik konsolidiert in internal/fileutil/fileutil.go
- V5: Cookie-Name als Konstante reqcontext.SessionCookieName
- V6: Strukturiertes Logging mit log/slog + JSON-Handler
- V7: DB-Pool wird im Graceful-Shutdown geschlossen
### Phase 6: Screenshot-Erzeugung
- player/agent/internal/screenshot/screenshot.go erstellt
- Integration in app.go mit MORZ_INFOBOARD_SCREENSHOT_EVERY Config
### UX: PDF.js Integration
- pdf.min.js + pdf.worker.min.js als lokale Assets eingebettet
- Automatisches Seitendurchblättern im Player
### Ansible: Neue Rollen
- signage_base, signage_server, signage_provision erstellt
- inventory.yml und site.yml erweitert
### Konzept-Docs
- GRUPPEN-KONZEPT.md, KAMPAGNEN-AKTIVIERUNG.md, MONITORING-KONZEPT.md
- PROVISION-KONZEPT.md, TEMPLATE-EDITOR.md, WATCHDOG-KONZEPT.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
18 KiB
Info-Board Neu - Template-Editor fuer globale Kampagnen
Ziel
Der Template-Editor ist ein Bereich des Admin-UI fuer die fachliche Erstellung und Verwaltung globaler Templates und deren operativen Aktivierungen als Kampagnen.
Dieses Dokument definiert:
- Welche Schritte ein Admin unternimmt, um ein Template zu erstellen
- Welche Felder und Optionen der Editor anbietet
- Wie Templates zu Kampagnen aktiviert werden
- Wie die Abbildung im Datenmodell aussieht
Grundlagen zu Template-Typen, Slot-Modell und Message-Wall finden sich in docs/TEMPLATE-KONZEPT.md.
1. Template-Verwaltung
Template-Liste
Seite: Admin → Templates
Anzeige:
Tabelle mit allen Templates:
| Name | Typ | Zielgruppe | Szenen | Erstellt | Status |
|---|---|---|---|---|---|
| Weihnachtsmotiv 2025 | full_screen_media | alle | 1 | 2025-01-15 | draft |
| Schriftzug Infowand | message_wall | wall-all | 9 | 2025-02-01 | active |
| Event-Tag 25.03 | screen_specific_scene | [info01, info02, ...] | 2 | 2025-03-01 | draft |
Aktionen pro Zeile:
- "Bearbeiten" — öffnet Template-Editor
- "Kopieren" — dupliziert als neue Draft
- "Löschen" — nur wenn keine aktiven Kampagnen
- "Vorschau" — zeigt Layout (fuer message_wall) oder Asset-Galerien
- "Aktivieren" — schneller Weg zu Kampagne starten
Template-Editor (Erstellung/Bearbeitung)
Phase 1 — Grunddaten
┌─────────────────────────────────────────┐
│ Neues Template erstellen │
├─────────────────────────────────────────┤
│ │
│ Name * │
│ [ Weihnachtsmotiv 2025_______________ ]│
│ technischer slug wird automatisch │
│ │
│ Template-Typ * │
│ ⦿ full_screen_media │
│ ○ message_wall │
│ ○ screen_specific_scene │
│ │
│ Beschreibung │
│ [ Weihnachtliche Grafik fuer alle___ ] │
│ [ Screens __________________________ ]│
│ │
│ Zielgruppe / Screens * │
│ ⦿ Alle Screens │
│ ○ Nach Gruppe auswaehlen │
│ [Dropdown: wall-all, single-all, ...] │
│ ○ Einzelne Screens auswaehlen │
│ [Checkbox-Liste mit Filterung] │
│ │
│ [Weiter >] [Abbrechen] │
└─────────────────────────────────────────┘
Validierung:
- Name ist erforderlich
- Name ist eindeutig
- Template-Typ ist erforderlich
- Zielgruppe ist erforderlich (keine leere Zuweisung)
Phase 2 — Szenen/Inhalte
Fuer full_screen_media:
┌─────────────────────────────────────────┐
│ Szenen und Inhalte │
├─────────────────────────────────────────┤
│ │
│ Szene 1: Vollbild-Grafik │
│ │
│ Medientyp * │
│ ○ Bild │
│ ○ Video │
│ ○ PDF │
│ ⦿ Webseite (HTML) │
│ │
│ Portrait-Asset (Hochformat) │
│ [Upload oder URL] │
│ [ Datei auswaehlen ] [Neue URL] │
│ oder vorher gemanagte Assets: [Liste] │
│ │
│ Landscape-Asset (Querformat) [optional] │
│ [ Datei auswaehlen ] [Neue URL] │
│ │
│ Anzeigedauer (Sekunden) │
│ [60_____] Standard: 10 │
│ │
│ Load-Timeout (Sekunden) │
│ [10_____] Standard: 10 │
│ │
│ gueltig ab │
│ [ 2025-03-25 ] [ 00:00 ] │
│ (leer = sofort gueltig) │
│ │
│ gueltig bis │
│ [ 2025-04-01 ] [ 00:00 ] │
│ (leer = unendlich) │
│ │
│ [+ Weitere Szene hinzufuegen] │
│ │
│ [Zurueck <] [Speichern & Aktivieren] │
│ [Speichern] │
│ [Abbrechen] │
└─────────────────────────────────────────┘
Fuer message_wall:
┌─────────────────────────────────────────┐
│ Message-Wall Layout │
├─────────────────────────────────────────┤
│ │
│ Layout-Template │
│ [Dropdown: 3x3-Grid, 2x2-Grid, ...] │
│ │
│ Anzeigedauer (Sekunden) │
│ [10_____] │
│ │
│ Gesamt-Grafik oder Text eingeben │
│ [Rich-Text-Editor oder Bild-Upload] │
│ │
│ Vorschau: [Zeigt Einteilung in Slots] │
│ │
│ Slot-Zuordnung: [Interaktive Zuordnung] │
│ Slot wall-r1-c1 → Screen info01 │
│ Slot wall-r1-c2 → Screen info02 │
│ ... (9 Slots insgesamt) │
│ │
│ [+ Layout-Typ aendernx] [Speichern] │
│ │
│ [Zurueck <] [Speichern & Aktivieren] │
│ [Speichern] │
│ [Abbrechen] │
└─────────────────────────────────────────┘
Fuer screen_specific_scene:
┌─────────────────────────────────────────┐
│ Monitorindividuelle Szenen │
├─────────────────────────────────────────┤
│ │
│ Szene 1: Infowand │
│ │
│ Zielgruppe │
│ ⦿ Gruppe: [Dropdown: wall-all] │
│ ○ Einzelne Screens: [Checkboxen] │
│ │
│ Asset │
│ [Upload oder URL] │
│ │
│ Dauer, Timeout, gueltig_von/bis │
│ [... wie oben ...] │
│ │
│ [+ Weitere Szene hinzufuegen] │
│ │
│ [Zurueck <] [Speichern & Aktivieren] │
└─────────────────────────────────────────┘
2. Kampagnen-Verwaltung
Kampagnen sind die operativen Instanzen von Templates.
Kampagnen-Liste
Seite: Admin → Kampagnen
Anzeige:
| Name | Template | Aktiv | Zielgruppe | gueltig von | gueltig bis | Betroffene Screens |
|---|---|---|---|---|---|---|
| Weihnachten Dekoration | Weihnachtsmotiv 2025 | ✓ | alle | 2025-12-01 | 2025-12-26 | 13 Screens |
| Schriftzug Januar | Schriftzug Infowand | ✗ | wall-all | 2025-01-06 | 2025-01-31 | 9 Screens |
Aktionen:
- "Bearbeiten" — Kampagnen-Eigenschaften aendern
- "Aktivieren/Deaktivieren" — Toggle sofort
- "Vorschau" — zeigt betroffene Screens mit Rendering
- "Duplizieugen" — als neue Kampagne mit anderem Template
- "Loeschen" — wenn inaktiv und abgelaufen
Neue Kampagne starten
Workflow Option 1 — Von Template aus:
Template-Liste → [Template] → "Aktivieren"
┌─────────────────────────────────────────┐
│ Kampagne starten: Weihnachtsmotiv 2025 │
├─────────────────────────────────────────┤
│ │
│ Kampagnen-Name │
│ [ Weihnachten 2025 einfuehrung____ ] │
│ │
│ Aktiv ab sofort? │
│ ⦿ Ja │
│ ○ Geplant fuer: [Datum/Zeit auswaehlen]│
│ [ 2025-12-01 ] [ 09:00 ] │
│ │
│ Gueltig von │
│ [ 2025-12-01 ] [ 00:00 ] │
│ │
│ Gueltig bis │
│ [ 2025-12-26 ] [ 23:59 ] │
│ │
│ Prioritaet (gegenueber Playlist) │
│ [1 (hoehere Werte sind wichtiger)] ___ │
│ │
│ Auto-Deaktivierung bei Ablauf? │
│ ⦿ Ja │
│ ○ Nein (Kampagne bleibt inaktiv) │
│ │
│ [Kampagne starten] [Abbrechen] │
└─────────────────────────────────────────┘
Workflow Option 2 — Neue Kampagne ohne Template:
Admin → Kampagnen → "+ Neue Kampagne"
[Template auswaehlen] → [Grunddaten] → [Aktivierung]
Kampagnen-Detailseite
Anzeige einer laufenden Kampagne:
Kampagne: Weihnachten 2025 einfuehrung
Status: AKTIV seit 2025-12-01 09:00
Template: Weihnachtsmotiv 2025 (full_screen_media)
Zielgruppe: Alle (13 Screens)
Gueltig: 2025-12-01 00:00 bis 2025-12-26 23:59
Prioritaet: 1
Betroffene Screens:
┌──────────────────────────────┐
│ info01 online aktiv │ [Screenshot]
│ info02 online aktiv │ [Screenshot]
│ info03 offline ausstehend │
│ info04 online aktiv │ [Screenshot]
│ ... (10 weitere) ... │
└──────────────────────────────┘
Aktionen:
[Deaktivieren] [Bearbeiten] [Vorschau aendernx]
Aktivierungsverlauf:
2025-12-01 09:00 — Kampagne gestartet von admin@...
2025-12-01 09:05 — 9 Screens haben gerendert
2025-12-01 10:30 — info03 ging offline, Kampagnen-Inhalt wartet auf Rueckkehr
3. Verknuepfung zur Prioritaetsregel
Die Regel campaign > tenant_playlist > fallback ist:
- hardcoded im Player
- administrierbar ueber die Kampagnen-Aktivierung
- vorhersagbar durch klare Doku
Abbildung im System
Fuer jeden Screen:
IF Kampagne fuer diesen Screen aktiv UND gueltig_von <= jetzt <= gueltig_bis
THEN Zeige Kampagnen-Inhalt
ELSE IF Tenant-Playlist hat gueltige Items
THEN Zeige Tenant-Playlist
ELSE
Zeige Fallback
Diese Logik wird:
- Serverseitig berechnet bei jedem Sync-Request (HTTP
/api/v1/screens/{screenSlug}/playlist) - Spielerseitig nochmals geprueft beim Rendering (fuer Offline-Robustheit)
Admin-Sichtbarkeit
Die Admin-UI zeigt auf der Seite "Screens" fuer jeden Monitor:
info01
├── Kampagne (AKTIV bis 2025-12-26)
│ └── Weihnachten 2025 einfuehrung
├── Fallback (wird nach Kampagnen-Ablauf gezeigt)
└── Tenant Playlist
├── Playlist A (Tenant XYZ)
│ ├── Bild-1 (gueltig bis 2025-04-01)
│ ├── Video-2 (laedt...)
│ └── Webseite-3
└── Fallback-Verzeichnis
Diese View zeigt, was der Screen aktuell gerade zeigt und warum.
4. Datenmodell
Tabelle templates
CREATE TABLE templates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
description TEXT,
template_type TEXT NOT NULL CHECK (template_type IN ('message_wall', 'full_screen_media', 'screen_specific_scene')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by_user_id TEXT NOT NULL,
-- Serializierte Konfiguration (JSON)
config JSONB NOT NULL DEFAULT '{}'
-- Beispiele:
-- {
-- "target_mode": "all_screens" | "group" | "specific_screens",
-- "target_group": "wall-all" (wenn target_mode = "group"),
-- "target_screen_ids": ["..."] (wenn target_mode = "specific_screens"),
-- "scenes": [
-- {
-- "media_type": "image|video|pdf|webpage|html",
-- "asset_id": "...",
-- "portrait_asset_id": "..." (optional),
-- "landscape_asset_id": "..." (optional),
-- "duration_sec": 10,
-- "load_timeout_sec": 10,
-- "valid_from": "2025-03-25T00:00:00Z",
-- "valid_until": "2025-04-01T23:59:59Z"
-- }
-- ]
-- }
);
Tabelle campaigns
CREATE TABLE campaigns (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
template_id UUID NOT NULL REFERENCES templates(id),
active BOOLEAN NOT NULL DEFAULT false,
priority INT NOT NULL DEFAULT 1,
valid_from TIMESTAMPTZ NOT NULL,
valid_until TIMESTAMPTZ,
auto_deactivate BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by_user_id TEXT NOT NULL,
-- ueberschreiben/erweitern Template-Zielgruppe (optional)
target_mode TEXT CHECK (target_mode IN ('template', 'all_screens', 'group', 'specific_screens')),
target_group TEXT,
target_screen_ids UUID[] DEFAULT '{}'::uuid[]
);
Tabelle campaign_screen_assignments (generiert)
Diese Tabelle wird serverseitig generiert/gepflegt, wenn eine Kampagne aktiv wird.
Sie expandiert Gruppen in konkrete Screen-IDs:
CREATE TABLE campaign_screen_assignments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
campaign_id UUID NOT NULL REFERENCES campaigns(id) ON DELETE CASCADE,
screen_id UUID NOT NULL REFERENCES screens(id) ON DELETE CASCADE,
assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(campaign_id, screen_id)
);
Logik:
IF campaign.target_mode = 'template'
THEN Fuelle campaign_screen_assignments aus template.config.target_screen_ids
ELSE IF campaign.target_mode = 'group'
THEN Fuelle campaign_screen_assignments aus allen Screens in campaign.target_group
ELSE IF campaign.target_mode = 'specific_screens'
THEN Fuelle campaign_screen_assignments aus campaign.target_screen_ids
ELSE
(alle Screens)
5. Praxis-Beispiele
Beispiel 1 — Weihnachtsplakatierung (full_screen_media)
Szenario:
Admin will ab 01.12.2025 fuer 4 Wochen ein rotes Weihnachtsmotiv auf allen Screens zeigen.
Schritte:
-
Admin → Templates → "+ Neues Template"
- Name:
Weihnachtsmotiv 2025 - Typ:
full_screen_media - Zielgruppe:
Alle Screens
- Name:
-
Szene hinzufuegen:
- Bild hochladen (passend fuer Portrait und Landscape)
- Dauer: 10 Sekunden
-
Speichern → Editor zeigt Draft mit Vorschau
-
Admin → Templates → [Weihnachtsmotiv 2025] → "Aktivieren"
- Kampagnen-Name:
Weihnachten 2025 globale Dekoration - Gueltig von: 2025-12-01
- Gueltig bis: 2025-12-26
- Aktiv ab: sofort
- Kampagnen-Name:
-
Kampagne speichern → Sofort sichtbar auf allen Screens
Beispiel 2 — Schriftzug ueber die Infowand (message_wall)
Szenario:
Admin hat eine neue message_wall-Gruppe "wall-all" mit 9 Screens. Er will ein riesiges rotes Schriftzug-Motiv aufteilen und auf allen 9 Screens verteilen.
Schritte:
-
Admin → Templates → "+ Neues Template"
- Name:
Rotes Schriftzug auf Infowand - Typ:
message_wall - Zielgruppe:
Gruppe: wall-all
- Name:
-
Layout waehlen:
3x3-Grid(passt zu 9 Screens) -
Gesamte Grafik hochladen (oder als Text eingeben)
-
Slot-Zuordnung:
- System zeigt interaktive 3x3-Vorschau
- Admin tuen: "Slot 1 → info01", "Slot 2 → info02", ...
- System generiert automatisch die Crop-Regionen
-
Speichern + Aktivieren
- Jeder Screen zeigt seinen Ausschnitt
Beispiel 3 — Deaktivierung und Fallback
Szenario:
Kampagne laueft seit 2 Wochen. Admin will sie sofort stoppen, damit Screens auf ihre normalen Playlists zurueckfallen.
Aktion:
Admin → Kampagnen → [Kampagne] → "Deaktivieren"
Folge:
- Server setzt
campaigns.active = false - Bei naechstem Sync ladet jeder Player wieder die Tenant-Playlist
- Fallback-Verzeichnis wird nur noch angezeigt, wenn tenantbezogene Playlist leer ist
6. Zusammenfassung
Der Template-Editor:
- ist zwei-stufig — Template-Verwaltung + Kampagnen-Aktivierung
- ist intuitiv — Multi-Step-Formulare mit Vorschauen
- unterstützt alle Template-Typen — full_screen, message_wall, screen_specific
- haelt die Prioritaetsregel transparent — Admin sieht, welche Kampagne welche Screens uebersteuert
- ist zukunftssicher — Datenmodell skaliert mit neuen Template-Typen