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:
@@ -110,7 +110,7 @@ Standorte (sites) sind die oberste Organisationsebene und gruppieren Screens.
|
|||||||
- URL playlist items are stored as dicts like `{"url": "https://...", "zoom": 0.8}` and the zoom value must survive save/reorder flows. File items may also be dicts (`{"name": "bild.jpg", ...}`); `save_playlist` copies existing dicts, so extra keys like `schedule` survive reordering.
|
- URL playlist items are stored as dicts like `{"url": "https://...", "zoom": 0.8}` and the zoom value must survive save/reorder flows. File items may also be dicts (`{"name": "bild.jpg", ...}`); `save_playlist` copies existing dicts, so extra keys like `schedule` survive reordering.
|
||||||
- `.html` items in `media/` are rendered inline as content, not in an iframe.
|
- `.html` items in `media/` are rendered inline as content, not in an iframe.
|
||||||
- `config.priority.enabled` makes the priority playlist show on every screen.
|
- `config.priority.enabled` makes the priority playlist show on every screen.
|
||||||
- `POST /api/customer` generates `welcome.html` and inserts it at the front of the lobby playlist for the specified site.
|
- `POST /api/customer` generates `welcome.html` and inserts it at the front of the playlist of every target screen for the specified site. Target screens are those with `welcome_enabled`; if no screen of the site has the key at all, the old behaviour applies (`lobby` only). Optional `valid_until` (`"YYYY-MM-DD"`) attaches a schedule so the page disappears on its own.
|
||||||
- New standorte can be added by creating `media/<neuer-standort>/<screen>/` directories and optionally adding config to `config.json["sites"][<neuer-standort>]`.
|
- New standorte can be added by creating `media/<neuer-standort>/<screen>/` directories and optionally adding config to `config.json["sites"][<neuer-standort>]`.
|
||||||
- Priority playlist (`config.priority`) is global and affects all sites/screens.
|
- Priority playlist (`config.priority`) is global and affects all sites/screens.
|
||||||
- Willkommensseite (`customer.html`) accepts up to 3 customer names; logos are fetched via OpenAI→Brandfetch and displayed in a flex row (portrait: column).
|
- Willkommensseite (`customer.html`) accepts up to 3 customer names; logos are fetched via OpenAI→Brandfetch and displayed in a flex row (portrait: column).
|
||||||
@@ -119,7 +119,9 @@ Standorte (sites) sind die oberste Organisationsebene und gruppieren Screens.
|
|||||||
- `customer_names` are preserved in form fields after POST (via `value`-Attribute).
|
- `customer_names` are preserved in form fields after POST (via `value`-Attribute).
|
||||||
- Admin-UI nutzt keyadmin-Design: `brand-surface` (#2b2f36), `nav-surface` (rot #DA002D), Dark Mode per `localStorage("signage-theme")`.
|
- Admin-UI nutzt keyadmin-Design: `brand-surface` (#2b2f36), `nav-surface` (rot #DA002D), Dark Mode per `localStorage("signage-theme")`.
|
||||||
- Gemeinsame HTML-Bausteine: `_header.html`, `_footer.html`, `_styles.html` (CSS-Variablen `--ccm-*`, Dark Mode, Card-Border-Radius 1rem).
|
- Gemeinsame HTML-Bausteine: `_header.html`, `_footer.html`, `_styles.html` (CSS-Variablen `--ccm-*`, Dark Mode, Card-Border-Radius 1rem).
|
||||||
- `add_customer_to_lobby_playlist` entfernt `welcome.html` sowohl als String als auch als Dict aus der Playlist vor dem Einfügen.
|
- `add_customer_to_playlists` (früher `add_customer_to_lobby_playlist`) entfernt `welcome.html` als String *und* als Dict aus **allen** Screens des Standorts und fügt sie danach nur in den Zielscreens an Position 0 wieder ein – ein abgewählter Screen bleibt so nicht mit einer alten Begrüßung stehen. Eingefügt wird ein Dict, kein String, damit `enabled` und `schedule` mitgeführt werden können.
|
||||||
|
- `welcome_target_screens(config, site)` fällt nur dann auf `lobby` zurück, wenn **kein** Screen des Standorts den Schlüssel `welcome_enabled` besitzt. Wurde er überall bewusst auf `False` gesetzt, ist die leere Liste das gewollte Ergebnis.
|
||||||
|
- Weder `save_welcome_page` noch `add_customer_to_playlists` legen Screens an. Das frühere `screens.setdefault("lobby", {})` erzeugte an Standorten ohne Lobby stillschweigend einen Screen.
|
||||||
- `add_screen` legt ein Verzeichnis unter `media/<site>/<screen>/` an und einen Config-Eintrag.
|
- `add_screen` legt ein Verzeichnis unter `media/<site>/<screen>/` an und einen Config-Eintrag.
|
||||||
- `delete_screen` entfernt den Screen aus der Config und löscht das Verzeichnis rekursiv.
|
- `delete_screen` entfernt den Screen aus der Config und löscht das Verzeichnis rekursiv.
|
||||||
- Custom-URL-Button: pro Screen konfigurierbar (`custom_url` + `custom_url_label` + `custom_url_enabled` + `custom_url_target` + `custom_url_position`) im Admin-Formular (Aktionen-Tab); wird im Player als Button an wählbarer Position (9 Positionen: oben/mitte/unten × links/mitte/rechts) angezeigt und öffnet die URL wahlweise in einem iframe-Overlay mit Zurück-Button (`overlay`) oder per Direkt-Weiterleitung (`redirect`); Player pausiert während das Overlay geöffnet ist. Die unteren Positionen weichen automatisch 16px über dem Newsticker-Balken aus.
|
- Custom-URL-Button: pro Screen konfigurierbar (`custom_url` + `custom_url_label` + `custom_url_enabled` + `custom_url_target` + `custom_url_position`) im Admin-Formular (Aktionen-Tab); wird im Player als Button an wählbarer Position (9 Positionen: oben/mitte/unten × links/mitte/rechts) angezeigt und öffnet die URL wahlweise in einem iframe-Overlay mit Zurück-Button (`overlay`) oder per Direkt-Weiterleitung (`redirect`); Player pausiert während das Overlay geöffnet ist. Die unteren Positionen weichen automatisch 16px über dem Newsticker-Balken aus.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Browserbasiertes Digital-Signage-System für interne Info-Screens.
|
|||||||
- Bilder, Videos (MP4/H.264), HTML-Seiten, URL-Playlist-Einträge
|
- Bilder, Videos (MP4/H.264), HTML-Seiten, URL-Playlist-Einträge
|
||||||
- **Multi-Standort**: Standorte (sites) gruppieren Screens
|
- **Multi-Standort**: Standorte (sites) gruppieren Screens
|
||||||
- **Priority-Playlist**: globale Inhalte wirken auf alle Player
|
- **Priority-Playlist**: globale Inhalte wirken auf alle Player
|
||||||
- **Willkommensseite**: bis zu 3 Kundenlogos via OpenAI + Brandfetch, pro Standort konfigurierbares Hintergrundbild
|
- **Willkommensseite**: bis zu 3 Kundenlogos via OpenAI + Brandfetch, pro Standort konfigurierbares Hintergrundbild; erscheint auf allen Screens mit `welcome_enabled` (mehrere möglich), optional mit Ablaufdatum
|
||||||
- Pro Screen eigene Playlist mit Drag-&-Drop-Reihenfolge
|
- Pro Screen eigene Playlist mit Drag-&-Drop-Reihenfolge
|
||||||
- Auto-Reload bei Playlist-Änderungen
|
- Auto-Reload bei Playlist-Änderungen
|
||||||
- Newsticker pro Screen
|
- Newsticker pro Screen
|
||||||
@@ -418,6 +418,7 @@ OPENAI_API_KEY=sk-...
|
|||||||
| `custom_url_target` | string | `"overlay"` (iframe) oder `"redirect"` |
|
| `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` |
|
| `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) |
|
| `stay_on_first` | bool | Player bleibt auf erstem Element stehen (kein Refresh) |
|
||||||
|
| `welcome_enabled` | bool | Willkommensseite wird auf diesem Screen vorne in die Playlist gesetzt |
|
||||||
| `voice_agent_enabled` | bool | Voice-Agent-Button im Player anzeigen |
|
| `voice_agent_enabled` | bool | Voice-Agent-Button im Player anzeigen |
|
||||||
| `voice_agent_show_image` | bool | Bild (dva.png) über dem Button anzeigen |
|
| `voice_agent_show_image` | bool | Bild (dva.png) über dem Button anzeigen |
|
||||||
| `voice_agent_label` | string | Button-Beschriftung (Default: "Digitaler Assistent") |
|
| `voice_agent_label` | string | Button-Beschriftung (Default: "Digitaler Assistent") |
|
||||||
@@ -496,6 +497,7 @@ Optional pro Eintrag. Ohne `schedule` läuft der Eintrag immer (sofern `enabled`
|
|||||||
- `_in_validity_range(schedule, day)` – optionalen Gültigkeitszeitraum prüfen
|
- `_in_validity_range(schedule, day)` – optionalen Gültigkeitszeitraum prüfen
|
||||||
- `schedule_text(schedule)` – Jinja-Filter: Klartext einer Zeitsteuerung für Tooltips
|
- `schedule_text(schedule)` – Jinja-Filter: Klartext einer Zeitsteuerung für Tooltips
|
||||||
- `load_priority_files()` – Priority-Playlist + Dateien laden
|
- `load_priority_files()` – Priority-Playlist + Dateien laden
|
||||||
|
- `welcome_target_screens(config, site)` – Zielscreens der Willkommensseite (Fallback `lobby`)
|
||||||
- `prio_redirect(site)` – Redirect-Pfad zur Priority-Seite
|
- `prio_redirect(site)` – Redirect-Pfad zur Priority-Seite
|
||||||
- `get_background_url(site)` – URL zum benutzerdefinierten Hintergrundbild oder `None`
|
- `get_background_url(site)` – URL zum benutzerdefinierten Hintergrundbild oder `None`
|
||||||
- `load_users()` / `save_users()` / `get_user()` – User aus `users.json`
|
- `load_users()` / `save_users()` / `get_user()` – User aus `users.json`
|
||||||
|
|||||||
45
app.py
45
app.py
@@ -892,13 +892,15 @@ def add_customer():
|
|||||||
Formular für die Willkommensseite:
|
Formular für die Willkommensseite:
|
||||||
- Bis zu 3 Kundennamen eingeben
|
- Bis zu 3 Kundennamen eingeben
|
||||||
- Logos per OpenAI → Brandfetch suchen
|
- Logos per OpenAI → Brandfetch suchen
|
||||||
- welcome.html wird generiert und in der Lobby-Playlist vorne platziert
|
- welcome.html wird generiert und in den Playlists aller Zielscreens
|
||||||
|
vorne platziert (Screens mit "welcome_enabled")
|
||||||
"""
|
"""
|
||||||
error = None
|
error = None
|
||||||
success = None
|
success = None
|
||||||
logo_urls = [None, None, None]
|
logo_urls = [None, None, None]
|
||||||
customer_names = ["", "", ""]
|
customer_names = ["", "", ""]
|
||||||
contacts = ["", "", ""]
|
contacts = ["", "", ""]
|
||||||
|
valid_until = (request.form.get("valid_until") or "").strip()
|
||||||
site = request.args.get("site") or request.form.get("site") or ""
|
site = request.args.get("site") or request.form.get("site") or ""
|
||||||
|
|
||||||
# Fallback auf ersten für den User zugänglichen Standort
|
# Fallback auf ersten für den User zugänglichen Standort
|
||||||
@@ -959,20 +961,34 @@ def add_customer():
|
|||||||
break
|
break
|
||||||
|
|
||||||
bg_url = get_background_url(site)
|
bg_url = get_background_url(site)
|
||||||
html_filename = generate_welcome_page.save_welcome_page(valid_names, valid_urls, contacts=valid_contacts, site=site, background_url=bg_url)
|
targets = generate_welcome_page.welcome_target_screens(load_config(), site)
|
||||||
|
|
||||||
|
if not targets:
|
||||||
|
error = ("Kein Screen für die Willkommensseite ausgewählt – "
|
||||||
|
"im Tab 'Einstellungen' eines Screens aktivieren")
|
||||||
|
html_filename = None
|
||||||
|
else:
|
||||||
|
html_filename = generate_welcome_page.save_welcome_page(
|
||||||
|
valid_names, valid_urls, contacts=valid_contacts,
|
||||||
|
site=site, background_url=bg_url, screens=targets)
|
||||||
|
|
||||||
if html_filename:
|
if html_filename:
|
||||||
if generate_welcome_page.add_customer_to_lobby_playlist(html_filename, site=site):
|
if generate_welcome_page.add_customer_to_playlists(
|
||||||
|
html_filename, site=site, screens=targets, valid_until=valid_until):
|
||||||
# Letzte Kundendaten für Background-Update speichern
|
# Letzte Kundendaten für Background-Update speichern
|
||||||
config = load_config()
|
config = load_config()
|
||||||
site_cfg = config.setdefault("sites", {}).setdefault(site, {})
|
site_cfg = config.setdefault("sites", {}).setdefault(site, {})
|
||||||
site_cfg["welcome_data"] = {"names": valid_names, "logo_urls": valid_urls, "contacts": valid_contacts}
|
site_cfg["welcome_data"] = {"names": valid_names, "logo_urls": valid_urls, "contacts": valid_contacts}
|
||||||
save_config(config)
|
save_config(config)
|
||||||
add_history_entry("customer_added", f"Kunden ({', '.join(valid_names)}) zur Willkommensseite an Standort '{site}' hinzugefügt", current_user.email)
|
add_history_entry("customer_added", f"Kunden ({', '.join(valid_names)}) zur Willkommensseite an Standort '{site}' hinzugefügt (Screens: {', '.join(targets)})", current_user.email)
|
||||||
success = f"✅ Willkommensseite mit {len(valid_names)} Kunden erstellt!"
|
success = (f"✅ Willkommensseite mit {len(valid_names)} Kunden erstellt "
|
||||||
|
f"– angezeigt auf: {', '.join(targets)}")
|
||||||
|
if valid_until:
|
||||||
|
success += f" (bis einschliesslich {valid_until})"
|
||||||
else:
|
else:
|
||||||
error = "Fehler beim Hinzufügen zur Playliste"
|
error = "Fehler beim Hinzufügen zur Playliste"
|
||||||
else:
|
elif targets:
|
||||||
|
# Ohne Zielscreens steht die Meldung dazu bereits in error
|
||||||
error = "Fehler beim Speichern der Willkommensseite"
|
error = "Fehler beim Speichern der Willkommensseite"
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
error = f"Konfigurationsfehler: {str(e)}"
|
error = f"Konfigurationsfehler: {str(e)}"
|
||||||
@@ -1002,6 +1018,7 @@ def add_customer():
|
|||||||
logo_urls=logo_urls,
|
logo_urls=logo_urls,
|
||||||
customer_names=customer_names,
|
customer_names=customer_names,
|
||||||
contacts=contacts,
|
contacts=contacts,
|
||||||
|
welcome_screens=generate_welcome_page.welcome_target_screens(config, site) if site else [],
|
||||||
site=site,
|
site=site,
|
||||||
site_list=site_list,
|
site_list=site_list,
|
||||||
current_site=site,
|
current_site=site,
|
||||||
@@ -1047,18 +1064,27 @@ def api_add_customer():
|
|||||||
|
|
||||||
contact = data.get("contact", "").strip()
|
contact = data.get("contact", "").strip()
|
||||||
|
|
||||||
html_filename = generate_welcome_page.save_welcome_page([customer_name], [logo_url], contacts=[contact], site=site)
|
valid_until = (data.get("valid_until") or "").strip()
|
||||||
|
targets = generate_welcome_page.welcome_target_screens(load_config(), site)
|
||||||
|
|
||||||
|
if not targets:
|
||||||
|
return jsonify({"error": "No welcome screen configured for this site"}), 400
|
||||||
|
|
||||||
|
html_filename = generate_welcome_page.save_welcome_page(
|
||||||
|
[customer_name], [logo_url], contacts=[contact], site=site, screens=targets)
|
||||||
|
|
||||||
if not html_filename:
|
if not html_filename:
|
||||||
return jsonify({"error": "Failed to create welcome page"}), 500
|
return jsonify({"error": "Failed to create welcome page"}), 500
|
||||||
|
|
||||||
if generate_welcome_page.add_customer_to_lobby_playlist(html_filename, site=site):
|
if generate_welcome_page.add_customer_to_playlists(
|
||||||
|
html_filename, site=site, screens=targets, valid_until=valid_until):
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": f"Customer '{customer_name}' added successfully",
|
"message": f"Customer '{customer_name}' added successfully",
|
||||||
"welcome_page": html_filename,
|
"welcome_page": html_filename,
|
||||||
"logo_url": logo_url,
|
"logo_url": logo_url,
|
||||||
"site": site
|
"site": site,
|
||||||
|
"screens": targets
|
||||||
}), 201
|
}), 201
|
||||||
else:
|
else:
|
||||||
return jsonify({"error": "Failed to add to playlist"}), 500
|
return jsonify({"error": "Failed to add to playlist"}), 500
|
||||||
@@ -1527,6 +1553,7 @@ def update_screen(site, screen):
|
|||||||
cfg["newsticker_text"] = request.form.get("newsticker_text", "")
|
cfg["newsticker_text"] = request.form.get("newsticker_text", "")
|
||||||
cfg["newsticker_enabled"] = "newsticker_enabled" in request.form
|
cfg["newsticker_enabled"] = "newsticker_enabled" in request.form
|
||||||
cfg["stay_on_first"] = "stay_on_first" in request.form
|
cfg["stay_on_first"] = "stay_on_first" in request.form
|
||||||
|
cfg["welcome_enabled"] = "welcome_enabled" in request.form
|
||||||
|
|
||||||
save_config(config)
|
save_config(config)
|
||||||
return redirect(f"/admin/{site}")
|
return redirect(f"/admin/{site}")
|
||||||
|
|||||||
@@ -296,57 +296,134 @@ def generate_welcome_html(customer_names, logo_urls, contacts=None, site="stuttg
|
|||||||
# Speichern + Playlist-Einfügung
|
# Speichern + Playlist-Einfügung
|
||||||
# -------------------------------------------------
|
# -------------------------------------------------
|
||||||
|
|
||||||
def save_welcome_page(customer_names, logo_urls, contacts=None, site="stuttgart", background_url=None):
|
def welcome_target_screens(config, site):
|
||||||
"""Speichert die generierte Willkommensseite als welcome.html im Lobby-Verzeichnis.
|
"""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)
|
contacts: Liste der Ansprechpartner-Texte (Markdown, optional)
|
||||||
background_url: Benutzerdefiniertes Hintergrundbild oder None (Standard).
|
background_url: Benutzerdefiniertes Hintergrundbild oder None (Standard).
|
||||||
Gibt den Dateinamen zurück oder None bei Fehler."""
|
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:
|
try:
|
||||||
welcome_dir = os.path.join(MEDIA_DIR, site, "lobby")
|
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)
|
os.makedirs(welcome_dir, exist_ok=True)
|
||||||
filepath = os.path.join(os.path.abspath(welcome_dir), WELCOME_FILENAME)
|
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)
|
|
||||||
|
|
||||||
with open(filepath, "w", encoding="utf-8") as f:
|
with open(filepath, "w", encoding="utf-8") as f:
|
||||||
f.write(html_content)
|
f.write(html_content)
|
||||||
if os.path.getsize(filepath) == 0:
|
if os.path.getsize(filepath) == 0:
|
||||||
raise IOError("File not written")
|
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 gespeichert in: {filepath}")
|
||||||
|
|
||||||
|
names_str = ", ".join(customer_names)
|
||||||
|
print(f"✅ Generate - Willkommensseite generiert für: {names_str} "
|
||||||
|
f"(site: {site}, screens: {', '.join(screens)})")
|
||||||
return WELCOME_FILENAME
|
return WELCOME_FILENAME
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Error saving welcome page: {e}")
|
print(f"❌ Error saving welcome page: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def add_customer_to_lobby_playlist(html_filename, site="stuttgart"):
|
def _strip_welcome(playlist, filename):
|
||||||
"""Fügt die Willkommensseite (welcome.html) an Position 0 in die Lobby-Playlist ein.
|
"""Entfernt alle welcome.html-Einträge aus einer Playlist (String- und Dict-Form)."""
|
||||||
Entfernt vorher alle vorhandenen welcome.html-Einträge (als String oder Dict).
|
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)."""
|
Importiert load_config/save_config aus app.py (kein Zirkelimport)."""
|
||||||
try:
|
try:
|
||||||
from app import load_config, save_config
|
from app import load_config, save_config
|
||||||
|
|
||||||
config = load_config()
|
config = load_config()
|
||||||
sites_cfg = config.setdefault("sites", {})
|
site_cfg = config.setdefault("sites", {}).setdefault(site, {"screens": {}})
|
||||||
site_cfg = sites_cfg.setdefault(site, {"screens": {}})
|
all_screens = site_cfg.setdefault("screens", {})
|
||||||
screens = site_cfg.setdefault("screens", {})
|
|
||||||
lobby = screens.setdefault("lobby", {})
|
if screens is None:
|
||||||
playlist = lobby.setdefault("playlist", [])
|
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)
|
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:
|
except Exception as e:
|
||||||
print(f"❌ Generate - Error adding to playlist: {e}")
|
print(f"❌ Generate - Error adding to playlist: {e}")
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -344,13 +344,21 @@
|
|||||||
<label class="form-check-label">Videos anzeigen</label>
|
<label class="form-check-label">Videos anzeigen</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check form-switch mb-4">
|
<div class="form-check form-switch mb-3">
|
||||||
<input class="form-check-input" type="checkbox"
|
<input class="form-check-input" type="checkbox"
|
||||||
name="stay_on_first"
|
name="stay_on_first"
|
||||||
{% if cfg.stay_on_first %}checked{% endif %}>
|
{% if cfg.stay_on_first %}checked{% endif %}>
|
||||||
<label class="form-check-label">Auf erstem Element stehen bleiben (kein Refresh)</label>
|
<label class="form-check-label">Auf erstem Element stehen bleiben (kein Refresh)</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check form-switch mb-1">
|
||||||
|
<input class="form-check-input" type="checkbox"
|
||||||
|
name="welcome_enabled"
|
||||||
|
{% if cfg.welcome_enabled %}checked{% endif %}>
|
||||||
|
<label class="form-check-label">Willkommensseite hier anzeigen</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-hint mb-4">Erzeugte Willkommensseiten werden auf allen so markierten Screens dieses Standorts vorne in die Playlist gesetzt.</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
Einstellungen speichern
|
Einstellungen speichern
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -153,9 +153,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr class="mt-4 mb-3">
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" for="valid_until">Anzeigen bis <span class="text-muted">(optional)</span></label>
|
||||||
|
<input type="date" class="form-control" id="valid_until" name="valid_until"
|
||||||
|
value="{{ request.form.get('valid_until', '') }}" style="max-width: 260px;">
|
||||||
|
<div class="form-hint">Ohne Angabe bleibt die Willkommensseite stehen, bis sie von Hand entfernt wird. Mit Datum verschwindet sie nach diesem Tag auf allen Screens von selbst.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3 text-muted">
|
<div class="mb-3 text-muted">
|
||||||
<p class="mb-1">Das System sucht automatisch nach den Kundenlogos</p>
|
<p class="mb-1">Das System sucht automatisch nach den Kundenlogos</p>
|
||||||
<p class="mb-0">und erstellt eine angepasste Willkommensseite für die Lobby-Playlist.</p>
|
<p class="mb-0">und erstellt eine angepasste Willkommensseite.</p>
|
||||||
|
<p class="mb-1">Sie erscheint auf allen Screens dieses Standorts, bei denen im Tab „Einstellungen" die Option <em>Willkommensseite hier anzeigen</em> aktiv ist{% if welcome_screens %} – aktuell: <strong>{{ welcome_screens | join(', ') }}</strong>{% else %} – <strong>aktuell keiner</strong>{% endif %}.</p>
|
||||||
<p class="mb-0 small"><i class="ti ti-markdown me-1"></i>Ansprechpartner unterstützen <strong>fett</strong>, <em>kursiv</em>, <code>Code</code>, Aufzählungen (<code>- </code>) sowie Zeilenumbrüche.</p>
|
<p class="mb-0 small"><i class="ti ti-markdown me-1"></i>Ansprechpartner unterstützen <strong>fett</strong>, <em>kursiv</em>, <code>Code</code>, Aufzählungen (<code>- </code>) sowie Zeilenumbrüche.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -249,7 +249,9 @@
|
|||||||
<p>Auf der Willkommensseite können bis zu 3 Kundennamen eingegeben werden. Die Logos werden automatisch via OpenAI + Brandfetch gesucht.</p>
|
<p>Auf der Willkommensseite können bis zu 3 Kundennamen eingegeben werden. Die Logos werden automatisch via OpenAI + Brandfetch gesucht.</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Aufruf über <code>/willkommen?site=<standort></code> (oder Menüpunkt)</li>
|
<li>Aufruf über <code>/willkommen?site=<standort></code> (oder Menüpunkt)</li>
|
||||||
<li>Die generierte <code>welcome.html</code> wird automatisch an den Anfang der Lobby-Playlist gesetzt</li>
|
<li><strong>Zielscreens:</strong> Die generierte <code>welcome.html</code> wird automatisch an den Anfang der Playlist <em>jedes</em> Screens gesetzt, bei dem im Tab "Einstellungen" die Option <em>Willkommensseite hier anzeigen</em> aktiv ist. So lässt sich dieselbe Begrüßung auf mehreren Screens zeigen, ohne sie mehrfach anzulegen. Ist an einem Standort nichts eingestellt, geht sie wie bisher in die Lobby.</li>
|
||||||
|
<li>Wird ein Screen später abgewählt, verschwindet die Willkommensseite dort beim nächsten Generieren automatisch.</li>
|
||||||
|
<li><strong>Anzeigen bis:</strong> Optionales Datum im Formular. Ist es gesetzt, verschwindet die Willkommensseite nach diesem Tag auf allen Screens von selbst – kein manuelles Aufräumen nach dem Besuch. Ohne Angabe bleibt sie stehen.</li>
|
||||||
<li>Pro Standort kann ein eigenes Hintergrundbild hochgeladen werden (Tab "Medien" im Standort-Dashboard)</li>
|
<li>Pro Standort kann ein eigenes Hintergrundbild hochgeladen werden (Tab "Medien" im Standort-Dashboard)</li>
|
||||||
<li>Pro Kunde können <strong>Ansprechpartner</strong> eingegeben werden (Markdown-Format):
|
<li>Pro Kunde können <strong>Ansprechpartner</strong> eingegeben werden (Markdown-Format):
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
Reference in New Issue
Block a user