Setzt Phase 1 aus Visualization.md um: Vanilla-PHP-REST-API mit SQLite-Persistenz, statischen API-Keys pro Agent, Codeberg-Sync fuer Backlog/Done, PHPUnit-Tests und vollstaendiger API.md-Dokumentation - Vorgaben aus Detailantworten.md (F1-F16) umgesetzt. Entwicklung/Build/Test laufen komplett in Docker-Compose-Containern (PHP 8.2 + SQLite + Composer + PHPUnit/pcov), analog zum NC-Gantt-Projekt ist auf dem Host kein PHP/Composer noetig. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
221 lines
7.8 KiB
Markdown
221 lines
7.8 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)
|
||
- [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)).
|
||
|
||
## Authentifizierung
|
||
|
||
Zwei Schluesseltypen, beide statisch (F14):
|
||
|
||
| Header | Verwendet fuer | Herkunft |
|
||
|---|---|---|
|
||
| `X-Admin-Key` | `POST /api/v1/agents` (Registrierung neuer Agenten) | Umgebungsvariable `MONITOR_ADMIN_API_KEY` |
|
||
| `X-Api-Key` | Alle anderen schreibenden Endpunkte | Wird bei der Agenten-Registrierung einmalig zurueckgegeben |
|
||
|
||
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`, `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`).
|
||
|
||
### `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"
|
||
},
|
||
"api_key": "‹64 Hex-Zeichen, nur jetzt sichtbar›"
|
||
}
|
||
```
|
||
|
||
### `GET /api/v1/agents` — Alle Agenten
|
||
|
||
Response: `{ "agents": [ ‹Agent-Objekte wie oben, ohne api_key› ] }`
|
||
|
||
### `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.
|
||
|
||
## Tickets
|
||
|
||
Spalten (F3): `backlog`, `bereit`, `in_arbeit`, `qa`, `testing`, `fertig`, `done`.
|
||
|
||
- `backlog` und `done` werden zusaetzlich regelmaessig aus Codeberg-Issues befuellt
|
||
(siehe [Codeberg-Sync](#codeberg-sync-backlogdone)), alle anderen Bewegungen ergeben
|
||
sich ausschliesslich aus Agenten-Meldungen (F4).
|
||
- Ticket-Typ und Farbe (statt Prioritaet) werden bei Codeberg-Tickets aus dem ersten
|
||
Issue-Label uebernommen (F4).
|
||
- 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", "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`.
|
||
|
||
## 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).
|
||
|
||
## KPIs
|
||
|
||
F7: Konkrete Kennzahlen sind noch offen ("kommt spaeter"). Phase 1 stellt nur die
|
||
generische Speicher-/Abfragestruktur bereit.
|
||
|
||
### `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" }, ... ] }, ... ] }`
|
||
|
||
## Codeberg-Sync (Backlog/Done)
|
||
|
||
`api/bin/sync-codeberg.php` (per Cron oder manuell auszufuehren) liest offene und
|
||
geschlossene Issues aus dem konfigurierten Codeberg-Repo (`CODEBERG_OWNER`/`CODEBERG_REPO`)
|
||
und gleicht sie ueber `external_ref` (= Issue-Nummer) mit den Tickets ab:
|
||
|
||
- Offene Issues → Spalte `backlog` (nur falls das Ticket noch in `backlog`/`done` ist -
|
||
manuell auf dem Board bewegte Tickets werden nicht zurueckgesetzt).
|
||
- Geschlossene Issues → Spalte `done`.
|
||
- Ticket-Typ/-Farbe kommen vom ersten Issue-Label (Name/Farbe).
|
||
|
||
Aufruf via Docker: `docker compose run --rm api php bin/sync-codeberg.php`.
|
||
|
||
## 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) |
|