### 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>
535 lines
16 KiB
Markdown
535 lines
16 KiB
Markdown
# Info-Board Neu - Gruppierungs- und Slot-Modell fuer monitoruebergreifende Layouts
|
|
|
|
## Ziel
|
|
|
|
Dieses Dokument definiert, wie Screens in Gruppen und Slots organisiert werden.
|
|
|
|
Gruppen und Slots sind notwendig fuer:
|
|
|
|
- **Massenaktionen** — mehrere Screens mit einer Kampagne ansprechen
|
|
- **Monitorwaende** — Schriftzuege und Layouts auf mehrere Screens verteilen
|
|
- **zukuenftige Skalierbarkeit** — neue Displays ohne Neustrukturierung hinzufuegen
|
|
|
|
Siehe auch `docs/TEMPLATE-KONZEPT.md` fuer Template-Typen, die Gruppen/Slots verwenden.
|
|
|
|
## 1. Screen-Gruppen
|
|
|
|
### Konzept
|
|
|
|
Eine Gruppe ist eine semantische Zusammenfassung mehrerer Screens.
|
|
|
|
**Beispiele:**
|
|
|
|
- `all` — alle Screens im System
|
|
- `wall-all` — alle 9 Infowand-Screens
|
|
- `wall-row-1` — die 3 Screens der ersten Reihe
|
|
- `wall-row-2` — die 3 Screens der zweiten Reihe
|
|
- `single-all` — alle Einzelanzeigen (z.B. Vertretungsplan-Displays)
|
|
- `outdoor` — alle Aussenanzeigetafeln
|
|
|
|
### Typen von Gruppen
|
|
|
|
#### Physische Gruppen
|
|
|
|
Spiegeln die **reale Anordnung** wider:
|
|
|
|
- `wall-all` — alle Displays einer Infowand
|
|
- `wall-row-1`, `wall-row-2`, `wall-row-3` — Reihen einer Wand
|
|
- `wall-column-1`, `wall-column-2`, `wall-column-3` — Spalten einer Wand
|
|
|
|
#### Funktionale Gruppen
|
|
|
|
Spiegeln den **Verwendungszweck** wider:
|
|
|
|
- `main-hall-all` — alle Displays im Hauptkorridor
|
|
- `cafeteria-all` — alle Displays in der Kaffeteria
|
|
- `info-all` — alle Informationsanzeigen
|
|
|
|
#### Typen-Gruppen
|
|
|
|
Spiegeln das **Geraetemodell** wider:
|
|
|
|
- `portrait-all` — alle Displays im Hochformat
|
|
- `landscape-all` — alle Displays im Querformat
|
|
- `4k-displays` — nur 4K-Monitore
|
|
|
|
#### Tenant-Gruppen (Phase 2)
|
|
|
|
Spiegeln die **Mandanten-Zugehoerigkeit** wider:
|
|
|
|
- `tenant-xyz-all` — alle Displays fuer Mandant XYZ
|
|
- `tenant-xyz-public` — nur oeffentliche Displays des Mandants
|
|
|
|
### Hierarchische Struktur
|
|
|
|
Gruppen koennen verschachtelt sein:
|
|
|
|
```
|
|
all
|
|
├── wall-all
|
|
│ ├── wall-row-1
|
|
│ │ ├── info01
|
|
│ │ ├── info02
|
|
│ │ └── info03
|
|
│ ├── wall-row-2
|
|
│ │ ├── info04
|
|
│ │ ├── info05
|
|
│ │ └── info06
|
|
│ └── wall-row-3
|
|
│ ├── info07
|
|
│ ├── info08
|
|
│ └── info09
|
|
├── single-all
|
|
│ ├── info10 (Vertretungsplan 1)
|
|
│ └── info11 (Vertretungsplan 2)
|
|
└── fallback-displays
|
|
└── [none currently]
|
|
```
|
|
|
|
**Automatische Inferenz:**
|
|
|
|
Ein Screen kann in mehreren Gruppen sein:
|
|
|
|
```
|
|
info01:
|
|
- all
|
|
- wall-all
|
|
- wall-row-1
|
|
- portrait-all
|
|
- online-displays (automatisch basierend auf Status)
|
|
```
|
|
|
|
## 2. Slot-Modell
|
|
|
|
### Konzept
|
|
|
|
Slots beschreiben **feste Positionen innerhalb eines Layouts**.
|
|
|
|
Sie werden hauptsaechlich fuer `message_wall`-Templates verwendet, um Ausschnitte von Grossmotiven auf einzelne Screens zu verteilen.
|
|
|
|
**Beispiel: 3x3 Infowand**
|
|
|
|
```
|
|
┌─────────────────────────────────┐
|
|
│ [0,0] [0,1] [0,2] │ Slot wall-r1-c1, wall-r1-c2, wall-r1-c3
|
|
├─────────────────────────────────┤
|
|
│ [1,0] [1,1] [1,2] │ Slot wall-r2-c1, wall-r2-c2, wall-r2-c3
|
|
├─────────────────────────────────┤
|
|
│ [2,0] [2,1] [2,2] │ Slot wall-r3-c1, wall-r3-c2, wall-r3-c3
|
|
└─────────────────────────────────┘
|
|
```
|
|
|
|
**Slot-Nomenclatur:**
|
|
|
|
- `wall-r{reihe}-c{spalte}` (Zeile/Spalte im 0er-System oder 1er-System)
|
|
- `wall-slot-{nummer}` (durchnummeriert, z.B. wall-slot-0 bis wall-slot-8)
|
|
|
|
### Geometrische Definition
|
|
|
|
Fuer jeden Slot wird definiert:
|
|
|
|
```json
|
|
{
|
|
"slot_id": "wall-r1-c1",
|
|
"row": 0,
|
|
"col": 0,
|
|
"layout_name": "3x3_grid",
|
|
"crop_x": 0,
|
|
"crop_y": 0,
|
|
"crop_width": 640,
|
|
"crop_height": 1080,
|
|
"assigned_screen_id": "info01"
|
|
}
|
|
```
|
|
|
|
Diese Werte sind:
|
|
|
|
- **serverseitig generiert** — Admin muss nicht manuell Pixel-Koordinaten eingeben
|
|
- **automatisch skalierbar** — bei verschiedenen Aufloesungen
|
|
|
|
## 3. Datenmodell
|
|
|
|
### Tabelle `screen_groups`
|
|
|
|
```sql
|
|
CREATE TABLE screen_groups (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
slug TEXT NOT NULL UNIQUE,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
group_type TEXT NOT NULL CHECK (group_type IN (
|
|
'physical', 'functional', 'device_type', 'tenant', 'custom'
|
|
)),
|
|
parent_group_id UUID REFERENCES screen_groups(id),
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
**Beispiele:**
|
|
|
|
```sql
|
|
INSERT INTO screen_groups (slug, name, group_type)
|
|
VALUES
|
|
('all', 'Alle Screens', 'custom'),
|
|
('wall-all', 'Infowand - Alle', 'physical'),
|
|
('wall-row-1', 'Infowand - Reihe 1', 'physical'),
|
|
('single-all', 'Einzelanzeigen', 'functional'),
|
|
('portrait-all', 'Hochformat', 'device_type');
|
|
```
|
|
|
|
### Tabelle `screen_group_members`
|
|
|
|
```sql
|
|
CREATE TABLE screen_group_members (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
group_id UUID NOT NULL REFERENCES screen_groups(id) ON DELETE CASCADE,
|
|
screen_id UUID NOT NULL REFERENCES screens(id) ON DELETE CASCADE,
|
|
added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
UNIQUE(group_id, screen_id)
|
|
);
|
|
```
|
|
|
|
**Beispiel:**
|
|
|
|
```sql
|
|
INSERT INTO screen_group_members (group_id, screen_id)
|
|
SELECT
|
|
(SELECT id FROM screen_groups WHERE slug = 'wall-row-1'),
|
|
id
|
|
FROM screens
|
|
WHERE slug IN ('info01', 'info02', 'info03');
|
|
```
|
|
|
|
### Tabelle `layout_definitions`
|
|
|
|
```sql
|
|
CREATE TABLE layout_definitions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
slug TEXT NOT NULL UNIQUE,
|
|
name TEXT NOT NULL,
|
|
layout_type TEXT NOT NULL CHECK (layout_type IN (
|
|
'3x3_grid', '2x2_grid', '1x9_row', '9x1_column', 'custom'
|
|
)),
|
|
rows INT NOT NULL,
|
|
cols INT NOT NULL,
|
|
description TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
**Beispiel:**
|
|
|
|
```sql
|
|
INSERT INTO layout_definitions (slug, name, layout_type, rows, cols)
|
|
VALUES ('3x3_infowand', 'Infowand 3x3', '3x3_grid', 3, 3);
|
|
```
|
|
|
|
### Tabelle `layout_slots`
|
|
|
|
```sql
|
|
CREATE TABLE layout_slots (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
layout_id UUID NOT NULL REFERENCES layout_definitions(id) ON DELETE CASCADE,
|
|
slot_slug TEXT NOT NULL,
|
|
row INT NOT NULL,
|
|
col INT NOT NULL,
|
|
UNIQUE(layout_id, slot_slug)
|
|
);
|
|
```
|
|
|
|
**Beispiel:**
|
|
|
|
```sql
|
|
INSERT INTO layout_slots (layout_id, slot_slug, row, col)
|
|
SELECT
|
|
(SELECT id FROM layout_definitions WHERE slug = '3x3_infowand'),
|
|
'wall-r' || (r) || '-c' || (c),
|
|
r - 1, c - 1
|
|
FROM
|
|
CROSS JOIN LATERAL (SELECT GENERATE_SERIES(1, 3) AS r)
|
|
CROSS JOIN LATERAL (SELECT GENERATE_SERIES(1, 3) AS c);
|
|
```
|
|
|
|
### Tabelle `slot_screen_assignments`
|
|
|
|
```sql
|
|
CREATE TABLE slot_screen_assignments (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
layout_id UUID NOT NULL REFERENCES layout_definitions(id),
|
|
slot_id UUID NOT NULL REFERENCES layout_slots(id) ON DELETE CASCADE,
|
|
screen_id UUID NOT NULL REFERENCES screens(id),
|
|
assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
UNIQUE(layout_id, slot_id, screen_id)
|
|
);
|
|
```
|
|
|
|
**Beispiel:**
|
|
|
|
```sql
|
|
-- Zuordnung: Slot wall-r1-c1 → Screen info01 (in 3x3-Layout)
|
|
INSERT INTO slot_screen_assignments (layout_id, slot_id, screen_id)
|
|
SELECT
|
|
l.id,
|
|
ls.id,
|
|
s.id
|
|
FROM
|
|
layout_definitions l,
|
|
layout_slots ls,
|
|
screens s
|
|
WHERE
|
|
l.slug = '3x3_infowand'
|
|
AND ls.layout_id = l.id
|
|
AND ls.slot_slug = 'wall-r1-c1'
|
|
AND s.slug = 'info01';
|
|
```
|
|
|
|
## 4. Admin-Verwaltung
|
|
|
|
### Gruppen verwalten
|
|
|
|
**Seite:** Admin → Gruppen
|
|
|
|
```
|
|
┌──────────────────────────────────────────┐
|
|
│ Screen-Gruppen │
|
|
├──────────────────────────────────────────┤
|
|
│ │
|
|
│ Gruppe Typ Screens│
|
|
│────────────────────────────────────────│
|
|
│ all custom 13 │
|
|
│ wall-all physical 9 │
|
|
│ wall-row-1 physical 3 │
|
|
│ wall-row-2 physical 3 │
|
|
│ wall-row-3 physical 3 │
|
|
│ single-all functional 2 │
|
|
│ portrait-all device_type 12 │
|
|
│ │
|
|
│ [+ Neue Gruppe] [Gruppe bearbeiten] │
|
|
└──────────────────────────────────────────┘
|
|
```
|
|
|
|
### Gruppe erstellen/bearbeiten
|
|
|
|
```
|
|
┌──────────────────────────────────────────┐
|
|
│ Neue Gruppe │
|
|
├──────────────────────────────────────────┤
|
|
│ │
|
|
│ Name * │
|
|
│ [ Infowand Reihe 2 __________________ ] │
|
|
│ slug: wall-row-2 (automatisch) │
|
|
│ │
|
|
│ Gruppentyp * │
|
|
│ ⦿ physical (Wand-Anordnung) │
|
|
│ ○ functional (Verwendungszweck) │
|
|
│ ○ device_type (Geraetetyp) │
|
|
│ ○ tenant (Mandant) │
|
|
│ ○ custom (benutzerdefiniert) │
|
|
│ │
|
|
│ Beschreibung │
|
|
│ [ Die obere Reihe der Infowand ______ ] │
|
|
│ │
|
|
│ Screens hinzufuegen │
|
|
│ [ Suchfeld: "info" ] │
|
|
│ □ info01 ← obere Reihe │
|
|
│ □ info02 ← obere Reihe │
|
|
│ ☑ info03 ← obere Reihe │
|
|
│ □ info04 │
|
|
│ ... (nur unzugeordnete zeigen) │
|
|
│ │
|
|
│ Ausgewaehlte Screens │
|
|
│ info03 (portrait, online) │
|
|
│ [ + weitere hinzufuegen ] │
|
|
│ │
|
|
│ Uebergruppe │
|
|
│ [Dropdown: all > wall-all] │
|
|
│ (optional, zur Hierarchie) │
|
|
│ │
|
|
│ [Speichern] [Abbrechen] │
|
|
└──────────────────────────────────────────┘
|
|
```
|
|
|
|
### Layout-Definition erstellen (fuer Message-Wall)
|
|
|
|
**Seite:** Admin → Layouts
|
|
|
|
```
|
|
┌──────────────────────────────────────────┐
|
|
│ Layout-Definitionen │
|
|
├──────────────────────────────────────────┤
|
|
│ │
|
|
│ Layout-Name Typ Grid Slots│
|
|
│─────────────────────────────────────────│
|
|
│ 3x3 Infowand 3x3_grid 3x3 9 │
|
|
│ Vertretungsplan 2x2_grid 2x2 4 │
|
|
│ News-Lauf 1x9_row 1x9 9 │
|
|
│ │
|
|
│ [+ Neues Layout] [Bearbeiten] │
|
|
└──────────────────────────────────────────┘
|
|
```
|
|
|
|
Detailseite eines Layouts:
|
|
|
|
```
|
|
Layout: 3x3 Infowand
|
|
|
|
Visualisierung:
|
|
┌─────────┬─────────┬─────────┐
|
|
│ Slot 1 │ Slot 2 │ Slot 3 │
|
|
├─────────┼─────────┼─────────┤
|
|
│ Slot 4 │ Slot 5 │ Slot 6 │
|
|
├─────────┼─────────┼─────────┤
|
|
│ Slot 7 │ Slot 8 │ Slot 9 │
|
|
└─────────┴─────────┴─────────┘
|
|
|
|
Slot-Zuordnungen:
|
|
Slot 1 (wall-r1-c1) → Screen info01 (portrait, 1920x1080)
|
|
Slot 2 (wall-r1-c2) → Screen info02 (portrait, 1920x1080)
|
|
...
|
|
|
|
[Screen-Zuordnungen aendernx] [Layout loeschen]
|
|
```
|
|
|
|
## 5. Anwendung in Kampagnen
|
|
|
|
### Kampagne auf Gruppe anwenden
|
|
|
|
**Beispiel:** Admin aktiviert Weihnachtsmotiv auf `wall-all`:
|
|
|
|
```
|
|
Template: Weihnachtsmotiv 2025 (full_screen_media)
|
|
|
|
Zielgruppe auswaehlen:
|
|
⦿ Alle Screens
|
|
○ Nach Gruppe:
|
|
[Dropdown: wall-all ]
|
|
oder wall-row-1, single-all, ...
|
|
○ Einzelne Screens
|
|
|
|
→ Kampagne wird auf alle 9 Screens in wall-all aktiviert
|
|
→ Jeder Screen zeigt dasselbe Motiv
|
|
→ (Portrait/Landscape-Varianten werden serverseitig beruecksichtigt)
|
|
```
|
|
|
|
### Message-Wall-Kampagne mit Slot-Modell
|
|
|
|
**Beispiel:** Admin teilt Schriftzug auf Infowand auf:
|
|
|
|
```
|
|
Template: Schriftzug (message_wall)
|
|
|
|
Layout: 3x3 Infowand
|
|
Zielgruppe: wall-all (auto-expandiert zu Slots)
|
|
|
|
Gesamte Grafik hochladen oder zeichnen
|
|
↓
|
|
System generiert automatisch:
|
|
- Slot wall-r1-c1 → Ausschnitt x0-640 y0-1080 → Screen info01
|
|
- Slot wall-r1-c2 → Ausschnitt 640-1280 y0-1080 → Screen info02
|
|
- Slot wall-r1-c3 → Ausschnitt 1280-1920 y0-1080 → Screen info03
|
|
- ... (9 Zuweisungen insgesamt)
|
|
↓
|
|
Kampagne aktivieren
|
|
↓
|
|
Jeder Screen ladet seinen zustaendigen Ausschnitt
|
|
↓
|
|
Schriftzug erscheint verteilt ueber alle 9 Screens
|
|
```
|
|
|
|
## 6. Automatische Gruppe-Inferenz
|
|
|
|
Der Server kann bestimmte Gruppen automatisch generieren:
|
|
|
|
```python
|
|
# Automatisch generierte Gruppen
|
|
|
|
all:
|
|
- alle Screens im System (manuelle Verwaltung nicht noetig)
|
|
|
|
online-all:
|
|
- alle Screens, die gerade online sind
|
|
- wird alle 5 Min aktualisiert
|
|
|
|
offline-all:
|
|
- alle Screens, die gerade offline sind
|
|
|
|
portrait-all:
|
|
- alle Screens mit Orientierung = "portrait"
|
|
|
|
landscape-all:
|
|
- alle Screens mit Orientierung = "landscape"
|
|
|
|
device_type_*:
|
|
- fuer jeden konfigurieren Screen-Typ (z.B. device_type_raspberry_pi)
|
|
|
|
region_*:
|
|
- optional: auf Basis von Geo-Daten oder Tags
|
|
```
|
|
|
|
Diese automatischen Gruppen sind **read-only** im Admin-UI, aber voll verwendbar fuer Kampagnen.
|
|
|
|
## 7. Beispiel: Neuinstallation einer Infowand
|
|
|
|
**Szenario:** Admin installiert neue 3x3-Infowand mit Screens info01-info09.
|
|
|
|
**Schritte:**
|
|
|
|
1. **Screens anlegen** (via Provisionierungs-UI oder direkt)
|
|
```
|
|
info01, info02, ..., info09
|
|
Alle: Orientierung portrait, Geraetetyp "raspberry_pi"
|
|
```
|
|
|
|
2. **Gruppen anlegen**
|
|
```
|
|
screen_groups:
|
|
- slug: wall-all, name: "Infowand Alle", type: physical
|
|
- slug: wall-row-1, name: "Infowand Reihe 1", type: physical
|
|
- slug: wall-row-2, name: "Infowand Reihe 2", type: physical
|
|
- slug: wall-row-3, name: "Infowand Reihe 3", type: physical
|
|
```
|
|
|
|
3. **Screens den Gruppen zuordnen**
|
|
```
|
|
wall-all: info01-info09
|
|
wall-row-1: info01, info02, info03
|
|
wall-row-2: info04, info05, info06
|
|
wall-row-3: info07, info08, info09
|
|
```
|
|
|
|
4. **Layout definieren**
|
|
```
|
|
layout_definitions:
|
|
- slug: 3x3_infowand, rows: 3, cols: 3
|
|
|
|
layout_slots:
|
|
- wall-r1-c1, wall-r1-c2, wall-r1-c3 (row 0)
|
|
- wall-r2-c1, wall-r2-c2, wall-r2-c3 (row 1)
|
|
- wall-r3-c1, wall-r3-c2, wall-r3-c3 (row 2)
|
|
|
|
slot_screen_assignments:
|
|
- wall-r1-c1 → info01
|
|
- wall-r1-c2 → info02
|
|
- ... (9 gesamt)
|
|
```
|
|
|
|
5. **Kampagnen verwenden**
|
|
```
|
|
Template: Schriftzug
|
|
Zielgruppe: wall-all
|
|
Layout: 3x3_infowand
|
|
→ Kampagne kann sofort aktiviert werden
|
|
```
|
|
|
|
## 8. Zusammenfassung
|
|
|
|
Das Gruppierungs- und Slot-Modell:
|
|
|
|
- **ist flexibel** — physische, funktionale und typen-basierte Gruppen
|
|
- **ist hierarchisch** — Gruppen koennen Untergruppen enthalten
|
|
- **ist automatisch** — Gruppen wie "all" und "online-all" werden inferiert
|
|
- **ist geometrisch** — Slots definieren Layouts fuer verteilte Motive
|
|
- **ist skalierbar** — neue Screens werden einfach Gruppen zugeordnet
|
|
- **ist intuitiv** — Admin-UI zeigt Zuordnungen und Vorschauen
|