### 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>
16 KiB
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 Systemwall-all— alle 9 Infowand-Screenswall-row-1— die 3 Screens der ersten Reihewall-row-2— die 3 Screens der zweiten Reihesingle-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 Infowandwall-row-1,wall-row-2,wall-row-3— Reihen einer Wandwall-column-1,wall-column-2,wall-column-3— Spalten einer Wand
Funktionale Gruppen
Spiegeln den Verwendungszweck wider:
main-hall-all— alle Displays im Hauptkorridorcafeteria-all— alle Displays in der Kaffeteriainfo-all— alle Informationsanzeigen
Typen-Gruppen
Spiegeln das Geraetemodell wider:
portrait-all— alle Displays im Hochformatlandscape-all— alle Displays im Querformat4k-displays— nur 4K-Monitore
Tenant-Gruppen (Phase 2)
Spiegeln die Mandanten-Zugehoerigkeit wider:
tenant-xyz-all— alle Displays fuer Mandant XYZtenant-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:
{
"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
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:
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
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:
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
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:
INSERT INTO layout_definitions (slug, name, layout_type, rows, cols)
VALUES ('3x3_infowand', 'Infowand 3x3', '3x3_grid', 3, 3);
Tabelle layout_slots
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:
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
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:
-- 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:
# 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:
-
Screens anlegen (via Provisionierungs-UI oder direkt)
info01, info02, ..., info09 Alle: Orientierung portrait, Geraetetyp "raspberry_pi" -
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 -
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 -
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) -
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