diff --git a/app.py b/app.py index ab565f3..3f11584 100755 --- a/app.py +++ b/app.py @@ -23,7 +23,7 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__)) MEDIA_DIR = os.path.join(BASE_DIR, "media") CONFIG_FILE = os.path.join(BASE_DIR, "config.json") -APP_VERSION = "2.1.0" +APP_VERSION = "3.1.0" UPLOAD_EXTENSIONS = {".jpg", ".jpeg", ".png", ".mp4"} app = Flask(__name__) @@ -48,6 +48,14 @@ def save_config(cfg): os.fsync(f.fileno()) print("✅ config.json gespeichert") + +def is_url(value): + return ( + isinstance(value, str) + and value.lower().startswith(("http://", "https://")) + ) + + # ------------------------------------------------- # Auth / Benutzer # ------------------------------------------------- @@ -64,6 +72,7 @@ def load_user(user_id): @app.route("/login", methods=["GET", "POST"]) def login(): config = load_config() + error = None if request.method == "POST": if ( @@ -72,8 +81,9 @@ def login(): ): login_user(Admin()) return redirect("/admin") + error = "Ungültige Zugangsdaten" - return render_template("login.html") + return render_template("login.html", error=error) @app.route("/logout") def logout(): @@ -124,13 +134,23 @@ def player(screen): # 1. Playlist-Reihenfolge for name in playlist: + if is_url(name): + normal_files.append({"kind": "url", "url": name}) + continue + if os.path.exists(os.path.join(folder, name)) and allowed_file(name): - normal_files.append(name) + normal_files.append({"kind": "file", "name": name}) + + existing_file_names = { + item["name"] for item in normal_files if item["kind"] == "file" + } # 2. Neue Dateien hintendran for f in sorted(os.listdir(folder)): - if f not in normal_files and allowed_file(f): - normal_files.append(f) + if f in existing_file_names or f.startswith("._"): + continue + if allowed_file(f): + normal_files.append({"kind": "file", "name": f}) # ------------------------------ # Priority-Playlist (global) @@ -139,7 +159,15 @@ def player(screen): priority_cfg = config.get("priority", {}) if priority_cfg.get("enabled", False): - prio_files = priority_cfg.get("playlist", []) + prio_files = [] + for name in priority_cfg.get("playlist", []): + if is_url(name): + prio_files.append({"kind": "url", "url": name}) + continue + + file_path = os.path.join(MEDIA_DIR, "priority", name) + if os.path.exists(file_path): + prio_files.append({"kind": "file", "name": name}) else: prio_files = [] @@ -148,7 +176,9 @@ def player(screen): screen=screen, normal_files=normal_files, # ✅ explizit prio_files=prio_files, # ✅ IMMER Liste - interval=screen_cfg.get("interval", 10) + interval=screen_cfg.get("interval", 10), + newsticker_text=screen_cfg.get("newsticker_text", ""), + newsticker_enabled=screen_cfg.get("newsticker_enabled", False) ) @@ -193,10 +223,26 @@ def admin(): continue files = [] - playlist = cfg["screens"][screen].get("playlist", []) + screen_cfg = cfg.setdefault("screens", {}).setdefault(screen, { + "interval": 10, + "show_images": True, + "show_videos": True, + "playlist": [] + }) + playlist = screen_cfg.get("playlist", []) + screen_cfg["newsticker_text"] = screen_cfg.get("newsticker_text", "") + screen_cfg["newsticker_enabled"] = screen_cfg.get("newsticker_enabled", False) # 1️⃣ Playlist-Reihenfolge for name in playlist: + if is_url(name): + files.append({ + "name": name, + "type": "url", + "size": "URL" + }) + continue + file_path = os.path.join(path, name) if not os.path.exists(file_path): continue @@ -227,7 +273,7 @@ def admin(): "size": size }) - screens[screen] = cfg["screens"][screen] + screens[screen] = screen_cfg media_files[screen] = files screen_status[screen] = "active" if files else "empty" @@ -241,7 +287,15 @@ def admin(): # 1️⃣ Playlist-Reihenfolge for name in prio_playlist: - file_path = os.path.join(MEDIA_DIR, "priority") + if is_url(name): + priority_files.append({ + "name": name, + "type": "url", + "size": "URL" + }) + continue + + file_path = os.path.join(prio_dir, name) if not os.path.exists(file_path): continue @@ -291,11 +345,18 @@ def admin(): @login_required def update_screen(screen): config = load_config() - cfg = config["screens"][screen] + cfg = config.setdefault("screens", {}).setdefault(screen, { + "interval": 10, + "show_images": True, + "show_videos": True, + "playlist": [] + }) cfg["interval"] = int(request.form.get("interval", 10)) cfg["show_images"] = "show_images" in request.form cfg["show_videos"] = "show_videos" in request.form + cfg["newsticker_text"] = request.form.get("newsticker_text", "") + cfg["newsticker_enabled"] = "newsticker_enabled" in request.form save_config(config) return redirect("/admin") @@ -319,11 +380,38 @@ def upload(screen): return redirect("/admin") + +# ------------------------------------------------- +# Admin: URL hinzufügen +# ------------------------------------------------- +@app.route("/admin/add-url/", methods=["POST"]) +@login_required +def add_url(screen): + url = request.form.get("url", "").strip() + if not is_url(url): + return redirect("/admin") + + config = load_config() + if screen == "priority": + config.setdefault("priority", {}).setdefault("playlist", []).append(url) + else: + screen_cfg = config.setdefault("screens", {}).setdefault(screen, { + "interval": 10, + "show_images": True, + "show_videos": True, + "playlist": [] + }) + screen_cfg.setdefault("playlist", []).append(url) + + save_config(config) + return redirect("/admin") + + # ------------------------------------------------- # Admin: Datei löschen # ------------------------------------------------- -@app.route("/admin/delete//", methods=["POST"]) +@app.route("/admin/delete//", methods=["POST"]) @login_required def delete_file(screen, filename): cfg = load_config() @@ -358,9 +446,18 @@ def save_playlist(screen): data = request.get_json() if screen == "priority": + cfg.setdefault("priority", {}).setdefault("playlist", []) cfg["priority"]["playlist"] = data["playlist"] else: - cfg["screens"][screen]["playlist"] = data["playlist"] + screen_cfg = cfg.setdefault("screens", {}).setdefault(screen, { + "interval": 10, + "show_images": True, + "show_videos": True, + "playlist": [], + "newsticker_text": "", + "newsticker_enabled": False + }) + screen_cfg["playlist"] = data["playlist"] save_config(cfg) return "", 204 diff --git a/config.json b/config.json index 70acafc..f4a5c2c 100755 --- a/config.json +++ b/config.json @@ -1,27 +1,33 @@ { "priority": { "enabled": true, - "playlist": [] + "playlist": [ + "https://www.meteoblue.com/en/meteotv/d7b0fd" + ] }, "screens": { "lobby": { - "interval": 10, + "interval": 15, "show_images": true, - "show_videos": true, + "show_videos": false, "playlist": [ - "HilfeKI.jpg", - "QRCode.png", - "Will_AI_Replace_Developers_1.jpg", - "Video CANCOM Jahresr\u00fcckblick 2025.MP4" - ] + "https://wbxroompresence.cancom.io/standort?find=Stuttgart", + "Hilfe_KI.jpg", + "e00a687f-d82a-446d-b8f3-07895fbc7309.png", + "Video_CANCOM_LIVE_2025_Stuttgart.MP4" + ], + "newsticker_text": "Hallo dies ist der Newsticker", + "newsticker_enabled": false }, "casino": { "interval": 10, "show_images": true, "show_videos": true, "playlist": [ - "JET-Design.JPG" - ] + "https://wbxroompresence.cancom.io/standort?find=Stuttgart" + ], + "newsticker_text": "Hallo dies ist der Newsticker", + "newsticker_enabled": false } }, "admin": { diff --git a/templates/admin.html b/templates/admin.html index d4967de..828bb64 100755 --- a/templates/admin.html +++ b/templates/admin.html @@ -38,6 +38,7 @@ background-color: #eeeeee; border-left: 6px solid #DA002D; padding-left: 1rem; + font-size: 1.1rem; /* leicht größer */ font-weight: 600; } @@ -93,6 +94,22 @@ .drag-handle:active { cursor: grabbing; } + + /* CANCOM CI Tooltip Styling */ + .tooltip { + --bs-tooltip-border-color: #DA002D; + } + .tooltip .tooltip-inner { + border: 1px solid #DA002D; + background-color: #ffffff; + color: #212121; + box-shadow: none; + } + .tooltip .tooltip-arrow::before { + border-top-color: #DA002D; + border-bottom-color: #DA002D; + background-color: #ffffff; + } @@ -116,7 +133,7 @@ Admin Dashboard

