372 lines
14 KiB
Markdown
Executable File
372 lines
14 KiB
Markdown
Executable File
# CANCOM Simple Signage
|
||
|
||
Browserbasiertes Digital-Signage-System für interne Info-Screens.
|
||
|
||
---
|
||
|
||
## Features
|
||
|
||
- Browserbasierter Player (Kiosk-Modus, Chromium, Firefox)
|
||
- Bilder, Videos (MP4/H.264), HTML-Seiten, URL-Playlist-Einträge
|
||
- **Multi-Standort**: Standorte (sites) gruppieren Screens
|
||
- **Priority-Playlist**: globale Inhalte wirken auf alle Player
|
||
- **Willkommensseite**: bis zu 3 Kundenlogos via OpenAI + Brandfetch, pro Standort konfigurierbares Hintergrundbild
|
||
- Pro Screen eigene Playlist mit Drag-&-Drop-Reihenfolge
|
||
- Auto-Reload bei Playlist-Änderungen
|
||
- Newsticker pro Screen
|
||
- **Custom-URL-Button**: pro Screen konfigurierbarer Aktions-Button mit frei wählbarer Position (9 Positionen) im Player (öffnet URL in iframe-Overlay mit Zurück-Button oder per Direkt-Weiterleitung)
|
||
- **Digital Voice Agent**: global konfigurierbarer Voice-Agent-Button pro Screen (9 Positionen, iframe-Overlay mit positionsgetreuem Zurück-Button, Typewriter-Tagline mit mehrsprachigen Wechseltexten, optionales Bild über dem Button)
|
||
- **Stay-on-First**: Screen kann auf erstem Playlist-Element stehen bleiben (kein automatischer Refresh)
|
||
- **User-Verwaltung**: Mehrere User mit Rollen (Admin / Superuser / User), E-Mail als Login, Passwort-Hashing (scrypt), Berechtigungen pro Standort
|
||
- **Passwort-Workflow**: First-Login-Änderung, Admin-Reset mit temporärem Passwort, Notfall-Login bei fehlendem Hash
|
||
- **Admin Dashboard**: `/admin/dashboard` – Statistiken (Sites, Screens, User, Admins, Superuser) mit Sparkline-Charts + Trendanzeige, Aktivitätsverlauf
|
||
- **Aktivitätsverlauf**: Alle Erstell-/Lösch-/Änderungsaktionen sowie Login/Logout werden in `history.json` protokolliert und im Dashboard angezeigt
|
||
- Tab-basierte Admin-UI pro Screen: Playlist, Einstellungen, Aktionen, Digital Voice Agent, Medien (Tabler Tabs)
|
||
- Priority-Seite ebenfalls mit Tabs: Playlist und Medien
|
||
- Dark Mode (localStorage-persistiert)
|
||
- CI-konformes Admin-UI (CANCOM-Design: `brand-surface`, `nav-surface` rot)
|
||
|
||
---
|
||
|
||
## Architektur
|
||
|
||
```
|
||
Browser (Player)
|
||
│
|
||
▼
|
||
Flask App (Server)
|
||
├── Admin UI /admin/<site>
|
||
├── Player UI /player/<site>/<screen>
|
||
├── Priority-Seite /admin/<site>/priority
|
||
├── config.json
|
||
├── users.json
|
||
├── history.json
|
||
├── media/
|
||
│ ├── <site>/
|
||
│ │ ├── <screen>/
|
||
│ │ │ ├── bild.jpg
|
||
│ │ │ ├── video.mp4
|
||
│ │ │ └── welcome.html
|
||
│ └── priority/
|
||
└── generate_welcome_page.py
|
||
```
|
||
|
||
- **Server:** Python 3 + Flask
|
||
- **Player:** Jeder moderne Browser (Chrome Kiosk, Edge, Firefox)
|
||
- **State:** `config.json` + Dateisystem
|
||
- **Frontend:** Tabler Core + Tabler Icons + SortableJS (CDN)
|
||
|
||
---
|
||
|
||
## Projektstruktur
|
||
|
||
```
|
||
signage/
|
||
├── app.py # Flask-App (alle Routen)
|
||
├── generate_welcome_page.py # Logo-Suche + Willkommensseite-Generierung
|
||
├── config.json # Persistente Konfiguration
|
||
├── users.json # User-Datenbank (gehashte Passwörter)
|
||
├── history.json # Aktivitätsverlauf (Dashboard)
|
||
├── media/
|
||
│ ├── <site>/
|
||
│ │ ├── lobby/
|
||
│ │ ├── casino/
|
||
│ │ └── videosysteme/
|
||
│ └── priority/
|
||
├── templates/
|
||
│ ├── admin.html # Admin-Dashboard (Übersicht)
|
||
│ ├── admin_dashboard.html # Admin Dashboard (Statistiken + Verlauf)
|
||
│ ├── priority.html # Priority-Playlist (eigene Seite)
|
||
│ ├── customer.html # Willkommensseite-Formular
|
||
│ ├── player.html # Player-Ansicht
|
||
│ ├── login.html
|
||
│ ├── change_password.html # Passwort ändern (First-Login / Reset)
|
||
│ ├── user_list.html # User-Liste (Admin)
|
||
│ ├── user_create.html # User anlegen (Admin)
|
||
│ ├── user_edit.html # User bearbeiten (Admin)
|
||
│ ├── _header.html # Gemeinsamer Header
|
||
│ ├── _footer.html # Gemeinsamer Footer
|
||
│ └── _styles.html # Zentrale CSS (Variablen, Dark Mode)
|
||
├── static/
|
||
│ ├── cancom.svg
|
||
│ ├── dva.png
|
||
│ └── wallpaper.png
|
||
└── AGENTS.md
|
||
```
|
||
|
||
---
|
||
|
||
## Installation
|
||
|
||
### Voraussetzungen
|
||
|
||
- Python ≥ 3.9
|
||
- pip
|
||
- ffmpeg (optional, für Videokonvertierung)
|
||
|
||
### Setup
|
||
|
||
```bash
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
### Starten
|
||
|
||
```bash
|
||
python app.py
|
||
```
|
||
|
||
Server läuft auf `http://localhost:5005`.
|
||
|
||
### Docker
|
||
|
||
```bash
|
||
docker compose up -d
|
||
```
|
||
|
||
Server läuft auf `http://localhost:5005` (gunicorn mit 4 Workern, non-root User).
|
||
|
||
---
|
||
|
||
## Routen
|
||
|
||
| Route | Beschreibung |
|
||
|-------|-------------|
|
||
| `GET /` | Weiterleitung zum Login |
|
||
| `GET /login` | Admin-Login |
|
||
| `GET /logout` | Ausloggen |
|
||
| `GET /admin` | Redirect zum ersten konfigurierten Standort |
|
||
| `GET /admin/dashboard` | Admin Dashboard (Statistiken + Verlauf) |
|
||
| `GET /admin/<site>` | Admin-Dashboard für einen Standort |
|
||
| `GET /admin/<site>/priority` | Priority-Playlist (separate Seite) |
|
||
| `POST /admin/<site>/update/<screen>` | Allgemeine Screen-Einstellungen speichern (Intervall, Newsticker, Bilder/Videos, stay_on_first) |
|
||
| `POST /admin/<site>/update-actions/<screen>` | Aktionen-Einstellungen speichern (Custom-URL) |
|
||
| `POST /admin/<site>/update-voice/<screen>` | Voice-Agent-Einstellungen speichern (Enabled, Label, Target, Position, Bild) |
|
||
| `POST /admin/<site>/upload/<screen>` | Medien hochladen |
|
||
| `POST /admin/<site>/add-url/<screen>` | URL zur Playlist hinzufügen |
|
||
| `POST /admin/<site>/delete/<screen>/<filename>` | Datei löschen |
|
||
| `POST /admin/<site>/playlist/<screen>` | Playlist-Reihenfolge speichern (JSON) |
|
||
| `POST /admin/<site>/delete-screen/<screen>` | Screen + Medien löschen |
|
||
| `GET /admin/<site>/add-screen?name=<name>` | Neuen Screen anlegen |
|
||
| `GET /add-site?name=<name>` | Neuen Standort anlegen |
|
||
| `POST /admin/<site>/delete-site` | Standort + alle Screens/Medien löschen |
|
||
| `POST /admin/<site>/upload-background` | Hintergrundbild für Willkommensseite hochladen |
|
||
| `POST /admin/<site>/delete-background` | Hintergrundbild zurücksetzen |
|
||
| `GET /player/<site>/<screen>` | Player-Ansicht |
|
||
| `GET /playlist/<site>/<screen>/hash` | Playlist-Checksumme (für Auto-Reload) |
|
||
| `GET /willkommen?site=<site>` | Willkommensseite-Formular (GET + POST) |
|
||
| `POST /api/customer` | API-Endpunkt für Willkommensseite (JSON) |
|
||
| `GET /change-password` | Passwort ändern (First-Login / nach Admin-Reset) |
|
||
| `GET /admin/users` | User-Liste (Admin) |
|
||
| `GET /admin/users/create` | User anlegen (Admin) |
|
||
| `GET /admin/users/edit/<email>` | User bearbeiten (Admin) |
|
||
| `POST /admin/users/create` | User anlegen (Admin) |
|
||
| `POST /admin/users/delete/<email>` | User löschen (Admin, letzter Admin geschützt) |
|
||
| `POST /admin/users/reset-password/<email>` | Passwort-Reset mit temporärem Passwort (Admin) |
|
||
| `POST /admin/users/edit/<email>` | User bearbeiten speichern (Admin) |
|
||
| `GET /media/<site>/<screen>/<file>` | Medien-Datei ausliefern |
|
||
| `GET /media/priority/<file>` | Priority-Medien (global) |
|
||
| `GET /media/<site>/background/<filename>` | Hintergrundbild der Willkommensseite |
|
||
|
||
### Admin Portal
|
||
|
||
```
|
||
http://localhost:5005/admin/<standort>
|
||
```
|
||
|
||
- Screens konfigurieren (Tab-basiert: Playlist, Einstellungen, Aktionen, Digital Voice Agent, Medien)
|
||
- Medien hochladen / löschen
|
||
- Playlist per Drag & Drop sortieren
|
||
- Priority-Playlist verwalten (ebenfalls mit Tabs)
|
||
- Willkommensseite generieren (bis zu 3 Kundenlogos, Hintergrundbild pro Standort)
|
||
- Custom-URL-Aktionsbutton pro Screen konfigurieren (Position, iframe-Overlay oder Weiterleitung)
|
||
- Digital Voice Agent pro Screen konfigurieren (Position, Bild ein/aus, Typewriter-Tagline)
|
||
- Stay-on-First-Modus pro Screen (kein automatischer Refresh)
|
||
- Standorte anlegen & löschen
|
||
- **Userverwaltung**: User anlegen, bearbeiten, löschen, Passwort-Reset (temporäres Passwort wird angezeigt)
|
||
|
||
### Admin Dashboard
|
||
|
||
```
|
||
http://localhost:5005/admin/dashboard
|
||
```
|
||
|
||
- 5 Statistik-Cards (Standorte, Screens, User, Admins, Superuser) mit ApexCharts-Sparkline + Trendanzeige
|
||
- Aktivitätsverlauf (History) als Liste mit Zeitstempel, Aktion und User
|
||
- Alle Erstell-/Lösch-/Änderungsaktionen sowie Login/Logout werden protokolliert
|
||
|
||
### Player-URL
|
||
|
||
```
|
||
http://localhost:5005/player/<standort>/<screen>
|
||
```
|
||
|
||
Beispiel:
|
||
```
|
||
http://localhost:5005/player/stuttgart/lobby
|
||
```
|
||
|
||
### Willkommensseite
|
||
|
||
```
|
||
http://localhost:5005/willkommen?site=stuttgart
|
||
```
|
||
|
||
Maximal 3 Kunden eingeben → Logos werden via OpenAI + Brandfetch gesucht → `welcome.html` wird in der Lobby-Playlist vorne eingefügt. Pro Standort kann ein eigenes Hintergrundbild hochgeladen werden (Fallback auf `static/wallpaper.png`).
|
||
|
||
---
|
||
|
||
### Globale Konfiguration (config.json)
|
||
|
||
| Feld | Typ | Beschreibung |
|
||
|------|-----|-------------|
|
||
| `server_url` | string | Öffentliche Server-URL für Player-Links |
|
||
| `voice_agent_url` | string | URL des Digital Voice Agents (global, read-only im Admin) |
|
||
|
||
### User-Rollen (users.json)
|
||
|
||
| Rolle | Zugriff | Userverwaltung |
|
||
|-------|---------|----------------|
|
||
| **Admin** | Alle Standorte | Ja (anlegen/bearbeiten/löschen/Reset) |
|
||
| **Superuser** | Alle Standorte | Nein |
|
||
| **User** | Nur freigegebene Standorte | Nein |
|
||
|
||
- Usernamen sind E-Mail-Adressen
|
||
- Passwörter werden als scrypt-Hash gespeichert
|
||
- `users.json` wird beim ersten Start aus `config.json.admin` befüllt
|
||
- **Notfall**: `users.json` löschen → Server-Neustart erzeugt neuen Admin aus `config.json`
|
||
|
||
| Feld | Typ | Beschreibung |
|
||
|------|-----|-------------|
|
||
| `server_url` | string | Öffentliche Server-URL für Player-Links |
|
||
| `voice_agent_url` | string | URL des Digital Voice Agents (global, read-only im Admin) |
|
||
|
||
## Konfiguration (`config.json`)
|
||
|
||
```json
|
||
{
|
||
"server_url": "http://signage.ccmake.de",
|
||
"voice_agent_url": "https://voice-agent.example.com",
|
||
"admin": { "username": "...", "password": "..." },
|
||
"sites": {
|
||
"stuttgart": {
|
||
"screens": {
|
||
"lobby": {
|
||
"playlist": [...],
|
||
"interval": 10,
|
||
"show_images": true,
|
||
"show_videos": true,
|
||
"newsticker_text": "...",
|
||
"newsticker_enabled": false,
|
||
"custom_url_enabled": false,
|
||
"custom_url": "https://...",
|
||
"custom_url_label": "Infos",
|
||
"custom_url_target": "overlay",
|
||
"custom_url_position": "top-left",
|
||
"stay_on_first": false,
|
||
"voice_agent_enabled": true,
|
||
"voice_agent_show_image": false,
|
||
"voice_agent_label": "Digitaler Assistent",
|
||
"voice_agent_target": "overlay",
|
||
"voice_agent_position": "top-left"
|
||
},
|
||
"casino": { "playlist": [...], "interval": 15 }
|
||
},
|
||
"welcome_data": {
|
||
"names": ["Firma A"],
|
||
"logo_urls": ["https://..."],
|
||
"background_url": null
|
||
}
|
||
}
|
||
},
|
||
"priority": {
|
||
"enabled": true,
|
||
"playlist": [...]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Screen-Konfiguration (pro Screen in `config.json`)
|
||
|
||
| Feld | Typ | Beschreibung |
|
||
|------|-----|-------------|
|
||
| `playlist` | Array | Playlist-Einträge (Strings oder Dicts mit `url`/`zoom`) |
|
||
| `interval` | int | Anzeige-Intervall in Sekunden |
|
||
| `show_images` | bool | Bilder anzeigen |
|
||
| `show_videos` | bool | Videos anzeigen |
|
||
| `newsticker_text` | string | Text für Newsticker (max. 200 Zeichen) |
|
||
| `newsticker_enabled` | bool | Newsticker anzeigen |
|
||
| `custom_url_enabled` | bool | Custom-URL-Button im Player anzeigen |
|
||
| `custom_url` | string | URL des Aktions-Buttons |
|
||
| `custom_url_label` | string | Button-Beschriftung |
|
||
| `custom_url_target` | string | `"overlay"` (iframe) oder `"redirect"` |
|
||
| `custom_url_position` | string | Position des Buttons: `top-left`, `top-center`, `top-right`, `middle-left`, `middle-center`, `middle-right`, `bottom-left`, `bottom-center`, `bottom-right` |
|
||
| `stay_on_first` | bool | Player bleibt auf erstem Element stehen (kein Refresh) |
|
||
| `voice_agent_enabled` | bool | Voice-Agent-Button im Player anzeigen |
|
||
| `voice_agent_show_image` | bool | Bild (dva.png) über dem Button anzeigen |
|
||
| `voice_agent_label` | string | Button-Beschriftung (Default: "Digitaler Assistent") |
|
||
| `voice_agent_target` | string | `"overlay"` (iframe) oder `"redirect"` |
|
||
| `voice_agent_position` | string | Position des Buttons: `top-left`, `top-center`, `top-right`, `middle-left`, `middle-center`, `middle-right`, `bottom-left`, `bottom-center`, `bottom-right` |
|
||
|
||
## Helper-Funktionen (`app.py`)
|
||
|
||
- `load_config()` / `save_config()` – JSON lesen/schreiben
|
||
- `get_site_list(cfg)` – alle Standorte sortiert
|
||
- `get_screen_config(cfg, site, screen)` – Screen-Konfiguration mit Defaults
|
||
- `is_url(item)` – prüft ob Item eine URL ist
|
||
- `normalize_url(item)` – normalisiert URL-Item zu `{"url", "zoom"}`
|
||
- `playlist_item_name(item)` – Name aus String oder Dict extrahieren
|
||
- `playlist_item_enabled(item)` – Enabled-Status prüfen
|
||
- `load_priority_files()` – Priority-Playlist + Dateien laden
|
||
- `prio_redirect(site)` – Redirect-Pfad zur Priority-Seite
|
||
- `get_background_url(site)` – URL zum benutzerdefinierten Hintergrundbild oder `None`
|
||
- `load_users()` / `save_users()` / `get_user()` – User aus `users.json`
|
||
- `init_user_db()` – initialen Admin aus `config.json` anlegen
|
||
- `get_accessible_sites(cfg, user)` – Standorte nach User-Berechtigung filtern
|
||
- `admin_required` / `site_access_required` – Zugriffs-Dekoratoren
|
||
|
||
---
|
||
|
||
## Dark Mode
|
||
|
||
Wird über `localStorage("signage-theme")` persistiert. Umschalt-Button im Header. CSS-Variablen `--ccm-*` in `_styles.html`.
|
||
|
||
---
|
||
|
||
## Sicherheit
|
||
|
||
- Admin-Bereich per Flask-Login geschützt (E-Mail + Passwort)
|
||
- Passwörter gehasht (scrypt) in `users.json`
|
||
- Drei Rollen: Admin (alle Standorte + Userverwaltung), Superuser (alle Standorte), User (nur zugewiesene Standorte)
|
||
- Zugriffskontrolle per Decorator (`@admin_required`, `@site_access_required`)
|
||
- Letzter Admin kann nicht gelöscht werden
|
||
- Player-Seiten Read-Only
|
||
- Externe Nutzung via Reverse Proxy + TLS empfohlen
|
||
|
||
---
|
||
|
||
## Git & Medien
|
||
|
||
> Das Repository ist für Code gedacht, nicht für Medien.
|
||
|
||
`media/` und Medien-Dateiendungen sind in `.gitignore` ausgeschlossen.
|
||
|
||
---
|
||
|
||
## Video-Empfehlungen
|
||
|
||
- Format: MP4 (H.264)
|
||
- Auflösung: max. 1920×1080
|
||
|
||
```bash
|
||
ffmpeg -i input.mov -c:v libx264 -pix_fmt yuv420p -movflags +faststart output.mp4
|
||
```
|
||
|
||
---
|
||
|
||
## Maintainer
|
||
|
||
**CANCOM Simple Signage** – Interne Lösung, nicht für externe Weitergabe bestimmt.
|