Bug 28: dynamische Routen (distance=0) nicht mehr über die Übersicht editierbar

Eine dynamische/verbundene Route (automatisch angelegt durch eine
IP-Adresse auf einem Interface) scheiterte beim Bearbeiten mit "no such
item (4)" — bestätigt per /ip route print detail: D-Flag, distance=0.
RouterOS' "dynamic"-Flag steht nicht zuverlässig in print terse
(dasselbe Problem schon bei DHCP-Leases dokumentiert), aber distance=0
ist ein verlässliches Signal, da keine echte statische Route das je
haben kann. Solche Routen bekommen jetzt kein editTarget mehr — ihre
.id ist ohnehin nicht stabil, RouterOS kann sie jederzeit neu anlegen.

59 Unit-Tests grün (neuer Test OverviewGraphTests.
testDynamicRouteHasNoEditTarget).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YDmUd93KxsYGr2kLTotWnG
This commit is contained in:
Kay
2026-09-15 13:54:11 +02:00
co-authored by Claude Sonnet 5
parent f25d02af17
commit 18fefc76a8
4 changed files with 67 additions and 18 deletions
+7
View File
@@ -790,3 +790,10 @@ Test, WLAN/Bonding/PPPoE-Live-Tests).
Merksatz für künftige neue Dateien notiert. Nach `xcodegen generate`
58 Unit-Tests grün (inkl. `ExpertViewModelTests` jetzt tatsächlich
ausgeführt). HANDOFF.md aktualisiert, Commit + Push.
- "neuer fehler: no such item (4)... /ip/route/set" → Nutzer bestätigte
auf Nachfrage per `/ip route print detail`: Route ist dynamisch
(`D`-Flag, `distance=0`, automatisch durch die Bridge-IP angelegt).
Bug 28: dynamische Routen bieten jetzt gar kein "Bearbeiten" mehr an
(`distance=0` als verlässliches Signal, da RouterOS' "dynamic"-Flag
nicht in `print terse` steht). 59 Unit-Tests grün, HANDOFF.md
aktualisiert, Commit + Push.
+35 -17
View File
@@ -567,6 +567,19 @@ lief — Xcodegen erzeugt die `sources`-Dateiliste einmalig beim
Generieren, eine neu angelegte Datei taucht im generierten `.xcodeproj`
nicht automatisch auf. **Merksatz: nach jeder neu angelegten Datei
`xcodegen generate` erneut laufen lassen, nicht nur nach Datei-Edits.**
**Auch nach dem Bug-27-Fix scheiterte dieselbe Route noch, jetzt mit
"no such item (4)"** (Bug 28) — der `/ip route print detail`-Gegencheck
bestätigte: die Route ist dynamisch (`D`-Flag, `distance=0`, automatisch
angelegt durch die IP-Adresse auf `bridge`). RouterOS' eigenes
"dynamic"-Flag steht nicht zuverlässig in `print terse` (dasselbe
Problem schon bei DHCP-Leases dokumentiert, siehe `LanDevice.swift`),
aber `distance=0` ist ein verlässliches Signal — keine echte statische
Route kann das haben (RouterOS erzwingt 1255 bei `set`). Fix:
Übersicht-Tab bietet für Routen mit `distance=0` gar kein "Bearbeiten"
mehr an (`editTarget=nil`) — die `.id` einer dynamischen Route ist
ohnehin nicht stabil, RouterOS kann sie jederzeit neu anlegen. 59
Unit-Tests grün (neuer Test `OverviewGraphTests.
testDynamicRouteHasNoEditTarget`).
## `xcodebuild test` hängt — Gatekeeper, kein Code-Bug
@@ -850,16 +863,19 @@ Bezug zu einem Router-Item).
**Live gegen Hardware verifiziert** — Nutzer bestätigte Kommentar-
Änderung an einer Firewall-Regel über den neuen Bearbeiten-Weg
("funktioniert"). Direkt danach beim Weitertesten drei echte Bugs in der
gemeinsamen Bearbeiten-Logik gefunden (Bug 25, 26, 27 — siehe Bug-Liste
oben): geleertes Textfeld blieb bestehen, Route-Bearbeiten scheiterte
erst an einem nur-lesbaren Feld (`immediate-gw`), dann am unveränderten
`distance=0` einer dynamischen Route. Alle drei betreffen `ExpertViewModel.
pendingCommand` und damit **auch den Experte-Tab direkt**, nicht nur den
neuen Übersicht-Weg — gefixt (vereinheitlicht: nur geänderte Felder
werden beim Bearbeiten gesendet, kuratiert wie unkuratiert), alle 58
Unit-Tests grün (inkl. neuer `ExpertViewModelTests` und `OverviewGraphTests.
testEditableNodesCarryTheirRouterOSMenuAndItemID`).
("funktioniert"). Direkt danach beim Weitertesten vier echte Bugs
gefunden (Bug 2528 — siehe Bug-Liste oben): geleertes Textfeld blieb
bestehen, Route-Bearbeiten scheiterte erst an einem nur-lesbaren Feld
(`immediate-gw`), dann am unveränderten `distance=0` einer dynamischen
Route, schließlich an deren instabiler `.id` ("no such item"). Bug
2527 betreffen `ExpertViewModel.pendingCommand` und damit **auch den
Experte-Tab direkt**, nicht nur den neuen Übersicht-Weg — gefixt
(vereinheitlicht: nur geänderte Felder werden beim Bearbeiten gesendet,
kuratiert wie unkuratiert). Bug 28 gefixt in `OverviewViewModel`: Routen
mit `distance=0` (dynamisch) bieten gar kein "Bearbeiten" mehr an. 59
Unit-Tests grün (inkl. neuer `ExpertViewModelTests` und
`OverviewGraphTests.testEditableNodesCarryTheirRouterOSMenuAndItemID`/
`testDynamicRouteHasNoEditTarget`).
## Stand der Milestones
@@ -960,13 +976,15 @@ testEditableNodesCarryTheirRouterOSMenuAndItemID`).
verdrängt) plus neue Fähigkeit: Knoten (IP-Adresse, Pool, DHCP-Server/
-Netzwerk/-Client, Route, Firewall-Filter-/NAT-Regel, WireGuard-Peer)
direkt über dieselbe Sheet wie im Experte-Tab bearbeiten und
zurückschreiben. Dabei drei Bugs in der gemeinsamen Bearbeiten-Logik
gefunden+gefixt (Bug 25, 26, 27 — betreffen auch den Experte-Tab
direkt): geleertes Feld blieb bestehen statt geleert zu werden,
Route-Bearbeiten scheiterte erst an einem nur-lesbaren Feld
(`immediate-gw`), dann am unveränderten `distance=0` einer dynamischen
Route. **Live gegen Hardware verifiziert** — Nutzer bestätigte
Kommentar-Änderung an einer Firewall-Regel über den neuen Weg
zurückschreiben. Dabei vier Bugs gefunden+gefixt (Bug 2528): geleertes
Feld blieb bestehen statt geleert zu werden, Route-Bearbeiten
scheiterte an einem nur-lesbaren Feld (`immediate-gw`), dann am
unveränderten `distance=0` einer dynamischen Route, schließlich an
deren instabiler `.id`. Bug 2527 betreffen auch den Experte-Tab
direkt, Bug 28 ist Übersicht-spezifisch (dynamische Routen bieten kein
"Bearbeiten" mehr an). **Live gegen Hardware verifiziert** — Nutzer
bestätigte Kommentar-Änderung an einer Firewall-Regel über den neuen
Weg
("funktioniert").
## Nächste Schritte
@@ -213,11 +213,20 @@ final class OverviewViewModel: ObservableObject {
guard let dst = item.fields["dst-address"] else { continue }
let gateway = item.fields["gateway"] ?? "?"
let id = "route:\(index)"
// Dynamic/connected routes (auto-created by an IP address on an interface) always
// report distance=0 no static route can ever have that (RouterOS enforces 1-255 on
// `set`). RouterOS' own "dynamic" flag isn't reliably present in `print terse` output
// (confirmed live for DHCP leases too, see `LanDevice.swift`), so distance=0 is the
// one dependable signal available here. Editing such a route failed live two ways:
// resending its own distance=0 was rejected outright, and its `.id` isn't stable
// (RouterOS can recreate/renumber a dynamic route at any time), so even a
// comment-only edit could hit "no such item" if the id had since changed underneath.
let isDynamic = item.fields["distance"] == "0"
nodes.append(OverviewNode(
id: id, category: .route, kind: "route",
title: dst, subtitle: "über \(gateway)",
detail: sortedDetail(item.fields),
editTarget: .init(menuPath: "/ip route", restPath: "ip/route", itemID: item.id)
editTarget: isDynamic ? nil : .init(menuPath: "/ip route", restPath: "ip/route", itemID: item.id)
))
if knownInterfaceNames.contains(gateway) {
edges.append(OverviewEdge(from: nodeID(.interface, gateway), to: id, label: nil, kind: .route))
@@ -135,4 +135,19 @@ final class OverviewGraphTests: XCTestCase {
let addressList = graph.nodes.first { $0.id == "Firewall & NAT:addrlist:gesperrt" }
XCTAssertNil(addressList?.editTarget)
}
/// A dynamic/connected route (distance=0, auto-created by an interface address) must not be
/// offered for editing confirmed live: RouterOS rejects resending its own distance=0
/// ("value of distance out of range (1...255)"), and its .id isn't stable either.
func testDynamicRouteHasNoEditTarget() {
let graph = OverviewViewModel.buildGraph(
interfaces: [], bridgePorts: [], vlans: [], wireguardPeers: [], addresses: [],
pools: [], dhcpServers: [], dhcpNetworks: [], dhcpClients: [],
routes: [item(["dst-address": "192.168.88.0/24", "gateway": "bridge", "distance": "0"], id: "*201C5060")],
filterRules: [], natRules: [], addressLists: []
)
let route = graph.nodes.first { $0.id == "route:0" }
XCTAssertNil(route?.editTarget)
}
}