Vorab: drei Punkte der Roadmap-Liste waren Fehlannahmen. Eine Analyse mit
anschließender Gegenprüfung (jeder Befund musste einen Widerlegungsversuch
überstehen) hat sie ausgeräumt, bevor Code geändert wurde:
- "ab Seite 2 alles nach rechts verschoben" existiert nicht. Nachgemessen am
Prod-PDF ND2026-0015 mit pdftotext -bbox: Seite 1 und Seite 2 beginnen beide
bei 16,0 mm. Die echten Umbruchfehler waren andere.
- measure_status validieren war seit Phase 1 erledigt.
- Werkzeug-IDs / Teilnetz-Gruppierung / TCPDF-Fußzeile: verworfen, die
vorgeschlagenen Änderungen hätten das PDF verschlechtert.
PDF (alle Punkte am mehrseitigen Dokument nachgeprüft):
- Tabellenkopf der Geräteliste wird auf Folgeseiten wiederholt. Vorher standen
ab Seite 2 unbeschriftete Spalten — bei leeren MAC/Hostname-Feldern vier
namenlose Spalten.
- Messungs-Titelzeile und Ergebnis werden zusammengehalten. Vorher blieb die
Überschrift samt Ampel am Seitenende allein zurück, darunter ein leerer,
unten offener Rahmen; in einem Testlauf über 61 Umbruchlagen 5-mal (~8 %).
- Spalte "Gerätetyp" hatte 15 mm, ließ aber 10 Zeichen zu — "Chromecast/TV"
lief bis 199,0 mm bei 195 mm Tabellenkante über den Rahmen in den Druckrand.
- Deutsche Bezeichnungen mit Einheiten statt roher JSON-Schlüssel: aus
"VerlustProzent: 0 | MinMs: 4.4 | UptimeSek: 8123456" wird "Paketverlust: 0 %
| Kürzeste Antwortzeit: 4.4 ms | Betriebszeit: 94 Tage 1 Std". Als Whitelist
(netdiagKundenfelder(), gemeinsam für Karte und PDF) — interne Felder wie
arpAvailable, mdnsOk, probed, answered fallen damit automatisch heraus.
- "ARP-Tabelle nicht lesbar (/proc/net/arp) — braucht Root" wird beim Drucken
zu einem kundentauglichen Satz. Altdaten stehen so in der DB, deshalb
Ersetzung beim Drucken statt nur in der App.
Gerätemerkmale (der eigentliche Roadmap-Punkt): Der Techniker sah in der App
"Drucker HP, Port 9100", im Kundenprotokoll stand nur die IP. Die Felder
fehlten dabei nicht in der Übertragung, sondern durchgängig — ein Fix allein
in der API wäre folgenlos geblieben, weil Dolibarrs setSaveQuery() nur
deklarierte $fields schreibt. Ergänzt über die ganze Kette:
sql/llx_netdiag_device.sql + neue Migration llx_netdiag_device_v2.sql
(ADD COLUMN IF NOT EXISTS, wiederholbar, läuft bei jedem Modul-Update),
NetDiagDevice::$fields + Properties, api/protocols.php POST und GET,
Kartenansicht und PDF.
Neu: netbios_name, mdns_name, mdns_services, custom_name, open_ports,
found_via, last_seen. Im PDF steht jetzt statt "192.168.178.20" die Zeile
"Brother HL-L2350DW · Brother · Drucker · 80,443,9100" — der Name kommt aus
mDNS, obwohl der Hostname leer ist.
Sprachschlüssel Vendor -> NetDiagVendor: Die Gegenprüfung hielt den Punkt für
falsch (Translate::load() ist first-wins, im CLI-Test kam "Hersteller"), im
Browser stand in der Kartenansicht aber "Lieferant" — im HTTP-Kontext lädt
Dolibarr vorher andere Sprachdateien als im CLI. Statt der Ursache nachzugehen
jetzt ein eigener, kollisionsfreier Schlüssel; im Browser gegengeprüft.
Nebenbei: doppeltes "OK OK" beim Status 0 im PDF.
Gegen das Test-Dolibarr geprüft: Sync über die echte API (Login, POST, GET),
Felder in der DB kontrolliert, Kartenansicht im Browser, mehrseitiges PDF
gerendert und angesehen, Migration zweimal ausgeführt (idempotent).
Die App kann WLAN-Netze zum Ansehen/Vorführen simulieren (Emulator und Geräte
ohne Empfang zeigen sonst nichts). Solche Momentaufnahmen tragen demodaten:true
im Ergebnis — Kartenansicht und PDF weisen das jetzt als eigene Zeile aus.
Im PDF besonders wichtig: das Dokument geht zum Kunden, dort darf eine
Vorführung nie wie eine echte Messung am Standort aussehen.
Die App legt eine WLAN-Momentaufnahme jetzt als Messung ab (tool='wifikanal'),
damit sie beim Kunden aufgenommen auch im Protokoll auf dem Server ankommt —
bisher lag sie nur lokal auf dem Handy.
- netdiagFormatWifiKanal(): Zusammenfassung (Anzahl Netze, eigenes Netz mit
Kanal/Pegel/Bewertung, störungsärmster 2,4-GHz-Kanal), Hinweisliste und
vollständige Netztabelle. Eigener Zweig wie beim Dauertest, weil die
generische Darstellung das Netz-Array per dol_trunc(...,200) mitten im Satz
abschneiden würde.
- netdiagPdfWifiKanal(): dieselbe Aufbereitung als PDF-Tabelle.
- Nebenbei: PDF zeigte bei Status 0 doppelt "OK OK" (Zeichen + Label).
Gegen das Test-Dolibarr geprüft: Kartenansicht rendert Zusammenfassung, beide
Warnhinweise und alle 12 Netze; PDF-Erzeugung ebenfalls verifiziert.
Der Dauertest liefert seit App-Phase 2 deutlich mehr Ergebnisfelder
(Zeitreihe in Minuten-Buckets, Ausfallsegmente, p95, laengster Ausfall).
netdiagFormatResult() (Web-Karte) und netdiagPdfFlattenResult() (PDF) wuerden
das zu einem einzigen Fliesstext zusammenkleben — die Web-Karte kuerzt jeden
Wert zusaetzlich per dol_trunc(...,200), eine Ausfallliste mit mehreren
Eintraegen waere also mitten im Satz abgeschnitten.
- netdiagFormatResult($json, $tool='') bekommt einen optionalen zweiten
Parameter; fuer $tool==='stresstest' greift netdiagFormatStressTest() statt
der generischen Kuerzung: Kennzahlenzeile (Ziel, Dauer, Takt, Proben,
Verlust, Oe/Min/Max/p95, laengster Ausfall) + eine echte <ul>-Ausfallliste,
ungekuerzt. netdiagprotocol_card.php uebergibt jetzt $m->tool.
- netdiagPdfStressTest() im PDF-Generator macht dasselbe mit TCPDF-Zellen:
Kennzahlenzeile + Ausfalltabelle als eigene Zeilen. Die Zeitreihe
(`verlauf`) wird bewusst NICHT gedruckt — als Diagramm in der App nuetzlich,
als Fliesstext auf Papier nicht.
- Beide Formatierer nutzen dieselben vorformatierten Ausfall-Zeilen
("HH:MM:SS – HH:MM:SS (Xs)"), die auch die App in ihrer Messungen-Liste
zeigt — eine Quelle der Wahrheit statt mehrfacher Formatierung.
Lokal verifiziert (Test-Dolibarr, Modul manuell deployt): Web-Karte zeigt
Kennzahlenzeile + Ausfaelle-Liste mit echten Uhrzeiten statt Rohschluessel,
PDF ebenso (2 Seiten, TCPDF-Ausfalltabelle je Lauf lesbar).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Die App kennt seit Phase 1 eine vierte Bewertung: der Test konnte gar nicht
durchgefuehrt werden (Switch antwortet nicht auf SNMP, kein Netz, Gegenstelle
fehlt). Ohne diesen Zustand musste sich ein fehlgeschlagener Test als ok, warn
oder fail ausgeben — in der Praxis meist als Gruen.
- protocols.php nimmt nur noch Bewertungen 0-3 an. Ein unbekannter Wert landete
bisher ungeprueft als Array-Index in Karte und PDF und lief dort ins Leere;
jetzt wird daraus eine Warnung, nie ein OK.
- Protokollkarte und PDF stellen Status 3 grau dar ("Nicht messbar") — weder
gruen (waere gelogen) noch rot (waere eine Aussage ueber das Kundennetz, die
die Messung nicht hergibt).
- Das PDF zeigt die Ampel zusaetzlich als Zeichen (OK / ! / X / ?), damit sie im
Schwarz-Weiss-Ausdruck beim Kunden lesbar bleibt.
- Sprachschluessel NetDiagMeasureUnmeasurable in de_DE und en_US.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
NetDiag war die letzte AWL-App mit eigenem Login: eigenes JWT, eigener Schluessel,
eigene Gueltigkeitsdauer, keine Moeglichkeit ein verlorenes Handy gezielt abzumelden.
- api/auth.php prueft das Passwort ueber 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-verraeterische Fehlermeldung. Das Token kommt aus
awlauth_issue_bearer() und legt eine Sitzungszeile an: das Geraet erscheint als
"NetDiag-App · Android" in der awlauth-Geraeteliste und ist dort einzeln abmeldbar.
- netdiag_api_authenticate() prueft zuerst das awlauth-Token; ein widerrufenes Token
fuehrt sofort zu 401, auch wenn die Signatur noch stimmt.
- Uebergangsweise gilt ein bereits ausgestelltes altes netdiag-JWT weiter, damit die
Umstellung niemanden mitten im Einsatz aussperrt. Faellt weg, sobald alle Geraete
einmal neu angemeldet sind.
- Ohne aktives awlauth laeuft das Modul unveraendert mit dem eigenen JWT weiter.
Die Antwortform von auth.php bleibt bewusst {token, expiresIn, user}: bereits
installierte APKs laufen nach einer einmaligen Neuanmeldung ohne Update weiter.
Haette man sie geaendert, waeren alle Geraete ausgesperrt — und die neue APK gibt es
nur ueber update.php, das Anmeldung verlangt.
CORS: Wildcard-Origin raus. Die API liefert Kundendaten aus; ein * erlaubt jeder
Webseite die Antwort auszulesen, sobald sie an ein Token kommt. Erlaubt sind jetzt nur
die App-Origins und der Vite-Dev-Server, dazu Vary: Origin. Anfragen ohne Origin
(nativer Client, APK-Downloader im Plugin, curl) sind unveraendert.
Lokal gegen das Test-Dolibarr geprueft: Login liefert awlauth-Token, Sitzungszeile
entsteht, orders.php mit Bearer = 200, falsches Passwort = 401, kein/manipuliertes
Token = 401, 4. Fehlversuch = 429 mit Wartezeit, Alt-Token = 200 (Fallback),
fremder Origin bekommt keinen CORS-Header. Anschliessend im Emulator durchgespielt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Spalte c.tms in SELECT aufgenommen (Dolibarr-Änderungszeitstempel)
- ORDER BY c.tms DESC statt date_commande DESC — zuletzt bearbeitete
Aufträge stehen oben, passend zur App-Anzeige "bearb. <Datum>"
- tms-Feld in der API-Antwort ergänzt
- README: Hinweis auf Deploy-Pipeline ergänzt
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Die App kann die private Forgejo-Registry nicht erreichen (403/CORS) —
darum sagte der In-App-Updater faelschlich sofort 'aktuell'.
update.php prueft die Registry serverseitig (Token aus NETDIAG_FORGEJO_TOKEN)
und liefert der App:
GET update.php -> { version }
GET update.php?download=1 -> die APK (zum Installieren)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DER eigentliche Fehler: Die App schickt dateDiag/dateMeasure als
JavaScript-Millisekunden (Date.now(), 13-stellig). Dolibarrs idate()
erwartet Unix-Sekunden -> MySQL: "Incorrect datetime value: Bad value
1779211311036 for date" -> createCommon scheitert -> HTTP 500.
Fix: netdiag_api_timestamp() rechnet ms-Zeitstempel (> 1e11) auf Sekunden
um. protocols.php nutzt sie fuer date_diag und date_measure.
Serverseitig bewusst — so synchronisieren auch bereits installierte
App-Versionen ohne APK-Update korrekt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der Endpoint gab bei Speicherfehlern nur $obj->error (Singular) aus — das
ist leer, weil CommonObject::createCommon den Grund nach $obj->errors[]
(Array) schreibt. Ergebnis: "Protokoll speichern fehlgeschlagen: " ohne
Grund. Jetzt errorsToString() — liefert error + errors[] zusammen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nachtrag zum tms-Fix: explicit_defaults_for_timestamp=1 auf der Prod-DB —
ein INSERT mit tms=NULL in die NOT-NULL-Spalte schlaegt fehl. createCommon
fuegt tms aber als NULL ein, wenn die Property leer ist.
Loesung: protocols.php setzt tms = dol_now() vor jedem create/update von
Protokoll, Geraet und Messung. Damit landet ein gueltiger Zeitstempel im
INSERT, kein NULL.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
protocols.php (POST) gab bei JEDEM Sync HTTP 500 zurueck — 0 Protokolle
wurden je gespeichert.
Ursache: protocol/device/measurement-Klassen deklarierten das tms-Feld im
$fields-Array als 'notnull' => 1. Dolibarrs createCommon() bricht ab (-1),
wenn ein notnull-Feld den Wert NULL hat und kein 'default' gesetzt ist.
tms wird von der App nie gesetzt (die DB fuellt es per CURRENT_TIMESTAMP),
also schlug jedes Insert fehl, bevor es ueberhaupt an die DB ging.
Fix: tms auf 'notnull' => 0 — so wie es der Dolibarr-Modul-Builder auch
generiert. Die DB-Spalte bleibt NOT NULL DEFAULT CURRENT_TIMESTAMP.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Die App erfasst Fehler/Meldungen und schiebt sie per POST hierher; sie
landen in llx_netdiag_applog und sind serverseitig auswertbar — ohne Kabel.
- api/applog.php: JWT-Auth (wie alle Endpoints), Batch-Insert, legt die
Tabelle bei Bedarf an (Modul kann schon vorher installiert gewesen sein)
- sql/llx_netdiag_applog.sql + .key.sql: Tabelle fuer saubere Neuinstallation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Die App zeigt Aufträge jetzt nach Kunde + Ort statt nach Nummer —
dafür liefert orders.php in der Liste zusätzlich s.address und
c.note_public.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
netdiag_api_bootstrap() includete master.inc.php innerhalb der Funktion.
Dadurch landeten $conf/$db/$langs/$user im Funktions-Scope und waren
nach return weg — der erste DB-Zugriff (checkLoginPassEntity -> global
$db) lief gegen null: Call to a member function query() on null.
master.inc.php wird jetzt im File-Scope der Lib geladen, die Funktion
macht nur noch CORS + OPTIONS-Preflight.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Netzwerk-Diagnose-Modul mit JSON-API für die NetDiag-App:
- 3 Tabellen (protocol/device/measurement), generisches JSON-result
- JSON-API: auth, customers, orders, protocols (idempotenter Sync), pdf
- JWT-Auth (HS256), CORS für die Capacitor-App
- Tabs an Thirdparty + Auftrag, Protokoll-Card, PDF-Generator
- QR-Code zum App-Download in der Modul-Konfiguration
- de_DE + en_US, Rechtesystem netdiag->protocol read/write/delete
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>