diff --git a/.gitignore b/.gitignore index 1a6801d..06b477d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ media/ *.mp4 *.mov agents.md +opencode.md \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 4f339d4..2a36c0e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,7 +31,7 @@ Standorte (sites) sind die oberste Organisationsebene und gruppieren Screens. | `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 /playlist///hash` | Playlist-Checksumme für Auto-Reload + zeichnet Heartbeat auf (Online-Status) | | `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 | @@ -120,7 +120,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()`. +- 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()`. - Zugriffs-Dekoratoren in `app.py`: `admin_required`, `site_access_required`. - 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. @@ -129,3 +129,53 @@ Standorte (sites) sind die oberste Organisationsebene und gruppieren Screens. - `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`. - `static/dva.png`: Bild für den Voice-Agent-Button (optional, per Admin-Toggle ein-/ausblendbar). + +## Heartbeat / Online-Status (seit v6.1.0) + +Der Player-Status (Online/Offline) wird mittels dateibasiertem Heartbeat ermittelt. + +### Funktionsweise + +| Schritt | Beschreibung | +|---------|-------------| +| **Player-pollt** alle 5s `/playlist///hash` (`checkForUpdates`) | Bestehender Mechanismus, kein Extra-Request | +| **Server zeichnet Heartbeat auf** | `record_heartbeat(site, screen)` erstellt/updated `/tmp/signage-heartbeat//` via `path.touch()` | +| **Admin-UI fragt Status ab** | `screen_is_active(site, screen)` prüft ob `mtime < 60s` alt | +| **Anzeige** | Grüner "Online"-Button (filled success) oder roter "Offline"-Button (filled danger) im Card-Header neben "Löschen" | + +### Helper-Funktionen in `app.py` + +```python +HEARTBEAT_DIR = Path("/tmp/signage-heartbeat") +HEARTBEAT_TIMEOUT = 60 # Sekunden + +def record_heartbeat(site, screen): + path = HEARTBEAT_DIR / site / screen + path.parent.mkdir(parents=True, exist_ok=True) + path.touch() + +def screen_is_active(site, screen): + path = HEARTBEAT_DIR / site / screen + if not path.exists(): + return False + return (time.time() - path.stat().st_mtime) < HEARTBEAT_TIMEOUT +``` + +### Integration + +- `playlist_hash()`-Route ruft `record_heartbeat()` direkt am Anfang auf +- `admin()`-Route setzt `screen_status["online"/"offline"]` via `screen_is_active()` für jeden Screen +- Admin-Template (`admin.html`) zeigt beide Buttons als ` -159: -160: -161: +(no output) +``` + +**Tool: bash** + +**Input:** +```json +{ + "description": "Keep only original history entries", + "command": "python3 -c \"\nimport json\nhist = json.load(open('history.json'))\n# Keep only entries before todays test entries (first 2 are from 2026-06-22)\noriginal = [e for e in hist if 'timestamp' not in e or not e['timestamp'].startswith('2026-06-22')]\nwith open('history.json', 'w') as f:\n json.dump(original, f, indent=2, ensure_ascii=False)\nprint(f'Kept {len(original)} original entries, removed {len(hist) - len(original)} test entries')\n\"" +} +``` + +**Output:** +``` +Kept 33 original entries, removed 2 test entries -(Showing lines 152-161 of 569. Use offset=162 to continue.) - ``` --- -## Assistant (Build · DeepSeek V4 Flash Free · 4.8s) +## Assistant (Build · DeepSeek V4 Flash Free · 2.6s) _Thinking:_ -The delete-site button is visible to all users who can access the site, but the route is protected with `@admin_required`. So the route is safe, but the button might be confusing for non-admin users to see. +Let me verify the final state of everything: -The user probably wants the button hidden for non-admins too. Let me wrap it in a `{% if current_user.is_admin %}` block. - -**Tool: edit** +**Tool: bash** **Input:** ```json { - "filePath": "/Users/erik/Documents/DEV/signage/templates/admin.html", - "newString": " \n {% if current_user.is_admin %}\n
\n
\n \n
\n
\n {% endif %}", - "oldString": " \n
\n
\n \n
\n
" + "description": "Verify final clean state", + "command": "python3 -c \"\nimport json\ncfg = json.load(open('config.json'))\nprint('Sites:', list(cfg['sites'].keys()))\nprint('dva in config:', 'dva' in cfg['sites'])\n\" && git diff --stat && echo \"---\" && ls media/ 2>/dev/null" } ``` **Output:** ``` -Edit applied successfully. +Sites: ['stuttgart', 'karlsruhe'] +dva in config: False + config.json | 35 +++-------------------------------- + history.json | 26 +++++++++++++------------- + templates/player.html | 23 ++++++++++++----------- + 3 files changed, 28 insertions(+), 56 deletions(-) +--- +karlsruhe +priority +stuttgart + ``` --- -## Assistant (Build · DeepSeek V4 Flash Free · 3.4s) +## Assistant (Build · DeepSeek V4 Flash Free · 2.6s) -War schon mit `@admin_required` geschützt – der Button war nur für Nicht-Admins sichtbar, obwohl die Route blockiert war. Ist jetzt auch im Template hinter `{% if current_user.is_admin %}` versteckt. +Alles sauber: +- **config.json**: `dva`-Site entfernt +- **history.json**: Test-Einträge entfernt +- **media/dva/**: Gelöscht +- **player.html**: Kein Debug-Code mehr, Timer auf `setInterval` umgestellt --- diff --git a/templates/admin.html b/templates/admin.html index f21830f..4041d6e 100755 --- a/templates/admin.html +++ b/templates/admin.html @@ -175,13 +175,13 @@
- {% if screen_status[screen] == "active" %} - Aktiv + {% if screen_status[screen] == "online" %} + {% else %} - Leer + {% endif %}