dolibarr.netdiag/ChangeLog.md
Eduard Wisch 90cbee6e81 Geraete-Monitor: eigener PDF-Zweig statt einer Sammelzelle
Die Dauerueberwachung lief im generischen else-Zweig und wurde von
netdiagPdfFlattenResult() zu EINER |-getrennten Zeile zusammengeschoben.
Bei zehn ueberwachten Geraeten standen damit die zehn Geraete-Zeilen und
saemtliche Einzelausfaelle in einer einzigen Tabellenzelle - vollstaendig,
aber beim Kunden nicht lesbar. Dauertest und WLAN-Kanal hatten laengst je
einen Tabellen-Renderer, der Monitor nicht.

netdiagPdfMonitor() gibt jetzt aus: Kennzahlenzeile, Tabelle "Ueberwachte
Geraete", Tabelle "Einzelne Ausfaelle" mit Uhrzeiten, Hinweis zum
Messverfahren am Schluss. Tabellenkoepfe wiederholen sich nach einem
Seitenumbruch.

Die Falle dabei, gegen die im Code ein Kommentar steht: beim Dauertest
fuehrt ein gesetztes 'hinweis' zum sofortigen Abbruch der Ausgabe. Der
Monitor legt aber IMMER einen Hinweis an (das Messverfahren gehoert zur
Aussage dazu) - derselbe Aufbau haette hier bei jedem Lauf alle Zahlen
verschluckt. Nur 'fehler' bricht ab.

Obergrenze 200 Ausfallzeilen gegen ein im Sekundentakt flappendes Geraet.
Gekuerzt wird sichtbar ("... und N weitere Ausfaelle") - stilles Kuerzen
waere bei einem Abnahmebeleg eine Falschaussage.

Ausserdem: 'ausfallzeitSek' heisst jetzt "Ausfallzeit (schlechtestes
Geraet)". Der Wert ist der des schlechtesten Geraets, nicht die Summe -
bei zehn Geraeten las sich "Ausfallzeit: 8 min" wie eine Gesamtaussage.

Geprueft gegen die lokale Testinstanz mit vier konstruierten Faellen
(10 Geraete mit Aussetzern, ein durchgehend totes Geraet, 250 Ausfaelle
ueber sechs Seiten, abgerissener Lauf) und gegen die echte Monitor-
Messung aus ND2026-0016. PDF gerendert und angesehen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 22:22:02 +02:00

134 lines
7.4 KiB
Markdown

