Kompletter Umbau (ROADMAP_UMSETZUNG.md Phase 2). Die alte Fassung hatte vier
Probleme, die den Test praktisch wertlos machten: kein festes Intervall (ein
"Messpunkt" dauerte so lange wie das Ziel zufaellig antwortete — Prod #126:
38 Punkte in 300 s statt der ueblichen ~250-300), keine Zeitreihe (nur
Summen), kein Foreground-Service trotz gegenteiliger Doku-Behauptung, kein
Abbruchpfad. Vor allem aber: waehrend der 1-60 Minuten sah man nichts ausser
"Messung laeuft ...".
Nativ (NetDiagScannerPlugin.kt, MonitorService.kt)
StressRun komplett neu: feste Taktung per naechstem Fixpunkt (nextTick +=
intervalSec*1000, kein Zeitverzug ueber eine Stunde), Rohzaehler
(sentTotal/receivedTotal/rttSum/Min/Max) + echte Zeitreihe (Liste aus
{ts,rtt}), Ausfallsegmente werden waehrend des Laufs erkannt und
geschlossen. Laeuft im MonitorService (Foreground, wie der Geraete-Monitor)
— dafuer wurde der Dienst auf eine Job-Registry umgestellt (jobId -> Text),
damit ein gleichzeitig laufender Geraete-Monitor und Dauertest sich beim
Beenden nicht gegenseitig die Benachrichtigung wegnehmen.
Events stressSample (live, pro Probe) und stressFinished (einmalig, Grund
completed/stopped) + getStressStatus zur Wiederaufnahme nach Seitenwechsel.
WICHTIG geloest: laeuft der Test natuerlich aus, waehrend niemand zuhoert
(App im Hintergrund, andere Seite offen), bleibt der Lauf-Eintrag in
stressRuns bestehen (wie monitorRuns) statt sofort geloescht zu werden —
sonst waere das Ergebnis unwiederbringlich weg. Neuer Aufraeum-Call
dismissStressRun.
Nebenbei: der geteilte io-Scope aller Plugin-Methoden lief bisher auf einem
gewoehnlichen Job — eine unbehandelte Exception in EINER Coroutine (z.B.
IP-Scan mit kaputtem Host) haette strukturell alle Geschwister-Coroutinen
mitgerissen, auch einen parallel laufenden Monitor/Dauertest. Jetzt
SupervisorJob + CoroutineExceptionHandler.
Kritischer Bug gefunden UND gefixt (im Emulator, echter Netzausfall-Test):
`.put("rtt", rtt as Any?)` mit Kotlin null wird von org.json.JSONObject
behandelt wie `remove(key)` — der Schluessel verschwindet komplett statt
als JSON null anzukommen. Die App bekam damit `undefined` statt `null` fuer
verlorene Proben; `!== null`-Filter hielten das faelschlich fuer "erhalten".
Ergebnis: ein per Flugmodus ausgeloester echter Ausfall zeigte live "0 %
Verlust" und "NaN ms" bei Min/Max/Oe. Betraf NUR die Live-Anzeige — die
gespeicherte Messung war unabhaengig davon korrekt, weil buildStressResult()
ausschliesslich aus dem nativen Kotlin-Zustand rechnet, nie ueber die
Pro-Probe-JSON-Bruecke. Fix: ueberall explizit JSONObject.NULL statt bloss
`null` (betraf auch die Traceroute-Korrektur aus Phase 1). Zusaetzlich
TS-seitig als zweite Verteidigungslinie `!= null` statt `!== null` (deckt
sowohl null als auch undefined ab).
Neu: gemeinsame Bewertung (tools/rating.ts)
ping.ts und der alte Dauertest bewerteten denselben Netzzustand
unterschiedlich (ping.ts: avgMs>100 -> Rot; Dauertest: nur lossPct/maxMs).
rateLatency() ist jetzt die einzige Stelle: Verlust, Jitter, Latenz nach
Zielklasse (LAN/WLAN/WAN) UND eine eigene Regel "jeder Ausfall > 5 s = Rot"
unabhaengig vom Gesamt-Verlustprozentsatz. ping.ts umgestellt, unveraendertes
Verhalten fuer die bisherigen Schwellen (inkl. Jitter>10ms, das beim
Portieren fast uebersehen wurde).
Neue Route statt Dialog (routes/protokoll/[id]/stresstest/)
Wie IP-Test/WLAN/Monitor eine eigene Seite statt des blockierenden
Werkzeug-Dialogs. Einrichtung (Ziel leer=Gateway, Dauer, Takt) mit
Vorlaufprobe wie zuvor in stresstest.ts. Laufend: grosse Live-Zahl mit
Ampelfarbe (rateLatency), Fortschrittsbalken mit Restzeit, Streifendiagramm
(StressChart.svelte, live rollierendes 120s-Fenster), Zaehler
gesendet/verloren/Ø/Min/Max, offener-Ausfall-Banner, Ereignisliste mit
Uhrzeit ("Ausfall — wieder da nach 16 s"), Abbrechen. Abgeschlossene Laeufe
darunter aufklappbar mit demselben Diagramm (kompletter Verlauf statt
Fenster). stresstest.ts (Tool-Registry-Eintrag) ist jetzt nur noch
Namens-Lookup fuer die Messungen-Liste, run() wirft bewusst einen Fehler,
falls doch noch etwas den alten Weg aufruft.
Sicherheitsnetz gegen verpasste Events (lib/stresstest.ts)
Ein natuerlich beendeter Test, waehrend niemand auf der Stresstest-Seite
ist, verpasst dort das stressFinished-Event. Die Hauptprotokollseite prueft
deshalb beim Oeffnen zusaetzlich per resumeFinishedStressSessions() nach
(praktisch immer besucht, bevor ein Protokoll abgeschlossen wird) und
traegt das Ergebnis nach. Bekannter Rest-Fall (dokumentiert): verlaesst der
Techniker das Protokoll komplett ohne eine der beiden Seiten noch einmal zu
oeffnen, bleibt der fertige Lauf bis zum naechsten Besuch liegen.
Ergebnis-Speicherung: Zeitreihe in 1-Minuten-Buckets + Ausfallsegmente +
laengster Ausfall als Measurement (tool=stresstest, sync-faehig) — Ausfaelle
werden beim Abschliessen mit dem nativen (autoritativen) Ergebnis abgeglichen,
nicht nur mit der lokal live mitgefuehrten Kopie.
Getestet im Emulator (Pixel 6 / Android 14):
- 1-Minuten-Lauf komplett bei ausgeschaltetem Display durchgelaufen
(Foreground-Service haelt den Prozess), automatischer Abschluss + Toast
- Echter Netzausfall per Flugmodus (15s) waehrend laufendem 5-Minuten-Test:
korrekt 23,2%/16,7% Verlust, Min/Max ohne NaN, Ereignisliste mit exakter
Uhrzeit und Dauer — nach dem NULL-Fix verifiziert
- Abbrechen-Pfad: sofortiger Stopp, korrektes Teil-Ergebnis
- Wiederaufnahme: laufender Test nach APK-Neuinstallation (Prozess-Kill)
korrekt als beendet erkannt
- gradle compileDebugKotlin ok, svelte-check 0 Fehler
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Vier Werkzeuge haben nachweislich Unsinn ins Kundenprotokoll geschrieben. Belege
sind echte Prod-Messungen; jeder Fix wurde im Emulator gegen den passenden
Fehlerfall nachgestellt.
SNMP (Snmp.kt, snmp.ts)
Snmp.get lieferte bei JEDEM Problem null -> "-" -> parseInt("-")||0 -> 0.
Ein Switch, der gar nicht antwortet, stand damit als "0 Fehler" in Gruen im
Kunden-PDF (Prod #95/#96). Neu: Ergebnistyp Value/Timeout/Failure statt
String?, Request-ID- und error-status-Pruefung, v2c-Ausnahmen (0x80/0x81/0x82)
erkannt, ein Wiederholversuch bei Timeout. Das Werkzeug meldet jetzt
"nicht messbar" statt Zahlen zu erfinden.
Test: SNMP gegen einen Host ohne Agent -> status 3 "keine SNMP-Antwort".
Traceroute (pingWithTtl, traceroute.ts)
Der Fallback-Regex \((\d+\.\d+\.\d+\.\d+)\) traf IMMER zuerst die ping-Kopfzeile
"PING 8.8.8.8 (8.8.8.8) ...". Ein stummer Hop wurde dadurch als Ziel ausgegeben
und der Trace beendet — daher "2 Hops bis 8.8.8.8, beide 0 ms" (Prod #13).
Zwischen-Hops liefern zudem nie ein time=, die Zeit war strukturell 0.
Neu: Kopfzeile verworfen, zeilenweise geparst, Reverse-DNS-Antworten erkannt,
RTT selbst per nanoTime gestoppt, 3 Proben je TTL, waitFor(3s)+destroyForcibly,
"keine Antwort" ist null und nicht 0, Bewertung haengt an "Ziel erreicht".
Test: 8.8.8.8 -> 1 Hop mit echten 13,3 ms (statt 0 ms);
192.0.2.1 -> status 3, alle Hops "* (keine Antwort)" statt gruen.
Ping (measurePing, ping.ts)
Kein WifiLock, keine Aufwaermproben: zwischen den Proben schlief das Funkmodul
ein, die Aufwachzeit landete in der Messung — 296 ms zum LAN-Gateway bei 0 %
Verlust und rotem Status (Prod #4). WAKE_LOCK fehlte im Manifest, weshalb
WifiLock.acquire() eine SecurityException warf, die ein leerer catch schluckte.
Neu: WAKE_LOCK im Manifest, WifiLock (LOW_LATENCY) + PARTIAL_WAKE_LOCK um jede
Messung, 2 verworfene Aufwaermproben, Median und Messaufschlag (avg-min)
zusaetzlich ausgewiesen, Verlust als Kommazahl statt Ganzzahl-Division,
Bewertung auf den Median statt den Mittelwert, "keine Antwort" != "0 ms".
Test: 20 Proben -> Median 0,3 ms, Messaufschlag 0,1 ms, Verfahren dokumentiert.
Durchsatz (measureThroughput, iperf.ts)
Kein soTimeout: nahm die Gegenstelle an, ohne zu senden, blockierte read()
unbegrenzt und das Werkzeug-Fenster hing dauerhaft auf "Messung laeuft ...".
Neu: soTimeout 4 s je Phase, harter withTimeout-Guard, Ist-Zeit statt
angenommener halber Dauer, measured-Flag — ohne uebertragene Bytes gibt es
kein rotes "0 Mbit/s" mehr, sondern "nicht messbar".
Test: Gegenstelle nimmt an und schweigt -> Ergebnis nach 30 s statt Dauerhaenger.
Netzerkennung (firstLocalIpv4, getLocalSubnet, hostsInSubnet)
firstLocalIpv4() gab bei fehlendem Netz hart "192.168.1.1" zurueck; zusammen mit
dem Praefix-Fallback /24 entstand das Phantom-Netz 192.168.1.0/24, das auch noch
ins Protokoll geschrieben wurde. Der Fehlerzweig in ipscan.ts konnte deshalb nie
greifen. Neu: "" statt Phantom-IP, Link-Local uebersprungen, getLocalSubnet
meldet einen Fehler, DHCP-Fallback nur bei WLAN-Transport (sonst wurde bei
gestecktem USB-RJ45 die Maske aus dem WLAN genommen), unlesbares CIDR-Praefix
wird abgelehnt statt still durch /24 ersetzt.
Test: Flugmodus -> "Kein Netzbereich — WLAN/LAN nicht aktiv?" statt Phantom-Netz.
Stresstest-Ziel (stresstest.ts)
Vorgabe 192.168.1.1 im Kundennetz 192.168.178.0/24 -> 5 Minuten ins Leere
gemessen, Ergebnis "100 % Verlust" rot im Protokoll (Prod #126). Neu: kein
fester Vorgabewert, leer = Gateway des aktiven Adapters, und eine Vorlaufprobe
bricht ab, bevor minutenlang gegen ein totes Ziel gemessen wird.
Test: Ziel 192.168.1.1 -> nach 7 s "antwortet nicht — Test nicht gestartet"
(vorher: 60 s Messung mit rotem 100-%-Ergebnis).
Neue Bewertung "nicht messbar" (MeasureStatus 3)
Bisher gab es nur ok/warn/fail. Ein Test, der gar nicht durchgefuehrt werden
konnte, musste sich als eines davon ausgeben — meist als Gruen. Status 3 wird
neutral grau dargestellt (Messkarte und Geraetekarte) und ist keine Aussage
ueber das Kundennetz.
Geprueft: gradle compileDebugKotlin ok, svelte-check 0 Fehler (2 vorbestehende
Warnungen), alle sieben Faelle im Emulator (Pixel 6 / Android 14) durchgespielt
und die Ergebnisse in der Datenbank gegengelesen statt am Bildschirm geraten.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Aufraeumen vor den eigentlichen Korrekturen — ohne Verhaltensaenderung am Code.
Doppelte Plugin-Quelle:
Die drei Kotlin-Dateien lagen byte-identisch in native-plugin/ UND in
android/app/src/main/java/de/data_it_solution/netdiag/. Gebaut wurde immer nur die
Kopie unter android/ — eine Aenderung an der falschen Datei sah deshalb aus wie
"der Fix wirkt nicht". native-plugin/ enthaelt jetzt nur noch die Anleitung, die
auf den echten Ort verweist.
Doku sagte drei Dinge, die nicht stimmen:
- stresstest.ts behauptete "laeuft nativ als Foreground-Service". Tut es nicht —
im Plugin steht an der Stelle nur ein Kommentar, dass man einen ergaenzen
sollte. Bei ausgeschaltetem Display friert Android den Lauf ein.
- iperf.ts/scanner.ts/README nannten den Durchsatztest "iperf-kompatibel". Er
spricht kein iperf3-Protokoll (kein Cookie, kein Parameterblock) und scheitert
gegen einen echten iperf3-Server.
- ipscan.ts beschrieb sich als "ARP + Ping + Namen". Gefunden wird nur, wer auf
Ping antwortet oder sich per mDNS meldet; die ARP-Tabelle wird lediglich
nachgeschlagen und ist ab Android 10 meist gar nicht lesbar.
Statt der falschen Zusagen stehen dort jetzt die tatsaechlichen Grenzen plus
Verweis auf die Phase, die sie behebt.
README: DHCP-Check und WLAN-Scan als Werkzeuge entfernt (sind nicht mehr in
tools/index.ts registriert, tauchen aber in alten Protokollen noch auf).
Geprueft: vite build ok, svelte-check 0 Fehler (2 vorbestehende Warnungen).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
IP-Test: USB-RJ45-Adapter in Netzwerkdose stecken und sofort IP-Adresse,
DHCP-Server, Gateway und Link-Geschwindigkeit (10/100/1000 Mbit) ablesen.
Auto-Refresh alle 2 s, Speichern mit optionalem Raum/Dose-Name ins Protokoll.
WLAN-Empfangstracker: Netz auswählen und beim Durchgehen live RSSI verfolgen.
Hybrid-Modus: 500 ms Polling bei verbundenem Netz (kein Scan-Throttling),
~30 s Scan-Sweep bei Fremd-BSSID. Sessions mit Samples, Min/Max/Avg und
Sparkline-Verlauf werden im Protokoll gespeichert.
Ersetzt DHCP-Info-Tool und WLAN-Scan-Tool (eigene Routen /iptest/ + /wifi/).
Kotlin-Plugin: linkInfo(), startWifiScan(), startWifiTrack/stop/status().
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- arpConflictScan (Plugin): pingt das Subnetz über mehrere Runden und liest
je Runde die ARP-Tabelle; mehrere MACs pro IP = Konflikt. Kein Root nötig
- Erkennt den Fall, dass /proc/net/arp nicht lesbar ist (Android-Limit) und
meldet das ehrlich, statt fälschlich Entwarnung zu geben
- ipconflict.ts: neues Protokoll-Tool, in der Tool-Registry eingetragen —
listet betroffene IPs samt der konkurrierenden MAC-Adressen
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- NetBIOS-Namensabfrage (UDP 137) je Host integriert
- mdnsScan: neue Plugin-Methode, mDNS/Bonjour via NsdManager — findet
Drucker, Kameras, Chromecast, AirPlay; liefert Namen + Diensttypen
- Quick-Port-Probe (22/80/443/554/9100 …) speist eine deviceType-Heuristik
(Kamera, Drucker, Router, Switch, NAS, Wallbox, Server …)
- OUI-Vendor-Tabelle von 6 auf ~150 kuratierte Einträge erweitert
- ipscan.ts führt IP-Scan + mDNS pro IP zusammen, mDNS-only-Geräte ergänzt
- neue DeviceCard-Komponente: zeigt Geräteart-Badge, offene Ports,
mDNS-Dienste, mac, NetBIOS-Name; ersetzt die Inline-Geräteliste
- upsertDevice überschreibt vorhandene Daten nicht mehr mit undefined
(Favorit/eigener Name bleiben bei magerem Re-Scan erhalten)
- Manifest: CHANGE_WIFI_MULTICAST_STATE für die mDNS-Suche
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Auftragsliste: Kundenname + Adresse sind jetzt die Überschrift, die
Auftragsnummer nur noch Kleingedrucktes — Aufträge sind so ohne
Nummer-im-Kopf wiederzufinden. Auftragsnotiz wird mit angezeigt.
IP-Scanner: ist kein Netzbereich angegeben, nutzt das Tool den im
Protokoll hinterlegten; ist auch der leer, fragt es den aktiven
WLAN-/LAN-Adapter ab und scannt dessen Subnetz. Der ermittelte
Netzbereich wird ins Protokoll übernommen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>