diff --git a/AGENTS.md b/AGENTS.md index 8ef7f4e..d4b4da1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,8 +29,11 @@ Standorte (sites) sind die oberste Organisationsebene und gruppieren Screens. | `GET /admin//priority` | Priority-Playlist als separate Seite | | `GET /media///` | Medien-Datei ausliefern | | `GET /media/priority/` | Priority-Medien (global) | +| `GET /media//background/` | Hintergrundbild der Willkommensseite | | `GET /playlist///hash` | Playlist-Checksumme für Auto-Reload | | `GET /willkommen?site=` | Willkommensseite für Standort generieren | +| `POST /admin//upload-background` | Hintergrundbild für Willkommensseite hochladen | +| `POST /admin//delete-background` | Hintergrundbild zurücksetzen auf Standard | | `POST /api/customer` | API-Endpunkt (JSON mit "site"-Feld) | | `GET /admin//add-screen?name=` | Neuen Screen anlegen | | `POST /admin//delete-screen/` | Screen + Medien löschen | @@ -59,8 +62,8 @@ Standorte (sites) sind die oberste Organisationsebene und gruppieren Screens. - Neue Standorte können über den `+`-Button im Header oder über `GET /add-site?name=` angelegt werden. - `delete_site` entfernt den Standort aus Config und löscht das Medienverzeichnis rekursiv. - `config.json["server_url"]` (z. B. `http://signage.ccmake.de`) wird in der Admin-Ansicht für die Player-URLs verwendet. -- Screen-Card-Body hat Tabler-Tabs: **Playlist** (1, aktiv), **Einstellungen** (2), **Medien** (3); Priority-Seite ebenfalls Tabs **Playlist** und **Medien**. -- Tab-Reihenfolge in Screen-Cards: Playlist → Einstellungen → Medien. +- Screen-Card-Body hat Tabler-Tabs: **Playlist** (1, aktiv), **Einstellungen** (2), **Aktionen** (3), **Medien** (4); Priority-Seite ebenfalls Tabs **Playlist** und **Medien**. +- Tab-Reihenfolge in Screen-Cards: Playlist → Einstellungen → Aktionen → Medien. - Player-URL im Screen-Header ist ein klickbarer Link in grauer Farbe. ## Repo Quirks @@ -68,7 +71,10 @@ Standorte (sites) sind die oberste Organisationsebene und gruppieren Screens. - `media/` and media file extensions are gitignored. - The app has no configured tests, lint, typecheck, formatter, or CI. - Hardcoded secrets exist in tracked files; do not commit new secrets or reshuffle them casually. -- Wichtige Helper-Funktionen in `app.py`: `load_config()`, `save_config()`, `get_site_list()`, `get_screen_config()`, `is_url()`, `normalize_url()`, `playlist_item_name()`, `playlist_item_enabled()`, `load_priority_files()`, `prio_redirect()`. +- Wichtige Helper-Funktionen in `app.py`: `load_config()`, `save_config()`, `get_site_list()`, `get_screen_config()`, `is_url()`, `normalize_url()`, `playlist_item_name()`, `playlist_item_enabled()`, `load_priority_files()`, `prio_redirect()`, `get_background_url()`. +- Hintergrundbild der Willkommensseite wird pro Standort unter `media//background.*` gespeichert; Fallback auf `static/wallpaper.png` wenn keine Datei existiert. +- `get_background_url(site)` prüft auf benutzerdefiniertes Hintergrundbild für einen Standort. +- `generate_welcome_html(customer_names, logo_urls, background_url=None)` akzeptiert optionalen `background_url`-Parameter. - `welcome.html` wird in `media//lobby/` gespeichert. - `search_customer_logo` in `generate_welcome_page.py` nutzt OpenAI GPT-4 + Brandfetch CDN. - `admin_priority` rendert `priority.html` mit `site_list`, `current_site`, `priority_files`, `server_url`. diff --git a/app.py b/app.py index 41ddea2..a1bfbf6 100755 --- a/app.py +++ b/app.py @@ -185,6 +185,44 @@ def prio_redirect(site): return f"/admin/{site}/priority" +def get_background_url(site): + """Prüft, ob für einen Standort ein benutzerdefiniertes Hintergrundbild existiert. + Gibt die URL zurück oder None (dann wird das Standard-Wallpaper verwendet).""" + site_dir = os.path.join(MEDIA_DIR, site) + if not os.path.isdir(site_dir): + return None + for f in sorted(os.listdir(site_dir)): + if f.startswith("background."): + ext = os.path.splitext(f)[1].lower() + if ext in (".jpg", ".jpeg", ".png"): + return f"/media/{site}/background/{f}" + return None + + +def _regenerate_welcome_for_site(site): + """Regeneriert die Willkommensseite mit aktuellem Hintergrund, + falls zuvor Kundendaten gespeichert wurden.""" + config = load_config() + welcome_data = config.get("sites", {}).get(site, {}).get("welcome_data") + if welcome_data: + names = welcome_data.get("names", []) + urls = welcome_data.get("logo_urls", []) + if names: + bg_url = get_background_url(site) + generate_welcome_page.save_welcome_page(names, urls, site=site, background_url=bg_url) + print(f"♻️ Willkommensseite für {site} mit aktualisiertem Hintergrund neu generiert") + + +# ------------------------------------------------- +# Medien ausliefern (statische Dateien) +# ------------------------------------------------- +@app.route("/media//background/") +def media_background(site, filename): + """Liefert das Hintergrundbild für die Willkommensseite aus.""" + path = os.path.join(MEDIA_DIR, site) + return send_from_directory(path, filename) + + # ------------------------------------------------- # User & Login (Flask-Login) # ------------------------------------------------- @@ -285,10 +323,16 @@ def add_customer(): logo_urls[i] = vu break - html_filename = generate_welcome_page.save_welcome_page(valid_names, valid_urls, site=site) + bg_url = get_background_url(site) + html_filename = generate_welcome_page.save_welcome_page(valid_names, valid_urls, site=site, background_url=bg_url) if html_filename: if generate_welcome_page.add_customer_to_lobby_playlist(html_filename, site=site): + # Letzte Kundendaten für Background-Update speichern + config = load_config() + site_cfg = config.setdefault("sites", {}).setdefault(site, {}) + site_cfg["welcome_data"] = {"names": valid_names, "logo_urls": valid_urls} + save_config(config) success = f"✅ Willkommensseite mit {len(valid_names)} Kunden erstellt!" else: error = "Fehler beim Hinzufügen zur Playliste" @@ -301,6 +345,7 @@ def add_customer(): config = load_config() site_list = get_site_list(config) + background_url = get_background_url(site) return render_template( "customer.html", error=error, @@ -312,7 +357,8 @@ def add_customer(): current_site=site, version=APP_VERSION, year=datetime.now().year, - hostname=os.uname().nodename + hostname=os.uname().nodename, + background_url=background_url ) @@ -711,6 +757,55 @@ def admin_priority(site): ) +# ------------------------------------------------- +# Admin: Hintergrundbild für Willkommensseite hochladen +# ------------------------------------------------- +@app.route("/admin//upload-background", methods=["POST"]) +@login_required +def upload_background(site): + """Lädt ein Hintergrundbild für die Willkommensseite hoch (pro Standort). + Gespeichert als media//background..""" + file = request.files.get("background") + if not file: + return redirect(f"/willkommen?site={site}") + + ext = os.path.splitext(file.filename)[1].lower() + if ext not in UPLOAD_EXTENSIONS: + return "Ungültiger Dateityp", 400 + + site_dir = os.path.join(MEDIA_DIR, site) + os.makedirs(site_dir, exist_ok=True) + + # Alte Hintergrundbilder löschen + for f in os.listdir(site_dir): + if f.startswith("background."): + os.remove(os.path.join(site_dir, f)) + + filename = f"background{ext}" + file.save(os.path.join(site_dir, filename)) + + # Willkommensseite mit neuen Hintergrund neu generieren + _regenerate_welcome_for_site(site) + + return redirect(f"/willkommen?site={site}") + + +@app.route("/admin//delete-background", methods=["POST"]) +@login_required +def delete_background(site): + """Löscht das Hintergrundbild für die Willkommensseite.""" + site_dir = os.path.join(MEDIA_DIR, site) + if os.path.isdir(site_dir): + for f in os.listdir(site_dir): + if f.startswith("background."): + os.remove(os.path.join(site_dir, f)) + + # Willkommensseite mit Standard-Hintergrund neu generieren + _regenerate_welcome_for_site(site) + + return redirect(f"/willkommen?site={site}") + + # ------------------------------------------------- # Admin: Screen-Einstellungen speichern # ------------------------------------------------- diff --git a/config.json b/config.json index 15c4481..21aa9b9 100755 --- a/config.json +++ b/config.json @@ -15,7 +15,7 @@ "screens": { "lobby": { "interval": 10, - "show_images": true, + "show_images": false, "show_videos": false, "playlist": [ "welcome.html", @@ -34,12 +34,12 @@ "enabled": false } ], - "newsticker_text": "Herzlich Willkommen bei der CANCOM - wir begr\u00fc\u00dfen unsere G\u00e4ste und w\u00fcnschen Ihnen einen angenehmen Tag! !", - "newsticker_enabled": true, + "newsticker_text": "", + "newsticker_enabled": false, "custom_url": "https://dva-stage.cancom-ds.de/voice-agent", "custom_url_label": "Welcome Agent", "custom_url_enabled": false, - "custom_url_target": "redirect" + "custom_url_target": "overlay" }, "casino": { "interval": 15, @@ -70,6 +70,14 @@ "newsticker_text": "Hallo dies ist ein Test f\u00fcr Michael", "newsticker_enabled": false } + }, + "welcome_data": { + "names": [ + "cisco" + ], + "logo_urls": [ + "https://cdn.brandfetch.io/www.cisco.com/logo?c=1idyd4Tpb2nKaXIIc8T" + ] } }, "karlsruhe": { @@ -84,6 +92,14 @@ "newsticker_text": "Herzlich Willkommen bei der CANCOM - wir begr\u00fc\u00dfen unsere G\u00e4ste und w\u00fcnschen Ihnen einen angenehmen Tag!", "newsticker_enabled": true } + }, + "welcome_data": { + "names": [ + "cisco" + ], + "logo_urls": [ + "https://cdn.brandfetch.io/www.cisco.com/logo?c=1idyd4Tpb2nKaXIIc8T" + ] } } }, diff --git a/generate_welcome_page.py b/generate_welcome_page.py index fe2f50d..f5ac987 100644 --- a/generate_welcome_page.py +++ b/generate_welcome_page.py @@ -97,7 +97,7 @@ def search_customer_logo(customer_name): # HTML-Generierung # ------------------------------------------------- -def generate_welcome_html(customer_names, logo_urls, site="stuttgart"): +def generate_welcome_html(customer_names, logo_urls, site="stuttgart", background_url=None): """ Generiert den HTML-Code für die Willkommensseite. - Links oben: CANCOM-Logo + "meets" + Kundenlogos (280px Breite) @@ -106,11 +106,14 @@ def generate_welcome_html(customer_names, logo_urls, site="stuttgart"): customer_names: Liste der Kundennamen (max. 3) logo_urls: Liste der Logo-URLs (gleiche Reihenfolge) site: Standort-Name für die Anzeige + background_url: URLs zum Hintergrundbild (None → Standard-Wallpaper) """ site_display = site.capitalize() names_str = ", ".join(customer_names) if customer_names else "Unbekannt" print(f"✅ Generate - Willkommensseite wird generiert für: {names_str} (site: {site})") site_display = site.capitalize() + + bg_image = background_url if background_url else "/static/wallpaper.png" cancom_svg = '''''' logo_html = "" @@ -138,7 +141,7 @@ def generate_welcome_html(customer_names, logo_urls, site="stuttgart"): width: 100vw; height: 100vh; overflow: hidden; font-family: 'Segoe UI', 'Helvetica Neue', sans-serif; display: flex; align-items: center; justify-content: center; - background-image: url("/static/wallpaper.png"); + background-image: url("{bg_image}"); background-repeat: no-repeat; background-size: cover; }} .screen {{ @@ -212,14 +215,15 @@ def generate_welcome_html(customer_names, logo_urls, site="stuttgart"): # Speichern + Playlist-Einfügung # ------------------------------------------------- -def save_welcome_page(customer_names, logo_urls, site="stuttgart"): +def save_welcome_page(customer_names, logo_urls, site="stuttgart", background_url=None): """Speichert die generierte Willkommensseite als welcome.html im Lobby-Verzeichnis. + 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, site=site) + html_content = generate_welcome_html(customer_names, logo_urls, site=site, background_url=background_url) with open(filepath, "w", encoding="utf-8") as f: f.write(html_content) diff --git a/templates/admin.html b/templates/admin.html index db35935..f0a69ff 100755 --- a/templates/admin.html +++ b/templates/admin.html @@ -200,6 +200,11 @@ Einstellungen +