feat: Willkommensseite auf mehreren Screens anzeigen

Die Willkommensseite ging bisher fest in die Lobby-Playlist. Welche
Screens Begruessungsschirme sind, ist aber eine Eigenschaft der
Installation und nicht des einzelnen Kundenbesuchs - deshalb ein Flag
pro Screen statt einer Auswahl bei jedem Vorgang.

- Neue Screen-Einstellung "welcome_enabled" (Checkbox im Tab
  Einstellungen). Die generierte welcome.html wird in jeden markierten
  Screen des Standorts geschrieben und dort an Position 0 gesetzt.
- welcome_target_screens() faellt nur auf "lobby" zurueck, wenn kein
  Screen des Standorts den Schluessel besitzt. Bestehende Standorte
  verhalten sich damit ohne Konfigurationsaufwand unveraendert.
- add_customer_to_playlists() (frueher add_customer_to_lobby_playlist)
  raeumt welcome.html aus allen Screens des Standorts und fuegt sie nur
  in den Zielscreens wieder ein. Ein abgewaehlter Screen bleibt so nicht
  mit einer alten Begruessung stehen.
- Eingefuegt wird ein Dict statt eines Strings, damit der Eintrag
  enabled und schedule tragen kann.
- Optionales "Anzeigen bis" im Formular: setzt eine Zeitsteuerung, die
  Seite verschwindet danach auf allen Screens von selbst.
- Das Formular zeigt die aktuellen Zielscreens an und meldet
  verstaendlich, wenn keiner markiert ist.
- screens.setdefault("lobby", {}) entfernt - das legte an Standorten
  ohne Lobby stillschweigend einen Screen an. Es werden nur noch
  vorhandene Screens bespielt.

Die Seite referenziert alle Bilder absolut (/static/..., /media/...),
ist also ortsunabhaengig; je Screen liegt eine identische Kopie.

README, AGENTS.md und die Hilfeseite nachgezogen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik Thiele
2026-09-02 12:26:22 +02:00
parent 3761497421
commit 848bcf93ab
7 changed files with 177 additions and 49 deletions

View File

