morz-infoboard/docs/GRUPPEN-KONZEPT.md
Jesko Anschütz dd3ec070f7 Security-Review + Phase 6: CSRF, Rate-Limiting, Tenant-Isolation, Screenshot, Ansible
### 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>
2026-03-23 21:06:35 +01:00

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