From a99f8a57848114f033f45cb73156263cccb81c50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesko=20Ansch=C3=BCtz?= Date: Sun, 22 Mar 2026 20:41:47 +0100 Subject: [PATCH] Aktualisiere Doku und gleiche /api/v1-Toolsliste mit /api/v1/meta an - docs/PLAYER-STATUS-HTTP.md vollstaendig neu strukturiert: q=, derived_state=, DELETE-Endpoint und Datei-Persistenz ergaenzt, Abgrenzung "keine dauerhafte Speicherung" entfernt - GET /api/v1 listet jetzt dieselben 5 Tools wie /api/v1/meta (player-status-ingest und screen-status-delete ergaenzt) Co-Authored-By: Claude Sonnet 4.6 --- docs/PLAYER-STATUS-HTTP.md | 184 +++++++++++------- server/backend/internal/httpapi/router.go | 2 + .../backend/internal/httpapi/router_test.go | 10 +- 3 files changed, 121 insertions(+), 75 deletions(-) diff --git a/docs/PLAYER-STATUS-HTTP.md b/docs/PLAYER-STATUS-HTTP.md index 7d16f25..6c2e131 100644 --- a/docs/PLAYER-STATUS-HTTP.md +++ b/docs/PLAYER-STATUS-HTTP.md @@ -4,42 +4,45 @@ Dieses Dokument beschreibt den ersten bewusst kleinen Statuspfad zwischen `player/agent` und `server/backend`. -Er ist fuer die aktuelle Entwicklungsstufe gedacht und noch kein finales Persistenz-, Authentifizierungs- oder MQTT-Modell. +Er ist fuer die aktuelle Entwicklungsstufe gedacht und noch kein finales Authentifizierungs- oder MQTT-Modell. ## V1-Dev-Entscheidung - der Agent sendet zunaechst Statusdaten per HTTP an das Backend -- das Backend validiert und bestaetigt den Request und haelt den letzten bekannten Status pro Screen in-memory vor +- das Backend validiert und bestaetigt den Request und haelt den letzten bekannten Status pro Screen vor - der Pfad dient als erste echte Backend-Agent-Integration vor Registration, Sync und MQTT -## Endpoint +## Endpoints im Ueberblick -- `POST /api/v1/player/status` +| Methode | Pfad | Beschreibung | +|----------|---------------------------------------|-------------------------------------| +| POST | /api/v1/player/status | Statusingest vom Player-Agent | +| GET | /api/v1/screens/status | JSON-Uebersicht aller Screens | +| GET | /api/v1/screens/{screenId}/status | JSON-Detail fuer einen Screen | +| DELETE | /api/v1/screens/{screenId}/status | Screen-Eintrag loeschen | +| GET | /status | HTML-Diagnoseseite (Uebersicht) | +| GET | /status/{screenId} | HTML-Diagnoseseite (Einzelscreen) | -## Request-Felder +## POST /api/v1/player/status -Mindestens enthalten: +### Request-Felder + +Pflichtfelder: - `screen_id` -- `ts` -- `status` - -Fuer die aktuelle Entwicklungsstufe sind zulaessig: - +- `ts` (RFC3339-Zeitstempel) - `status`: `starting`, `running`, `stopped` +- `heartbeat_every_seconds` (positive Ganzzahl) + +Optionale Felder: + - `server_connectivity`: `unknown`, `online`, `degraded`, `offline` -- `heartbeat_every_seconds`: positive Ganzzahl - -Aktuell zusaetzlich enthalten: - -- `server_connectivity` - `server_url` - `mqtt_broker` -- `heartbeat_every_seconds` -- `started_at` -- `last_heartbeat_at` +- `started_at` (RFC3339-Zeitstempel) +- `last_heartbeat_at` (RFC3339-Zeitstempel) -## Beispiel +### Beispiel ```json { @@ -55,99 +58,132 @@ Aktuell zusaetzlich enthalten: } ``` -## Antwort - -Bei gueltigem Request liefert das Backend aktuell: +### Antwort ```json -{ - "status": "accepted" -} +{ "status": "accepted" } ``` -Bei ungueltigen Requests wird wie bei den anderen API-Endpunkten der gemeinsame Fehlerumschlag verwendet. +Bei ungueltigen Requests wird der gemeinsame Fehlerumschlag verwendet. -## Aktueller Read-Pfad +## GET /api/v1/screens/status -Zusätzlich zur Write-Route gibt es in dieser Stufe: +Liefert eine Uebersicht aller bisher berichtenden Screens mit ihrem jeweils letzten bekannten Datensatz. -- `GET /status` -- `GET /api/v1/screens/status` -- `GET /api/v1/screens/{screenId}/status` +Die Antwort enthaelt: -`GET /status` liefert eine kleine serverseitig gerenderte HTML-Statusseite fuer den Browser. -Sie nutzt dieselbe in-memory Statusuebersicht wie die JSON-Endpunkte und ist als erste sichtbare Diagnoseoberflaeche gedacht. +- `summary` mit kompakten Counts fuer `total`, `online`, `degraded`, `offline`, `stale` +- `screens` sortiert nach Prioritaet: zuerst `offline`, dann `degraded`, dann `online`; innerhalb derselben Gruppe nach `screen_id` -Die Seite bietet aktuell bewusst nur leichte Diagnosehilfen auf Basis des bestehenden Read-Pfads: +Die `summary` beschreibt immer den gesamten bekannten Statusbestand, unabhaengig von aktiven Filtern. +Die `screens`-Liste wird durch die Query-Parameter eingeschraenkt. -- automatisches Refresh alle 15 Sekunden -- Shortcut-Links fuer Connectivity- und Freshness-Filter wie `server_connectivity=offline|degraded` sowie `stale=true|false` -- ein kleines Filterformular fuer dieselben Uebersichtsparameter wie im JSON-Read-Pfad -- direkte JSON-Detail-Links pro Screen auf `GET /api/v1/screens/{screenId}/status` -- einen Link zur aktuell gefilterten JSON-Uebersicht auf `GET /api/v1/screens/status` +### Query-Parameter -`GET /api/v1/screens/status` liefert eine kleine Uebersicht aller bisher berichtenden Screens mit ihrem jeweils letzten bekannten Datensatz. -Die Rueckgabe wird aktuell fuer Diagnosezwecke priorisiert sortiert: zuerst `offline`, dann `degraded`, dann `online`, innerhalb derselben Gruppe nach `screen_id`. -Zusaetzlich enthaelt die Antwort eine `summary` mit kompakten Counts fuer `total`, `online`, `degraded`, `offline` und `stale`. +| Parameter | Erlaubte Werte | Fehlercode bei Verstoß | +|---------------------|-----------------------------------------|-------------------------------------| +| `q` | beliebig (Substring-Suche, case-insen.) | – | +| `derived_state` | `online`, `degraded`, `offline` | `invalid_derived_state` | +| `server_connectivity` | `online`, `degraded`, `offline`, `unknown` | `invalid_server_connectivity` | +| `stale` | `true`, `false` | `invalid_stale` | +| `updated_since` | RFC3339-Zeitstempel | `invalid_updated_since` | +| `limit` | positive Ganzzahl | `invalid_limit` | -Aktuell unterstuetzte Query-Parameter fuer die Uebersicht: +`q` filtert nach Screen-ID-Substring (Gross-/Kleinschreibung wird ignoriert). -- `server_connectivity=` zum Filtern nach Reachability-Zustand; erlaubte Werte: `online`, `offline`, `degraded`, `unknown`; ungueltige Werte liefern 400 (`invalid_server_connectivity`) -- `stale=true|false` zum Filtern nach serverseitiger Veraltet-Einschaetzung; ungueltige Werte liefern 400 (`invalid_stale`) -- `updated_since=` zum Filtern nach `received_at`; ungueltige Zeitstempel liefern 400 (`invalid_updated_since`) -- `limit=` zum Begrenzen der Anzahl zurueckgelieferter Screens; nicht-positive Werte liefern 400 (`invalid_limit`) +Dieselben Parameter koennen sowohl an `GET /api/v1/screens/status` als auch an `GET /status` uebergeben werden, damit Browser-Ansicht und JSON-Uebersicht dieselbe Diagnosesicht teilen. -Die Query-Parameter beeinflussen die Liste in `screens`; die `summary` beschreibt weiterhin den gesamten aktuell bekannten Statusbestand. -Dieselben Parameter koennen aktuell sowohl an `GET /api/v1/screens/status` als auch an `GET /status` verwendet werden, damit Browser-Ansicht und JSON-Uebersicht dieselbe Diagnose-Sicht teilen. +## GET /api/v1/screens/{screenId}/status -`GET /status/{screenId}` liefert eine HTML-Detailseite fuer einen einzelnen Screen. -Sie zeigt denselben Datensatz wie der JSON-Endpunkt – Derived State, Player-Status, Connectivity, Frische, Timestamps und Endpoints – in derselben visuellen Sprache wie die Uebersichtsseite. -Bei unbekanntem Screen liefert sie 404 mit einer erklaerenden HTML-Fehlermeldung und einem Rueck-Link auf `/status`. - -Fehlerfall bei ungueltigem Query-Parameter auf `/status` (z.B. `?stale=banana`): statt rohem JSON liefert der Endpunkt jetzt eine HTML-Fehlerseite mit erklaerenden Hinweisen und einem Rueck-Link. - -`GET /api/v1/screens/{screenId}/status` liefert den zuletzt akzeptierten Status fuer einen einzelnen Screen zurueck. +Liefert den zuletzt akzeptierten Status fuer einen einzelnen Screen. Wenn fuer den Screen noch kein Status vorliegt, liefert das Backend `404` mit dem gemeinsamen Fehlerumschlag. -Der aktuell zurueckgelieferte Datensatz enthaelt damit sowohl den Lifecycle-Status (`status`) als auch den vom Agenten lokal abgeleiteten Reachability-Zustand (`server_connectivity`). +Der Datensatz enthaelt neben den gespeicherten Feldern: -Zusaetzlich fuegt das Backend im Read-Pfad derzeit hinzu: +- `received_at` – serverseitiger Annahmezeitpunkt des letzten gueltigen Reports +- `stale` – ob der letzte Report bereits veraltet wirkt (mehr als zwei Heartbeat-Intervalle ohne neuen Report) +- `derived_state` – zusammengefasste Diagnoseeinschaetzung (s.u.) -- `received_at` als serverseitigen Annahmezeitpunkt des letzten gueltigen Reports -- `stale` als einfache serverseitige Einordnung, ob der letzte Report bereits veraltet wirkt -- `derived_state` als zusammengefasste Diagnoseeinschaetzung fuer Konsumenten des Read-Pfads +## DELETE /api/v1/screens/{screenId}/status -`stale` ist aktuell bewusst nur eine kleine Diagnosehilfe fuer die Entwicklungsstufe und noch kein vollstaendiges Online-/Offline-Modell fuer spaetere Admin-Oberflaechen. -Die Schwelle wird derzeit einfach aus dem gemeldeten `heartbeat_every_seconds` abgeleitet: mehr als zwei Intervalle ohne neuen Report gelten als veraltet. +Loescht den gespeicherten Statuseintrag fuer einen Screen endgueltig. +Wenn kein Eintrag vorhanden ist, liefert das Backend `404`. -`derived_state` wird aktuell bewusst einfach abgeleitet: +### Antwort + +```json +{ "status": "deleted" } +``` + +## GET /status + +Serverseitig gerenderte HTML-Diagnoseseite fuer den Browser. +Nutzt dieselbe Statusuebersicht wie der JSON-Endpunkt. + +- automatisches Refresh alle 15 Sekunden +- Shortcut-Links fuer Connectivity- und Freshness-Filter +- kleines Filterformular fuer dieselben Parameter wie beim JSON-Read-Pfad +- direkte JSON-Detail-Links pro Screen + +Bei ungueltigen Query-Parametern liefert `/status` eine HTML-Fehlerseite (kein JSON), damit Browser-Aufrufe direkt lesbare Hinweise erhalten. + +## GET /status/{screenId} + +HTML-Detailseite fuer einen einzelnen Screen. +Zeigt denselben Datensatz wie `GET /api/v1/screens/{screenId}/status` (Derived State, Player-Status, Connectivity, Frische, Timestamps, Endpoints). + +Bei unbekanntem Screen: 404 mit HTML-Fehlermeldung und Rueck-Link auf `/status`. + +## Serverseitig abgeleitete Felder + +### stale + +Die Schwelle wird aus dem gemeldeten `heartbeat_every_seconds` abgeleitet: mehr als zwei Intervalle ohne neuen Report gelten als veraltet. + +`stale` ist aktuell eine Diagnosehilfe fuer die Entwicklungsstufe und noch kein vollstaendiges Online-/Offline-Modell fuer Admin-Oberflaechen. + +### derived_state - `offline` bei `stale = true` oder `server_connectivity = offline` - `degraded` bei `server_connectivity = degraded|unknown` oder wenn `status` nicht `running` ist - `online` in den verbleibenden Faellen +## Persistenz + +Der Status-Store kann dateibasiert betrieben werden. + +Konfiguration ueber die Umgebungsvariable: + +- `MORZ_INFOBOARD_STATUS_STORE_PATH` – Pfad zur JSON-Datei; leer lassen fuer reinen In-Memory-Betrieb + +Beim Datei-Store gilt: + +- beim Start wird die Datei eingelesen, falls sie vorhanden ist (fehlende Datei ist kein Fehler) +- bei jedem `Save` und `Delete` wird der Inhalt atomar in die Datei geschrieben (write + rename) +- nach einem Neustart des Backends bleibt der letzte bekannte Statusbestand erhalten + +## Agentseitige Connectivity-Ableitung + +Agent-seitig wird die Server-Erreichbarkeit lokal als `unknown`, `online`, `degraded` oder `offline` aus dem Erfolg der HTTP-Reports abgeleitet. + +Fuer den transportierten Wert im erfolgreichen HTTP-Report gilt: + +- wenn ein Report vom Backend akzeptiert wurde, wird dieser Report selbst als `server_connectivity = online` gespeichert +- anhaltende Ausfaelle werden primaer ueber lokale Agent-Zustaende und serverseitige `stale`-Ableitung sichtbar + ## Abgrenzung Noch nicht Teil dieser Stufe: -- dauerhafte Speicherung in Datenbank oder State-Store - Screen-Authentifizierung - MQTT-Heartbeat oder MQTT-Status - Admin-UI-Anzeige des letzten Status - Retry-Queue oder lokale Zwischenspeicherung im Agent -Agent-seitig wird die Server-Erreichbarkeit aktuell lokal als `unknown`, `online`, `degraded` oder `offline` aus dem Erfolg der HTTP-Reports abgeleitet. - -Fuer den transportierten Wert im erfolgreichen HTTP-Report gilt aktuell bewusst einfach: - -- wenn ein Report vom Backend akzeptiert wurde, wird dieser Report selbst als `server_connectivity = online` gespeichert -- anhaltende Ausfaelle werden primaer ueber lokale Agent-Zustaende und serverseitige `stale`-Ableitung sichtbar - ## Folgeschritte Auf diesem Pfad bauen spaeter auf: - Screen-Identitaet und Authentifizierung -- persistierter letzter Heartbeat und letzter Status auf dem Server - Trennung zwischen HTTP-Snapshot und MQTT-Heartbeat - Admin-Vorschau fuer Online-/Offline- und Degraded-Zustaende diff --git a/server/backend/internal/httpapi/router.go b/server/backend/internal/httpapi/router.go index c23a066..df9597b 100644 --- a/server/backend/internal/httpapi/router.go +++ b/server/backend/internal/httpapi/router.go @@ -25,6 +25,8 @@ func NewRouter(store playerStatusStore) http.Handler { "message-wall-resolve", "screen-status-list", "screen-status-detail", + "player-status-ingest", + "screen-status-delete", }, }) }) diff --git a/server/backend/internal/httpapi/router_test.go b/server/backend/internal/httpapi/router_test.go index ec54888..e9012e3 100644 --- a/server/backend/internal/httpapi/router_test.go +++ b/server/backend/internal/httpapi/router_test.go @@ -66,7 +66,7 @@ func TestRouterBaseAPI(t *testing.T) { t.Fatalf("version = %q, want %q", got, want) } - if got, want := len(response.Tools), 3; got != want { + if got, want := len(response.Tools), 5; got != want { t.Fatalf("len(tools) = %d, want %d", got, want) } @@ -81,6 +81,14 @@ func TestRouterBaseAPI(t *testing.T) { if got, want := response.Tools[2], "screen-status-detail"; got != want { t.Fatalf("tool[2] = %q, want %q", got, want) } + + if got, want := response.Tools[3], "player-status-ingest"; got != want { + t.Fatalf("tool[3] = %q, want %q", got, want) + } + + if got, want := response.Tools[4], "screen-status-delete"; got != want { + t.Fatalf("tool[4] = %q, want %q", got, want) + } } func TestRouterMeta(t *testing.T) {