forked from kay/RouterOS
M17-M19: Bekannte Router, Live-Traffic-Anzeige, Übersicht-Animation+Drag
M17: "Bekannte Router" im Verbinden-Tab (SavedRouter/SavedRoutersStore), Standort-Freitextfeld, Scroll-Cap ab 4 Einträgen. Bugfix: Umbenennen- TextField steckte in einem sich selbst deaktivierenden Button. M18: Live-Traffic-Punkt an Interfaces (InterfaceTrafficMonitor, eigene SSH-Verbindung, monitor-traffic-Polling). Dabei zwei reale CLI-Parser-Bugs gefunden und gefixt: running/disabled-Flags werden als Buchstaben vor dem ersten Feld codiert, nicht als key=value; monitor-traffic liefert "50.7kbps" statt einer reinen Zahl. M19: Übersicht-Tab — animierte Flussrichtung auf allen Verbindungslinien (TimelineView+dashPhase), frei verschiebbare Knoten mit Live-folgenden Linien, Zurücksetzen-Button. Zusätzlich (noch nicht live getestet, nur Build+Unit-Tests grün): LAN-Port-Konflikt-Prüfung im Einrichten-Assistenten mit doppelter Sicherheitsbestätigung, "Fertig"-Button nach erfolgreichem Anwenden. 82 Tests grün. HANDOFF.md/README.md/Manual.md/CHATLOG.md aktualisiert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDmUd93KxsYGr2kLTotWnG
This commit is contained in:
+137
@@ -1066,6 +1066,143 @@ Unit-Tests grün (inkl. neuer `ExpertViewModelTests` und
|
||||
Fehlertexte, keine kuratierten UI-Strings; offen bleiben die übrigen
|
||||
vier Tabs (Einrichten/Übersicht/Geräte/Sicherungen), die noch auf
|
||||
festen deutschen String-Literalen laufen.
|
||||
- ✅ M17: "Bekannte Router" im Verbinden-Tab — Nutzerwunsch: "mehrere
|
||||
Logins hinterlegen können, quasi eine Liste bekannter Router". Neue
|
||||
`SavedRouter`/`SavedRoutersStore` (JSON-Array in UserDefaults, kein
|
||||
eigenes Verzeichnis wie bei Backups nötig) merkt sich Host+Benutzername
|
||||
nach jeder erfolgreichen Verbindung, mit einem beim ersten Mal
|
||||
automatisch gesetzten Anzeigenamen — `RouterDeviceInfo.boardName`
|
||||
(RouterOS' "board-name", die Marketing-Bezeichnung wie "hEX", die
|
||||
bereits beim Verbinden geladen wird), als nächstliegende Auslegung von
|
||||
"werksmäßige Bezeichnung". Der Name bleibt bei späteren Verbindungen
|
||||
zum selben Host/Benutzernamen unverändert, auch wenn sich die erkannte
|
||||
Modellbezeichnung ändern sollte — nur explizites Umbenennen überschreibt
|
||||
ihn. Passwörter bleiben unverändert im macOS-Schlüsselbund (`Keychain-
|
||||
Service`, Schlüssel "Benutzername@Host"), diese Liste speichert nur
|
||||
Host/Benutzername/Anzeigename/letzte Verbindungszeit. Klick auf einen
|
||||
Eintrag füllt Host/Benutzername/Passwort ins Formular, ohne sofort zu
|
||||
verbinden; ein "…"-Menü pro Eintrag bietet Umbenennen (inline) und
|
||||
Entfernen. **Live bestätigt** ("ok, funktioniert jetzt") — allerdings
|
||||
erst im zweiten Anlauf: die Liste füllt sich nur bei einer *neuen*
|
||||
erfolgreichen Verbindung nach dem Rebuild, eine bereits laufende
|
||||
Alt-Verbindung zählt nicht rückwirkend — das war der erste
|
||||
Verwirrungspunkt beim Testen ("ich sehe 'Bekannte Router' nicht").
|
||||
74 Tests grün (5 neue `SavedRoutersStoreTests` + 5 neue
|
||||
`ConnectionServiceTests`-Ergänzungen für die parallel gebaute
|
||||
Port-Konflikt-Prüfung, siehe unten).
|
||||
- **Bugfix, live gefunden:** Umbenennen tat nichts — das TextField steckte
|
||||
innerhalb eines `Button(action: onSelect)`, dessen `.disabled(isRenaming)`
|
||||
auch das TextField selbst deaktivierte. Fix: kein umschließender Button
|
||||
mehr, `.onTapGesture` übernimmt die Zeilen-Auswahl, TextField bekommt
|
||||
eigenen `@FocusState`. **Live bestätigt** ("funktioniert").
|
||||
- **Erweiterung, Nutzerwunsch:** freies Standort-Feld (Etage/Raum/Zweck)
|
||||
pro Eintrag, "damit macht die Zuordnung bei mehreren Geräten einfacher".
|
||||
`SavedRouter.location: String` (leer=nicht gesetzt), eigene
|
||||
`SavedRoutersStore.updateLocation(_:to:)`. Menüpunkt "Umbenennen" wurde
|
||||
zu "Bearbeiten" (öffnet Name + Standort gemeinsam). Rückwärtskompatibles
|
||||
Decoding (`init(from:)` mit `decodeIfPresent`) für bereits gespeicherte
|
||||
Listen ohne `location`-Feld. 76 Tests grün (2 neue). **Live bestätigt**
|
||||
("funktioniert").
|
||||
- **Erweiterung, Nutzerwunsch:** Liste auf ca. 4 sichtbare Einträge
|
||||
gedeckelt (eigene `ScrollView` mit `maxHeight`-Cap statt das ganze
|
||||
Formular wachsen zu lassen) — "die Liste der gespeicherten Router soll
|
||||
nicht zu lang werden". Noch nicht separat live bestätigt (kein
|
||||
ausdrückliches Feedback, aber auch kein Einwand in den folgenden
|
||||
Test-Runden).
|
||||
|
||||
**Gleichzeitig gebaut, noch nicht live bestätigt** (nur Build+Unit-Tests
|
||||
grün, wartet auf Test durch den Nutzer in Xcode):
|
||||
- **LAN-Port-Konflikt-Prüfung im Einrichten-Assistenten** —
|
||||
Nutzerwunsch: vor dem Anlegen eines neuen LAN prüfen, ob der gewählte
|
||||
Port frei ist (nicht in einer Bridge, keine bestehende IP-Adresse, kein
|
||||
WAN-DHCP-Client/PPPoE), und bei einem Konflikt mit doppelter
|
||||
Sicherheitsbestätigung nachfragen, bevor die App den Port selbständig
|
||||
freimacht. Neu: `PortConflict`-Modell (Core/Models), `ConnectionService.
|
||||
checkPortConflict(interfaceName:)` (liest `/interface bridge port`,
|
||||
`/ip address`, `/ip dhcp-client`, `/interface pppoe-client` parallel
|
||||
per `async let`), `SetupViewModel.checkPortConflict(for:)`/
|
||||
`acknowledgePortConflict(for:)`/`hasUnresolvedPortConflict(for:)`,
|
||||
`LanStepView`s neue `PortConflictWarningView` mit zwei aufeinander
|
||||
folgenden `.confirmationDialog`s (Konsequenzen erklären, dann "Wirklich
|
||||
sicher?"). Tatsächlich ausgeführt wird die Port-Freigabe erst beim
|
||||
finalen "Jetzt anwenden" im Review-Schritt, nicht sofort bei der
|
||||
Bestätigung. "Weiter" bleibt gesperrt, bis jeder Konflikt entweder
|
||||
bestätigt oder durch Wahl eines anderen Ports vermieden wurde.
|
||||
- **"Fertig"-Button nach erfolgreichem Anwenden** — vorher gab es nach
|
||||
"Jetzt anwenden" nur einen (weiterhin aktiven) "Zurück"-Button und den
|
||||
jetzt dauerhaft deaktivierten "Jetzt anwenden"-Button, keinen klaren
|
||||
Abschluss. `SetupViewModel.finish()` setzt den Assistenten komplett
|
||||
zurück (frische Default-Werte, `prepareDefaults` erneut gegen die
|
||||
aktuellen Live-Interfaces) für einen sauberen nächsten Durchlauf.
|
||||
|
||||
- ✅ M18: Live-Traffic-Anzeige im Verbinden-Tab — Nutzerwunsch: der Punkt
|
||||
vor jedem Interface soll erkennen lassen, ob es "aktuell in Verwendung
|
||||
ist und Daten überträgt", nicht nur ob der Link steht. Neue
|
||||
`InterfaceTraffic`/`InterfaceTrafficMonitor` (Actor, eigene dedizierte
|
||||
SSH-Verbindung unabhängig von REST/SSH-Hauptverbindung — RouterOS' `/
|
||||
interface monitor-traffic` ist ein reiner CLI-Befehl ohne REST-
|
||||
Äquivalent, gleiche Begründung wie bei `BackupService`/`UpdateService`),
|
||||
pollt alle 3s pro Interface. `ConnectViewModel.startTrafficPolling(...)`/
|
||||
`stopTrafficPolling()`, angestoßen über `.onChange(of: connectionService.
|
||||
state)`. Punkt: grau = kein Link, grün (fest) = Link aber keine Daten,
|
||||
grün pulsierend = überträgt gerade tatsächlich Daten.
|
||||
- **Zwei reale Bugs beim ersten Live-Test gefunden** ("alle buttons
|
||||
sind grau, keine Animation") — auf Bitte des Nutzers zwei Diagnose-
|
||||
Befehle direkt am Router ausgeführt statt zu raten:
|
||||
1. `RouterOSCliParser.parseInterfaces` suchte `running=`/`disabled=`
|
||||
als `key=value`-Paare — die gibt es in echtem `/interface print
|
||||
terse`-Output gar nicht. RouterOS codiert das stattdessen als
|
||||
Buchstaben-Flags vor dem ersten Feld (`"0 R name=ether1 ..."`,
|
||||
`"2 S name=ether3 ..."`: R=running, X=disabled, S=Bridge-Slave)
|
||||
— **jedes Interface las `running` bisher immer als `false`**, ein
|
||||
vorbestehender, nie zuvor aufgefallener Bug (nicht durch M18
|
||||
verursacht, nur durch M18 erstmals sichtbar geworden). Fix: neue
|
||||
`flagsColumn(of:)`-Hilfsfunktion isoliert den Flag-Bereich vor dem
|
||||
ersten "=", prüft dort auf "R"/"X" statt auf nicht existierende
|
||||
Schlüssel.
|
||||
2. `/interface monitor-traffic ... once` liefert Werte wie
|
||||
`"50.7kbps"` (mit Einheit und Dezimalpunkt), keine reine Zahl —
|
||||
`Int(...)` scheiterte daran lautlos zu 0. Fix: `SSHTransport.
|
||||
parseBitsPerSecond(_:)` erkennt Gbps/Mbps/kbps/bps-Suffixe
|
||||
(längere Suffixe zuerst geprüft, da "kbps" selbst auf "bps"
|
||||
endet).
|
||||
- 82 Tests grün (2 neue Parser-Tests mit dem exakten vom Nutzer
|
||||
eingefügten Live-Output + 5 neue Bits-pro-Sekunde-Tests). **Live
|
||||
bestätigt** ("sehr gut").
|
||||
|
||||
- ✅ M19: Übersicht-Tab — animierte Flussrichtung + verschiebbare Knoten.
|
||||
- **Animierte Flussrichtung** (Nutzerwunsch: "die Linien im Übersicht-
|
||||
Tab auch animiert... mit einer animierten Flußrichtung"): alle
|
||||
Verbindungslinien laufen jetzt gestrichelt mit über die Zeit
|
||||
wandernder `dashPhase` in Richtung "von → nach". Technisch über
|
||||
`TimelineView(.animation)` um den bestehenden `Canvas` gelegt — ein
|
||||
reiner `@State`-Wert mit `withAnimation(.repeatForever)` hätte den
|
||||
(immediate-mode) `Canvas` nicht laufend neu gezeichnet,
|
||||
`TimelineView` ruft den Draw-Closure dagegen bei jedem Frame mit
|
||||
frischem Datum neu auf.
|
||||
- **Verschiebbare Knoten + Zurücksetzen-Button** (Nutzerwunsch: "kann
|
||||
man die einzelnen Kästchen verschiebbar machen? ... Die Linien
|
||||
sollten den Kästchen automatisch folgen. ein Button für den Reset
|
||||
wäre super"): neuer `nodeOffsets`-State pro Knoten-ID plus ein
|
||||
`@GestureState` für die gerade laufende Drag-Bewegung, kombiniert in
|
||||
`effectivePositions` — sowohl Knoten-Karten als auch `EdgesCanvas`
|
||||
zeichnen auf dieser verschobenen Position, Linien folgen live mit.
|
||||
`.simultaneousGesture` (nicht `.gesture`) fürs Draggen, damit der
|
||||
bestehende Klick-zum-Auswählen weiter funktioniert. Toolbar-Button
|
||||
"Zurücksetzen" leert `nodeOffsets`, nur aktiv wenn etwas verschoben
|
||||
wurde.
|
||||
- **Zwei kleine UI-Nachbesserungen, beide live gefunden:** Der
|
||||
Reset-Button erschien zunächst gar nicht (`Label` mit Text+Icon
|
||||
braucht mehr Platz als die reinen Icon-Buttons davor, vermutlich
|
||||
von macOS in die Toolbar-Overflow verschoben) — behoben durch
|
||||
Icon-only, dann von Nutzer als "verwirrend, ähnlich wie Refresh"
|
||||
zurückgemeldet — endgültig auf einen reinen Text-Button
|
||||
"Zurücksetzen" umgestellt (wie der bereits funktionierende
|
||||
"100%"-Button).
|
||||
- 82 Tests grün (unverändert — reine SwiftUI-Interaktions-/Animations-
|
||||
Logik ohne neue reine Parser-Funktionen). **Live bestätigt**
|
||||
("das vierschieben funktioniert super, die Linien folgen auch" /
|
||||
"ok, der button ist da und funktioniert" / "funktioniert").
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
|
||||
Reference in New Issue
Block a user