Tabs in der Admin UI

This commit is contained in:
Erik Thiele
2026-06-07 12:27:19 +02:00
parent 4b29d9a27f
commit 0efc5bb346
8 changed files with 3076 additions and 262 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -58,6 +58,10 @@ Standorte (sites) sind die oberste Organisationsebene und gruppieren Screens.
- Custom-URL-Button: pro Screen konfigurierbar (`custom_url` + `custom_url_label` + `custom_url_target`) im Admin-Formular; wird im Player als Button oben links 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. - Custom-URL-Button: pro Screen konfigurierbar (`custom_url` + `custom_url_label` + `custom_url_target`) im Admin-Formular; wird im Player als Button oben links 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.
- Neue Standorte können über den `+`-Button im Header oder über `GET /add-site?name=<name>` angelegt werden. - Neue Standorte können über den `+`-Button im Header oder über `GET /add-site?name=<name>` angelegt werden.
- `delete_site` entfernt den Standort aus Config und löscht das Medienverzeichnis rekursiv. - `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.
- Player-URL im Screen-Header ist ein klickbarer Link in grauer Farbe.
## Repo Quirks ## Repo Quirks
@@ -67,3 +71,4 @@ Standorte (sites) sind die oberste Organisationsebene und gruppieren Screens.
- 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()`.
- `welcome.html` wird in `media/<site>/lobby/` gespeichert. - `welcome.html` wird in `media/<site>/lobby/` gespeichert.
- `search_customer_logo` in `generate_welcome_page.py` nutzt OpenAI GPT-4 + Brandfetch CDN. - `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`.

View File

