M26+M27: Seriennummer bei Bekannte Router, Schlüsselbund-Trennung, terse-Fallback verallgemeinert

M26: SavedRouter bekommt ein optionales serialNumber-Feld. Zwei
physisch verschiedene Router mit identischem Host+Benutzername
(Werks-Adresse) blieben bisher ein gemeinsamer "Bekannte Router"-
Eintrag; recordSuccessfulConnection matcht jetzt zusätzlich nach
Seriennummer, mit sauberer Migration bestehender Einträge ohne
Seriennummer. Drei neue Tests.

M27, beim Live-Test von M26 gefunden:
- Passwort-Anzeige-Button (Augen-Symbol) im Verbinden-Tab
- Bug 35: KeychainService speicherte Passwörter nur nach
  "username@host" — beide Router teilten sich denselben
  Schlüsselbund-Eintrag trotz getrennter SavedRouter-Einträge. Fix:
  optionaler serialNumber-Parameter qualifiziert den Account-Key,
  mit Fallback auf den alten Key für bereits gespeicherte Passwörter.
- Bug 36: neuer Router zeigte keine Routerboard-Infos/Seriennummer —
  derselbe Root Cause wie der zuvor gemeldete "Jetzt prüfen"-Fehler:
  /system routerboard und /system package update scheitern auf diesem
  Router mit "expected end of command" statt dem bisher einzig
  abgefangenen "bad parameter terse". SSHTransport.fetchMenuItems
  erkennt jetzt beide Formulierungen.

