Skip to content

Commit 76f4d38

Browse files
committed
Erweiterungen für UI + Doku
1 parent 4c3a17a commit 76f4d38

6 files changed

Lines changed: 545 additions & 67 deletions

File tree

config/nginx.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ events {
1010
http {
1111
include /etc/nginx/mime.types;
1212
default_type application/octet-stream;
13+
absolute_redirect off;
1314

1415
access_log off;
1516
sendfile on;

docs/erweiterung.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,45 @@ MYAPP_INTERVAL=3600
5555
MYAPP_DEBUG=false
5656
```
5757

58+
### Statusdateien im Webroot bereitstellen
59+
60+
Wenn eine App einen JSON-Status nach außen bereitstellen soll, gilt dasselbe Muster wie bei `sysinfo` und `mesh-status`:
61+
62+
- Die App schreibt ihre Laufzeitdatei atomar in ein flüchtiges Laufzeitverzeichnis, zum Beispiel nach `/run/freifunk/state/myapp-status.json`
63+
- Für den stabilen HTTP-Pfad wird im Webroot `/run/freifunk/www/` ein Symlink angelegt, zum Beispiel `/run/freifunk/www/myapp-status.json`
64+
- `nginx` liefert nur diesen stabilen Pfad aus und erzeugt den Inhalt nicht selbst
65+
66+
Beispiel:
67+
68+
```text
69+
/run/freifunk/state/myapp-status.json
70+
/run/freifunk/www/myapp-status.json -> /run/freifunk/state/myapp-status.json
71+
GET /myapp-status.json
72+
```
73+
74+
Praktisch bedeutet das:
75+
76+
- Producer schreiben in ihr eigenes Runtime-Verzeichnis
77+
- Das Webroot enthält nur veröffentlichte Dateinamen oder Symlinks
78+
- Der HTTP-Pfad bleibt stabil, auch wenn sich das interne Runtime-Verzeichnis einer App ändert
79+
80+
Wichtig für die Auslieferung:
81+
82+
- Die Datei selbst sollte mit mindestens `0644` geschrieben werden
83+
- Alle übergeordneten Verzeichnisse auf dem Pfad zum Symlink-Ziel müssen für den `nginx`-Prozess durchsuchbar sein, in der Praxis also typischerweise mindestens `0755`
84+
- Fehlt dieses Execute-Bit auf einem Verzeichnis, endet der Request trotz vorhandenem Symlink mit `403 Forbidden`
85+
86+
Minimal nötig sind also drei Dinge: Runtime-Datei schreiben, Symlink im Webroot anlegen und den Pfad für `nginx` lesbar bzw. durchsuchbar machen.
87+
88+
### Wann reicht ein JSON-Endpoint?
89+
90+
Nicht jede Erweiterung braucht einen eigenen Menüpunkt in der UI.
91+
92+
- Ein zusätzlicher JSON-Endpoint reicht, wenn die bestehende UI nur weitere Daten anzeigen soll, zum Beispiel ein zusätzliches Panel oder weitere Kennzahlen auf einer vorhandenen Seite
93+
- Ein eigener UI-View ist erst dann sinnvoll, wenn die Erweiterung einen eigenen Bedienkontext, eigene Interaktion oder eine eigene Seite innerhalb der Navigation braucht
94+
95+
Faustregel: Neue Daten allein sind noch keine UI-Erweiterung. Ein neuer View ist erst dann sinnvoll, wenn die bestehende Seite dafür fachlich zu eng wird.
96+
5897
### Optionaler Plattform-Dienst: Mesh-Status
5998

6099
Apps können den aktuellen Mesh-Zustand über `/run/freifunk/state/mesh-status.json` lesen, müssen davon aber nicht abhängen.
@@ -93,6 +132,83 @@ Bedeutung:
93132
Für Apps ist `mesh.stable` das robustere Startsignal als `gateway.connected`, weil Mesh auch ohne selektiertes Gateway sinnvoll benutzbar sein kann.
94133
Wenn eine App keinen Mesh-Bezug hat, kann dieser Plattform-Dienst ignoriert werden.
95134

135+
### UI-Erweiterungen
136+
137+
Wenn eine App einen eigenen Menüpunkt in der Standard-UI bekommen soll, klinkt sie sich als zusätzlicher View in die bestehende SPA ein.
138+
Topbar, Sidebar, Routing und Grundlayout bleiben dabei in der Basis-UI.
139+
Die Erweiterung liefert nur ihren eigenen Inhaltsbereich.
140+
141+
Ziel ist ein einheitliches Erscheinungsbild ohne harte Runtime-Kopplung an das Basis-Frontend.
142+
Für UI-Erweiterungen gilt folgender Minimalvertrag:
143+
144+
- Die Erweiterung verwendet dieselbe visuelle Sprache: Farben, Abstände, Status-Pills, Tabellenstil und allgemeine Layout-Konventionen
145+
- Die Erweiterung kann intern dasselbe Framework wie die Basis-UI verwenden, zum Beispiel Preact
146+
- Die Erweiterung hängt aber nicht von der konkret im Basis-Image eingebauten Framework-Version ab
147+
- Die Erweiterung liefert ein eigenes Bundle aus und erwartet nicht, dass Host-Komponenten oder die Host-Runtime direkt importierbar sind
148+
- Geteilt wird ein kleiner UI-Vertrag, nicht die komplette interne Frontend-Struktur
149+
150+
Minimaler technischer Vertrag:
151+
152+
- Die Basis-UI besitzt Navigation, Hash-Routing, Kopfbereich und allgemeines Seitenlayout
153+
- Eine Erweiterung wird nicht per Verzeichnis-Scan gefunden, sondern über eine Registry-Datei angemeldet
154+
- Das Bundle der Erweiterung wird im abgeleiteten Image nach `/usr/local/share/freifunk/ui-extensions/APP/` kopiert
155+
- Zur Laufzeit wird es nach `/run/freifunk/www/ui/extensions/APP/` veröffentlicht
156+
- Die Registry-Datei `/ui/extensions/index.json` wird von der Plattform erzeugt; einzelne Erweiterungen schreiben diese Datei nicht direkt
157+
- Wenn mehrere Erweiterungen vorhanden sind, führt die Plattform deren Einträge zu einer gemeinsamen Registry zusammen
158+
- Die Basis-UI lädt `/ui/extensions/index.json`, baut daraus die Menüeinträge und lädt das aktive Bundle dynamisch
159+
- Erweiterungen deklarieren die von ihnen benötigten JSON-Endpunkte im jeweiligen Registry-Eintrag, damit die Basis-UI diese in ihren gemeinsamen Refresh-Zyklus aufnehmen kann
160+
- Eine Erweiterung liefert mindestens Menü-Key, Label, Reihenfolge und einen Renderer für den Content-Bereich
161+
162+
Beispiel für die Registry-Datei:
163+
164+
```json
165+
{
166+
"extensions": [
167+
{
168+
"id": "metadata",
169+
"label": "Metadaten",
170+
"order": 100,
171+
"hash": "metadata",
172+
"entry": "/ui/extensions/metadata/index.js",
173+
"endpoints": [
174+
"/metadata.json"
175+
]
176+
}
177+
]
178+
}
179+
```
180+
181+
Minimaler Renderer-Vertrag:
182+
183+
```javascript
184+
export function render(container, context) {
185+
container.textContent = 'Metadata view';
186+
}
187+
188+
export function dispose(container) {
189+
container.textContent = '';
190+
}
191+
```
192+
193+
Dabei gilt:
194+
195+
- `hash` bestimmt den View-Key in der SPA, zum Beispiel `#metadata`
196+
- `entry` verweist auf das gebaute Bundle der Erweiterung
197+
- `render()` rendert nur den Inhaltsbereich, nicht die gesamte Seite
198+
- Die Basis-UI lädt Core- und Extension-Daten in einem gemeinsamen Refresh-Zyklus, typischerweise alle 30 Sekunden
199+
- Erweiterungen starten dafür standardmäßig keine eigenen Polling-Timer
200+
- `context` enthält nur kleine stabile Host-Helfer und den aktuellen Datenstand der Erweiterung, zum Beispiel `data`, `error`, `refreshNow()`, `fetchJson(url)`, `fetchText(url)`, `safe(value)` und einfache Formatierungsfunktionen
201+
- Alles außerhalb dieses kleinen `context`-Vertrags gilt als intern und wird von Erweiterungen nicht direkt verwendet
202+
203+
Bewusst nicht Teil des Vertrags sind:
204+
205+
- direkte Imports interner Komponenten aus der Basis-SPA
206+
- Abhängigkeit von einer exakt gleichen Host-Framework-Version
207+
- eigene Topbar oder Sidebar innerhalb der Erweiterung
208+
- eine separate, vollständig unabhängige Web-App für kleine Zusatzfunktionen
209+
210+
Damit bleibt die UI konsistent, und Updates des Basis-Images können erfolgen, ohne dass jede Erweiterung an interne Frontend-Details gekoppelt ist.
211+
96212
### Installation
97213

98214
Die Anwendung wird im Dockerfile in das Image installiert.

scripts/dev-sync-container.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ docker-compose exec -T dockernode sh -lc 'mkdir -p /run/freifunk/www/licenses &&
6868
if [ "$mode" = "full" ]; then
6969
docker-compose exec -T dockernode sh -lc 'nginx -t && sv restart registrar && sv restart sysinfo && sv restart wireguard && sv restart fastd && sv restart bmxd && sv restart mesh-status && sv restart nginx && sleep 1 && sv status registrar && sv status sysinfo && sv status wireguard && sv status fastd && sv status bmxd && sv status mesh-status && sv status nginx'
7070
else
71-
docker-compose exec -T dockernode sh -lc 'nginx -t >/dev/null && sv status nginx'
71+
docker-compose exec -T dockernode sh -lc 'nginx -t >/dev/null && sv restart nginx && sleep 1 && sv status nginx'
7272
fi
7373

7474
echo "sync complete ($mode)"

0 commit comments

Comments
 (0)