Die App ermittelt seit dem IP-Scanner-Umbau NetBIOS-/mDNS-Namen, offene Ports
und den Fundweg je Gerät, hat sie aber nie übertragen — im Kundenprotokoll
stand nur die IP, während der Techniker in der App "Drucker HP, Port 9100" sah.
Der Payload enthält jetzt netbiosName, mdnsName, mdnsServices, customName,
openPorts, foundVia und lastSeen. Die Gegenstelle (Dolibarr-Modul netdiag)
nimmt sie ab derselben Version entgegen; ältere Modulstände ignorieren die
zusätzlichen Schlüssel einfach.
Zwei Widersprüche, die beim Durchsehen der offenen Punkte auffielen. Beide
entstanden erst dadurch, dass der Kanalgraph seit dem letzten Commit die
Wahrheit zeigt — Empfehlung und Kennzeichnung waren noch auf dem alten Stand.
1. Kanalempfehlung ignorierte die Kanalbreite.
interferenceWeight() rechnete nur mit dem Abstand der Kanalnummern. Ein
40-MHz-Netz auf Kanal 1 belegt real 2402-2442 MHz und damit auch Kanal 6
(2437), wurde wegen |1-6| = 5 aber als "stört nicht" gewertet. Gerechnet
wird jetzt in MHz, mit derselben occupiedRange(), die auch das Diagramm
zeichnet.
Belegt mit tools/test-recommend.mjs: in zwei von drei realistischen Lagen
nennt die alte Formel einen ANDEREN Kanal als den tatsächlich besten —
z.B. bei einem starken 40-MHz-Netz auf Kanal 1 empfahl sie Kanal 6, obwohl
dieses Netz genau dorthin reicht (richtig ist Kanal 11). In der Demo-Lage
ändert sich die Spitzenempfehlung nicht, nur die Bewertung der übrigen
Kanäle (K6 von 0,62 auf 0,80).
2. DFS-Kanäle 52-64 waren im Diagramm nicht als solche erkennbar.
Der Block hieß "UNII-1/2A" und trug keine DFS-Kennzeichnung, während
channelWarnings() Kanal 52-144 als DFS behandelt. Ein im Diagramm "frei"
wirkender Kanal 56 hätte den Kunden ungewollt auf DFS gesetzt (möglicher
Radar-Zwangswechsel), obwohl die Warnung darunter das Gegenteil sagte.
UNII-1 (36-48) und UNII-2A (52-64) sind jetzt getrennte Blöcke mit
korrekter Kennzeichnung.
Damit die zusätzliche Aufteilung nicht zu mehr Scrollen führt: ein Block ohne
Netze wird kompakt gezeichnet (ohne dBm-Skala, ~1/4 der Höhe) statt in voller
Größe. Er bleibt sichtbar — er ist die Antwort auf "wo ist noch Platz".
Nebenbei im Render-Werkzeug behoben: es stapelte alle Segmente mit gleicher
Höhe und erzeugte dadurch künstliche Lücken im Testbild, die es in der App
nicht gibt.
Auf Eddys Vorschlag. Der Android-Emulator hat keine WLAN-Hardware und liefert
bei jedem Scan null Netze — Kanalgraph, Gruppierung, Kanalempfehlung und die
Warnregeln ließen sich damit auf dem Gerät überhaupt nicht ansehen. Genau
deshalb ging die erste Fassung der Diagramme unbemerkt unlesbar in den Einsatz.
Schalter in den Einstellungen (Diagnose → WLAN-Demodaten). Ist er an, zeigt die
Kanalanalyse eine realistische Beispielumgebung statt echter Messwerte. Die
Daten decken bewusst die Grenzfälle ab: Mesh über zwei Bänder, mehrere Netze
auf demselben Kanal, ein 40-MHz-Netz im 2,4-GHz-Band und ein DFS- sowie ein
160-MHz-Netz im 5-GHz-Band (lösen beide Warnregeln aus), Netze in allen drei
UNII-Blöcken, ein Netz am Bandrand, ein offenes Netz ohne Standard-Angabe.
Leichtes Rauschen je Scan, damit mehrere Momentaufnahmen einen Zeitverlauf mit
sichtbarem Auf und Ab ergeben.
Damit keine Demo je für eine echte Messung gehalten wird:
- deutlich sichtbarer Hinweis im Kopf der Seite, solange der Modus an ist
- Messungs-Label beginnt mit "DEMODATEN — "
- result-JSON trägt demodaten:true; Dolibarr zeigt das in der Kartenansicht und
im PDF als eigene Zeile (PDF besonders wichtig, das Dokument geht zum Kunden)
Im Emulator durchgespielt: Schalter an -> Kanalanalyse zeigt alle Diagramme mit
Netzen, 5-GHz-Band in drei lesbaren UNII-Blöcken, Fazit-Zeile und alle drei
Warnhinweise korrekt. Momentaufnahme gespeichert, synchronisiert und in der
Test-DB geprüft: Label und result-Flag korrekt als Demo markiert.
Zweitnutzen: Vorführung beim Kunden, unabhängig davon was gerade in der Luft ist.
Eddys Rückmeldung nach dem ersten Einsatz auf dem Handy: das zweite Diagramm
ergibt keinen Sinn, Zahlen überlappen, und die Signalstärke ist in den
Diagrammen nirgends ablesbar. Alle drei Punkte bestätigt und behoben.
Ursache (nachgerechnet, nicht geraten): die 745 MHz des 5-GHz-Bandes wurden
in dieselbe Breite gequetscht wie die 80 MHz des 2,4-GHz-Bandes — 9,3-fach
schlechtere Auflösung, 8,6 statt 80 Einheiten je 20-MHz-Kanal. Bei 8,6
Einheiten Tick-Abstand und 12,6 Einheiten Breite einer dreistelligen Zahl ist
Überlappung unvermeidbar ("3640444852566064"). Der vorhandene Tick-Filter
filterte nachweislich 0 von 25 Ticks weg, weil er nur gegen den
Frequenzbereich prüfte, in dem ohnehin alle Kanäle liegen.
Kanalgraph:
- 5 GHz in die regulatorischen Blöcke geteilt (UNII-1/2A, UNII-2C, UNII-3),
ein gemeinsamer Maßstab je Band -> 20-MHz-Kanal jetzt ~24 statt 8,6
Einheiten breit, alle Kanalzahlen einzeln lesbar.
- Leere Blöcke werden gezeichnet ("frei — keine Netze") statt weggelassen.
Weglassen ließe das Band voller aussehen als es ist und versteckt genau
die gesuchte Antwort: wo ist noch Platz.
- Trapez statt Parabel — die Fläche zeigt den tatsächlich belegten
Frequenzbereich. Bei der Parabel lag nur die Spitze auf dem Messwert; mit
dBm-Achse wäre alles außer der Bogenmitte falsch abgelesen worden.
- Kanalbreite über centerFreq0 statt Primärfrequenz: ein 80-MHz-Netz auf
Kanal 36 wurde sonst um 5180 statt 5210 gezeichnet, Nachbarkanäle sahen
fälschlich frei aus.
- Farbe je Netz (stabil über BSSID-Hash), gleiche Farbe in Graph, Zeitverlauf
und Netzliste. Vorher verschmolzen überlappende graue Blöcke zu einer Fläche.
- Belegte Kanalnummern hell, freie gedämpft; SSID von hinten gekürzt (sonst
heißen alle FRITZ!Box-Netze gleich); gekappte Blöcke werden markiert.
dBm-Achse (neue gemeinsame Komponente RssiAxis, für Kanalgraph UND
Zeitverlauf — damit sie konstruktiv nicht auseinanderlaufen können):
- feste Skala -95..-30 dBm, 2,0 Einheiten je dB (glatte Rasterlinien),
beschriftet -30/-50/-70/-90, Einheit "dBm", Praxiszonen grün/rot.
- Bewusst "Empfangspegel", nicht "Sendestärke": die Achse zeigt den Pegel am
Standort des Handys, nicht die Leistung des Routers.
- Schrift 7 -> 9/10 (war ~7,6 CSS-px, unter der Lesegrenze), Kontrast der
Achsenbeschriftung von zinc-600 (2,29:1, unter WCAG-Minimum) auf zinc-200/300.
Momentaufnahme wird jetzt synchronisiert: sie lag bisher nur lokal und kam nie
im Protokoll auf dem Server an, obwohl sie genau dafür gemacht wird. Landet
jetzt zusätzlich als Messung (tool='wifikanal') in Sync und PDF.
Klartext-Fazit über dem Diagramm ("Dein Netz: Kanal 6 · -48 dBm (sehr gut) ·
2,4 GHz freister Kanal: 1") — unter Zeitdruck schneller als jedes Diagramm.
Neu: tools/render-chart.mjs rendert die echten Svelte-Komponenten
serverseitig zu SVG/PNG, inklusive Randfällen (1 Netz, 25 Netze, 5 Netze auf
einem Kanal, Bandränder, leeres Band, Zeitverlauf mit Aussetzer). Das ist die
Lehre aus dem Fehler: in Phase 4 wurde die Chart-Mathematik nur durchgerechnet
(keine NaN, nichts außerhalb des Zeichenbereichs) und daraus geschlossen, das
genüge. Rechnerisch korrekt heißt nicht lesbar — ein Diagramm muss man ansehen.
Weiterhin offen: der Emulator hat keine WLAN-Hardware und liefert immer 0
Netze. Alle Diagramme sind mit realistischen Testdaten gerendert und geprüft,
aber nicht mit echtem Empfang auf dem Handy.
Details: ROADMAP_UMSETZUNG.md Phase 4 / Nachbesserung
Bisher zeigte die App nur eine Liste sichtbarer Netze ohne Kanalgraph, ohne
Empfehlung und ohne Möglichkeit, einen Kanalwechsel vorher/nachher zu
vergleichen. Fester 800-ms-Sleep statt auf den echten Scan zu warten, keine
Reaktion auf ausgeschaltete Ortung, Standortberechtigung auch dort nötig, wo
Android 13+ das gar nicht mehr verlangt.
Nativ (NetDiagScannerPlugin.kt):
- wifiScan liefert jetzt frequency, widthMhz, centerFreq0/1, standard
(Wi-Fi 4/5/6/7), security (offen/WEP/WPA/WPA2/WPA3), ageMs, band
(inkl. 6 GHz) je Netz. freqToChannel() um das 6-GHz-Band erweitert
(inkl. Sonderfall Kanal 2/PSC).
- wifiScan({trigger:true}) löst einen frischen Scan aus und wartet auf
SCAN_RESULTS_AVAILABLE_ACTION (5s Zeitgrenze) statt blind zu schlafen.
- isLocationEnabled()-Prüfung + openLocationSettings(): ausgeschaltete
Ortung (< Android 13) ergibt jetzt einen Klartextfehler mit direktem
Weg zu den Einstellungen statt einer stillen leeren Liste.
- NEARBY_WIFI_DEVICES (API 33+, neverForLocation) statt Standortrecht —
eigenes Berechtigungs-Alias, wifiScanPermissionAlias() wählt je nach
SDK-Version; gilt für wifiScan/startWifiScan/startWifiTrack gemeinsam.
App:
- WifiChannelChart.svelte: Kanalgraph 2,4/5/6 GHz als Inline-SVG — Bögen
mit Kanalbreite als Bogenbreite, Signalstärke als Bogenhöhe, Überlapp
(Co-/Adjacent-Channel) direkt als sichtbare Farbverdichtung.
- wifi/recommend.ts: beste der drei nicht überlappenden 2,4-GHz-Kanäle
nach Störlast + zwei feste Warnregeln (40 MHz im 2,4-GHz-Band,
160 MHz/DFS im 5-GHz-Band).
- wifi/rating.ts: rssiRating() vereinheitlicht — ersetzt drei Kopien mit
unterschiedlichen Schwellen (iptest/+page.svelte, wifi/+page.svelte
zweimal).
- Neue Route /protokoll/{id}/wifikanal/ + Werkzeug-Kachel "WLAN-Kanäle":
Netzliste nach SSID gruppiert (Mesh), Momentaufnahmen speichern
(protocol.wifiSurveys, nur lokal), automatischer Kanaländerungs-Hinweis
seit der letzten Aufnahme, Zeitverlauf mehrerer Netze aus den
gespeicherten Momentaufnahmen (echte Zeitstempel, feste Y-Skala
-100..-20 dBm, Messlücken reißen die Linie statt sie zu überbrücken).
Im Emulator getestet (API 33+): NEARBY_WIFI_DEVICES-Berechtigungsdialog
erscheint und wird korrekt verarbeitet — echte Bestätigung, kein
Codereview-only. Leerer Netz-Zustand rendert sauber. Der AVD hat keine
echte WLAN-Hardware (liefert grundsätzlich 0 Scan-Ergebnisse) — Kanalgraph
mit echten Netzen und der Ortung-aus-Zweig (<Android 13, auf diesem API-33-
AVD unerreichbar) konnten deshalb nicht live geprüft werden, nur die
Chart-Mathematik gegen die Mock-Werte durchgerechnet (keine NaN/Werte
außerhalb des Zeichenbereichs). Sollte auf einem echten Handy mit echtem
WLAN-Empfang gegengeprüft werden.
Details: ROADMAP_UMSETZUNG.md Phase 4
Bisher fand der IP-Scan Geräte nur per ICMP-Ping; ein Windows-PC mit aktiver
Firewall (Ping blockiert, Ports offen) fiel komplett durch. Kein Fortschritt
während des Laufs, kein Abbrechen, kein Hinweis was gescannt wird.
Nativ (NetDiagScannerPlugin.kt):
- Mehrgleisige Suche: gefunden bei Ping ODER Port-Probe ODER ARP-Tabellen-
Eintrag. Runde 1 über alle Adressen, ARP-Abgleich, Runde 2 NUR für die
weiterhin stillen Adressen, ARP erneut abgleichen.
- startIpScan()/cancelIpScan() statt eines einzigen langen ipScan()-Promise:
läuft als Lauf mit Live-Events (ipScanProgress/ipScanFinished), Abbruch
liefert das bis dahin gefundene Teilergebnis statt nichts.
- Eigener limitedParallelism(128)-Dispatcher, damit ein großer Sweep keinen
gleichzeitigen Ping/Monitor/Dauertest ausbremst.
- arpAvailable ehrlich über File.canRead() statt "Tabelle war halt leer".
- mDNS-Budget 4s -> 9s, Discovery/Auflösung entkoppelt (eigene Nachlaufzeit),
discoveryOk meldet einen echten Suchfehler statt stiller 0 Treffer.
App:
- scanner.ts: neue Events onIpScanProgress/onIpScanFinished.
- ipscan.ts: protocolPatch statt direktem Beschreiben von ctx.protocol;
fehlgeschlagener Scan erzeugt jetzt auch eine Messung (Status 2 statt
nichts); "neu"/"nicht mehr erreichbar" als Diagnosefelder.
- ToolDialog: generischer Vorschau-Schritt (tool.preview — Adapter, eigene
IP, Gateway, Adressenzahl; ab >1024 Adressen Bestätigung "Trotzdem
scannen") und Live-Fortschritt (tool.supportsProgress — Fortschrittsbalken,
laufende Trefferliste, Abbrechen). Beide Mechanismen sind generisch für
alle Tools nutzbar, nicht IP-Scan-spezifisch fest verdrahtet.
- DeviceCard zeigt den Fundweg ("via Ping/Port/ARP/mDNS") als Badge.
- Geräteliste numerisch nach IP sortiert (Favoriten weiter zuerst).
Im Emulator getestet (nicht nur Codereview): ICMP per iptables zu einem
Ziel geblockt, nur TCP/22 offen -> Gerät korrekt "via Port" gefunden.
/24 zweimal komplett gescannt: gleiche 4 Geräte, zweiter Lauf ohne "neu".
/20 (4094 Adressen) löst die Bestätigung korrekt aus. Abbrechen mitten im
Lauf liefert sauber ein Teilergebnis. Sync nach Dolibarr per DB-Query
verifiziert.
Dabei zwei echte Bugs gefunden, die reines Codereview nicht gefunden hätte:
Fortschritt zeigte "504 von 254" (Runde 2 zählte im Zähler von Runde 1
weiter), und das native Feld hieß "gefundenVia" statt "foundVia" wie von
der TS-Seite erwartet — der Fundweg kam nie an.
Details: ROADMAP_UMSETZUNG.md Phase 3
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>
- Auge-Symbol im Passwortfeld. Haben alle anderen AWL-Apps seit 06.07.2026; NetDiag
war die letzte ohne. Grund: bei falschem Tastaturlayout tippt man sich sonst blind
fest. Der Typ wird imperativ am Element gesetzt, weil Svelte 5 ein dynamisches
type-Attribut an einem Feld mit bind:value verbietet; type="button" am Schalter ist
Pflicht, sonst schickt er das Formular ab.
- Serveradresse laesst sich in den Einstellungen aendern. Bisher stand sie dort nur
als Text — eine falsche Adresse liess sich nur durch Abmelden korrigieren. Ein
Wechsel meldet bewusst ab, weil das Token nur beim bisherigen Server gilt.
- Login-Fehler bleiben 8 s stehen statt 3 s und zeigen die Servermeldung im Wortlaut.
Bei 429 steht dort die Wartezeit der Brute-Force-Bremse, bei 403 der Grund.
Im Emulator (Pixel 6 / Android 14) gegen das Test-Dolibarr geprueft: Ein- und
Ausblenden des Passworts, Anmeldung ueber awlauth, Auftragsliste, Einstellungen.
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>
Für das Kamera-Problem: mehrere Geräte auswählen und ihre Erreichbarkeit
über längere Zeit überwachen — jeder Ausfall wird mit Uhrzeit protokolliert.
- MonitorService: schlanker Vordergrund-Dienst, hält den Prozess am Leben,
damit die Überwachung bei Display aus / App-Wechsel weiterläuft
- Plugin startMonitor/stopMonitor/getMonitorStatus: pingt die Geräte im
gewählten Intervall, Wechsel erreichbar↔weg erzeugt ein monitorEvent;
WifiLock gegen WLAN-Schlaf, Heads-up-Benachrichtigung bei Ausfall
- Monitor-Seite (protokoll/[id]/monitor): Geräte-Mehrfachauswahl,
Intervallwahl, Live-Ereignisliste, frühere Überwachungen mit Ausfallzahl
- Überwachung läuft beim Verlassen der Seite weiter; Rückkehr nimmt den
Stand wieder auf (getMonitorStatus)
- Manifest: MonitorService + FOREGROUND_SERVICE_DATA_SYNC, POST_NOTIFICATIONS
- Kachel "Geräte-Monitor" im Werkzeuge-Raster
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>
- "Scan speichern" friert den aktuellen Geräte-Stand als benannten
Snapshot ein (saveScan in protocols.ts, tiefe Kopie)
- Abschnitt "Gespeicherte Scans" auf der Protokollseite: aufklappbare
Liste mit Name, Subnetz, Datum und Gerätezahl; Snapshot zeigt die
damals gefundenen Geräte (DeviceCard read-only)
- Scan löschen per ConfirmDialog
- Snapshots bleiben am Auftrag erhalten, auch über spätere Re-Scans hinweg
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Favoriten-Stern je Gerät; favorisierte Geräte werden in der Liste oben
einsortiert. Favorit + eigener Name bleiben im Protokoll erhalten, auch
ohne gespeicherten Scan-Snapshot
- Gerät umbenennen (customName) über neuen TextPromptDialog
- toggleFavorite / renameDevice in protocols.ts
- TextPromptDialog + ConfirmDialog: schlanke Modal-Komponenten als Ersatz
für die verbotenen Browser-prompt()/confirm(); melden sich als Overlay an
(Hardware-Backbutton schließt sie)
- Protokoll-Löschen nutzt jetzt ConfirmDialog statt confirm()
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Resume: jede Navigation wird gemerkt; nach App-Neustart (oder wenn Android
den Prozess beim App-Wechsel beendet hat) öffnet die App wieder genau dort,
wo der Benutzer war — inklusive offenem Protokoll
- Protokoll wird beim Verlassen der Seite (Back-Tap/Navigation) und beim
Wechsel in den Hintergrund automatisch gesichert — auch Eingaben, die noch
nicht per onblur gespeichert wurden, gehen nicht mehr verloren
- Hardware-Backbutton schließt einen offenen Werkzeug-Dialog, statt gleich
die Seite zu verlassen (neue Overlay-Registry overlay.svelte.ts)
- backButton.svelte.ts: PluginListenerHandle korrekt aus @capacitor/core
importiert (war fälschlich @capacitor/app — Svelte-Check-Fehler behoben)
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 sortiert Aufträge mit lokalen Protokollen/Scans nach oben
(lokale updatedAt), da Dolibarr-tms bei reiner App-Tätigkeit unverändert
bleibt; Standard wieder "nur aktive Aufträge"
- Datumszeile zeigt "zuletzt bearb." aus lokalem Protokoll, sonst Server-tms
- Protokollzähler-Badge berücksichtigt auch lokale (noch nicht gesyncte) Protokolle
- types.ts: neue optionale Felder für kommende Geräte-Features
(Device: isFavorite/customName/openPorts/netbiosName/mdnsName/mdnsServices;
neu SavedScan, MonitorEvent, DeviceMonitorSession; Protocol.savedScans/monitorSessions)
- db.ts: normalizeProtocol ergänzt fehlende Arrays beim Laden alter Protokolle
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ersetzt den Browser-Umweg (window.open) durch einen echten In-App-Installer:
das native Plugin lädt die APK streamend herunter (Fortschritts-Events
updateProgress 0–100 %), prüft die Installationsberechtigung (Android 8+)
und öffnet den Paketinstaller über den vorhandenen FileProvider.
Versionsvergleich jetzt numerisch (YYYYMMDD-HHMM) statt lexikografisch.
Banner ist schließbar; Einstellungsseite zeigt separaten Fortschrittsbalken.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nach einem WebView-Reload lebt die native SQLite-Connection weiter.
createConnection() warf dann 'Connection netdiag already exists' -> initDb()
brach ab -> onMount im Layout brach ab -> die App haengt ewig auf
'NetDiag startet...'.
Fix: vorhandene Verbindung per isConnection/retrieveConnection wiederverwenden,
nur oeffnen wenn noch nicht offen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der Updater fragte die private Forgejo-Registry direkt ab -> 403/CORS ->
catch -> 'App ist aktuell'. Er hat NIE wirklich geprueft.
- updater.ts: geht jetzt ueber update.php (eigener, authentifizierter
Endpoint, CORS-frei). checkForUpdate() verschluckt Fehler NICHT mehr,
sondern wirft mit Klartext-Grund.
- Einstellungen: 'Auf Update pruefen' zeigt bei Fehler die echte Meldung
('Update-Pruefung fehlgeschlagen: <Grund>') statt falschem 'aktuell'.
- +layout: Start-Pruefung still (nur Banner bei Erfolg, kein Fehler-Toast).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der Kommentar ueber .safe-top enthielt die Zeichenfolge pb-*/px-* — das
darin steckende */ schliesst den CSS-Kommentar vorzeitig. Die .safe-top-
Regel landete dadurch hinter kaputtem Selektor-Text und wurde vom Parser
komplett verworfen (.safe-bottom danach blieb heil).
Folge: header padding-top = 0 statt 12px, Titel klebte oben.
Verifiziert per WebView-DevTools im Emulator: headerPadTop jetzt 12px.
Fix: Kommentar ohne */ umformuliert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Damit App-Fehler (Scan/Sync) ohne Kabel nachvollziehbar sind:
- debuglog.svelte.ts: faengt window.error, unhandledrejection und console.*
in einem Ringpuffer ab, gespiegelt in Preferences (ueberlebt Neustart)
- Auto-Upload zum neuen Endpoint applog.php (gedrosselt, best effort);
ToolDialog- und Sync-Fehler werden explizit mitgeloggt
- Seite Einstellungen -> Debug-Log: Eintraege ansehen, manuell senden, leeren
- initDebugLog() zuerst in +layout onMount, damit Startfehler erfasst werden
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NetDiagScannerPlugin.kt (latente Bugs, erst durch aktivierten Kotlin-Compiler sichtbar):
- traceroute: hop.first/.second -> hop.ip/.ms (Hop ist data class, kein Pair)
- startStressTest: getInteger() liefert Int?, mit '?: 0' abgesichert
Titelleiste klebte an der Statusleiste / war oben abgeschnitten:
- safe-top/safe-bottom enthalten jetzt den Basis-Innenabstand via calc() --
sonst ueberschreibt die unlayered CSS-Klasse das padding von Tailwind py-*
- Header/Toast/Update-Banner/Login auf pb-*/px-* statt py-*/p-* umgestellt
Siehe KB #551.
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>