diff --git a/AGENTS.md b/AGENTS.md index e00d61c..8725463 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -132,7 +132,7 @@ 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 (nur Admins). - `delete_site` entfernt den Standort aus Config und löscht das Medienverzeichnis rekursiv (nur Admins). - `config.json["server_url"]` (z. B. `http://signage.ccmake.de`) wird in der Admin-Ansicht für die Player-URLs verwendet. -- **Query-Parameter-Passthrough**: Alle Query-Parameter, die an die Player-URL (`/player//`) angehängt werden, werden automatisch an alle Playlist-URLs (iframe + Overlay) weitergereicht. Beispiel: `/player/stuttgart/lobby?customer=acme` hängt `?customer=acme` an jede URL in der Playlist an. Funktioniert über `appendParams()` in `player.html` + `player_params` im Flask-Route. +- **Query-Parameter-Passthrough**: Query-Parameter an der Player-URL (`/player//`) können pro Playlist-URL im Edit-Modal (Checkbox) gesteuert werden. Standardmäßig **aus** – nur URLs mit aktivierter Checkbox erhalten die Parameter. Beispiel: `/player/stuttgart/lobby?customer=acme` hängt `?customer=acme` nur an URLs mit Passthrough=an. Funktioniert über `appendParams()` in `player.html` + `player_params` im Flask-Route. - Screen-Card-Body hat Tabler-Tabs: **Playlist** (1, aktiv), **Einstellungen** (2), **Aktionen** (3), **Digital Voice Agent** (4), **Medien** (5), **Info** (6); Priority-Seite ebenfalls Tabs **Playlist** und **Medien**. - `stay_on_first`: Wenn aktiviert bleibt der Player auf dem ersten Playlist-Element stehen (kein Durchlauf). - Tab-Reihenfolge in Screen-Cards: Playlist → Einstellungen → Aktionen → Digital Voice Agent → Medien → Info. diff --git a/README.md b/README.md index b8b5fd0..a41af3e 100755 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Browserbasiertes Digital-Signage-System für interne Info-Screens. - **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, Info (Tabler Tabs) +- **Query-Parameter-Passthrough**: Per Player-URL übergebene Query-Parameter (z. B. `/player/stuttgart/lobby?customer=acme`) können pro Playlist-URL im Edit-Modal aktiviert werden – standardmäßig aus - Priority-Seite ebenfalls mit Tabs: Playlist und Medien - Dark Mode (localStorage-persistiert) - CI-konformes Admin-UI (CANCOM-Design: `brand-surface`, `nav-surface` rot) @@ -271,6 +272,17 @@ Beispiel: http://localhost:5005/player/stuttgart/lobby ``` +### Query-Parameter-Passthrough + +Die Player-URL akzeptiert beliebige Query-Parameter, z. B. `?customer=acme&token=xyz`. Ob diese Parameter an eine Playlist-URL weitergegeben werden, wird pro URL im Admin-Playlist-Edit-Modal gesteuert (Checkbox "Query-Parameter an diese URL übergeben", standardmäßig aus). + +Beispiel: +``` +/player/stuttgart/lobby?customer=acme&token=xyz +``` +- URL in Playlist mit **aktiviertem** Passthrough: `https://dashboard.acme.com/status?customer=acme&token=xyz` +- URL in Playlist mit **deaktiviertem** Passthrough: `https://dashboard.acme.com/status` + ### Willkommensseite ``` diff --git a/app.py b/app.py index 84b6138..3023244 100755 --- a/app.py +++ b/app.py @@ -252,7 +252,8 @@ def load_priority_files(): "type": "url", "size": "URL", "zoom": url_data.get("zoom", 1.0), - "enabled": playlist_item_enabled(item) + "enabled": playlist_item_enabled(item), + "passthrough": item.get("passthrough_params", False) if isinstance(item, dict) else False }) continue @@ -980,7 +981,7 @@ def player(site, screen): if is_url(item): url_data = normalize_url(item) if url_data: - normal_files.append({"kind": "url", "url": url_data["url"], "zoom": url_data.get("zoom", 1.0), "enabled": True}) + normal_files.append({"kind": "url", "url": url_data["url"], "zoom": url_data.get("zoom", 1.0), "enabled": True, "passthrough": item.get("passthrough_params", False) if isinstance(item, dict) else False}) continue if item_name and os.path.exists(os.path.join(folder, item_name)) and allowed_file(item_name): @@ -1010,7 +1011,7 @@ def player(site, screen): if is_url(item): url_data = normalize_url(item) if url_data: - prio_files.append({"kind": "url", "url": url_data["url"], "zoom": url_data.get("zoom", 1.0), "enabled": True}) + prio_files.append({"kind": "url", "url": url_data["url"], "zoom": url_data.get("zoom", 1.0), "enabled": True, "passthrough": item.get("passthrough_params", False) if isinstance(item, dict) else False}) continue file_path = os.path.join(MEDIA_DIR, "priority", item_name) @@ -1200,7 +1201,8 @@ def admin(site): "type": "url", "size": "URL", "zoom": url_data.get("zoom", 1.0), - "enabled": playlist_item_enabled(item) + "enabled": playlist_item_enabled(item), + "passthrough": item.get("passthrough_params", False) if isinstance(item, dict) else False }) continue @@ -1608,7 +1610,16 @@ def update_playlist_item(site, screen): for i, item in enumerate(playlist): name = playlist_item_name(item) if name == old_name: - playlist[i] = {"url": new_url, "zoom": zoom} + updated = {"url": new_url, "zoom": zoom} + passthrough = data.get("passthrough", None) + if passthrough is not None: + updated["passthrough_params"] = bool(passthrough) + else: + # Bestehenden Wert übernehmen falls vorhanden + existing = item.get("passthrough_params") if isinstance(item, dict) else None + if existing is not None: + updated["passthrough_params"] = existing + playlist[i] = updated save_config(config) return jsonify({"success": True}) diff --git a/templates/admin.html b/templates/admin.html index f087724..2af334d 100755 --- a/templates/admin.html +++ b/templates/admin.html @@ -250,7 +250,7 @@ data-bs-toggle="tooltip" title="URL: {{ file.name }}{% if file.zoom %} (Zoom: {{ file.zoom }}x){% endif %}" {% endif %}> - {{ file.name }}{% if file.type == "url" and file.zoom != 1.0 %} [{{ file.zoom }}x]{% endif %} + {{ file.name }}{% if file.type == "url" and file.zoom != 1.0 %} [{{ file.zoom }}x]{% endif %}{% if file.type == "url" %}{% if file.get('passthrough', False) %} {% else %} {% endif %}{% endif %} @@ -290,7 +290,8 @@ data-zoom="{{ file.zoom | default(1.0) }}" data-site="{{ current_site }}" data-screen="{{ screen }}" - data-oldname="{{ file.name | e }}"> + data-oldname="{{ file.name | e }}" + data-passthrough="{{ 'true' if file.get('passthrough', False) else 'false' }}"> Edit {% else %} @@ -552,6 +553,7 @@ document.addEventListener("click", function (e) { editState.oldName = btn.dataset.oldname; document.getElementById("edit-url-input").value = btn.dataset.oldname; document.getElementById("edit-zoom-input").value = btn.dataset.zoom; + document.getElementById("edit-passthrough-input").checked = btn.dataset.passthrough === "true"; document.getElementById("edit-url-error").style.display = "none"; new bootstrap.Modal(document.getElementById("edit-url-modal")).show(); }); @@ -629,7 +631,8 @@ document.addEventListener("DOMContentLoaded", function () { body: JSON.stringify({ old_name: editState.oldName, new_url: newUrl, - zoom: zoom + zoom: zoom, + passthrough: document.getElementById("edit-passthrough-input").checked }) }).then(function (r) { if (r.ok) { @@ -666,6 +669,10 @@ document.addEventListener("DOMContentLoaded", function () { +
+ + +
diff --git a/templates/player.html b/templates/player.html index e9b90c1..efa1360 100755 --- a/templates/player.html +++ b/templates/player.html @@ -456,7 +456,7 @@ function playNext() { // URL im iframe anzeigen (mit Zoom-Unterstützung) if (item.kind === "url") { iframe.style.display = "block"; - iframe.src = appendParams(item.url, playerParams); + iframe.src = (item.passthrough === true) ? appendParams(item.url, playerParams) : item.url; const zoom = item.zoom || 1.0; if (zoom !== 1.0) { const scale = 1 / zoom; diff --git a/templates/priority.html b/templates/priority.html index 4bc3995..045653d 100644 --- a/templates/priority.html +++ b/templates/priority.html @@ -152,7 +152,7 @@ data-bs-toggle="tooltip" title="URL: {{ file.name }}{% if file.zoom %} (Zoom: {{ file.zoom }}x){% endif %}" {% endif %}> - {{ file.name }}{% if file.type == "url" and file.zoom != 1.0 %} [{{ file.zoom }}x]{% endif %} + {{ file.name }}{% if file.type == "url" and file.zoom != 1.0 %} [{{ file.zoom }}x]{% endif %}{% if file.type == "url" %}{% if file.get('passthrough', False) %} {% else %} {% endif %}{% endif %} {% if file.type == "image" %} @@ -192,7 +192,8 @@ data-zoom="{{ file.zoom | default(1.0) }}" data-site="{{ current_site }}" data-screen="priority" - data-oldname="{{ file.name | e }}"> + data-oldname="{{ file.name | e }}" + data-passthrough="{{ 'true' if file.get('passthrough', False) else 'false' }}"> Edit {% else %} @@ -265,6 +266,7 @@ document.addEventListener("click", function (e) { editState.oldName = btn.dataset.oldname; document.getElementById("edit-url-input").value = btn.dataset.oldname; document.getElementById("edit-zoom-input").value = btn.dataset.zoom; + document.getElementById("edit-passthrough-input").checked = btn.dataset.passthrough === "true"; document.getElementById("edit-url-error").style.display = "none"; new bootstrap.Modal(document.getElementById("edit-url-modal")).show(); }); @@ -337,7 +339,8 @@ document.addEventListener("DOMContentLoaded", function () { body: JSON.stringify({ old_name: editState.oldName, new_url: newUrl, - zoom: zoom + zoom: zoom, + passthrough: document.getElementById("edit-passthrough-input").checked }) }).then(function (r) { if (r.ok) { @@ -374,6 +377,10 @@ document.addEventListener("DOMContentLoaded", function () { +
+ + +