4.7 KiB
Design: Globaler Override & Wochenend-Sperre
Datum: 2026-03-27 Status: Approved
Zusammenfassung
Schul-Ferien, Elternabende und Wochenenden erfordern, dass Monitorzeitpläne temporär ignoriert werden. Diese Funktion ergänzt das bestehende Zeitplansystem um:
- Globaler Override — alle zugänglichen Monitore bis zu einem Zeitpunkt aus- oder einschalten
- Per-Screen Override — einen einzelnen Monitor trotz globalem Off oder Wochenende einschalten
- Wochenend-Sperre — samstags und sonntags werden Zeitpläne ignoriert, Monitore bleiben aus
Datenmodell
Migration 006 — Tabelle global_override
CREATE TABLE global_override (
id int PRIMARY KEY DEFAULT 1,
type text NOT NULL, -- 'on' oder 'off'
until timestamptz NOT NULL,
set_at timestamptz NOT NULL DEFAULT now(),
CHECK (id = 1)
);
Immer genau eine Zeile (Upsert auf id = 1). Kein aktiver Override = leere Tabelle.
Migration 007 — Spalte in screen_schedules
ALTER TABLE screen_schedules
ADD COLUMN override_on_until timestamptz;
NULL = kein per-Screen-Override aktiv.
Store-Erweiterungen
GlobalOverrideStore:Get(ctx),Upsert(ctx, type, until),Delete(ctx)ScreenScheduleStore.Upsert: nimmtoverride_on_untilauf;Getliefert es zurück
Prioritätslogik
Die neue Funktion resolveDesiredState() im Reconciler löst desiredState() ab:
1. per-Screen override_on_until > now → "on"
2. Globaler Override aktiv (until > now):
type "off" → "off"
type "on" → "on"
3. Wochenende (Samstag oder Sonntag) → "off"
4. Normaler Zeitplan (bestehende Logik)
Scheduler (Minuten-Tick): Sendet kein Kommando, wenn Override oder Wochenende aktiv ist — der Reconciler korrigiert den Zustand.
Reconciler (5-Minuten-Tick): Nutzt resolveDesiredState(). Abgelaufene Overrides werden automatisch ignoriert (until < now), kein explizites Löschen nötig.
Sofortige Aktivierung: Beim Setzen eines globalen Overrides schickt der Handler nach dem DB-Upsert sofort MQTT-Kommandos an alle zugänglichen Screens.
API-Endpunkte
Globaler Override
| Methode | Pfad | Beschreibung |
|---|---|---|
GET |
/api/v1/global-override |
Aktuellen Override abrufen (204 wenn keiner aktiv) |
POST |
/api/v1/global-override |
Override setzen + sofort MQTT an alle Screens |
DELETE |
/api/v1/global-override |
Override löschen (Reconciler normalisiert) |
POST-Body:
{ "type": "off", "until": "2026-04-05T18:00:00Z" }
GET-Response (200):
{ "type": "off", "until": "2026-04-05T18:00:00Z", "set_at": "..." }
Auth: authUser (eingeloggt). Handler filtert auf alle zugänglichen Screens des Users.
Per-Screen Override
Erweiterung des bestehenden Endpunkts:
POST /api/v1/screens/{screenSlug}/schedule
Neues optionales Feld im Body:
{ "override_on_until": "2026-04-05T18:00:00Z" }
null löscht den Override. Kein neuer Endpunkt nötig.
UI
Monitorübersicht (/manage) — globaler Override
- Neuer Banner-Bereich oberhalb der Monitorkarten
- Zwei Buttons: "Alle ausschalten bis…" / "Alle einschalten bis…"
- Klick klappt ein Inline-Formular auf (
<input type="datetime-local">+ "Setzen"-Button) - Wenn Override aktiv: farbiges Hinweisband mit Zeitangabe und "Aufheben"-Link
- Bestehender 30s-Poll aktualisiert auch diesen Status
Monitorkarte (Übersicht) — per-Screen Override
- Unter den Ein/Aus-Buttons: aufklappbarer Bereich mit
<input type="datetime-local">+ "Einschalten bis"-Button - Wenn aktiv: Badge
"ein bis 05.04. 18:00 ✕"
Detailseite (/manage/{slug}) — per-Screen Override
- Im bestehenden "Zeitplan"-Kasten: neues Feld "Einschalten bis (Override)"
<input type="datetime-local">+ "Setzen" / "Aufheben"-Button- Wird über den bestehenden
saveSchedule()-Aufruf gespeichert (neues Feld im Body)
Timezone
<input type="datetime-local"> liefert Browser-Lokalzeit ohne Timezone-Offset. Das Frontend sendet die Zeit als ISO-8601-String mit Offset (z.B. +01:00). Der Backend-Handler parst mit time.Parse(time.RFC3339, ...). Die bestehende TZ-Env-Variable des Servers steuert die Interpretation im Scheduler/Reconciler — kein neuer Konfigurationsaufwand.
Nicht im Scope
- Per-Screen "ausschalten bis" — nur "einschalten bis" pro Monitor
- Globaler Override ist nicht per-Screen einschränkbar (kommt aus der Übersicht, gilt für alle zugänglichen Screens)
- Sofortige Aktion beim Override-Ablauf — Reconciler (≤5 Min) reicht