Alle vier durch eine systematische Gegenpruefung des echten Codes gefunden,
nicht durch Symptome — sie waren bereits ausgeliefert und still.
1. Whitelist-Schluessel 'typ' existiert nicht. Die App liefert das
LinkInfo-Objekt durch, dort heisst das Feld 'type' (app/src/lib/types.ts).
Folge: im Kundendokument fehlte ausgerechnet beim IP-Test an der Dose die
Angabe, ob per LAN oder WLAN gemessen wurde - bei einem Abnahmebeleg die
halbe Aussage. Zusaetzlich 'rssi' aufgenommen: bei einer WLAN-Dose IST der
Empfangspegel der Messwert, er wurde bisher verworfen.
Die Rohwerte sind englisch; netdiagKundentext() bildet sie jetzt ab
('ethernet' -> 'LAN (Kabel)'), sonst stuende "Anschlussart: ethernet" im
Abnahmeprotokoll eines deutschen Handwerksbetriebs.
2. Leeres Array druckte "Offene Ports: " ohne Wert. Die Leerpruefung 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 Veraenderung. Jetzt "keine" - Weglassen waere
schlechter, weil der Kunde sonst nicht unterscheiden kann, ob nichts
gefunden oder nichts geprueft wurde.
3. 'error' fehlte in der Whitelist. Der IP-Konflikt gibt im Abbruchfall
{ error: ... } mit roter Ampel zurueck - im Kundendokument stand eine rote
Ampel ohne einen Buchstaben Erklaerung. Die App vereinheitlicht kuenftig
auf 'fehler', aber Altdaten lassen sich nicht aendern.
4. tool/category/label wurden beim Sync nicht auf die Spaltenlaenge gekuerzt
(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 abschliessen laesst, weil eine Beschriftung zu
lang war. Die App setzt Labels durch Verketten zusammen, 255 Zeichen sind
erreichbar. found_via wurde 20 Zeilen darueber laengst gekappt.
Ausserdem:
- GET-Zweig der API prueft jetzt eine Berechtigung. Bisher konnte jeder
angemeldete Benutzer mit einer geratenen ID ein fremdes Protokoll samt
Geraeteliste, IP-Adressen und offenen Ports abrufen. 'write' wird bewusst
mit akzeptiert: die Rechte sind in Dolibarr einzeln vergebbar, und ein
Techniker mit Schreib- ohne ausdruecklichem Leserecht duerfte nicht
ausgesperrt werden - das waere erst beim Kunden aufgefallen.
- netdiagDauerLesbar: "1 Tage 0 Std" -> "1 Tag". floor() liefert einen Float,
1.0 === 1 ist in PHP false.
Geprueft: php -l, und netdiagFeldFuerKunde() mit 13 echten Faellen gegen die
lokale Instanz durchgerechnet (LAN/WLAN/Mobilfunk, leere und gefuellte Arrays,
error/fehler, tote Schluessel, Interna).
Regression aus dem vorherigen Commit, gefunden bei der Bestandsprüfung von
Phase 6: netdiagKundenfelder() war gegen selbst erzeugte Testdaten gebaut,
nicht gegen die Namen, welche die App-Werkzeuge tatsächlich liefern.
Konkret liefert die App "ziel"/"zielErreicht" (Traceroute), "gegenstelle"/
"downloadMbps"/"uploadMbps" (Durchsatz), "scanned"/"open" (Portscan),
"geprueft"/"runden"/"konflikte" (IP-Konflikt), "port"/"linkSpeed"/
"eingangsFehler"/"ausgangsFehler" (SNMP) sowie das gespreadete LinkInfo beim
IP-Test. Keiner dieser Schlüssel stand in der Tabelle — und weil
netdiagFeldFuerKunde() unbekannte Schlüssel verwirft, wäre im Kundendokument
von diesen fünf Werkzeugen nur noch die Ampel übrig geblieben, ohne einen
einzigen Messwert. Das ist schlechter als der Zustand davor (rohe Schlüssel).
Alle real emittierten Schlüssel ergänzt und gegen die echten Ergebnis-
Strukturen geprüft, Beispiel Portscan:
"Gerät: 192.168.1.50 | Geprüfte Ports: 10 | Offene Ports: 80/http, 443/https"
Lehre für künftige Werkzeuge: die Tabelle ist eine Whitelist, ein neues
Werkzeug braucht dort einen Eintrag — sonst verschwinden seine Messwerte
stillschweigend aus dem Kundendokument.
- Werkzeug-Klarnamen statt interner IDs: "IP-Scanner — 46 Geräte im Netz …"
statt "[netzwerk] ipscan — …". Bewusst eine kurze Zuordnung in
netdiagToolName() statt eines zweiten Satzes Sprachschlüssel — die
kanonischen Namen stehen in der App, doppelte Pflege wäre eine Fehlerquelle.
Enthält auch dhcpcheck/wifiscan: in der App gibt es sie nicht mehr, in der
PRODUKTIONSDATENBANK stehen dazu aber noch Messungen (4 bzw. 2). Die
Gegenprüfung hielt den Punkt für gegenstandslos, hatte dabei aber nur die
Testdatenbank angesehen.
- Messparameter anzeigen (Prod-Messung #126): unter jeder Messung steht jetzt
"Ziel: 192.168.1.1 · Dauer (s): 300" in Karte und PDF. Vorher stand das
Ergebnis ohne Bezugspunkt da — man sah nicht, wogegen gemessen wurde.
- Listenseite: Ampel je Protokoll (schlechteste Einzelmessung) plus Anzahl,
dazu ein Filter "nur mit Befund". Status 3 "nicht messbar" geht bewusst
NICHT ins Maximum ein — er ist keine Aussage über das Kundennetz — sondern
wird separat als "n.m." ausgewiesen. Im Browser geprüft: der Filter liefert
ausschließlich Protokolle mit Warnung oder Fehler.
- N+1-Queries behoben: fetchAllByProtocol() las nur die rowids und setzte je
Zeile ein eigenes fetch() ab. Jetzt eine Abfrage mit setVarsFromFetchObj(),
zusätzlich mit Entity-Filter (fehlte bisher ganz). Nachgemessen über
SHOW SESSION STATUS: 46 Geräte + 7 Messungen brauchen statt 55 Abfragen
noch eine.
- Standort aus der Kundenadresse vorbelegen, wenn der Techniker nichts
eingetragen hat; eine vorhandene Angabe wird nie überschrieben. Über die
echte API geprüft.
Offen bleibt aus Phase 5 nur der Vergleich zweier Protokolle (Geräte-Diff
nach MAC) — eigenes Feature mit eigener Ansicht.
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>