@@ -296,57 +296,134 @@ def generate_welcome_html(customer_names, logo_urls, contacts=None, site="stuttg
# Speichern + Playlist-Einfügung
# -------------------------------------------------
def save_welcome_page(customer_names, logo_urls, contacts=None, site="stuttgart", background_url=None):
"""Speichert die generierte Willkommensseite als welcome.html im Lobby-Verzeichnis.
def welcome_target_screens(config, site):
"""Ermittelt die Screens eines Standorts, auf denen die Willkommensseite laufen soll.
Ein Screen ist Ziel, wenn "welcome_enabled" gesetzt ist. Ist der Schluessel bei
keinem Screen des Standorts vorhanden, greift das alte Verhalten: nur "lobby".
Damit bleiben bestehende Standorte ohne Konfigurationsaufwand unveraendert."""
screens = config.get("sites", {}).get(site, {}).get("screens", {})
configured = [name for name, cfg in screens.items()
if isinstance(cfg, dict) and cfg.get("welcome_enabled")]
if configured:
return sorted(configured)
# Kein Screen markiert: nur dann auf "lobby" zurückfallen, wenn noch nirgends
# eine bewusste Entscheidung getroffen wurde. Wurde das Häkchen überall
# entfernt, ist "keine Screens" das gewollte Ergebnis.
entschieden = any(isinstance(cfg, dict) and "welcome_enabled" in cfg
for cfg in screens.values())
if entschieden:
return []
return ["lobby"] if "lobby" in screens else []
def save_welcome_page(customer_names, logo_urls, contacts=None, site="stuttgart",
background_url=None, screens=None):
"""Speichert die generierte Willkommensseite als welcome.html in den Zielscreens.
screens: Liste der Screen-Namen; None → Zielscreens aus der Konfiguration.
contacts: Liste der Ansprechpartner-Texte (Markdown, optional)
background_url: Benutzerdefiniertes Hintergrundbild oder None (Standard).
Gibt den Dateinamen zurück oder None bei Fehler."""
try:
welcome_dir = os.path.join(MEDIA_DIR, site, "lobby")
os.makedirs(welcome_dir, exist_ok=True)
filepath = os.path.join(os.path.abspath(welcome_dir), WELCOME_FILENAME)
html_content = generate_welcome_html(customer_names, logo_urls, contacts=contacts, site=site, background_url=background_url)
Gibt den Dateinamen zurück oder None bei Fehler.
Die Seite referenziert alle Bilder absolut (/static/..., /media/<site>/...),
ist also ortsunabhängig - eine Kopie je Screen rendert identisch."""
try:
from app import load_config
if screens is None:
screens = welcome_target_screens(load_config(), site)
if not screens:
print(f"⚠️ Generate - Kein Zielscreen für die Willkommensseite (site: {site})")
return None
html_content = generate_welcome_html(customer_names, logo_urls, contacts=contacts,
site=site, background_url=background_url)
for screen in screens:
welcome_dir = os.path.join(MEDIA_DIR, site, screen)
os.makedirs(welcome_dir, exist_ok=True)
filepath = os.path.join(os.path.abspath(welcome_dir), WELCOME_FILENAME)
with open(filepath, "w", encoding="utf-8") as f:
f.write(html_content)
if os.path.getsize(filepath) == 0:
raise IOError("File not written")
print(f"✅ Generate - Willkommensseite gespeichert in: {filepath}")
with open(filepath, "w", encoding="utf-8") as f:
f.write(html_content)
if os.path.getsize(filepath) == 0:
raise IOError("File not written")
names_str = ", ".join(customer_names)
print(f"✅ Generate - Willkommensseite generiert für: {names_str} (site: {site})")
print(f"✅ Generate - Willkommensseite gespeichert in: {filepath}")
print(f"✅ Generate - Willkommensseite generiert für: {names_str} "
f"(site: {site}, screens: {', '.join(screens)})")
return WELCOME_FILENAME
except Exception as e:
print(f"❌ Error saving welcome page: {e}")
return None
def add_customer_to_lobby_playlist(html_filename, site="stuttgart"):
"""Fügt die Willkommensseite (welcome.html) an Position 0 in die Lobby-Playlist ein.
Entfernt vorher alle vorhandenen welcome.html-Einträge (als String oder Dict).
def _strip_welcome(playlist, filename):
"""Entfernt alle welcome.html-Einträge aus einer Playlist (String- und Dict-Form)."""
playlist[:] = [
item for item in playlist
if not (
(isinstance(item, str) and item == filename) or
(isinstance(item, dict) and item.get("name") == filename)
)
]
def add_customer_to_playlists(html_filename, site="stuttgart", screens=None, valid_until=None):
"""Setzt die Willkommensseite an Position 0 der Playlist jedes Zielscreens.
screens: Liste der Zielscreens; None → aus der Konfiguration ermitteln.
valid_until: optionales Datum "YYYY-MM-DD". Ist es gesetzt, bekommt der
Eintrag eine Zeitsteuerung und verschwindet danach von selbst.
Aus allen übrigen Screens des Standorts wird die Seite entfernt, damit ein
abgewählter Screen nicht mit einer alten Begrüßung stehen bleibt.
Importiert load_config/save_config aus app.py (kein Zirkelimport)."""
try:
from app import load_config, save_config
config = load_config()
sites_cfg = config.setdefault("sites", {})
site_cfg = sites_cfg.setdefault(site, {"screens": {}})
screens = site_cfg.setdefault("screens", {})
lobby = screens.setdefault("lobby", {})
playlist = lobby.setdefault("playlist", [])
site_cfg = config.setdefault("sites", {}).setdefault(site, {"screens": {}})
all_screens = site_cfg.setdefault("screens", {})
if screens is None:
screens = welcome_target_screens(config, site)
# Nur Screens, die es wirklich gibt - kein stillschweigendes Anlegen
targets = [name for name in screens if name in all_screens]
entry = {"name": html_filename, "enabled": True}
if valid_until:
# Ganztaegig bis einschliesslich valid_until
entry["schedule"] = {
"mode": "daily",
"start": "00:00",
"end": "23:59",
"valid_until": valid_until
}
for name, screen_cfg in all_screens.items():
if not isinstance(screen_cfg, dict):
continue
playlist = screen_cfg.setdefault("playlist", [])
_strip_welcome(playlist, html_filename)
if name in targets:
playlist.insert(0, dict(entry))
full_filename = f"{html_filename}"
# Alte welcome.html-Einträge entfernen (sowohl String- als auch Dict-Form)
playlist[:] = [
item for item in playlist
if not (
(isinstance(item, str) and item == full_filename) or
(isinstance(item, dict) and item.get("name") == full_filename)
)
]
playlist.insert(0, full_filename)
save_config(config)
print(f"✅ Generate - Willkommensseite in Lobby-Playliste platziert (site: {site})")
return True
if targets:
print(f"✅ Generate - Willkommensseite platziert (site: {site}, "
f"screens: {', '.join(targets)})")
else:
print(f"⚠️ Generate - Kein Zielscreen vorhanden (site: {site})")
return bool(targets)
except Exception as e:
print(f"❌ Generate - Error adding to playlist: {e}")
return False