All checks were successful
Build APK / build-apk (push) Successful in 6m6s
Der Knopf oeffnet die awlauth-Anmeldeseite im Chrome-Tab (@capacitor/browser), der die Anmeldung mit Chrome teilt - wer im Handy-Browser angemeldet ist, ist ohne Eingabe zurueck in der App. Ruecksprung ueber das eigene Schema de.data-it-solution.netdiag:/sso (Intent-Filter), abgesichert mit state und PKCE; verarbeitet im Layout (appUrlOpen + getLaunchUrl). Passwort und Fingerabdruck bleiben. Mitgenommen: die bisher nicht committete README-Ergaenzung vom 19.08. (Plugin-Dateien, "Was noch offen ist"). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
245 lines
13 KiB
Markdown
245 lines
13 KiB
Markdown
# NetDiag — Diagnose-App (Android)
|
|
|
|
Mobile Netzwerk-Diagnose-App. Erfasst vor Ort beim Kunden (Handy am WLAN oder
|
|
USB-C→RJ45-Adapter) Geräte, Ports und Messungen und hängt die Protokolle ans
|
|
Dolibarr-Modul `netdiag` an Kunde und Auftrag.
|
|
|
|
## Stack
|
|
|
|
SvelteKit 2 · Svelte 5 · Tailwind 4 · Vite 7 · Capacitor 6 · SQLite-Offline
|
|
|
|
## Entwicklung (Browser)
|
|
|
|
```bash
|
|
npm install
|
|
npm run dev # http://localhost:5175
|
|
```
|
|
|
|
Im Browser liefert ein **Mock** (`src/lib/scanner.ts`) Beispiel-Scandaten — die
|
|
Oberfläche lässt sich ohne Gerät entwickeln. API-Aufrufe gehen über den
|
|
Vite-Proxy an den Dolibarr-Testserver (`192.168.155.11`, siehe `vite.config.ts`).
|
|
|
|
## Android-Build
|
|
|
|
```bash
|
|
npm run build
|
|
npx cap add android # einmalig
|
|
# android/ liegt komplett im Repo — cap sync kopiert nur die Web-Assets
|
|
npx cap sync android
|
|
npx cap open android
|
|
```
|
|
|
|
Release-APK über CI: Commit mit `[apk]` in der Message → Forgejo baut und lädt
|
|
die APK in die Package Registry (`netdiag-apk`). Siehe `.forgejo/workflows/build.yml`.
|
|
|
|
## Architektur
|
|
|
|
```
|
|
src/lib/
|
|
api.ts JSON-API-Client (JWT, 401-Refresh, Timeout)
|
|
auth.svelte.ts Anmelde-Status
|
|
sso.ts Anmeldung per AWL-Konto (Chrome-Tab, PKCE, Rücksprung)
|
|
db.ts Offline-Speicher (SQLite nativ / localStorage Browser)
|
|
sync.svelte.ts Sync-Queue -> Dolibarr (idempotent über clientUuid)
|
|
scanner.ts Brücke zum nativen Plugin (+ Browser-Mock)
|
|
protocols.ts Protokoll-Operationen (addMeasurement, upsertDevice, …)
|
|
messfelder.ts Beschriftung der Ergebnisfelder — Gegenstück zur
|
|
Whitelist im Dolibarr-Modul (siehe „Neues Tool")
|
|
toolparams.ts Gedächtnis der zuletzt benutzten Werkzeug-Parameter
|
|
stresstest.ts Auswertung des Dauertests (überlebt App-Wechsel)
|
|
monitor.ts Auswertung der Geräte-Überwachung -> Verfügbarkeit
|
|
wifi/ Kanalgraph-Geometrie, Kanalempfehlung, Bewertung, Demodaten
|
|
overlay.svelte.ts Overlay-Stapel für den Hardware-Zurück-Knopf
|
|
toast.svelte.ts kurze Rückmeldungen
|
|
backButton.svelte.ts Hardware-Back (Single-Instance, KB #480/#549)
|
|
updater.ts APK-Auto-Update-Prüfung (KB #363). Das Token geht als
|
|
Authorization-Header mit, nicht in der Adresse.
|
|
tools/ erweiterbare Tool-Plattform
|
|
index.ts Registry — neues Tool hier eintragen
|
|
netzwerk/ IP-Scan, Port, Ping, IP-Konflikt, SNMP, Traceroute, Stress
|
|
internet/ Durchsatz-Test
|
|
telefonie/ SIP-Erreichbarkeit über OPTIONS (sipcheck.ts) samt
|
|
Bewertung in sip-bewertung.ts. Jede Statuszeile beweist
|
|
eine antwortende Instanz — 401 heißt „erreichbar, will
|
|
Zugangsdaten", nicht „tot". RTP-Sprachqualität bewusst
|
|
NICHT: ohne aufgebautes Gespräch nicht ehrlich messbar.
|
|
src/routes/
|
|
login/ auftraege/ kunden/ einstellungen/
|
|
protokoll/[id]/ Hauptseite mit Werkzeugen, Geräten, Messungen
|
|
protokoll/[id]/stresstest/ monitor/ iptest/ wifi/ wifikanal/
|
|
Werkzeuge mit Live-Anzeige — eigene Seite statt
|
|
Dialog, weil sie über Minuten/Stunden laufen
|
|
android/ natives Android-Projekt inkl. Kotlin-Plugin (einzige Quelle)
|
|
NetDiagScannerPlugin.kt alle Plugin-Methoden (Scan, Monitor, WLAN, SIP, Update,
|
|
Biometrie) — @PluginMethod-Namen = JS-Aufrufe
|
|
Dns.kt / Snmp.kt / Sip.kt eigene Protokoll-Clients (UDP/TCP), je ein
|
|
Ergebnistyp statt null — ein nicht erreichbarer Dienst
|
|
darf nie wie ein fehlerfreier aussehen
|
|
Biometrie.kt BiometricPrompt + Keystore (siehe „Anmeldung")
|
|
MonitorService.kt Foreground-Service für Dauertest und Geräte-Monitor
|
|
native-plugin/ nur noch Anleitung/Hinweise zum Plugin
|
|
tools/ Prüfwerkzeuge ohne Gerät (siehe unten)
|
|
```
|
|
|
|
## Prüfwerkzeuge (`tools/`, laufen ohne Handy)
|
|
|
|
```bash
|
|
node tools/render-chart.mjs # rendert die echten Diagramm-Komponenten
|
|
# nach .render/*.svg — ansehen, nicht annehmen
|
|
node tools/pruefe-monitor.mjs # rechnet die Verfügbarkeits-Auswertung gegen
|
|
# konstruierte Fälle mit Handwerten durch
|
|
node tools/pruefe-kette.mjs # Internet-Kette: Fehlerglied, Anmeldeseite
|
|
node tools/pruefe-dns.mjs # DNS: NXDOMAIN ist KEIN Ausfall
|
|
node tools/pruefe-geraetediff.mjs # Geräte-Vergleich, alle Fälle OHNE MAC
|
|
node tools/pruefe-geraeteaktionen.mjs # Sortierung nach Gerätetyp
|
|
node tools/pruefe-serveradresse.mjs # http:// bei https-App abweisen
|
|
node tools/pruefe-sip.mjs # SIP: 401/405 sind ERREICHBAR, kein Ausfall
|
|
node tools/test-recommend.mjs # Kanalempfehlung
|
|
```
|
|
|
|
**Warum es diese Prüfer gibt:** Der Browser-Mock liefert bei Netzwerkwerkzeugen
|
|
grundsätzlich den guten Fall — er gibt sogar jedem Gerät eine MAC-Adresse, die
|
|
beim Kunden nie ankommt. Wer im Mock klickt, prüft den Mock. Deshalb liegt die
|
|
Bewertungslogik jeweils in einer Datei **ohne Laufzeit-Import**
|
|
(`*-bewertung.ts`, `geraete-diff.ts`, `serveradresse.ts`), die Node dank
|
|
Type-Stripping direkt laden kann. Jeder dieser sechs Prüfer hat mindestens
|
|
einen echten Fehler gefunden (Muster und Bilanz: KB #1091).
|
|
|
|
`render-chart.mjs` gibt es, weil die erste Fassung der WLAN-Diagramme unbemerkt
|
|
unlesbar in den Einsatz ging: der Emulator hat keine WLAN-Hardware, also war
|
|
nie ein gefülltes Diagramm zu sehen. SVGs mit `rsvg-convert x.svg -o x.png`
|
|
in ein ansehbares Bild wandeln.
|
|
|
|
## Neues Tool hinzufügen
|
|
|
|
1. Datei unter `src/lib/tools/<kategorie>/<id>.ts` anlegen, `Tool` implementieren.
|
|
2. In `src/lib/tools/index.ts` importieren und in `TOOLS` eintragen.
|
|
3. `quickRun: true` setzen, **wenn** das Werkzeug ohne Eingabe sinnvoll startet
|
|
(kurzer Tap startet dann sofort, langes Halten öffnet die Optionen).
|
|
Ausdrücklich je Werkzeug entscheiden — es gibt kein `required` an den
|
|
Parametern, die Pflicht steht allein im `run()`-Rumpf. `portscan.ports` hat
|
|
keinen Vorgabewert und ist trotzdem optional, `iperf.host` hat ebenfalls
|
|
keinen und ist Pflicht.
|
|
4. **Jeden neuen Ergebnis-Schlüssel in BEIDE Feldtabellen eintragen:**
|
|
- `src/lib/messfelder.ts` (App-Anzeige)
|
|
- `netdiagKundenfelder()` in `dolibarr-modul/netdiag/lib/netdiag.lib.php`
|
|
— das ist eine **harte Whitelist**: was dort fehlt, verschwindet im
|
|
Kunden-PDF spurlos, übrig bleibt die nackte Ampel.
|
|
Ebenso den Klarnamen des Werkzeugs in `netdiagToolName()`, sonst steht beim
|
|
Kunden „[netzwerk] meintool".
|
|
|
|
> Punkt 4 ist der Schritt, der hier schon **zweimal** vergessen bzw. geraten
|
|
> wurde. Beim ersten Mal wurde die Whitelist gegen selbst erfundene Testdaten
|
|
> gebaut, beim zweiten Mal stand `'typ'` darin statt des real gelieferten
|
|
> `'type'` — im Abnahmebeleg für eine Netzwerkdose fehlte dadurch die Angabe,
|
|
> ob per LAN oder WLAN gemessen wurde. **Feldnamen immer aus dem erzeugenden
|
|
> Code ablesen**, nie aus dem Kopf. (KB #1084)
|
|
|
|
Sync und Datenbank brauchen keinen Eingriff — das Ergebnis ist generisches JSON.
|
|
Braucht das Tool eine neue native Messroutine: Methode im Kotlin-Plugin,
|
|
Eintrag im Interface `NetDiagScannerPlugin` **und** ein Zweig im Browser-Mock
|
|
(beides in `src/lib/scanner.ts`) — ohne Mock lässt sich nichts ohne Gerät
|
|
prüfen, und ein Mock, der nur den Gutfall zeigt, bestätigt genau die Annahme,
|
|
die im Feld nicht gilt (KB #1086).
|
|
|
|
## Anmeldung
|
|
|
|
Die App hängt am zentralen Auth-Modul **awlauth** (7. AWL-App). Das Token kommt aus
|
|
`auth.php` (Passwort) oder `sso.php` (AWL-Konto, siehe unten) und geht bei jeder Anfrage als
|
|
`Authorization: Bearer` mit — auch beim
|
|
APK-Download und beim PDF-Abruf. **Nie in der Adresse:** ein Token in der URL steht in
|
|
jedem Zugriffs- und Proxy-Log und gilt sieben Tage für die gesamte Kunden-API.
|
|
(Einzige, befristete Ausnahme: `update.php?download=1` für App-Fassungen im Feld —
|
|
siehe ChangeLog des Moduls 1.3.0.)
|
|
|
|
### Mit AWL-Konto anmelden (seit Modul 1.4.0 / awlauth 1.6.0)
|
|
|
|
Knopf „Mit AWL-Konto anmelden" öffnet die awlauth-Anmeldeseite im **Chrome-Tab**
|
|
(`@capacitor/browser`). Der Tab teilt sich die Anmeldung mit Chrome: wer im Handy-Browser
|
|
schon angemeldet ist (PWA, ERP), ist ohne Eingabe zurück in der App. Sonst Passwort oder
|
|
Fingerabdruck auf der awlauth-Seite, danach „Zurück zu NetDiag".
|
|
|
|
- Rücksprung über das eigene Schema `de.data-it-solution.netdiag:/sso?code=…&state=…` —
|
|
Intent-Filter im `AndroidManifest.xml`, Konstante `SSO_REDIRECT` in `src/lib/sso.ts`.
|
|
**Beide müssen übereinstimmen**, und awlauth vergleicht die Adresse Zeichen für Zeichen.
|
|
- PKCE (S256) + state: Geheimnis und state liegen bis zum Rücksprung in den Preferences
|
|
(Android darf die App beenden, während der Chrome-Tab vorne ist), gelten einmal und
|
|
höchstens zehn Minuten.
|
|
- Der Rücksprung wird im Layout verarbeitet (`appUrlOpen` + `getLaunchUrl`), nicht auf der
|
|
Login-Seite.
|
|
- Fragt `sso.php` (GET) `available:false` zurück (awlauth zu alt, App dort abgeschaltet),
|
|
zeigt die App den Grund; das Passwortfeld bleibt immer.
|
|
|
|
### Fingerabdruck statt Passwort
|
|
|
|
Einschaltbar unter Einstellungen → Konto. Was dabei passiert:
|
|
|
|
1. Das Sitzungs-Token wird mit einem AES-256/GCM-Schlüssel aus dem **Android-Keystore**
|
|
verschlüsselt (`setUserAuthenticationRequired`), Chiffrat und IV liegen in
|
|
`shared_prefs/netdiag_biometrie.xml`.
|
|
2. Die **Klartextfassung wird entfernt** (`Preferences.remove('token')`). Genau das ist
|
|
der Sicherheitsgewinn: vorher stand das Token offen in `CapacitorStorage.xml` und war
|
|
auf einem entsperrten Gerät ohne Weiteres lesbar.
|
|
3. Beim nächsten Start zeigt die Login-Maske „Mit Fingerabdruck anmelden". Der Finger
|
|
entschlüsselt das Token; es bleibt danach nur im Arbeitsspeicher.
|
|
|
|
Kein zweiter Faktor — der Finger **ersetzt** das Passwort (Vorgabe von Eddy). Die
|
|
Gültigkeit bestimmt weiterhin `AWLAUTH_TTL`, und „Gerät abmelden" in der
|
|
awlauth-Geräteliste wirkt unverändert: hinterlegt ist dasselbe Token. Wird es
|
|
serverseitig ungültig (401 **mit** Token), räumt die App die Hinterlegung selbst weg.
|
|
Ein 401 **ohne** Token ist dagegen nur „noch nicht angemeldet" und lässt sie stehen —
|
|
diese Unterscheidung ist nicht theoretisch: ohne sie löschte der erste Startaufruf die
|
|
gerade eingerichtete Anmeldung wieder.
|
|
|
|
Wird dem Gerät ein weiterer Fingerabdruck hinzugefügt, macht Android den Schlüssel
|
|
ungültig (`setInvalidatedByBiometricEnrollment`) — die App sagt das im Klartext und
|
|
verlangt einmal das Passwort. Gewollt: sonst könnte jemand mit dem entsperrten Gerät
|
|
einfach seinen eigenen Finger anlernen.
|
|
|
|
WebAuthn wie in den PWAs geht hier nicht — im Capacitor-WebView ist es nicht verlässlich
|
|
verfügbar (KB #855).
|
|
|
|
## Bedienung
|
|
|
|
1. **Anmelden** — „Mit AWL-Konto anmelden" oder Dolibarr-Zugang (auf dem Gerät zusätzlich Server-URL).
|
|
2. **Aufträge** — aktive direkt sichtbar, abgeschlossene per Checkbox, Suche.
|
|
Alternativ über **Kunden** suchen.
|
|
3. Auftrag/Kunde antippen → Diagnose-Protokoll öffnet sich.
|
|
4. **Werkzeuge** ausführen (IP-Scan füllt die Geräteliste, je Gerät weitere Tools).
|
|
Bei den meisten Karten steht „Tippen startet · Halten für Optionen": kurzer
|
|
Tap startet sofort mit den zuletzt benutzten Werten, langes Halten öffnet den
|
|
Parameterdialog. Nach der Messung springt die Ansicht zum frischen Ergebnis.
|
|
5. **Abschließen & synchronisieren** — Protokoll geht ans Dolibarr, PDF landet
|
|
im ECM. Offline bleibt es lokal und synct automatisch bei Verbindung.
|
|
|
|
Selten gebrauchte Aktionen (Protokoll löschen) liegen hinter den drei Punkten
|
|
in der Kopfzeile — bewusst nicht neben dem grünen Abschließen-Knopf.
|
|
|
|
## Was noch offen ist
|
|
|
|
Die Arbeitsdateien `ROADMAP.md` / `ROADMAP_UMSETZUNG.md` sind abgearbeitet und entfernt
|
|
(19.08.2026). Was aus ihnen **nicht** erledigt war, steht hier — damit es nicht
|
|
mit den Dateien verschwindet:
|
|
|
|
- **WLAN-Kanalanalyse auf echtem Empfang gegenprüfen.** Der Emulator hat keine
|
|
WLAN-Hardware und liefert grundsätzlich 0 Netze; alle Diagramme sind bisher nur mit
|
|
Demodaten geprüft. Auf dem Handy nachfahren: Kanal am Router wechseln, zwei
|
|
Momentaufnahmen vergleichen, Zeitverlauf ansehen.
|
|
- **Ortung-aus-Zweig (Android < 13).** Auf einem API-33-AVD nicht erreichbar, deshalb
|
|
nur per Codereview geprüft — die Meldung „Ortung ausschalten verhindert den WLAN-Scan"
|
|
samt Sprung in die Systemeinstellung ist am echten Altgerät noch nie gelaufen.
|
|
- **Vorschlags-Chips im ToolDialog.** Im Kopfbereich erledigt (`inputmode`), die Chips
|
|
für häufige Werte (Gateway, letzte Ziele) fehlen weiterhin.
|
|
|
|
Dazu einer, der beim Modul liegt: die befristete `?jwt=`-Ausnahme in `update.php` fällt,
|
|
sobald die APK-Fassung ≥ 20260819-2141 überall installiert ist (Hinweis steht dort im
|
|
Code und im ChangeLog 1.3.0).
|
|
|
|
## Ohne Gerät prüfen
|
|
|
|
- **Browser** (`npm run dev`): Mock-Scanner liefert Beispieldaten.
|
|
- **WLAN-Demomodus** (Einstellungen → WLAN-Demodaten): 18 erfundene Netze mit
|
|
allen Grenzfällen — Mesh, Co-Channel, 40 MHz im 2,4-GHz-Band, DFS, 160 MHz,
|
|
Bandrand. Nötig, weil der Emulator keine WLAN-Hardware hat und jeder Scan
|
|
null Netze liefert. Gespeicherte Momentaufnahmen werden als Demo gekennzeichnet.
|
|
- **Diagramme**: `tools/render-chart.mjs` (siehe oben).
|