Simple Signage

- s +
Logout @@ -139,7 +156,7 @@ ⚠ PRIORITY (global)
- wirkt auf alle Player + wirkt auf alle Player
@@ -161,6 +178,14 @@
+
+
+ + +
+
+

Playlist Reihenfolge

@@ -169,21 +194,37 @@
  • - {{ file.name }} + + {{ file.name }} + {% if file.type == "image" %} Bild + {% elif file.type == "url" %} + URL {% else %} Video {% endif %} - {{ file.size }} KB + + {% if file.type == "url" %} + {{ file.size }} + {% else %} + {{ file.size }} KB + {% endif %} + -
    @@ -215,9 +256,9 @@
    - Screen: {{ screen }} + Screen: https://signage.ccmake.de/player/{{ screen }} {% if screen_status[screen] == "active" %} - Aktiv + Aktiv {% else %} Leer {% endif %} @@ -226,13 +267,26 @@
    +

    Einstellungen

    -
    +
    +
    + + +
    + +
    + + +
    +
    Bilder anzeigen
    -
    +
    @@ -255,7 +309,7 @@
    -

    Medien hochladen

    +

    Medien hochladen / URL hinzufügen

    @@ -264,26 +318,54 @@
    +
    +
    + + +
    +
    + +
    + -

    Playlist Reihenfolge

    +

    Playlist Reihenfolge

      {% for file in media_files[screen] %}
    • - {{ file.name }} + + {{ file.name }} + {% if file.type == "image" %} - Bild + Bild + {% elif file.type == "url" %} + URL {% else %} - Video + Video {% endif %} - {{ file.size }} KB + + {% if file.type == "url" %} + {{ file.size }} + {% else %} + {{ file.size }} KB + {% endif %} + -