Wer einen Server aus der Ping-Ueberwachung nehmen wollte, musste sich bisher auf den Rechner mit check_fds.sh einloggen und die hosts.conf editieren - und die Kachel blieb danach trotzdem im System-Monitor stehen, weil ein einmal gemeldetes System nirgends wieder geloescht werden konnte. Die Liste steht jetzt in der Datenbank und ist ueber ein Zahnrad neben der Ueberschrift "Systeme" pflegbar, analog zum Zahnrad am PO-Platz (Issue #27). Jeder Host gehoert dem OPs-Agenten (Issue #66), der ihn anpingt: API und Ping-Check laufen nicht zwangslaeufig auf demselben Rechner, die API kann die hosts.conf also nicht selbst schreiben. check_fds.sh holt sie stattdessen bei jedem Lauf mit seinem Agenten-Key ab und schreibt daraus die lokale Datei; ist die API nicht erreichbar, pingt es unveraendert nach der zuletzt geschriebenen Liste weiter. Beim ersten Lauf laedt der Check seine bestehende hosts.conf hoch, damit die bereits ueberwachten Server nicht von Hand nachgetragen werden muessen. Dass die API das nur einmal annimmt (Marker in host_config_imports) und "noch nie konfiguriert" per 204 von "im Frontend geleert" per 200 unterscheidet, ist noetig, damit ein Ping-Rechner mit alter lokaler Datei die dort geloeschten Hosts nicht wieder anlegt. Loeschen raeumt mit auf: Ein entfernter Host nimmt seine Kachel mit - beide Ids, denn der status.txt-Import benennt ihn nach dem Hostnamen, die REST-Meldung nach dessen Slug. Kacheln ohne zugehoerigen Host (Selbstmelder oder Reste laengst entfernter Hosts) stehen im selben Dialog unter "Weitere Kacheln im System-Monitor" und sind dort einzeln loeschbar - dafuer gibt es neu DELETE /api/v1/systems/{id}. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015vNjbfr8M3HSfWMee92GTE
566 lines
28 KiB
Markdown
566 lines
28 KiB
Markdown
# monitor REST-API (Phase 1)
|
||
|
||
Diese API ist die zentrale Schnittstelle, ueber die entfernte Agenten (Menschen oder
|
||
automatisierte Systeme) ihre Aktivitaet melden: Tickets anlegen/verschieben, Events melden,
|
||
Systemstatus berichten, KPIs eintragen und den eigenen Zustand aktualisieren. Sie ist Teil
|
||
von Phase 1 der Neuausrichtung (siehe [Visualization.md](Visualization.md), Abschnitt 9).
|
||
|
||
Implementierung: Vanilla-PHP (kein Framework), Persistenz in SQLite
|
||
(`api/data/monitor.sqlite`). Details zum lokalen Setup: [api/README.md](api/README.md).
|
||
|
||
## Inhaltsverzeichnis
|
||
|
||
- [Grundlagen](#grundlagen)
|
||
- [Authentifizierung](#authentifizierung)
|
||
- [Agenten](#agenten)
|
||
- [Tickets](#tickets)
|
||
- [Events](#events)
|
||
- [Systeme](#systeme)
|
||
- [KPIs](#kpis)
|
||
- [Live-Updates (SSE)](#live-updates-sse)
|
||
- [Projekte (Codeberg-Sync konfigurieren)](#projekte-codeberg-sync-konfigurieren)
|
||
- [Hosts (Ping-Ueberwachung konfigurieren)](#hosts-ping-ueberwachung-konfigurieren)
|
||
- [Codeberg-Sync (Backlog/Done)](#codeberg-sync-backlogdone)
|
||
- [Fehlerformat](#fehlerformat)
|
||
|
||
## Grundlagen
|
||
|
||
- Basis-Pfad: `/api/v1`
|
||
- Request-/Response-Body: `application/json`
|
||
- Alle Zeitstempel: ISO-8601 UTC (`YYYY-MM-DDTHH:mm:ssZ`), serverseitig erzeugt via
|
||
`Database::now()`, sofern nicht explizit im Request angegeben.
|
||
- Lesende Endpunkte (`GET`) sind oeffentlich, da sie das Frontend bedienen.
|
||
- Schreibende Endpunkte (`POST`/`PATCH`) erfordern einen Agenten- oder Admin-Key
|
||
(siehe [Authentifizierung](#authentifizierung)).
|
||
- Alle Antworten tragen `Access-Control-Allow-Origin: *` (CORS), da Frontend und API im
|
||
Docker-Compose-Setup auf unterschiedlichen Ports laufen (siehe
|
||
[docker-compose.yml](docker-compose.yml): Frontend `:8090`, API `:8091`).
|
||
- Welche Basis-URL das Frontend anspricht, wird nicht geraten, sondern konfiguriert: Der
|
||
Webserver liefert sie zur Laufzeit als `/config.js`
|
||
(`window.MONITOR_API_BASE`) aus, gespeist aus der Umgebungsvariable `MONITOR_API_BASE`
|
||
(siehe `.env.example` und `docker/nginx/default.conf.template`). Leerer Wert = gleicher
|
||
Origin, dann wird der relative Pfad `/api/v1` verwendet.
|
||
|
||
## Authentifizierung
|
||
|
||
Zwei Schluesseltypen, beide statisch (F14):
|
||
|
||
| Header | Verwendet fuer | Herkunft |
|
||
|---|---|---|
|
||
| `X-Admin-Key` | `POST /api/v1/agents` (Registrierung neuer Agenten), `POST`/`PATCH`/`DELETE /api/v1/repo-configs` (Projekte des Codeberg-Sync), `POST /api/v1/tickets/resync` (Board neu einlesen) | Umgebungsvariable `MONITOR_ADMIN_API_KEY` |
|
||
| `X-Api-Key` | Alle anderen schreibenden Endpunkte | Wird bei der Agenten-Registrierung einmalig zurueckgegeben |
|
||
|
||
Ausnahme ist der Codeberg-Webhook (`POST /api/v1/webhooks/codeberg`): Codeberg kennt unsere
|
||
Keys nicht, signiert den Payload aber mit dem hinterlegten Webhook-Secret - dort gilt die
|
||
HMAC-Signatur statt eines Keys, siehe
|
||
[Codeberg-Sync](#post-apiv1webhookscodeberg--sofort-sync-per-webhook).
|
||
|
||
Der Klartext-API-Key eines Agenten wird **nur einmal** bei der Registrierung ausgegeben.
|
||
Gespeichert wird ausschliesslich ein SHA-256-Hash in SQLite (Tabelle `agents`,
|
||
Spalte `api_key_hash`) - der Klartext ist danach nicht mehr rekonstruierbar und muss vom
|
||
Agenten sicher verwahrt werden.
|
||
|
||
Fehlt oder passt ein Key nicht, antwortet die API mit `401 Unauthorized`.
|
||
|
||
## Agenten
|
||
|
||
Rollen (F1/F2): `entwickler`, `product_owner`, `qa`, `ops`, `user`. Ein Agent bekommt bei der
|
||
Registrierung fest einen von `MONITOR_DESK_COUNT` (Standard 8) Schreibtischen zugewiesen
|
||
(niedrigster freier Index). Die Rolle `user` bekommt nie einen Schreibtisch (kein fester
|
||
Sitzplatz). Sind alle Schreibtische belegt, wird trotzdem registriert, aber ohne
|
||
Schreibtisch (`desk_index: null`).
|
||
|
||
`ops` (Issue #66) ist die Rolle fuer den Betrieb - etwa den Ping-Check aus `check_fds.sh`,
|
||
der seine Ergebnisse per REST meldet (siehe README, "Weg 3"). Sie bekommt wie die uebrigen
|
||
Rollen einen Schreibtisch, in der Buero-Visualisierung mit dem Label "OPs" und einem
|
||
Zahnrad als Deko. Registrieren laesst sich der lokale OPs-Agent mit
|
||
`register-ops-agent.sh`, das zugleich `monitor-api.conf` schreibt.
|
||
|
||
### `POST /api/v1/agents` — Agent registrieren
|
||
|
||
Auth: `X-Admin-Key`.
|
||
|
||
```json
|
||
// Request
|
||
{ "display_name": "Alice", "role": "entwickler" }
|
||
```
|
||
|
||
```json
|
||
// Response 201
|
||
{
|
||
"agent": {
|
||
"id": 1, "display_name": "Alice", "role": "entwickler",
|
||
"desk_index": 0, "state": "idle", "current_ticket_id": null,
|
||
"created_at": "2026-07-27T10:00:00Z", "last_seen_at": "2026-07-27T10:00:00Z",
|
||
"is_present": true
|
||
},
|
||
"api_key": "‹64 Hex-Zeichen, nur jetzt sichtbar›"
|
||
}
|
||
```
|
||
|
||
### `GET /api/v1/agents` — Alle Agenten
|
||
|
||
Response: `{ "agents": [ ‹Agent-Objekte wie oben, ohne api_key› ] }`
|
||
|
||
Oeffentlich, unauthentifiziert (siehe [Grundlagen](#grundlagen)) - damit laesst sich
|
||
`last_seen_at`/`is_present` jedes Agenten jederzeit von aussen pruefen, z.B. auf der
|
||
Produktionsmaschine:
|
||
|
||
```bash
|
||
curl -s "$MONITOR_API_BASE/agents" | jq '.agents[] | {id, display_name, last_seen_at, is_present}'
|
||
```
|
||
|
||
`is_present` gilt `true`, solange `last_seen_at` hoechstens zehn Minuten zurueckliegt, sonst
|
||
`false` (`AgentRepository::isPresent()`, Issue #17/#20) - laeuft der Timeout ab, bleibt der
|
||
Schreibtisch des Agenten leer, obwohl er weiterhin registriert ist (Ausnahme sind die
|
||
[Problemzustaende](#problemzustaende-abgelaufene-claude-anmeldung), die sichtbar bleiben). Berechnet wird das
|
||
serverseitig via `gmdate()`/`time()` (also unabhaengig von der Zeitzone des Clients); das
|
||
Frontend (`isAgentPresent`, `html/js/office-scene.js`) liest `is_present` nur noch direkt aus
|
||
der API, statt selbst aus `last_seen_at` zu rechnen - vorher fuehrte das je nach
|
||
Client-Zeitzone zu falschen Ergebnissen (Issue #20). Bei direktem Zugriff auf die
|
||
Produktionsmaschine laesst sich `last_seen_at` auch direkt aus der SQLite-Datei lesen
|
||
(`MONITOR_DB_PATH`, Standard `api/data/monitor.sqlite`), `is_present` selbst steht dort
|
||
allerdings nicht drin, da es erst bei der API-Antwort berechnet wird:
|
||
|
||
```bash
|
||
sqlite3 "$MONITOR_DB_PATH" "SELECT id, display_name, last_seen_at FROM agents;"
|
||
```
|
||
|
||
### `POST /api/v1/agents/{id}/activity` — Eigenen Zustand melden
|
||
|
||
Auth: `X-Api-Key` des betroffenen Agenten (ein Agent darf nur seinen eigenen Zustand
|
||
melden, sonst `403`).
|
||
|
||
```json
|
||
// Request
|
||
{ "state": "working", "current_ticket": 42 }
|
||
```
|
||
|
||
Response 200: aktualisiertes Agent-Objekt.
|
||
|
||
`state` ist bewusst nicht serverseitig eingeschraenkt - welche Zustaende es gibt, entscheidet
|
||
allein die Darstellung (`AGENT_STATE_VISUALS` in `html/js/office-scene.js`), unbekannte Werte
|
||
fallen dort auf "idle am Schreibtisch" zurueck. Gebraeuchlich sind `idle`, `working` sowie die
|
||
Lauf-/Interaktionszustaende der Zustandsmaschine (`walking_to_board`, `interacting_with_board`,
|
||
`walking_to_monitor`, `interacting_with_monitor`, `walking_back`).
|
||
|
||
#### Problemzustaende: abgelaufene Claude-Anmeldung
|
||
|
||
Zwei Zustaende sind Sonderfaelle, weil sie ein menschliches Eingreifen erfordern:
|
||
|
||
| `state` | Bedeutung | Darstellung |
|
||
| --- | --- | --- |
|
||
| `auth_expiring` | Die Claude-Anmeldung des Agenten laeuft demnaechst ab, er arbeitet noch. | gelbes Schloss am Avatar, gelbes Namensschild, gelber Header-Banner |
|
||
| `auth_expired` | Die Anmeldung ist abgelaufen, der Agent kommt nicht mehr weiter. | rot pulsierendes Schloss, rotes Namensschild, roter Header-Banner |
|
||
|
||
Hintergrund: Laeuft die Anmeldung aus, laeuft das Cron-Skript um den Agenten herum
|
||
unbeeindruckt weiter - es meldet zu Beginn `working`, ruft `claude` auf, bekommt nichts
|
||
Brauchbares zurueck und meldet am Ende `idle`. Ohne eigenen Zustand ist der Ausfall vom
|
||
Normalbetrieb nicht zu unterscheiden: `last_seen_at` ist frisch, der Agent sieht beschaeftigt
|
||
aus, waehrend Lauf fuer Lauf nichts entsteht. Der Monitor-API-Key ist von der Claude-Anmeldung
|
||
unabhaengig und bleibt gueltig - der Agent kann den Zustand also weiterhin melden.
|
||
|
||
Zwei Besonderheiten:
|
||
|
||
- **Sie ueberleben den Praesenz-Timeout.** Hat ein Agent `auth_expired` gemeldet, bricht sein
|
||
Skript den Lauf ab und meldet bis zum naechsten Cron-Start nichts mehr. Das Frontend zeigt ihn
|
||
trotzdem weiter (`isAgentVisible()`), sonst verschwaende er genau dann vom Schreibtisch, wenn
|
||
seine Warnung dort stehen soll. `is_present`/`agents_online` bleiben davon unberuehrt - ein
|
||
blockierter Agent zaehlt nicht als online.
|
||
- **Sie loesen sich nur durch eine neue Meldung.** Es gibt bewusst kein Timeout; der naechste
|
||
erfolgreiche Lauf meldet ohnehin `working`/`idle` und raeumt den Zustand damit auf.
|
||
|
||
Ergaenzend gehoert ein Event (`POST /api/v1/events`, severity `error` bzw. `warning`) dazu - der
|
||
Zustand allein hat kein Datum, das Event beantwortet "seit wann".
|
||
|
||
```bash
|
||
curl -X POST "$MONITOR_API_BASE/agents/$ID/activity" \
|
||
-H "Content-Type: application/json" -H "X-Api-Key: $KEY" \
|
||
-d '{"state": "auth_expired", "current_ticket": null}'
|
||
```
|
||
|
||
## Tickets
|
||
|
||
Spalten (F3): `backlog`, `bereit`, `in_arbeit`, `qa`, `testing`, `fertig`, `done`.
|
||
|
||
- `backlog`, `bereit`, `in_arbeit`, `qa`, `testing` und `done` werden regelmaessig aus
|
||
Codeberg-Issues aus mehreren konfigurierten Projekten befuellt (siehe
|
||
[Codeberg-Sync](#codeberg-sync-backlogdone)); `fertig` ist fuer ein zukuenftiges Label
|
||
reserviert und ergibt sich ausschliesslich aus Agenten-Meldungen (F4).
|
||
- Ticket-Typ (statt Prioritaet) wird bei Codeberg-Tickets aus dem ersten Issue-Label
|
||
uebernommen, die Farbe dagegen fest aus dem Quell-Projekt (F4/F26) - so bleiben Tickets aus
|
||
unterschiedlichen Codeberg-Projekten auf einen Blick unterscheidbar, unabhaengig davon,
|
||
welche Labels dort jeweils existieren.
|
||
- `repo` (Format `owner/name`) identifiziert bei Codeberg-Tickets das Quell-Projekt und macht
|
||
`external_ref` (die Issue-Nummer) nur je Projekt eindeutig - Issue #5 in `fds2610/monitor`
|
||
und Issue #5 in `fds2610/Steuerkram` sind unterschiedliche Tickets.
|
||
- Stapel-Darstellung ("aelteste 4 + Rest als Stapel", F4) ist reine Frontend-Logik;
|
||
die API liefert Tickets dafuer sortiert nach Spalte und Alter.
|
||
|
||
### `GET /api/v1/tickets` — Board-Zustand
|
||
|
||
Query-Parameter: `column` (optional, filtert auf eine Spalte).
|
||
|
||
Response: `{ "tickets": [ { "id", "external_ref", "repo", "title", "description", "column_name", "type", "color", "assignee_agent_id", "source", "created_at", "updated_at" }, ... ] }`
|
||
|
||
### `POST /api/v1/tickets` — Ticket anlegen
|
||
|
||
Auth: `X-Api-Key`.
|
||
|
||
```json
|
||
// Request (nur "title" ist Pflicht)
|
||
{ "title": "Neues Bug-Ticket", "description": "...", "column": "backlog", "type": "bug", "color": "#ff0000" }
|
||
```
|
||
|
||
Response `201` mit dem angelegten Ticket, `422` bei fehlendem `title` oder ungueltiger
|
||
`column`.
|
||
|
||
### `PATCH /api/v1/tickets/{id}` — Ticket verschieben/aendern
|
||
|
||
Auth: `X-Api-Key`. Erlaubte Felder im Body: `column`, `title`, `description`, `type`,
|
||
`color`, `assignee_agent_id` (alle optional, nur uebergebene Felder werden geaendert).
|
||
|
||
Response `200` mit dem aktualisierten Ticket, `404` falls unbekannt, `422` bei ungueltiger
|
||
`column`.
|
||
|
||
### `POST /api/v1/tickets/resync` — Board komplett neu einlesen
|
||
|
||
Auth: `X-Admin-Key`. Issue #51: manueller "Reload"-Knopf am Kanban-Board im Frontend fuer den
|
||
Fall, dass Tickets (geloescht, verschoben, o.ae.) vom regulaeren Codeberg-Sync nicht mehr
|
||
bereinigt werden. Liest synchron alle konfigurierten Codeberg-Projekte neu ein (siehe
|
||
[Codeberg-Sync](#codeberg-sync-backlogdone)) und entfernt dabei je Projekt zusaetzlich Tickets,
|
||
deren Issue nicht mehr unter den offenen/geschlossenen Issues auftaucht - selbst wenn sie
|
||
manuell auf eine nicht vom Sync verwaltete Spalte wie `fertig` bewegt wurden. Tickets ohne
|
||
Projektbezug (z.B. per API angelegt) bleiben unberuehrt. Anders als der Webhook-Trigger kein
|
||
asynchroner Anstoss des sync-Containers, sondern derselbe Ablauf direkt im api-Container, damit
|
||
die Antwortzeit die tatsaechliche Sync-Dauer widerspiegelt.
|
||
|
||
Response `200` mit `{ "tickets": [...] }` (neuer Board-Zustand), `502` falls die Codeberg-API
|
||
nicht erreichbar war (bereits verarbeitete Projekte bleiben dann auf dem neuen Stand, noch
|
||
nicht erreichte auf dem alten - der naechste periodische Sync-Lauf oder Webhook gleicht das an).
|
||
|
||
## Events
|
||
|
||
`severity`: `warning` (gelb) oder `error` (rot). Neuestes Event zuerst (F5).
|
||
|
||
### `POST /api/v1/events` — Event melden
|
||
|
||
Auth: `X-Api-Key`.
|
||
|
||
```json
|
||
{ "system_id": "srv1", "name": "Ping", "message": "Host nicht erreichbar", "severity": "error" }
|
||
```
|
||
|
||
Pflichtfelder: `name`, `message`, `severity`. `occurred_at` optional (Default: jetzt).
|
||
Response `201` mit dem gespeicherten Event.
|
||
|
||
### `GET /api/v1/events` — Event-Liste
|
||
|
||
Query-Parameter: `limit` (Default `5`, entspricht der im Plan festgelegten Zeilenzahl des
|
||
Event-Monitors, F5; Maximum intern 200).
|
||
|
||
Response: `{ "events": [ { "id", "system_id", "name", "message", "severity", "occurred_at", "created_at" }, ... ] }`
|
||
|
||
## Systeme
|
||
|
||
Bis zu 9 Kacheln (F6, `SystemRepository::MAX_TILES`), Fehler zuerst sortiert. Aktuell
|
||
werden Systeme zusaetzlich aus der bestehenden `status.txt` (geschrieben von
|
||
`check_fds.sh`) importiert; per API gemeldete Systeme (`source: "api"`) haben Vorrang und
|
||
werden vom Legacy-Import nicht ueberschrieben.
|
||
|
||
### `POST /api/v1/systems/{id}/status` — Systemstatus melden
|
||
|
||
Auth: `X-Api-Key`.
|
||
|
||
```json
|
||
{ "status": "ok", "name": "Webserver", "type": "host", "detail": "optional" }
|
||
```
|
||
|
||
`status`: `ok` oder `error`. Response `200` mit dem gespeicherten System, `422` bei
|
||
ungueltigem `status`.
|
||
|
||
### `GET /api/v1/systems` — Systemuebersicht
|
||
|
||
Response: `{ "systems": [ { "id", "name", "type", "status", "detail", "source", "last_check_at" }, ... ] }`
|
||
(maximal 9 Eintraege).
|
||
|
||
### `DELETE /api/v1/systems/{id}` — Kachel entfernen
|
||
|
||
Auth: `X-Admin-Key`. Issue #70: Entfernt ein System aus dem Monitor. Gedacht fuer Kacheln,
|
||
die niemand mehr meldet - ein einmal gemeldetes System blieb sonst fuer immer stehen, nur
|
||
eben ohne Aktualisierung. Response `200` mit `{ "deleted": true|false }` (`false` = es gab
|
||
kein System mit dieser Id). Meldet sich das System spaeter erneut, legt
|
||
`POST /api/v1/systems/{id}/status` es unveraendert neu an.
|
||
|
||
Einen Host der Ping-Ueberwachung entfernt man dagegen ueber
|
||
[`DELETE /api/v1/hosts/{id}`](#hosts-ping-ueberwachung-konfigurieren) - der nimmt seine Kachel
|
||
selbst mit.
|
||
|
||
## KPIs
|
||
|
||
F7: Konkrete Kennzahlen sind noch offen ("kommt spaeter"). Phase 1 stellt nur die
|
||
generische Speicher-/Abfragestruktur bereit.
|
||
|
||
Zusaetzlich zu hier gemeldeten Kennzahlen zeigt der KPI-Monitor im Frontend vier weitere,
|
||
nicht per `POST /api/v1/kpis/{key}` gemeldete Kacheln, die direkt aus anderen Endpunkten
|
||
abgeleitet sind: "Agenten"/"Online"/"Problem" aus `GET /api/v1/agents` (Issue #39) und "Hooks"
|
||
(ganz unten) aus [`GET /api/v1/webhooks/stats`](#get-apiv1webhooksstats--empfangene-webhooks-zaehlen-hooks-kpi),
|
||
siehe Issue #38.
|
||
|
||
"Problem" zaehlt die Agenten in einem
|
||
[Problemzustand](#problemzustaende-abgelaufene-claude-anmeldung) und faerbt sich nur rot, wenn
|
||
der Wert ueber 0 liegt - eine dauerhaft rote Kachel mit einer 0 waere genau das Rauschen, das den
|
||
Ernstfall untergehen liesse.
|
||
|
||
Issue #59: die Kennzahl `open_prs` ("Offene PRs") wird nicht per `POST /api/v1/kpis/{key}`
|
||
gemeldet, sondern am Ende jedes Sync-Laufs (`CodebergSyncService::sync()`/`resync()`) automatisch
|
||
aus der Summe der offenen Pull Requests aller konfigurierten Projekte berechnet und ueber das
|
||
generische KPI-Repository gespeichert. Ein Sync-Lauf startet u.a. bei jedem eingehenden
|
||
Codeberg-Webhook (auch PR-Aenderungen) sowie periodisch, siehe
|
||
[`POST /api/v1/webhooks/codeberg`](#post-apiv1webhookscodeberg--sofort-sync-per-webhook).
|
||
|
||
### `POST /api/v1/kpis/{key}` — KPI-Datenpunkt melden
|
||
|
||
Auth: `X-Api-Key`.
|
||
|
||
```json
|
||
{ "value": 12.5, "label": "Offene Tickets", "unit": "count", "timestamp": "2026-07-27T10:00:00Z" }
|
||
```
|
||
|
||
Pflichtfeld: `value` (numerisch). `label`/`unit`/`timestamp` optional. Response `201` mit
|
||
dem KPI inkl. Verlauf.
|
||
|
||
### `GET /api/v1/kpis` — Alle KPIs
|
||
|
||
Response: `{ "kpis": [ { "key", "label", "unit", "current_value", "history": [ { "value", "recorded_at" }, ... ] }, ... ] }`
|
||
|
||
## Live-Updates (SSE)
|
||
|
||
F13: Das Frontend haelt sich per [Server-Sent Events](https://developer.mozilla.org/de/docs/Web/API/Server-sent_events)
|
||
ueber Aenderungen auf dem Laufenden, statt staendig zu pollen.
|
||
|
||
### `GET /api/v1/stream` — Aenderungs-Feed
|
||
|
||
Auth: keine (oeffentlich wie die anderen `GET`-Endpunkte). Antwort:
|
||
`Content-Type: text/event-stream`.
|
||
|
||
Die Verbindung bleibt fuer `MONITOR_SSE_MAX_ITERATIONS * MONITOR_SSE_POLL_INTERVAL` Sekunden
|
||
offen (Default: `30 * 2s` = ca. 1 Minute) und prueft alle `MONITOR_SSE_POLL_INTERVAL` Sekunden
|
||
serverseitig, ob sich Tickets, Events, Systeme, KPIs oder Agenten veraendert haben (billiger
|
||
"Fingerabdruck" aus Anzahl + juengstem Zeitstempel je Ressource, keine vollstaendige
|
||
Änderungshistorie). Danach schliesst der Server die Verbindung; `EventSource` im Browser
|
||
verbindet automatisch neu (Standardverhalten).
|
||
|
||
Bei einer Aenderung wird ein `update`-Event gesendet, das nur benennt, **welche** Ressourcen
|
||
sich geaendert haben - das Frontend laedt die betroffenen Endpunkte anschliessend regulaer per
|
||
`GET` neu:
|
||
|
||
```
|
||
event: update
|
||
data: {"changed": ["tickets", "events"]}
|
||
```
|
||
|
||
Ohne Aenderung sendet der Server stattdessen alle `MONITOR_SSE_POLL_INTERVAL` Sekunden einen
|
||
Kommentar (`: heartbeat`) als Keep-Alive, damit Proxys die Verbindung nicht vorzeitig
|
||
schliessen.
|
||
|
||
## Projekte (Codeberg-Sync konfigurieren)
|
||
|
||
Seit Issue #27 ist die Projektliste des Codeberg-Sync (vorher `CODEBERG_REPOS`) in der
|
||
Datenbank gepflegt statt in einer Umgebungsvariable - editierbar per API bzw. ueber das
|
||
Frontend-Modal (Klick auf den PO in der Buero-Visualisierung). Erster Start seedet die drei
|
||
bisherigen Default-Projekte `fds2610/monitor` (gelb), `fds2610/Steuerkram` (gruen) und
|
||
`fds2610/nc-gantt` (blau), siehe `api/src/Db/migrations/003_repo_configs.sql`.
|
||
|
||
| Endpunkt | Auth | Beschreibung |
|
||
|---|---|---|
|
||
| `GET /api/v1/repo-configs` | - | Liste aller konfigurierten Projekte, sortiert nach `sort_order` |
|
||
| `POST /api/v1/repo-configs` | `X-Admin-Key` | Legt ein Projekt an (`owner`, `repo`, `color` als 6-stelliger Hex-Wert, mit oder ohne `#`) |
|
||
| `PATCH /api/v1/repo-configs/{id}` | `X-Admin-Key` | Aendert `owner`/`repo`/`color` eines Projekts |
|
||
| `DELETE /api/v1/repo-configs/{id}` | `X-Admin-Key` | Entfernt ein Projekt (der Sync liest es danach nicht mehr) und loescht dessen Tickets aus dem Kanban (Issue #43) |
|
||
| `POST /api/v1/repo-configs/{id}/check` | `X-Admin-Key` | Prueft Schreibrechte + Webhook des Projekts (Issue #53, siehe unten) |
|
||
|
||
`owner`+`repo` muessen zusammen eindeutig sein (409 -> hier als `422` mit Fehlermeldung, da
|
||
kein Konflikt-Statuscode im Fehlerformat vorgesehen ist). Der laufende `sync`-Dienst
|
||
(`--watch`) liest die Projektliste bei jedem Lauf frisch ein - eine Aenderung wirkt also ohne
|
||
Neustart des Containers.
|
||
|
||
### `POST /api/v1/repo-configs/{id}/check` — Schreibrechte + Webhook pruefen
|
||
|
||
Issue #53: Das Frontend-Modal ruft diesen Endpunkt automatisch nach dem Anlegen/Bearbeiten
|
||
eines Projekts auf und zeigt das Ergebnis als gruenen Haken bzw. rotes Kreuz rechts neben den
|
||
Bearbeiten-/Loeschen-Symbolen (Tooltip = `message`). Beide Checks nutzen `CODEBERG_TOKEN`
|
||
(das Konto, unter dem der Sync laeuft):
|
||
|
||
```json
|
||
// Response 200
|
||
{
|
||
"checks": {
|
||
"write_access": { "ok": true, "message": "fds2610-bot ist Mitarbeiter mit Schreibrechten auf dieses Projekt." },
|
||
"webhook": { "ok": false, "message": "Kein aktiver Webhook auf \"/api/v1/webhooks/codeberg\" gefunden - unter Einstellungen -> Webhooks einrichten." }
|
||
}
|
||
}
|
||
```
|
||
|
||
- `write_access`: liest `permissions.push` aus `GET /repos/{owner}/{repo}` - erfordert
|
||
lediglich, dass das Projekt fuer den Token sichtbar ist.
|
||
- `webhook`: listet `GET /repos/{owner}/{repo}/hooks` und sucht einen aktiven Hook mit
|
||
`content_type: json`, dessen URL auf `/api/v1/webhooks/codeberg` endet. Das Auflisten von
|
||
Hooks erfordert Admin-Rechte auf das Projekt - fehlen die, liefert der Check `ok: false` mit
|
||
entsprechender Meldung statt eines Fehlers.
|
||
|
||
## Hosts (Ping-Ueberwachung konfigurieren)
|
||
|
||
Seit Issue #70 steht die Liste der per Ping ueberwachten Hosts in der Datenbank statt nur in
|
||
der `hosts.conf` neben `check_fds.sh` - pflegbar ueber das Zahnrad am Wandmonitor "Systeme"
|
||
in der Buero-Visualisierung. Jeder Host gehoert dem OPs-Agenten (Issue #66), der ihn anpingt:
|
||
API und Ping-Check laufen nicht zwangslaeufig auf demselben Rechner, die API kann die
|
||
`hosts.conf` also nicht selbst schreiben. Stattdessen holt `check_fds.sh` seine Liste bei
|
||
jedem Lauf mit dem eigenen Agenten-Key ab und schreibt daraus die lokale Datei.
|
||
|
||
| Endpunkt | Auth | Beschreibung |
|
||
|---|---|---|
|
||
| `GET /api/v1/hosts` | - | Alle Hosts ueber alle Ping-Rechner, je mit `agent_name` und `system_ids` (siehe unten) |
|
||
| `POST /api/v1/hosts` | `X-Admin-Key` | Legt einen Host an (`agent_id`, `hostname`, `address`) |
|
||
| `PATCH /api/v1/hosts/{id}` | `X-Admin-Key` | Aendert `hostname`/`address`; der zustaendige Agent bleibt, wie er ist |
|
||
| `DELETE /api/v1/hosts/{id}` | `X-Admin-Key` | Nimmt den Host aus der Ueberwachung und loescht dessen Kachel im System-Monitor |
|
||
| `GET /api/v1/hosts/conf` | `X-Api-Key` | Die `hosts.conf` des anfragenden Ping-Rechners als `text/plain` |
|
||
| `POST /api/v1/hosts/import` | `X-Api-Key` | Erstbefuellung aus einer hochgeladenen `hosts.conf` (`text/plain`) |
|
||
|
||
`hostname` und `address` duerfen keine Leerzeichen enthalten und nicht mit `#` beginnen -
|
||
`hosts.conf` ist zeilen- und leerzeichengetrennt, ein solcher Wert wuerde dort still
|
||
verstuemmelt. `hostname` muss je Ping-Rechner eindeutig sein (`422` bei einem Duplikat),
|
||
ueber verschiedene Rechner hinweg ist er erlaubt. `agent_id` muss zu einem registrierten
|
||
Agenten der Rolle `ops` gehoeren.
|
||
|
||
`system_ids` nennt die Ids, unter denen dieser Host im System-Monitor auftauchen kann - es
|
||
sind zwei, weil beide Meldewege ihn unterschiedlich benennen: der Legacy-Import aus
|
||
`status.txt` nimmt den Hostnamen unveraendert, die REST-Meldung aus `check_fds.sh` dessen
|
||
kleingeschriebenen Slug (`Nextcloud(lokal)` -> `nextcloud-lokal`). Das Frontend erkennt daran,
|
||
welche Kachel zu welchem Host gehoert; `DELETE /api/v1/hosts/{id}` raeumt beide ab.
|
||
|
||
### Ablauf im Ping-Check
|
||
|
||
1. `GET /api/v1/hosts/conf` mit dem Agenten-Key des OPs-Agenten.
|
||
- `200`: der Body ist die neue `hosts.conf` - auch wenn er keinen Host enthaelt (dann
|
||
wurden im Frontend alle entfernt und es wird nichts mehr geprueft).
|
||
- `204`: fuer diesen Rechner ist noch nie etwas konfiguriert worden. Dann laedt
|
||
`check_fds.sh` seine bestehende `hosts.conf` per `POST /api/v1/hosts/import` hoch, damit
|
||
die bereits ueberwachten Hosts nicht von Hand nachgetragen werden muessen.
|
||
- Alles andere (inkl. nicht erreichbarer API): die zuletzt geschriebene lokale Datei bleibt
|
||
in Kraft - eine Stoerung der API darf die Ueberwachung nicht anhalten.
|
||
2. `POST /api/v1/hosts/import` nimmt nur den allerersten Upload eines Rechners an und
|
||
antwortet danach mit `{ "imported": 0 }`. Sonst wuerde ein Ping-Rechner mit alter lokaler
|
||
Datei die im Frontend geloeschten Hosts wieder anlegen.
|
||
|
||
## Codeberg-Sync (Backlog/Done)
|
||
|
||
`api/bin/sync-codeberg.php` liest offene und geschlossene Issues aus den konfigurierten
|
||
Codeberg-Projekten (siehe [Projekte](#projekte-codeberg-sync-konfigurieren)) und gleicht sie
|
||
je Projekt ueber `repo` + `external_ref` (= Issue-Nummer) mit den Tickets ab:
|
||
|
||
- Offene Issues → Spalte je nach Label: `wip` → `in_arbeit`, sonst `testing` → `testing`,
|
||
sonst `help wanted` → `qa`, sonst `automate` → `bereit`, sonst `backlog`.
|
||
- Geschlossene Issues → Spalte `done`.
|
||
- Nur falls das Ticket noch in einer der Sync-Spalten (`backlog`, `bereit`, `in_arbeit`, `qa`,
|
||
`testing`, `done`) ist - auf `fertig` manuell verschobene Tickets werden nicht
|
||
zurueckgesetzt.
|
||
- Ticket-Typ kommt vom ersten Issue-Label, die Farbe fest vom Projekt (siehe oben).
|
||
|
||
Ausgefuehrt wird das Skript vom Compose-Service `sync` (siehe
|
||
[docker-compose.yml](docker-compose.yml)) - kein Host-Cron noetig. Der Service startet mit
|
||
dem Stack (`--watch`), macht sofort einen ersten Lauf und wiederholt ihn danach aus zwei
|
||
Gruenden: periodisch alle `MONITOR_SYNC_INTERVAL` Sekunden (Default 600, siehe
|
||
`.env.example`) und **sofort**, sobald ein Codeberg-Webhook eintrifft (siehe unten). Er nutzt
|
||
dasselbe Image und dieselbe SQLite-Datei wie `api` und verbindet sich selbst ausschliesslich
|
||
ausgehend nach codeberg.org - fuer diesen Service ist also kein Port-Mapping noetig.
|
||
|
||
```bash
|
||
docker compose up -d sync # Sync-Service starten
|
||
docker compose logs -f sync # Laeufe mitlesen
|
||
docker compose run --rm api php bin/sync-codeberg.php # einmalig manuell (Debug)
|
||
```
|
||
|
||
Beim manuellen Aufruf werden Fehler direkt auf der Konsole sichtbar (`Codeberg-API nicht
|
||
erreichbar: …` bzw. `Codeberg-API-Fehler (HTTP 4xx)`) - der erste Anlaufpunkt, wenn Backlog
|
||
und Done leer bleiben.
|
||
|
||
Schlaegt ein Lauf fehl (vereinzelte Timeouts nach codeberg.org kommen vor), wird der
|
||
naechste Versuch **vorgezogen** statt bis zum regulaeren Intervall zu warten: 5s, 10s, 20s,
|
||
… bis hoechstens `MONITOR_SYNC_INTERVAL` (`Sync\SyncSchedule`). Sonst ginge eine per Webhook
|
||
angeforderte Aktualisierung verloren - die Anforderung wird vor dem Lauf quittiert und
|
||
waere damit erst nach vollen `MONITOR_SYNC_INTERVAL` Sekunden nachgeholt. Nach einem
|
||
erfolgreichen Lauf gilt sofort wieder das regulaere Intervall. Im Log sieht das so aus:
|
||
|
||
```
|
||
[sync] Lauf fehlgeschlagen (1x in Folge, naechster Versuch in 5s): Codeberg-API nicht erreichbar: …
|
||
[sync] Lauf fehlgeschlagen (2x in Folge, naechster Versuch in 10s): …
|
||
```
|
||
|
||
### `POST /api/v1/webhooks/codeberg` — Sofort-Sync per Webhook
|
||
|
||
Damit Issue-Aenderungen nicht erst beim naechsten periodischen Lauf auf dem Board landen,
|
||
kann Codeberg jede Aenderung aktiv melden. Der Endpunkt wertet den Payload **nicht** aus, er
|
||
fordert nur einen regulaeren Sync an; der `sync`-Service greift die Anforderung im
|
||
Sekundentakt auf und laedt den aktuellen Stand **aller** konfigurierten Projekte (siehe
|
||
[Projekte](#projekte-codeberg-sync-konfigurieren)), nicht nur das des ausloesenden Repos. Vom Aendern des Issues bis zum
|
||
Board vergehen so wenige Sekunden (Webhook → Sync → SSE-Push ans Frontend).
|
||
|
||
Bei mehreren Projekten muss der Webhook in **jedem** Repo eingerichtet werden, dessen
|
||
Aenderungen sofort statt erst beim naechsten periodischen Lauf ankommen sollen - ein
|
||
fehlender Webhook auf einem der Projekte faengt der periodische Sync (`MONITOR_SYNC_INTERVAL`)
|
||
weiterhin ab.
|
||
|
||
Authentifizierung ist **kein** API-Key, sondern die HMAC-Signatur, die Codeberg selbst
|
||
mitschickt: `X-Forgejo-Signature` (bzw. `X-Gitea-Signature`) enthaelt den HMAC-SHA256 des
|
||
rohen Request-Bodys, gebildet mit dem Webhook-Secret aus `CODEBERG_WEBHOOK_SECRET`. Ist die
|
||
Variable nicht gesetzt, antwortet der Endpunkt **grundsaetzlich** mit 401 - ein offener
|
||
Sync-Ausloeser im Internet waere sonst die Folge.
|
||
|
||
| Antwort | Bedeutung |
|
||
|---------|-----------|
|
||
| `202` mit `{"status": "sync angefordert"}` | Sync wurde angestossen |
|
||
| `200` mit `{"status": "pong"}` | `ping`-Event beim Anlegen des Webhooks |
|
||
| `401` | Signatur fehlt, ist falsch oder es ist kein Secret konfiguriert |
|
||
|
||
Einrichtung im Codeberg-Repo unter **Einstellungen → Webhooks → Forgejo**:
|
||
|
||
- **Ziel-URL:** `https://<oeffentliche-api-url>/api/v1/webhooks/codeberg`
|
||
- **HTTP-Methode:** `POST`, Content-Type `application/json`
|
||
- **Secret:** derselbe Wert wie `CODEBERG_WEBHOOK_SECRET` (z.B. `openssl rand -hex 32`)
|
||
- **Ereignisse:** *Benutzerdefiniert* → `Issues` und `Issue-Label` (Issue-Kommentare sind
|
||
fuer das Board irrelevant und wuerden nur ueberfluessige Syncs ausloesen)
|
||
|
||
Anders als der `sync`-Service braucht **dieser** Endpunkt eine eingehende Freigabe: Proxy und
|
||
Firewall muessen `POST /api/v1/webhooks/codeberg` aus dem Internet an den `api`-Container
|
||
durchlassen. Ob eine Zustellung geklappt hat, zeigt Codeberg direkt beim Webhook unter
|
||
„Letzte Zustellungen" (inkl. Antwort-Body). Der periodische Sync laeuft unabhaengig weiter
|
||
und ist das Sicherheitsnetz fuer verpasste Zustellungen (Container-Neustart, Stoerung).
|
||
|
||
### `GET /api/v1/webhooks/stats` — Empfangene Webhooks zaehlen (Hooks-KPI)
|
||
|
||
Auth: keine (oeffentlich wie die anderen `GET`-Endpunkte).
|
||
|
||
```json
|
||
{ "count": 12 }
|
||
```
|
||
|
||
`count` zaehlt jede angenommene Zustellung an `POST /api/v1/webhooks/codeberg` (auch
|
||
`ping`) **seit dem letzten Start des `api`-Containers** - nicht seit Projektbeginn: die
|
||
Zaehldatei wird bei jedem Start dieses Containers zurueckgesetzt (`docker/entrypoint.sh`,
|
||
`MONITOR_HOOKS_COUNTER_FILE`). Ein Neustart des `sync`-Containers alleine setzt den Zaehler
|
||
nicht zurueck, da nur der `api`-Container Webhooks empfaengt. Das Frontend zeigt den Wert als
|
||
eigene, immer sichtbare Kachel ganz rechts im KPI-Monitor (Issue #38).
|
||
|
||
## Fehlerformat
|
||
|
||
Alle Fehlerantworten haben die Form `{ "error": "‹Meldung›" }` mit passendem HTTP-Status:
|
||
|
||
| Status | Bedeutung |
|
||
|---|---|
|
||
| `400` | Ungueltige Anfrage (z. B. unbekannter Endpunkt-Parameter) |
|
||
| `401` | Fehlender/ungueltiger `X-Api-Key`/`X-Admin-Key` |
|
||
| `403` | Aktion nicht erlaubt (z. B. fremde Agent-Aktivitaet melden) |
|
||
| `404` | Ressource nicht gefunden |
|
||
| `405` | HTTP-Methode fuer diesen Pfad nicht erlaubt |
|
||
| `422` | Validierungsfehler (Pflichtfeld fehlt, ungueltiger Enum-Wert) |
|