docs: Zeitsteuerung in AGENTS.md und DESIGN.md nachziehen
AGENTS.md: - Helper-Liste nannte playlist_item_enabled(), die es nicht mehr gibt; ersetzt durch is_item_active() und die neuen Schedule-Helfer - Playlist-Items: auch Datei-Eintraege koennen Dicts sein, save_playlist kopiert bestehende Dicts, daher ueberlebt schedule das Umsortieren - Neuer Abschnitt "Zeitsteuerung": Format, Auswertungsregeln, Reload-Kopplung ueber playlist_hash() und die beteiligten Stellen. Festgehalten sind besonders die Punkte, die man beim Umbau kaputt macht: unlesbare Angaben blenden nicht aus, Mitternachtsfenster gehoert zum Starttag, daily ignoriert days. DESIGN.md: - Playlist-Grid war mit 6 Spalten dokumentiert, tatsaechlich sind es 7 (Edit-Button fehlte). Dazu die Regel, dass Statusmarkierungen in den .playlist-name-Span gehoeren - als Geschwisterelement verschieben sie die gesamte Zeile. - Cards: festgehalten, dass .card global gestylt ist und deshalb nicht zum Gruppieren von Formularfeldern taugt; dafuer schlichte Zeilen mit <hr> + <h3>. - Schedule-Badge dokumentiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
37
AGENTS.md
37
AGENTS.md
@@ -107,7 +107,7 @@ Standorte (sites) sind die oberste Organisationsebene und gruppieren Screens.
|
||||
|
||||
- `GET /player/<site>/<screen>` renders the playlist and auto-reloads from `/playlist/<site>/<screen>/hash`.
|
||||
- `GET /admin` requires login; redirects to first accessible site for the user.
|
||||
- URL playlist items are stored as dicts like `{"url": "https://...", "zoom": 0.8}` and the zoom value must survive save/reorder flows.
|
||||
- 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.
|
||||
- `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.
|
||||
@@ -133,6 +133,7 @@ Standorte (sites) sind die oberste Organisationsebene und gruppieren Screens.
|
||||
- `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**: Query-Parameter an der Player-URL (`/player/<site>/<screen>`) 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. In der Playlist-Zeile zeigt ein Link-Icon (`ti-link`, grau) den Status an: sichtbar = an, durchgestrichen (`ti-link-off`, hellgrau) = aus. Funktioniert über `appendParams()` in `player.html` + `player_params` im Flask-Route.
|
||||
- **Zeitsteuerung**: Optionales `schedule`-Objekt pro Playlist-Eintrag (URL *und* Datei, Screen-Playlist *und* Priority). Ausgewertet ausschliesslich serverseitig in `is_item_active()`; siehe Abschnitt "Zeitsteuerung" unten.
|
||||
- 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.
|
||||
@@ -148,7 +149,7 @@ Standorte (sites) sind die oberste Organisationsebene und gruppieren Screens.
|
||||
- `users.json` is NOT gitignored (tracked for initial admin setup, contains no secrets by default).
|
||||
- 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()`, `get_background_url()`, `load_users()`, `save_users()`, `get_user()`, `init_user_db()`, `get_accessible_sites()`, `record_heartbeat()`, `screen_is_active()`, `get_client_info()`, `add_history_entry()`.
|
||||
- Wichtige Helper-Funktionen in `app.py`: `load_config()`, `save_config()`, `get_site_list()`, `get_screen_config()`, `is_url()`, `normalize_url()`, `playlist_item_name()`, `is_item_active()`, `_schedule_time()`, `_window_start_date()`, `_in_validity_range()`, `load_priority_files()`, `prio_redirect()`, `get_background_url()`, `load_users()`, `save_users()`, `get_user()`, `init_user_db()`, `get_accessible_sites()`, `record_heartbeat()`, `screen_is_active()`, `get_client_info()`, `add_history_entry()`.
|
||||
- Zugriffs-Dekoratoren in `app.py`: `admin_required`, `site_access_required`.
|
||||
- Hintergrundbild der Willkommensseite wird pro Standort unter `media/<site>/background.*` gespeichert; Fallback auf `static/wallpaper.png` wenn keine Datei existiert.
|
||||
- `get_background_url(site)` prüft auf benutzerdefiniertes Hintergrundbild für einen Standort.
|
||||
@@ -158,6 +159,38 @@ Standorte (sites) sind die oberste Organisationsebene und gruppieren Screens.
|
||||
- `admin_priority` rendert `priority.html` mit `site_list`, `current_site`, `priority_files`, `server_url`.
|
||||
- `static/dva.png`: Bild für den Voice-Agent-Button (optional, per Admin-Toggle ein-/ausblendbar).
|
||||
|
||||
## Zeitsteuerung
|
||||
|
||||
Jeder Playlist-Eintrag kann ein optionales `schedule`-Objekt tragen — URL wie Datei, in Screen-Playlists wie in der Priority-Playlist.
|
||||
|
||||
```jsonc
|
||||
{ "mode": "daily", "start": "08:00", "end": "12:00",
|
||||
"valid_from": "2026-09-10", "valid_until": "2026-09-20" } // valid_* optional
|
||||
{ "mode": "weekly", "start": "09:00", "end": "17:00", "days": ["0", "2", "4"] }
|
||||
{ "mode": "once", "start": "2026-09-10T08:00", "end": "2026-09-20T12:00" }
|
||||
```
|
||||
|
||||
### Regeln
|
||||
|
||||
- `once` vergleicht vollständige Zeitstempel. `daily`/`weekly` werten **nur die Uhrzeit** aus; ein Datum wird dort ausschliesslich über `valid_from`/`valid_until` gesetzt (beide optional, beide inklusiv).
|
||||
- Bei `daily` gilt **jeder** Wochentag; ein vorhandenes `days` wird in diesem Modus nicht ausgewertet (bleibt aber erhalten, damit ein Moduswechsel die Auswahl nicht vernichtet).
|
||||
- `start` > `end` bedeutet ein Fenster **über Mitternacht**. Ein solches Fenster gehört zum Wochentag *und* Gültigkeitstag seines **Starts** — deshalb liefert `_window_start_date()` das Datum des Fensterbeginns und nicht einfach `now.date()`.
|
||||
- Unlesbare oder unvollständige Zeitangaben geben `True` zurück. Ein Konfigurationsfehler soll den Screen nicht stillschweigend leeren — beim Ändern dieser Logik unbedingt beibehalten.
|
||||
- `_schedule_time()` liest `"HH:MM"`, `"HH:MM:SS"` und volle ISO-Zeitstempel. Letzteres ist nötig, weil ältere Versionen bei `daily`/`weekly` einen kompletten Zeitstempel gespeichert haben, dessen Datumsanteil ignoriert wurde.
|
||||
- Auswertung erfolgt **nur serverseitig** (`datetime.now()` des Servers, nicht des Players).
|
||||
|
||||
### Reload-Kopplung
|
||||
|
||||
`playlist_hash()` hasht neben der Konfiguration auch `[is_item_active(item) for item in ...]` für Screen- und Priority-Playlist. Ohne das würde der Player ein ablaufendes Zeitfenster erst beim nächsten Reload aus anderem Grund bemerken, da sich die Konfiguration dabei nicht ändert.
|
||||
|
||||
### Beteiligte Stellen
|
||||
|
||||
- `is_item_active()`, `_window_start_date()`, `_in_validity_range()`, `_schedule_time()` in `app.py`
|
||||
- Jinja-Filter `schedule_text()` für den Tooltip am "Zeit"-Badge
|
||||
- `add_url()` liest bei `daily`/`weekly` die Felder `schedule_start_time`/`schedule_end_time`, bei `once` `schedule_start`/`schedule_end`
|
||||
- `update_playlist_item()` behandelt `new_url: null` als Datei-Eintrag und schreibt `{"name": ...}`; `schedule: {}` entfernt die Zeitsteuerung, ein fehlender Key behält sie
|
||||
- UI in `admin.html` und `priority.html`: zwei Feldpaare (`datetime-local` für `once`, `time` für `daily`/`weekly`), per Modus umgeschaltet
|
||||
|
||||
## Heartbeat / Online-Status (seit v6.1.0)
|
||||
|
||||
Der Player-Status (Online/Offline) wird mittels dateibasiertem Heartbeat ermittelt.
|
||||
|
||||
11
DESIGN.md
11
DESIGN.md
@@ -141,6 +141,7 @@ The palette is rooted in CANCOM's corporate red and high-contrast neutrals.
|
||||
- **Muted (#6B7280 / #9AA4B2 Dark):** Secondary text, captions, metadata, placeholders.
|
||||
- **Online/Offline:** Green (#2FB344) for online status badge, Red (#D63939) for offline.
|
||||
- **Role Badges:** Red for Admin, Purple (#8B5CF6) for Superuser, Blue (#3B82F6) for User.
|
||||
- **Schedule Badge:** Bootstrap `bg-info` with white text, label "Zeit", marks a scheduled playlist item; the tooltip carries the plain-text schedule from the `schedule_text` filter.
|
||||
- **Newsticker:** Red bar (#DA002D) with white text.
|
||||
|
||||
### States
|
||||
@@ -194,7 +195,9 @@ Screen management cards have:
|
||||
|
||||
### Grid Areas
|
||||
|
||||
The playlist row uses a fixed grid: `28px 1fr 90px 80px 90px 32px` for checkbox, name, type badge, size, delete button, drag handle.
|
||||
The playlist row uses a fixed grid: `28px 1fr 90px 80px 50px 40px 32px` for checkbox, name, type badge, size, delete button, edit button, drag handle — identical in `admin.html` and `priority.html`.
|
||||
|
||||
**The column count is fixed.** Anything added to a row becomes an extra grid item and shifts every following column. Status markers therefore live *inside* the `.playlist-name` span, not as siblings — this applies to the passthrough link icon and the "Zeit" badge.
|
||||
|
||||
## Elevation & Depth
|
||||
|
||||
@@ -250,6 +253,10 @@ Red background (`--ccm-primary`). Contains white outline buttons (`btn-white`):
|
||||
- Body contains Tabler tabs (6 tabs for screen cards)
|
||||
- Tab background matches card surface; tab link text uses muted color; active tab uses surface background
|
||||
|
||||
**`.card` is styled globally**, so the red left accent and grey header apply to *every* element carrying the class. The card is the container for a top-level entity — a screen, a help topic, the priority playlist. It is not a way to group fields.
|
||||
|
||||
Grouping form fields inside a modal or a tab uses plain rows (`mb-3`, `form-check`) with an `<hr>` and an `<h3>` where a separator is wanted. A `.card` there reads as a foreign tile inside the form and violates the one-red-accent-per-card rule.
|
||||
|
||||
### Player Media Elements
|
||||
|
||||
- img, video, iframe: fullscreen (`100vw × 100vh`), fixed position, centered via `top:50%; left:50%; transform:translate(-50%,-50%)`
|
||||
@@ -323,6 +330,8 @@ Same base as action button plus:
|
||||
- Do keep player pages completely chromeless — no headers, navs, or footers
|
||||
- Don't add shadows to cards; use border + left accent instead
|
||||
- Don't use more than one red accent per card (the 6px left border is sufficient)
|
||||
- Don't use `.card` to group form fields inside a modal or tab — it inherits the global red accent and grey header; use plain rows with `<hr>` + `<h3>` instead
|
||||
- Don't add elements as direct children of a playlist row — the grid has a fixed column count; put status markers inside `.playlist-name`
|
||||
- Don't mix corner radii: cards use 1rem, player buttons use 20px — keep each category consistent
|
||||
- Do set `data-bs-theme` synchronously before rendering to prevent dark-mode flash
|
||||
- Do persist theme preference in `localStorage("signage-theme")`
|
||||
|
||||
Reference in New Issue
Block a user