M15 erweitert: Interfaces direkt editierbar (Übersicht + Experte-Tab)

Jedes Interface bekommt jetzt ein editTarget — VLAN-Interfaces über das
kuratierte /interface vlan-Schema (mit der eigenen .id aus /interface
vlan, nicht der generischen /interface-Liste), alle anderen Typen
(Ethernet, Bridge, WLAN, WireGuard) generisch über /interface.

Dabei live einen echten Stolperstein gefunden: für /interface gab es
bisher kein kuratiertes Schema, alle Felder landeten unbeschriftet in
"Weitere Parameter" — beim Versuch, ether5 zu ether51 umzubenennen,
wurde versehentlich default-name (RouterOS' Werksname, nie änderbar)
statt name geändert ("bad parameter default-name"). Fix: /interface
jetzt mit kuratiertem Schema (Name/Kommentar/Deaktiviert), Tooltip auf
"Name" warnt explizit vor der Verwechslung mit default-name.

Adress-Listen-Knoten bleiben bewusst weiter nicht editierbar (fassen
mehrere Einträge zusammen, bräuchten eine andere UI-Form).

Live bestätigt ("funktioniert"). 60 Unit-Tests grün. Manual.md/
HANDOFF.md aktualisiert.

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 15:10:55 +02:00
co-authored by Claude Sonnet 5
parent bd610b0a8c
commit 2e507aa9ef
6 changed files with 99 additions and 23 deletions
@@ -37,6 +37,29 @@ final class OverviewGraphTests: XCTestCase {
XCTAssertEqual(vlanNode?.subtitle, "VLAN 20")
}
/// A plain interface (no curated schema for bare "/interface" here) edits generically via
/// "/interface" using its own item id; a VLAN interface edits through the curated
/// "/interface vlan" schema using the VLAN's own item id from the "/interface vlan" listing,
/// not the generic "/interface" listing's id for the same object.
func testInterfaceEditTargetsUseGenericOrCuratedVlanSchema() {
let graph = OverviewViewModel.buildGraph(
interfaces: [
item(["name": "ether5", "type": "ether"], id: "*1"),
item(["name": "vlan20", "type": "vlan"], id: "*2")
],
bridgePorts: [],
vlans: [item(["name": "vlan20", "vlan-id": "20", "interface": "ether5"], id: "*A1")],
wireguardPeers: [], addresses: [], pools: [], dhcpServers: [], dhcpNetworks: [],
dhcpClients: [], routes: [], filterRules: [], natRules: [], addressLists: []
)
let ether5 = graph.nodes.first { $0.id == "Interfaces:ether5" }
XCTAssertEqual(ether5?.editTarget, OverviewNode.EditTarget(menuPath: "/interface", restPath: "interface", itemID: "*1"))
let vlan20 = graph.nodes.first { $0.id == "Interfaces:vlan20" }
XCTAssertEqual(vlan20?.editTarget, OverviewNode.EditTarget(menuPath: "/interface vlan", restPath: "interface/vlan", itemID: "*A1"))
}
func testDhcpServerEdgesToInterfaceAndPool() {
let graph = OverviewViewModel.buildGraph(
interfaces: [item(["name": "ether2", "type": "ether"])],