diff --git a/.env.example b/.env.example index d1a01ae..b5c446f 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,14 @@ # Administrator-Key zur Registrierung neuer Agenten (POST /api/v1/agents, Header X-Admin-Key) MONITOR_ADMIN_API_KEY=change-me +# Basis-URL, unter der das Frontend (Browser!) die REST-API erreicht. Der Default passt zum +# Port-Mapping des api-Service in docker-compose.yml (8091). Wer die Ports dort aendert oder +# das System hinter einem Reverse Proxy betreibt, muss diesen Wert entsprechend nachziehen - +# z.B. https://monitor.example.org:4444/api/v1. +# Leer lassen, wenn Frontend und API unter demselben Origin ausgeliefert werden (dann wird +# die relative URL /api/v1 verwendet). +MONITOR_API_BASE=http://localhost:8091/api/v1 + # Codeberg-Repo, aus dem Backlog/Done per Sync-Skript befuellt werden CODEBERG_OWNER=fds2610 CODEBERG_REPO=monitor diff --git a/API.md b/API.md index a5f67db..237a2eb 100644 --- a/API.md +++ b/API.md @@ -33,6 +33,11 @@ Implementierung: Vanilla-PHP (kein Framework), Persistenz in SQLite - 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 diff --git a/README.md b/README.md index 5001dad..833453f 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,8 @@ die dazugehörigen Klärungen in [Detailfragen.md](Detailfragen.md) und Server-Sent Events, Polling-Fallback), Klick auf Board/Monitore öffnet Detail-Modals, Agenten laufen animiert zwischen Schreibtisch/Board/Monitor-Wand/Kaffee-Ecke (Phase 3-5, siehe Visualization.md Abschnitt 9). Ist die API nicht erreichbar, zeigt die Szene - ersatzweise Demo-Daten. + ersatzweise Demo-Daten — kenntlich gemacht durch einen roten Banner „API nicht erreichbar!". + Die Basis-URL der API kommt aus `MONITOR_API_BASE` (siehe `.env.example`). - Weiterhin: klassische Ping-Überwachung (`check_fds.sh`, `hosts.conf`, `mail_fds.py`) als eine Datenquelle für den System-Monitor; die alte tabellarische Statusseite ist mit Phase 7 entfallen (F16), `status.txt` speist weiterhin den System-Monitor @@ -53,6 +54,14 @@ docker compose run --rm api composer test # PHPUnit (API) docker compose run --rm frontend # Node-Tests (Frontend-Layoutlogik) ``` +Der Browser erreicht die API nicht über das Compose-Netzwerk, sondern über die in +`MONITOR_API_BASE` (`.env`) hinterlegte URL — der Default `http://localhost:8091/api/v1` passt +zum Port-Mapping oben. **Wer die Ports in `docker-compose.yml` ändert oder das System hinter +einem Reverse Proxy betreibt, muss `MONITOR_API_BASE` entsprechend nachziehen**, sonst zeigt +die Szene den Banner „API nicht erreichbar!" und fällt auf Demo-Daten zurück. Der Webserver +liefert den Wert zur Laufzeit als `/config.js` aus (`docker/nginx/default.conf.template`), ein +Build-Schritt ist dafür nicht nötig. + Details zur API: [api/README.md](api/README.md). Details zu den Endpunkten: [API.md](API.md). diff --git a/docker-compose.yml b/docker-compose.yml index f9bf885..0984f75 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,9 @@ services: # Optional: echte status.txt aus der Produktion zum Testen einbinden, z.B. # - ./status.txt:/status.txt:ro working_dir: /app + # Achtung: Eine Aenderung des Host-Ports muss in .env (MONITOR_API_BASE) nachgezogen + # werden - das Frontend laeuft im Browser und erreicht die API nicht ueber das + # Compose-Netzwerk, sondern ueber genau diesen Port. ports: - "8091:8080" environment: @@ -31,8 +34,14 @@ services: # Ueberlagert den Platzhalter html/API.md - der muss existieren, weil in # einem read-only gemounteten Verzeichnis kein Mountpoint angelegt werden kann. - ./API.md:/usr/share/nginx/html/API.md:ro - # Setzt office.html als Einstiegsseite (sonst 403 auf der Basis-URL). - - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro + # Setzt office.html als Einstiegsseite (sonst 403 auf der Basis-URL) und liefert + # /config.js aus. Als Template gemountet, damit das nginx-Image beim Start + # MONITOR_API_BASE per envsubst einsetzt (Ausgabe: /etc/nginx/conf.d/default.conf). + - ./docker/nginx/default.conf.template:/etc/nginx/templates/default.conf.template:ro + environment: + # Wird in docker/nginx/default.conf.template eingesetzt und als /config.js an den + # Browser ausgeliefert. Leer = gleicher Origin (relatives /api/v1). + MONITOR_API_BASE: ${MONITOR_API_BASE:-} ports: - "8090:80" diff --git a/docker/nginx/default.conf b/docker/nginx/default.conf deleted file mode 100644 index e9818c1..0000000 --- a/docker/nginx/default.conf +++ /dev/null @@ -1,15 +0,0 @@ -server { - listen 80; - server_name localhost; - - root /usr/share/nginx/html; - - # Einstiegsseite ist die Buero-Visualisierung - ohne diese Direktive liefert - # nginx beim Aufruf der Basis-URL ein 403 (Directory-Listing ist deaktiviert, - # und eine index.html gibt es nicht). - index office.html; - - location / { - try_files $uri $uri/ =404; - } -} diff --git a/docker/nginx/default.conf.template b/docker/nginx/default.conf.template new file mode 100644 index 0000000..d20b788 --- /dev/null +++ b/docker/nginx/default.conf.template @@ -0,0 +1,29 @@ +server { + listen 80; + server_name localhost; + + root /usr/share/nginx/html; + + # Einstiegsseite ist die Buero-Visualisierung - ohne diese Direktive liefert + # nginx beim Aufruf der Basis-URL ein 403 (Directory-Listing ist deaktiviert, + # und eine index.html gibt es nicht). + index office.html; + + # Laufzeit-Konfiguration fuers Frontend: Das html/-Verzeichnis ist read-only gemountet, + # deshalb wird die API-Basis-URL nicht als Datei abgelegt, sondern hier aus der Umgebung + # ausgeliefert (MONITOR_API_BASE, siehe .env). Das nginx-Image ersetzt ${MONITOR_API_BASE} + # beim Containerstart per envsubst - nur Variablen, die tatsaechlich in der Umgebung + # definiert sind, nginx-eigene wie $uri bleiben unangetastet. Ist MONITOR_API_BASE gar + # nicht definiert, bleibt der Platzhalter stehen und nginx verweigert den Start + # ("unknown \"monitor_api_base\" variable") - besser ein lauter Fehler als eine still + # falsche URL. docker-compose.yml setzt die Variable daher immer (ggf. leer). + location = /config.js { + default_type application/javascript; + add_header Cache-Control "no-store"; + return 200 'window.MONITOR_API_BASE = "${MONITOR_API_BASE}";'; + } + + location / { + try_files $uri $uri/ =404; + } +} diff --git a/html/css/office.css b/html/css/office.css index 40247ea..95308b1 100644 --- a/html/css/office.css +++ b/html/css/office.css @@ -66,6 +66,24 @@ header.office-header p { font-size: 0.9rem; } +/* Warnbanner, wenn die API nicht erreichbar ist und die Szene auf Demo-Daten zurueckfaellt + (siehe setApiBanner() in js/office-render.js). */ +.api-banner { + flex: 0 0 auto; + padding: 10px 24px; + background: #dc2626; + color: #ffffff; + font-weight: 700; + text-align: center; + letter-spacing: 0.02em; +} + +/* Muss nach der Regel oben stehen: ohne diese Zeile wuerde das hidden-Attribut vom + flex-Wert ueberstimmt und der Banner waere dauerhaft sichtbar. */ +.api-banner[hidden] { + display: none; +} + #office-scene { flex: 1 1 auto; /* min-height: 0 hebt die Flex-Default-Mindesthoehe (min-content) auf - sonst kann diff --git a/html/js/office-api.js b/html/js/office-api.js index 6bd3d7e..aa2c284 100644 --- a/html/js/office-api.js +++ b/html/js/office-api.js @@ -1,13 +1,23 @@ // Duenne fetch()-Wrapper fuer die Phase-1-REST-API (siehe API.md). Reine Daten-Ebene, kein // DOM-Zugriff - office-render.js ruft diese Funktionen auf und reicht die Ergebnisse an die // Layoutlogik (office-scene.js) weiter. Basis-URL ist per window.MONITOR_API_BASE -// ueberschreibbar (z.B. wenn Frontend und API auf unterschiedlichen Ports laufen, siehe -// docker-compose.yml: web auf :8090, api auf :8091). +// ueberschreibbar - gesetzt wird sie zur Laufzeit von /config.js, das der Webserver aus der +// Umgebungsvariable MONITOR_API_BASE erzeugt (siehe .env.example und +// docker/nginx/default.conf.template). Noetig, weil Frontend und API auf unterschiedlichen +// Origins laufen koennen (Dev: web :8090, api :8091); die API liefert dafuer CORS-Header, +// siehe api/src/Http/Response.php. const DEFAULT_API_BASE = "/api/v1"; function apiBase() { - return (typeof window !== "undefined" && window.MONITOR_API_BASE) || DEFAULT_API_BASE; + const configured = typeof window !== "undefined" ? window.MONITOR_API_BASE : null; + // Leer = gleicher Origin. Der startsWith-Check faengt einen nicht ersetzten Platzhalter + // ab (z.B. wenn config.js von einem anderen Webserver ohne Substitution ausgeliefert + // wird) - der waere sonst eine truthy Muell-URL. + if (!configured || configured.startsWith("${")) { + return DEFAULT_API_BASE; + } + return configured; } async function getJson(path) { diff --git a/html/js/office-render.js b/html/js/office-render.js index fef25db..e765cf5 100644 --- a/html/js/office-render.js +++ b/html/js/office-render.js @@ -1013,6 +1013,17 @@ function agentPlacements(agents, deskLayout) { return placements; } +// Der Demo-Fallback unten sieht auf den ersten Blick wie eine echte Szene aus (fremde +// Agenten-Namen, erfundene Ticketzahlen, aber leere Detail-Modals). Damit das auf einem +// Produktiv-Monitor nicht fuer echte Daten gehalten wird, blendet der Fallback zusaetzlich +// einen roten Banner ein - und wieder aus, sobald die API antwortet. +function setApiBanner(visible) { + const banner = document.getElementById("api-status-banner"); + if (banner) { + banner.hidden = !visible; + } +} + async function boot() { const container = document.getElementById("office-scene"); if (!container) { @@ -1039,9 +1050,11 @@ async function boot() { events: data.events, kpis: data.kpis, }); + setApiBanner(false); } catch (err) { console.warn("[office] Live-API nicht erreichbar, zeige Demo-Daten.", err); renderOfficeScene(container); + setApiBanner(true); } } diff --git a/html/office.html b/html/office.html index ec714a7..faf6ead 100644 --- a/html/office.html +++ b/html/office.html @@ -18,18 +18,14 @@

+ +
- + +