Alles live bestätigt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kay
2026-09-16 14:02:27 +02:00
co-authored by Claude Sonnet 5
parent 8a5df3338a
commit 9de9311bee
11 changed files with 294 additions and 45 deletions
+94
View File
@@ -1517,6 +1517,96 @@ Nutzerwünsche in einer Runde):
setzt die Adressfelder jetzt explizit statt sich auf Modell-Defaults
zu verlassen). Live bestätigt ("gut soweit").
**M26: "Bekannte Router" hinterlegt jetzt die Seriennummer**
(2026-09-16). Nutzer-Szenario, live so passiert: mit einem zweiten
Router desselben Modells verbunden (Werks-Adresse+Zugangsdaten
identisch zum bisherigen Testrouter, `192.168.88.1`/`admin`) — die App
hat daraufhin den *bestehenden* "Bekannte Router"-Eintrag des alten
Testrouters einfach für den neuen umbenannt (`SavedRoutersStore.
recordSuccessfulConnection` matchte bisher nur auf Host+Benutzername,
kein Feld unterschied zwei physisch verschiedene Geräte an derselben
Werks-Adresse). Klick auf den alten Eintrag verband danach zwangsläufig
mit dem tatsächlich erreichbaren Gerät (dem neuen Router), nicht mit
dem gemeinten alten. Fix (Nutzervorschlag: "ist es nicht besser die
Seriennummer des Gerätes mit zu hinterlegen?"): `SavedRouter` bekam ein
neues optionales `serialNumber`-Feld (`/system routerboard`s
"serial-number", bereits seit M14 als `RouterBoardInfo.serialNumber`
ausgelesen — "unbekannt" wird dabei wie `nil` behandelt, sonst würden
zwei Geräte ohne auslesbare Seriennummer fälschlich als "gleich"
gelten). `recordSuccessfulConnection` matcht jetzt gestuft: exakte
Seriennummer gewinnt zuerst; ein einzelner bestehender Eintrag ohne
gespeicherte Seriennummer (alte, vor diesem Feld gespeicherte Einträge,
oder Geräte ohne physisches Routerboard) wird beim nächsten Connect
einmalig nachträglich damit befüllt statt dupliziert; alles andere legt
einen neuen, getrennten Eintrag an. In `SavedRouterRow` als kleine
"SN: ..."-Zeile sichtbar gemacht, damit zwei gleich benannte/adressierte
Einträge auch optisch unterscheidbar sind. Drei neue Tests
(`testDifferentSerialAtSameHostUsernameCreatesSeparateEntry`,
`testLegacyEntryWithoutSerialGetsUpgradedInPlaceNotDuplicated`,
`testSameSerialReconnectUpdatesRecencyNotDuplicated`). Migrationshinweis
für den konkreten Fall: der bereits verschmolzene Bestandseintrag zeigt
aktuell die Seriennummer des neuen Routers — der nächste Connect zum
alten Testrouter legt automatisch einen neuen, eigenen Eintrag an (da
dessen Seriennummer nicht mehr passt), bei Bedarf danach umbenennen.
Live bestätigt ("passt, funktioniert") — allerdings dabei zwei weitere,
eng verwandte Bugs gefunden, siehe M27 direkt danach.
**M27: Passwort-Anzeige-Button, Schlüsselbund-Trennung, `terse`-Fallback
verallgemeinert** (2026-09-16, beim Live-Test von M26 gefunden):
1. **Passwort-Anzeige-Button** (Nutzerwunsch: "setze hinter das Passwort
eine Auge-Symbol zum aufdecken des Passwortes, so kann ich prüfen, ob
die Daten übernommen wurden") — Augen-Icon neben dem Passwortfeld im
Verbinden-Tab, schaltet zwischen `SecureField` und `TextField` um.
War das Diagnose-Werkzeug, das die beiden folgenden Bugs erst
aufdeckte.
2. **Bug 35: Schlüsselbund teilte sich einen Eintrag zwischen zwei
Routern** — beim Umschalten zwischen zwei "Bekannte Router"-Einträgen
(seit M26 korrekt als getrennte Einträge geführt) blieb das
angezeigte Passwort trotzdem identisch. Aufwendig eingegrenzt: ein
temporärer Debug-Print bestätigte zunächst, dass `ConnectViewModel`s
`@Published`-Werte bei jedem Klick korrekt gesetzt wurden (auch die
View selbst rendert nachweislich jedes Mal frisch, per zweitem
Debug-Print in `mainContent` bestätigt) — zwei SwiftUI-`.id()`-
Reparaturversuche (erst auf Section-, dann auf Feld-Ebene) liefen
deshalb ins Leere, weil das eigentliche Problem gar keins der
Anzeige war. Tatsächliche Ursache: `KeychainService` speichert
Passwörter nur nach `"username@host"` — zwei physisch verschiedene
Router mit identischem Host+Benutzername (Werks-Adresse) teilen sich
dadurch denselben Schlüsselbund-Eintrag, unabhängig davon, dass
`SavedRoutersStore` sie seit M26 bereits korrekt als getrennte
Einträge führt. Fix: `KeychainService.save`/`loadPassword`/
`deletePassword` bekommen einen optionalen `serialNumber`-Parameter,
der den Account-Key auf `"username@host#seriennummer"` erweitert;
`loadPassword` fällt auf den alten, unqualifizierten Key zurück,
wenn kein qualifizierter Eintrag existiert (Rückwärtskompatibilität
für vor diesem Fix gespeicherte Passwörter). `ConnectViewModel.
connect()`s Passwort-Speicherung musste dafür hinter den
erfolgreichen Verbindungsaufbau verschoben werden (die Seriennummer
ist vorher noch nicht bekannt). Die beiden zuvor eingebauten
`.id()`-Umwege wieder entfernt, da wirkungslos und irreführend
(falsche Fehlerursache im Kommentar).
3. **Bug 36, direkt danach beim selben Live-Test gefunden: neuer Router
zeigte gar keine Routerboard-Infos, keine Seriennummer** — derselbe
Root Cause wie der vorherige "Jetzt prüfen"-Fehler (`/system package
update`, siehe oben): `/system routerboard print ... terse` schlägt
auf diesem Router/dieser RouterOS-Version mit `"expected end of
command (line 1 column N)"` fehl statt mit dem bisher einzig
abgefangenen `"bad parameter terse"`. `fetchMenuItems`s
Singleton-Fallback (Bug 8/20) griff deshalb nicht,
`routerBoardInfo` blieb `nil` (best-effort, Fehler wurde still
verschluckt) — betraf gleichzeitig die Seriennummer (M26) und die
komplette Routerboard-Anzeige im Verbinden-Tab. Fix:
`SSHTransport.fetchMenuItems`s Erkennung in eine neue
`rejectsTerse(_:)`-Hilfsfunktion ausgelagert, die jetzt beide
bekannten Formulierungen abfängt. Sicher eingegrenzt, da die
geprüfte Fehlermeldung ausschließlich vom exakten Aufruf `"<menuPath>
print without-paging terse"` stammt — jeder Parserfehler dort betrifft
per Konstruktion das anhängende `"terse"`. Behebt nebenbei auch den
"Jetzt prüfen"-Fehler von vorhin (`/system package update`), da
beide über denselben Code-Pfad laufen. Live bestätigt
("passt, funktioniert").
## Nächste Schritte
1. ~~M16: restliche `RouterOSSchemaCatalog.swift`-Sektionen übersetzen~~,
@@ -1713,6 +1803,10 @@ Nutzerwünsche in einer Runde):
dieses Symptom nochmal auftritt: zuerst per `defaults read` prüfen,
ob der Trust wirklich gespeichert wurde, nicht nur der UI-Bestätigung
vertrauen.
14. ~~M26 (Seriennummer bei "Bekannte Router") noch live testen~~ —
erledigt, siehe M27 oben (dabei zwei weitere Bugs gefunden und
gefixt: geteilter Schlüsselbund-Eintrag, `/system routerboard`s
`terse`-Ablehnung mit anderem Fehlertext als bisher erkannt).
Gitea-Remote `origin` ist eingerichtet und wird laufend gepusht (siehe
oben) — dieser Hinweis war veraltet, korrigiert am 2026-09-15.