# Changelog NetDiag
## 1.1.2 — 2026-08-16
### Geräte-Monitor bekommt einen eigenen PDF-Zweig
Die Dauerüberwachung lief bisher in den generischen `else`-Zweig von
`netdiag_pdf.lib.php` und wurde von `netdiagPdfFlattenResult()` zu **einer**
`|`-getrennten Zeile zusammengeschoben. Bei zehn überwachten Geräten standen
damit die zehn Geräte-Zeilen UND sämtliche Einzelausfälle in einer einzigen
Tabellenzelle — vollständig, aber beim Kunden nicht lesbar. Dauertest und
WLAN-Kanal hatten längst je einen Tabellen-Renderer, der Monitor nicht.
- **`netdiagPdfMonitor()`**: Kennzahlenzeile, Tabelle „Überwachte Geräte",
Tabelle „Einzelne Ausfälle" mit Uhrzeiten, Hinweis zum Messverfahren am Ende.
- **Die Falle dabei**, gegen die es einen Kommentar im Code gibt: beim Dauertest
führt ein gesetztes `hinweis` zum sofortigen Abbruch der Ausgabe. Der Monitor
legt aber IMMER einen Hinweis an (das Messverfahren gehört zur Aussage dazu) —
derselbe Aufbau hätte hier bei **jedem** Lauf alle Zahlen verschluckt. Nur
`fehler` bricht ab, der Hinweis steht am Schluss.
- Tabellenköpfe wiederholen sich nach einem Seitenumbruch („— Fortsetzung"),
wie in der Geräteliste.
- Obergrenze 200 Ausfallzeilen gegen den pathologischen Fall (ein im
Sekundentakt flappendes Gerät erzeugt über einen Arbeitstag vierstellig viele
Segmente). Gekürzt wird **sichtbar**: „… und N weitere Ausfälle" steht im
Dokument, sonst läse sich das Protokoll als vollständig.
- **`ausfallzeitSek` heißt jetzt „Ausfallzeit (schlechtestes Gerät)"** — der
Wert ist der des schlechtesten Geräts, nicht die Summe über alle. Bei zehn
überwachten Geräten las sich „Ausfallzeit: 8 min" wie eine Gesamtaussage.
Gleiche Korrektur in `app/src/lib/messfelder.ts`, damit App und PDF dasselbe
Feld gleich benennen.
Geprüft gegen die lokale Testinstanz mit vier konstruierten Fällen (10 Geräte
mit Aussetzern, ein durchgehend totes Gerät, 250 Einzelausfälle über sechs
Seiten, abgerissener Lauf) und gegen die echte Monitor-Messung aus ND2026-0016.
## 1.1.1 — 2026-08-16
### Vier stille Fehler im Kundendokument und im Sync
Alle vier waren bereits ausgeliefert und fielen nicht auf, weil das PDF ja
weiterhin erzeugt wurde — nur ohne die betroffene Zeile. Gefunden durch einen
systematischen Abgleich der Whitelist gegen den erzeugenden App-Code.
- **`'typ'` war ein toter Schlüssel.** Kein Werkzeug erzeugt ihn; die App reicht
ein `LinkInfo`-Objekt durch, dort heißt das Feld `'type'`. Folge: im
Abnahmebeleg für eine Netzwerkdose fehlte die Angabe, **ob per LAN oder WLAN
gemessen wurde**. Zusätzlich `'rssi'` aufgenommen — bei einer WLAN-Dose ist
der Empfangspegel der eigentliche Messwert und wurde bisher verworfen.
Die Rohwerte sind englisch; `netdiagKundentext()` bildet sie jetzt ab
(`ethernet` → „LAN (Kabel)"), sonst stünde „Anschlussart: ethernet" im
Abnahmeprotokoll eines deutschen Handwerksbetriebs.
- **Leeres Array druckte „Offene Ports: " ohne Wert.** Die Leerprüfung ist ein
`elseif` hinter dem Array-Zweig und wurde nie erreicht. Betraf ausgerechnet
die *guten* Ergebnisse: Portscan ohne offenen Port, IP-Konflikt ohne Konflikt,
IP-Scan ohne Veränderung. Jetzt „keine" — Weglassen wäre schlechter, weil der
Kunde sonst nicht unterscheiden kann, ob nichts gefunden oder nichts geprüft
wurde.
- **`'error'` fehlte in der Whitelist.** Der IP-Konflikt gibt im Abbruchfall
`{ error: … }` mit roter Ampel zurück — im Kundendokument stand eine rote
Ampel ohne einen Buchstaben Erklärung. Die App vereinheitlicht künftig auf
`'fehler'`, aber Altdaten lassen sich nicht ändern.
- **`tool`/`category`/`label` wurden beim Sync nicht auf die Spaltenlänge
gekürzt** (varchar 64/32/255). Bei striktem SQL-Modus kippt **ein** zu langes
Label den gesamten Sync per `rollback()` — der Techniker steht beim Kunden mit
einem Protokoll da, das sich nicht abschließen lässt, weil eine Beschriftung
zu lang war. Die App setzt Labels durch Verketten zusammen, 255 Zeichen sind
erreichbar. `found_via` wurde 20 Zeilen darüber längst gekappt.
### Rechteprüfung im GET-Zweig der Protokoll-API
Bisher prüfte der GET-Zweig **gar nichts**: jeder angemeldete Benutzer konnte
mit einer geratenen ID ein fremdes Protokoll samt Geräteliste, IP-Adressen und
offenen Ports abrufen (der POST-Zweig verlangte seit jeher `write`). Das ist
beim Kunden erhobene Netzstruktur und gehört hinter ein Recht.
`write` wird bewusst mit akzeptiert: die Rechte sind in Dolibarr einzeln
vergebbar, und ein Techniker mit Schreib- ohne ausdrückliches Leserecht dürfte
nicht ausgesperrt werden — das wäre erst beim Kunden aufgefallen.
### Geräte-Monitor erscheint jetzt im Kundendokument
Die App liefert ab dieser Version eine Auswertung der Dauerüberwachung als
richtige Messung (Verfügbarkeit, Ausfallzeit, Aussetzer je Gerät). Die sechs
neuen Schlüssel sind in der Whitelist ergänzt — ohne sie stünde im PDF nur die
Ampel.
### Kleinigkeit
`netdiagDauerLesbar()`: „1 Tage 0 Std" → „1 Tag". `floor()` liefert in PHP einen
Float, und `1.0 === 1` ist false — die Einzahl hatte deshalb nie gegriffen.
## 1.1.0 — 2026-08-14
### Anmeldung läuft über das zentrale Auth-Modul (awlauth)
NetDiag war die letzte AWL-App mit eigenem Login: eigenes JWT, eigener Schlüssel
(`NETDIAG_API_JWT_SECRET`), eigene Gültigkeitsdauer, keine Möglichkeit, ein verlorenes
Handy gezielt abzumelden. Ab sofort stellt awlauth das Token aus — dasselbe Token,
derselbe Schlüssel und dieselbe Sitzungs-Registry wie bei den sechs Browser-Apps.
- `api/auth.php` prüft das Passwort über `awlauth_login()`. Damit greifen dort auch die
**Brute-Force-Bremse** (5 Fehlversuche je IP in 15 min, 10 je Benutzername in 30 min)
und die einheitliche, nicht-verräterische Fehlermeldung. Das Token kommt aus
`awlauth_issue_bearer()` und legt eine Sitzungszeile an — das Gerät erscheint als
„NetDiag-App · Android" in der awlauth-Geräteliste und ist dort einzeln abmeldbar.
- `netdiag_api_authenticate()` prüft zuerst das awlauth-Token. Ein **abgelaufenes oder
widerrufenes** Token führt sofort zu 401, auch wenn die Signatur noch stimmt.
- **Übergangsweise** wird ein bereits ausgestelltes altes netdiag-JWT weiterhin akzeptiert,
damit die Umstellung niemanden mitten im Einsatz aussperrt. Dieser Pfad entfällt, sobald
alle Geräte einmal neu angemeldet sind.
- Ohne aktives awlauth-Modul läuft NetDiag unverändert mit dem eigenen JWT weiter.
**Die Antwortform von `auth.php` ist bewusst unverändert** (`{token, expiresIn, user}`).
Bereits installierte APKs laufen nach einer **einmaligen Neuanmeldung** ohne Update weiter.
### CORS enger gefasst
`Access-Control-Allow-Origin: *` ist raus. Die API liefert Kundendaten aus; ein Wildcard
erlaubt jeder beliebigen Webseite, die Antwort auszulesen, sobald sie an ein Token kommt.
Erlaubt sind jetzt nur die App-Origins (`https://localhost`, `capacitor://localhost`,
`http://localhost`, `ionic://localhost`) und der Vite-Dev-Server; dazu `Vary: Origin`.
Anfragen ohne Origin-Header (nativer HTTP-Client, der APK-Downloader im Kotlin-Plugin,
curl) bekommen wie bisher eine Antwort — dort greift die Same-Origin-Policy ohnehin nicht.
## 1.0.0
- Erstversion
- Datenmodell: Protokoll, Gerät, Messung
- JSON-API: auth, customers, orders, protocols (Sync), pdf
- Tab "Netzwerk-Diagnose" an Kunde und Auftrag
- PDF-Protokoll-Generator + ECM-Ablage
- Backend: Protokoll-Liste und Detailansicht
- Admin: Token-Einstellung, QR-Code zum App-Download