From ba52522415300d1b0388327d162673e96cc82d5b Mon Sep 17 00:00:00 2001
From: fds2610-bot
Date: Tue, 28 Jul 2026 22:54:36 +0200
Subject: [PATCH] Frontend-API-URL ueber MONITOR_API_BASE konfigurieren statt
aus dem Port raten
Die Basis-URL der API wurde bisher aus dem Port der Seite abgeleitet
(office.html: `if (location.port === "8090")`). Laeuft das Frontend hinter
anderen Ports - in Produktion 4443 (web) / 4444 (api) - griff die Bedingung
nicht, der relative Default /api/v1 lief gegen den Webserver ins Leere, und
die Szene fiel still auf Demo-Daten zurueck: fremde Agenten-Namen und
erfundene Ticketzahlen bei gleichzeitig leeren Detail-Modals.
Die URL kommt jetzt aus MONITOR_API_BASE (.env, Default passend zum
Port-Mapping in docker-compose.yml). Der Webserver liefert den Wert zur
Laufzeit als /config.js aus; das nginx-Image setzt ihn beim Start per
envsubst in die Config ein (default.conf -> default.conf.template). Damit
bleibt html/ ein rein statisches, read-only gemountetes Verzeichnis - kein
Build-Schritt, kein eigenes Entrypoint-Skript. Ein leerer Wert bedeutet
"gleicher Origin" und faellt auf /api/v1 zurueck.
Zusaetzlich macht ein roter Banner "API nicht erreichbar!" den Demo-Fallback
sichtbar, statt ihn wie bisher stillschweigend als echte Daten auszugeben.
Co-Authored-By: Claude Opus 5
---
.env.example | 8 ++++++++
API.md | 5 +++++
README.md | 11 ++++++++++-
docker-compose.yml | 13 +++++++++++--
docker/nginx/default.conf | 15 ---------------
docker/nginx/default.conf.template | 29 +++++++++++++++++++++++++++++
html/css/office.css | 18 ++++++++++++++++++
html/js/office-api.js | 16 +++++++++++++---
html/js/office-render.js | 13 +++++++++++++
html/office.html | 16 ++++++----------
10 files changed, 113 insertions(+), 31 deletions(-)
delete mode 100644 docker/nginx/default.conf
create mode 100644 docker/nginx/default.conf.template
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 @@
+ API nicht erreichbar!
+
-
+
+