@@ -15,6 +15,8 @@ Browserbasiertes Digital-Signage-System für interne Info-Screens.
- Auto-Reload bei Playlist-Änderungen - Auto-Reload bei Playlist-Änderungen
- Newsticker pro Screen - Newsticker pro Screen
- **Custom-URL-Button**: pro Screen konfigurierbarer Aktions-Button oben links im Player (öffnet URL in iframe-Overlay mit Zurück-Button oder per Direkt-Weiterleitung) - **Custom-URL-Button**: pro Screen konfigurierbarer Aktions-Button oben links im Player (öffnet URL in iframe-Overlay mit Zurück-Button oder per Direkt-Weiterleitung)
- Tab-basierte Admin-UI pro Screen: Playlist, Einstellungen, Medien (Tabler Tabs)
- Priority-Seite ebenfalls mit Tabs: Playlist und Medien
- Dark Mode (localStorage-persistiert) - Dark Mode (localStorage-persistiert)
- CI-konformes Admin-UI (CANCOM-Design: `brand-surface`, `nav-surface` rot) - CI-konformes Admin-UI (CANCOM-Design: `brand-surface`, `nav-surface` rot)
@@ -118,11 +120,15 @@ docker compose up -d
| `GET /admin` | Redirect zum ersten konfigurierten Standort | | `GET /admin` | Redirect zum ersten konfigurierten Standort |
| `GET /admin/<site>` | Admin-Dashboard für einen Standort | | `GET /admin/<site>` | Admin-Dashboard für einen Standort |
| `GET /admin/<site>/priority` | Priority-Playlist (separate Seite) | | `GET /admin/<site>/priority` | Priority-Playlist (separate Seite) |
| `GET /admin/<site>/update/<screen>` | Screen-Einstellungen speichern (POST) | | `POST /admin/<site>/update/<screen>` | Screen-Einstellungen speichern |
| `GET /admin/<site>/upload/<screen>` | Medien hochladen (POST) | | `POST /admin/<site>/upload/<screen>` | Medien hochladen |
| `GET /admin/<site>/add-url/<screen>` | URL zur Playlist hinzufügen (POST) | | `POST /admin/<site>/add-url/<screen>` | URL zur Playlist hinzufügen |
| `GET /admin/<site>/delete/<screen>/<filename>` | Datei löschen (POST) | | `POST /admin/<site>/delete/<screen>/<filename>` | Datei löschen |
| `GET /admin/<site>/playlist/<screen>` | Playlist-Reihenfolge speichern (POST JSON) | | `POST /admin/<site>/playlist/<screen>` | Playlist-Reihenfolge speichern (JSON) |
| `POST /admin/<site>/delete-screen/<screen>` | Screen + Medien löschen |
| `GET /admin/<site>/add-screen?name=<name>` | Neuen Screen anlegen |
| `GET /add-site?name=<name>` | Neuen Standort anlegen |
| `POST /admin/<site>/delete-site` | Standort + alle Screens/Medien löschen |
| `GET /player/<site>/<screen>` | Player-Ansicht | | `GET /player/<site>/<screen>` | Player-Ansicht |
| `GET /playlist/<site>/<screen>/hash` | Playlist-Checksumme (für Auto-Reload) | | `GET /playlist/<site>/<screen>/hash` | Playlist-Checksumme (für Auto-Reload) |
| `GET /willkommen?site=<site>` | Willkommensseite-Formular (GET + POST) | | `GET /willkommen?site=<site>` | Willkommensseite-Formular (GET + POST) |
@@ -138,10 +144,10 @@ docker compose up -d
http://localhost:5005/admin/<standort> http://localhost:5005/admin/<standort>
``` ```
- Screens konfigurieren - Screens konfigurieren (Tab-basiert: Playlist, Einstellungen, Medien)
- Medien hochladen / löschen - Medien hochladen / löschen
- Playlist per Drag & Drop sortieren - Playlist per Drag & Drop sortieren
- Priority-Playlist verwalten - Priority-Playlist verwalten (ebenfalls mit Tabs)
- Willkommensseite generieren (bis zu 3 Kundenlogos) - Willkommensseite generieren (bis zu 3 Kundenlogos)
- Custom-URL-Aktionsbutton pro Screen konfigurieren (iframe-Overlay oder Weiterleitung) - Custom-URL-Aktionsbutton pro Screen konfigurieren (iframe-Overlay oder Weiterleitung)
- Standorte anlegen & löschen - Standorte anlegen & löschen
@@ -171,6 +177,7 @@ Maximal 3 Kunden eingeben → Logos werden via OpenAI + Brandfetch gesucht → `
```json ```json
{ {
"server_url": "http://signage.ccmake.de",
"admin": { "username": "...", "password": "..." }, "admin": { "username": "...", "password": "..." },
"sites": { "sites": {
"stuttgart": { "stuttgart": {

7
app.py
View File

@@ -625,7 +625,8 @@ def admin(site):
screen_status=screen_status, screen_status=screen_status,
version=APP_VERSION, version=APP_VERSION,
year=datetime.now().year, year=datetime.now().year,
hostname=os.uname().nodename hostname=os.uname().nodename,
server_url=cfg.get("server_url", "")
) )
@@ -637,17 +638,17 @@ def admin(site):
def admin_priority(site): def admin_priority(site):
cfg = load_config() cfg = load_config()
site_list = sorted(cfg.get("sites", {}).keys()) site_list = sorted(cfg.get("sites", {}).keys())
priority_files = load_priority_files() priority_files = load_priority_files()
return render_template( return render_template(
"priority.html", "priority.html",
site_list=site_list, site_list=site_list,
current_site=site, current_site=site,
priority_files=priority_files,
version=APP_VERSION, version=APP_VERSION,
year=datetime.now().year, year=datetime.now().year,
hostname=os.uname().nodename, hostname=os.uname().nodename,
priority_files=priority_files server_url=cfg.get("server_url", "")
) )

View File

@@ -1,4 +1,5 @@
{ {
"server_url": "http://localhost:5005",
"priority": { "priority": {
"enabled": true, "enabled": true,
"playlist": [ "playlist": [
@@ -89,12 +90,5 @@
"admin": { "admin": {
"username": "admin", "username": "admin",
"password": "cancom@2026!" "password": "cancom@2026!"
},
"screens": {
"lobby": {
"playlist": [
"welcome.html"
]
}
} }
} }

2724
session-ses_1e96.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -62,6 +62,14 @@
font-size: 0.9em; font-size: 0.9em;
} }
.page-subtitle a {
color: inherit;
text-decoration: none;
}
.page-subtitle a:hover {
color: var(--ccm-primary);
}
.drag-handle { .drag-handle {
cursor: grab; cursor: grab;
font-size: 1.2em; font-size: 1.2em;
@@ -71,6 +79,26 @@
.drag-handle:active { .drag-handle:active {
cursor: grabbing; cursor: grabbing;
} }
.card-body .nav-tabs {
margin-left: -1rem;
margin-right: -1rem;
padding: 0 1rem;
border-bottom-color: var(--ccm-border);
}
.card-body .nav-tabs .nav-link {
background: transparent;
color: var(--ccm-muted);
}
.card-body .nav-tabs .nav-link.active {
background: var(--ccm-surface);
border-color: var(--ccm-border) var(--ccm-border) var(--ccm-surface);
color: var(--ccm-text);
}
.card-body .tab-content {
background: var(--ccm-surface);
padding: 1rem;
}
</style> </style>
</head> </head>
<body> <body>
@@ -132,10 +160,8 @@
<!-- Card Header --> <!-- Card Header -->
<div class="card-header d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
<span> <span>
<div class="page-title">Screen: <div class="page-title">{{ current_site | capitalize }}:&nbsp;<span class="fw-normal">Screen</span>&nbsp;<span class="text-uppercase">- {{ screen }}</span></div>
<div class="text-uppercase">&nbsp- {{ screen }} -</div> <div class="page-subtitle"><a href="{{ server_url }}/player/{{ current_site }}/{{ screen }}">{{ server_url }}/player/{{ current_site }}/{{ screen }}</a></div>
</div>
<div class="page-subtitle">http://localhost:5005/player/{{ current_site }}/{{ screen }}</div>
</span> </span>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
{% if screen_status[screen] == "active" %} {% if screen_status[screen] == "active" %}
@@ -153,160 +179,183 @@
<div class="card-body"> <div class="card-body">
<!-- Einstellungen --> <ul class="nav nav-tabs" data-bs-toggle="tabs" role="tablist">
<h2>Einstellungen</h2> <li class="nav-item" role="presentation">
<form action="/admin/{{ current_site }}/update/{{ screen }}" method="post"> <a href="#tab-playlist-{{ screen }}" class="nav-link active" data-bs-toggle="tab" role="tab">
<div class="mb-2"> <i class="ti ti-list me-1"></i>Playlist
<label class="form-label">Intervall (Sekunden)</label> </a>
<input type="number" class="form-control" </li>
name="interval" min="1" value="{{ cfg.interval }}"> <li class="nav-item" role="presentation">
</div> <a href="#settings-{{ screen }}" class="nav-link" data-bs-toggle="tab" role="tab">
<i class="ti ti-settings me-1"></i>Einstellungen
<div class="mb-4"> </a>
<label class="form-label">Newsticker-Text</label> </li>
<input type="text" class="form-control" name="newsticker_text" value="{{ cfg.newsticker_text|default('') }}" maxlength="200"> <li class="nav-item" role="presentation">
</div> <a href="#media-{{ screen }}" class="nav-link" data-bs-toggle="tab" role="tab">
<i class="ti ti-upload me-1"></i>Medien
<div class="form-check form-switch mb-2"> </a>
<input class="form-check-input" type="checkbox"
name="newsticker_enabled"
{% if cfg.newsticker_enabled %}checked{% endif %}>
<label class="form-check-label">Newsticker anzeigen</label>
</div>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox"
name="show_images"
{% if cfg.show_images %}checked{% endif %}>
<label class="form-check-label">Bilder anzeigen</label>
</div>
<div class="form-check form-switch mb-4">
<input class="form-check-input" type="checkbox"
name="show_videos"
{% if cfg.show_videos %}checked{% endif %}>
<label class="form-check-label">Videos anzeigen</label>
</div>
<hr class="my-4">
<h3>Aktions-Button (Player)</h3>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox"
name="custom_url_enabled"
{% if cfg.custom_url_enabled %}checked{% endif %}>
<label class="form-check-label">Aktions-Button anzeigen</label>
</div>
<div class="mb-2">
<label class="form-label">Button-URL</label>
<input type="url" name="custom_url" class="form-control"
placeholder="https://example.com" value="{{ cfg.custom_url|default('') }}">
</div>
<div class="mb-4">
<label class="form-label">Button-Beschriftung</label>
<input type="text" name="custom_url_label" class="form-control"
placeholder="z. B. Infos" value="{{ cfg.custom_url_label|default('') }}" maxlength="50">
</div>
<div class="mb-4">
<label class="form-label">Verhalten bei Klick</label>
<select name="custom_url_target" class="form-select">
<option value="overlay" {% if cfg.custom_url_target|default('overlay') == 'overlay' %}selected{% endif %}>Im Player (iframe mit Zurück-Button)</option>
<option value="redirect" {% if cfg.custom_url_target|default('overlay') == 'redirect' %}selected{% endif %}>Weiterleitung (gleiches Fenster)</option>
</select>
</div>
<button type="submit" class="btn btn-primary">
Einstellungen speichern
</button>
</form>
<hr>
<!-- Upload -->
<h2>Medien hochladen / URL hinzufügen - {{ screen }}</h2>
<form action="/admin/{{ current_site }}/upload/{{ screen }}"
method="post" enctype="multipart/form-data">
<div class="input-group mb-3">
<input type="file" name="file" class="form-control" required>
<button class="btn btn-primary" type="submit">Upload</button>
</div>
</form>
<form action="/admin/{{ current_site }}/add-url/{{ screen }}" method="post" class="mb-4">
<div class="mb-2">
<label class="form-label">URL</label>
<input type="url" name="url" class="form-control"
placeholder="https://example.com" required>
</div>
<div class="mb-2">
<label class="form-label">Zoom Faktor (z.B. 1.0, 1.5, 2.0)</label>
<input type="number" name="zoom" class="form-control" step="0.1" value="1.0" min="0.1">
</div>
<button class="btn btn-secondary" type="submit">URL hinzufügen</button>
</form>
<hr>
<!-- Playlist -->
<h2>Playlist Reihenfolge - {{ screen }}</h2>
<ul class="list-group mb-3" id="playlist-{{ screen }}">
{% for file in media_files[screen] %}
<li class="list-group-item playlist-row"
data-file="{{ file.name }}">
<input class="form-check-input playlist-enabled"
type="checkbox"
{% if file.enabled %}checked{% endif %}
title="Element aktivieren">
<span class="playlist-name"
{% if file.type == "image" %}
data-bs-toggle="tooltip"
data-bs-html="true"
title="<img src='/media/{{ current_site }}/{{ screen }}/{{ file.name }}' style='max-width: 200px; max-height: 150px; border: none; background: white; padding: 2px;' alt='Vorschau'>"
{% elif file.type == "url" %}
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 %} <strong style="color: #DA002D;">[{{ file.zoom }}x]</strong>{% endif %}
</span>
{% if file.type == "image" %}
<span class="badge bg-purple text-purple-fg">Bild</span>
{% elif file.type == "url" or file.type == "html" %}
<span class="badge bg-blue text-blue-fg">URL</span>
{% else %}
<span class="badge bg-orange text-orange-fg">Video</span>
{% endif %}
<span class="playlist-size">
{% if file.type == "url" %}
{{ file.size }}
{% else %}
{{ file.size }} KB
{% endif %}
</span>
<!-- Delete -->
<form action="{{ url_for('delete_file', site=current_site, screen=screen, filename=file.name) }}"
method="post">
<button type="submit"
class="btn btn-outline-danger btn-sm"
onclick="return confirm('Datei wirklich löschen?')">
Löschen
</button>
</form>
<!-- Drag -->
<span class="drag-handle" title="Reihenfolge ändern"></span>
</li> </li>
{% endfor %}
</ul> </ul>
<button class="btn btn-primary" <div class="tab-content mt-3">
onclick="savePlaylist('{{ current_site }}', '{{ screen }}')">
Playlist speichern <!-- Tab: Playlist -->
</button> <div class="tab-pane active" id="tab-playlist-{{ screen }}" role="tabpanel">
<ul class="list-group mb-3" id="playlist-{{ screen }}">
{% for file in media_files[screen] %}
<li class="list-group-item playlist-row"
data-file="{{ file.name }}">
<input class="form-check-input playlist-enabled"
type="checkbox"
{% if file.enabled %}checked{% endif %}
title="Element aktivieren">
<span class="playlist-name"
{% if file.type == "image" %}
data-bs-toggle="tooltip"
data-bs-html="true"
title="<img src='/media/{{ current_site }}/{{ screen }}/{{ file.name }}' style='max-width: 200px; max-height: 150px; border: none; background: white; padding: 2px;' alt='Vorschau'>"
{% elif file.type == "url" %}
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 %} <strong style="color: #DA002D;">[{{ file.zoom }}x]</strong>{% endif %}
</span>
{% if file.type == "image" %}
<span class="badge bg-purple text-purple-fg">Bild</span>
{% elif file.type == "url" or file.type == "html" %}
<span class="badge bg-blue text-blue-fg">URL</span>
{% else %}
<span class="badge bg-orange text-orange-fg">Video</span>
{% endif %}
<span class="playlist-size">
{% if file.type == "url" %}
{{ file.size }}
{% else %}
{{ file.size }} KB
{% endif %}
</span>
<!-- Delete -->
<form action="{{ url_for('delete_file', site=current_site, screen=screen, filename=file.name) }}"
method="post">
<button type="submit"
class="btn btn-outline-danger btn-sm"
onclick="return confirm('Datei wirklich löschen?')">
Löschen
</button>
</form>
<!-- Drag -->
<span class="drag-handle" title="Reihenfolge ändern"></span>
</li>
{% endfor %}
</ul>
<button class="btn btn-primary"
onclick="savePlaylist('{{ current_site }}', '{{ screen }}')">
Playlist speichern
</button>
</div>
<!-- Tab: Einstellungen -->
<div class="tab-pane" id="settings-{{ screen }}" role="tabpanel">
<form action="/admin/{{ current_site }}/update/{{ screen }}" method="post">
<div class="mb-2">
<label class="form-label">Intervall (Sekunden)</label>
<input type="number" class="form-control"
name="interval" min="1" value="{{ cfg.interval }}">
</div>
<div class="mb-4">
<label class="form-label">Newsticker-Text</label>
<input type="text" class="form-control" name="newsticker_text" value="{{ cfg.newsticker_text|default('') }}" maxlength="200">
</div>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox"
name="newsticker_enabled"
{% if cfg.newsticker_enabled %}checked{% endif %}>
<label class="form-check-label">Newsticker anzeigen</label>
</div>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox"
name="show_images"
{% if cfg.show_images %}checked{% endif %}>
<label class="form-check-label">Bilder anzeigen</label>
</div>
<div class="form-check form-switch mb-4">
<input class="form-check-input" type="checkbox"
name="show_videos"
{% if cfg.show_videos %}checked{% endif %}>
<label class="form-check-label">Videos anzeigen</label>
</div>
<hr class="my-4">
<h3>Aktions-Button (Player)</h3>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox"
name="custom_url_enabled"
{% if cfg.custom_url_enabled %}checked{% endif %}>
<label class="form-check-label">Aktions-Button anzeigen</label>
</div>
<div class="mb-2">
<label class="form-label">Button-URL</label>
<input type="url" name="custom_url" class="form-control"
placeholder="https://example.com" value="{{ cfg.custom_url|default('') }}">
</div>
<div class="mb-4">
<label class="form-label">Button-Beschriftung</label>
<input type="text" name="custom_url_label" class="form-control"
placeholder="z. B. Infos" value="{{ cfg.custom_url_label|default('') }}" maxlength="50">
</div>
<div class="mb-4">
<label class="form-label">Verhalten bei Klick</label>
<select name="custom_url_target" class="form-select">
<option value="overlay" {% if cfg.custom_url_target|default('overlay') == 'overlay' %}selected{% endif %}>Im Player (iframe mit Zurück-Button)</option>
<option value="redirect" {% if cfg.custom_url_target|default('overlay') == 'redirect' %}selected{% endif %}>Weiterleitung (gleiches Fenster)</option>
</select>
</div>
<button type="submit" class="btn btn-primary">
Einstellungen speichern
</button>
</form>
</div>
<!-- Tab: Medien -->
<div class="tab-pane" id="media-{{ screen }}" role="tabpanel">
<h3>Medien hochladen</h3>
<form action="/admin/{{ current_site }}/upload/{{ screen }}"
method="post" enctype="multipart/form-data">
<div class="input-group mb-3">
<input type="file" name="file" class="form-control" required>
<button class="btn btn-primary" type="submit">Upload</button>
</div>
</form>
<hr>
<h3>URL hinzufügen</h3>
<form action="/admin/{{ current_site }}/add-url/{{ screen }}" method="post" class="mb-4">
<div class="mb-2">
<label class="form-label">URL</label>
<input type="url" name="url" class="form-control"
placeholder="https://example.com" required>
</div>
<div class="mb-2">
<label class="form-label">Zoom Faktor (z.B. 1.0, 1.5, 2.0)</label>
<input type="number" name="zoom" class="form-control" step="0.1" value="1.0" min="0.1">
</div>
<button class="btn btn-primary" type="submit">URL hinzufügen</button>
</form>
</div>
</div>
</div> </div>
</div> </div>

View File

@@ -55,6 +55,26 @@
.drag-handle:active { .drag-handle:active {
cursor: grabbing; cursor: grabbing;
} }
.card-body .nav-tabs {
margin-left: -1rem;
margin-right: -1rem;
padding: 0 1rem;
border-bottom-color: var(--ccm-border);
}
.card-body .nav-tabs .nav-link {
background: transparent;
color: var(--ccm-muted);
}
.card-body .nav-tabs .nav-link.active {
background: var(--ccm-surface);
border-color: var(--ccm-border) var(--ccm-border) var(--ccm-surface);
color: var(--ccm-text);
}
.card-body .tab-content {
background: var(--ccm-surface);
padding: 1rem;
}
</style> </style>
</head> </head>
<body> <body>
@@ -95,100 +115,114 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<p class="text-muted">
Inhalte dieser Playlist werden <strong>auf allen Playern</strong>
zusätzlich zur normalen Playlist angezeigt.
<br>
Die Wiedergabe erfolgt <strong>abwechselnd</strong>
(Normal → Priority → Normal → …).
</p>
<!-- Upload --> <ul class="nav nav-tabs" data-bs-toggle="tabs" role="tablist">
<h4>Medien hochladen</h4> <li class="nav-item" role="presentation">
<form action="/admin/{{ current_site }}/upload/priority" <a href="#tab-prio-playlist" class="nav-link active" data-bs-toggle="tab" role="tab">
method="post" enctype="multipart/form-data"> <i class="ti ti-list me-1"></i>Playlist
<div class="input-group mb-3"> </a>
<input type="file" name="file" class="form-control" required> </li>
<button class="btn btn-primary" type="submit">Upload</button> <li class="nav-item" role="presentation">
</div> <a href="#tab-prio-media" class="nav-link" data-bs-toggle="tab" role="tab">
</form> <i class="ti ti-upload me-1"></i>Medien
</a>
<form action="/admin/{{ current_site }}/add-url/priority" method="post" class="mb-3">
<div class="mb-2">
<label class="form-label">URL</label>
<input type="url" name="url" class="form-control"
placeholder="https://example.com" required>
</div>
<div class="mb-2">
<label class="form-label">Zoom Faktor (z.B. 1.0, 1.5, 2.0)</label>
<input type="number" name="zoom" class="form-control" step="0.1" value="1.0" min="0.1">
</div>
<button class="btn btn-secondary" type="submit">URL hinzufügen</button>
</form>
<!-- Playlist -->
<h4>Playlist Reihenfolge</h4>
<ul class="list-group mb-3" id="playlist-priority">
{% for file in priority_files %}
<li class="list-group-item playlist-row"
data-file="{{ file.name }}">
<input class="form-check-input playlist-enabled"
type="checkbox"
{% if file.enabled %}checked{% endif %}
title="Element aktivieren">
<span class="playlist-name"
{% if file.type == "image" %}
data-bs-toggle="tooltip"
data-bs-html="true"
title="<img src='/media/priority/{{ file.name }}' style='max-width: 200px; max-height: 150px; border: none; background: white; padding: 2px;' alt='Vorschau'>"
{% elif file.type == "url" %}
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 %} <strong style="color: #DA002D;">[{{ file.zoom }}x]</strong>{% endif %}
</span>
{% if file.type == "image" %}
<span class="badge bg-purple text-purple-fg">Bild</span>
{% elif file.type == "url" or file.type == "html" %}
<span class="badge bg-blue text-blue-fg">URL</span>
{% else %}
<span class="badge bg-orange text-orange-fg">Video</span>
{% endif %}
<span class="playlist-size">
{% if file.type == "url" %}
{{ file.size }}
{% else %}
{{ file.size }} KB
{% endif %}
</span>
<!-- Delete -->
<form action="{{ url_for('delete_file', site=current_site, screen='priority', filename=file.name) }}"
method="post"
class="d-inline"
onmousedown="event.stopPropagation()">
<button type="submit"
class="btn btn-outline-danger btn-sm"
onclick="return confirm('Datei wirklich löschen?')">
Löschen
</button>
</form>
<!-- Drag -->
<span class="drag-handle" title="Reihenfolge ändern"></span>
</li> </li>
{% endfor %}
</ul> </ul>
<button class="btn btn-primary" <div class="tab-content">
onclick="savePlaylist('{{ current_site }}', 'priority')">
Priority-Playlist speichern <!-- Tab: Playlist -->
</button> <div class="tab-pane active" id="tab-prio-playlist" role="tabpanel">
<ul class="list-group mb-3" id="playlist-priority">
{% for file in priority_files %}
<li class="list-group-item playlist-row"
data-file="{{ file.name }}">
<input class="form-check-input playlist-enabled"
type="checkbox"
{% if file.enabled %}checked{% endif %}
title="Element aktivieren">
<span class="playlist-name"
{% if file.type == "image" %}
data-bs-toggle="tooltip"
data-bs-html="true"
title="<img src='/media/priority/{{ file.name }}' style='max-width: 200px; max-height: 150px; border: none; background: white; padding: 2px;' alt='Vorschau'>"
{% elif file.type == "url" %}
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 %} <strong style="color: #DA002D;">[{{ file.zoom }}x]</strong>{% endif %}
</span>
{% if file.type == "image" %}
<span class="badge bg-purple text-purple-fg">Bild</span>
{% elif file.type == "url" or file.type == "html" %}
<span class="badge bg-blue text-blue-fg">URL</span>
{% else %}
<span class="badge bg-orange text-orange-fg">Video</span>
{% endif %}
<span class="playlist-size">
{% if file.type == "url" %}
{{ file.size }}
{% else %}
{{ file.size }} KB
{% endif %}
</span>
<!-- Delete -->
<form action="{{ url_for('delete_file', site=current_site, screen='priority', filename=file.name) }}"
method="post"
class="d-inline"
onmousedown="event.stopPropagation()">
<button type="submit"
class="btn btn-outline-danger btn-sm"
onclick="return confirm('Datei wirklich löschen?')">
Löschen
</button>
</form>
<!-- Drag -->
<span class="drag-handle" title="Reihenfolge ändern"></span>
</li>
{% endfor %}
</ul>
<button class="btn btn-primary"
onclick="savePlaylist('{{ current_site }}', 'priority')">
Priority-Playlist speichern
</button>
</div>
<!-- Tab: Medien -->
<div class="tab-pane" id="tab-prio-media" role="tabpanel">
<h3>Medien hochladen</h3>
<form action="/admin/{{ current_site }}/upload/priority"
method="post" enctype="multipart/form-data">
<div class="input-group mb-3">
<input type="file" name="file" class="form-control" required>
<button class="btn btn-primary" type="submit">Upload</button>
</div>
</form>
<hr>
<h3>URL hinzufügen</h3>
<form action="/admin/{{ current_site }}/add-url/priority" method="post" class="mb-3">
<div class="mb-2">
<label class="form-label">URL</label>
<input type="url" name="url" class="form-control"
placeholder="https://example.com" required>
</div>
<div class="mb-2">
<label class="form-label">Zoom Faktor (z.B. 1.0, 1.5, 2.0)</label>
<input type="number" name="zoom" class="form-control" step="0.1" value="1.0" min="0.1">
</div>
<button class="btn btn-primary" type="submit">URL hinzufügen</button>
</form>
</div>
</div>
</div> </div>
</div> </div>