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
@@ -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))