### 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>
494 lines
18 KiB
Markdown
494 lines
18 KiB
Markdown
# 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:
|
|
|
|
1. **Serverseitig** berechnet bei jedem Sync-Request (HTTP `/api/v1/screens/{screenSlug}/playlist`)
|
|
2. **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`
|
|
|
|
```sql
|
|
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`
|
|
|
|
```sql
|
|
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:
|
|
|
|
```sql
|
|
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:**
|
|
|
|
1. Admin → Templates → "+ Neues Template"
|
|
- Name: `Weihnachtsmotiv 2025`
|
|
- Typ: `full_screen_media`
|
|
- Zielgruppe: `Alle Screens`
|
|
|
|
2. Szene hinzufuegen:
|
|
- Bild hochladen (passend fuer Portrait und Landscape)
|
|
- Dauer: 10 Sekunden
|
|
|
|
3. Speichern → Editor zeigt Draft mit Vorschau
|
|
|
|
4. Admin → Templates → [Weihnachtsmotiv 2025] → "Aktivieren"
|
|
- Kampagnen-Name: `Weihnachten 2025 globale Dekoration`
|
|
- Gueltig von: 2025-12-01
|
|
- Gueltig bis: 2025-12-26
|
|
- Aktiv ab: sofort
|
|
|
|
5. 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:**
|
|
|
|
1. Admin → Templates → "+ Neues Template"
|
|
- Name: `Rotes Schriftzug auf Infowand`
|
|
- Typ: `message_wall`
|
|
- Zielgruppe: `Gruppe: wall-all`
|
|
|
|
2. Layout waehlen: `3x3-Grid` (passt zu 9 Screens)
|
|
|
|
3. Gesamte Grafik hochladen (oder als Text eingeben)
|
|
|
|
4. Slot-Zuordnung:
|
|
- System zeigt interaktive 3x3-Vorschau
|
|
- Admin tuen: "Slot 1 → info01", "Slot 2 → info02", ...
|
|
- System generiert automatisch die Crop-Regionen
|
|
|
|
5. 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
|