Diagramm-Fixes: echter Geometrie-Bug behoben (VLAN-/Bridge-Port-Kanten laufen innerhalb derselben Spalte, eine Firewall/NAT-Kante rückwärts — die Kurven-Berechnung nahm immer "rechts raus, links rein" an und schoss dabei über den Canvas hinaus, links abgeschnitten). Hover-Flackern durch Trägheit + Beschränkung auf hervorgehobene Kanten behoben. Klick-vs- Hover-Priorität vertauscht (Klick gewinnt jetzt über Hover, vorher verdrängte das Streifen fremder Karten beim Nachfahren einer Linie die Auswahl). Linien-Klick zeigt jetzt volle Erklärung im rechten Panel (EdgeDetailView) statt nur Hover-Tooltip. Ein Auto-Fit-Versuch (GeometryReader) brach das Scroll-Verhalten und wurde wieder zurückgezogen. Neue Fähigkeit: ein Knoten (IP-Adresse, Pool, DHCP-Server/-Netzwerk/ -Client, Route, Firewall-Filter-/NAT-Regel, WireGuard-Peer) lässt sich direkt über denselben Dialog wie im Experte-Tab bearbeiten und zurückschreiben (OverviewNode.EditTarget + wiederverwendete ExpertItemEditView). Live bestätigt: Kommentar-Änderung an einer Firewall-Regel. 59 Unit-Tests grün. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDmUd93KxsYGr2kLTotWnG
99 lines
4.1 KiB
Swift
99 lines
4.1 KiB
Swift
import Foundation
|
|
|
|
/// One box in the Übersicht diagram — an interface, IP address, DHCP server, firewall rule etc.
|
|
/// `kind` is a finer-grained tag than `category` (e.g. "vlan" vs "bridge" within the interface
|
|
/// category) used only to pick an icon/color, never for graph logic.
|
|
struct OverviewNode: Identifiable, Equatable {
|
|
enum Category: String, CaseIterable, Identifiable {
|
|
case interface = "Interfaces"
|
|
case ipAddress = "IP-Adressen"
|
|
case service = "Pools & DHCP"
|
|
case route = "Routen"
|
|
case firewall = "Firewall & NAT"
|
|
|
|
var id: String { rawValue }
|
|
}
|
|
|
|
/// Which live RouterOS item this node was built from, and where to write changes back to —
|
|
/// nil for nodes that don't map to exactly one editable item (e.g. an address-list node folds
|
|
/// several list entries together). Lets the Übersicht tab open the same edit form the
|
|
/// Experte-Tab uses, pre-filled, instead of duplicating its field schemas/write logic.
|
|
struct EditTarget: Equatable {
|
|
let menuPath: String
|
|
let restPath: String
|
|
/// RouterOS' own ".id" for this item (e.g. "*7") — required to `set` the right one.
|
|
let itemID: String
|
|
}
|
|
|
|
let id: String
|
|
let category: Category
|
|
let kind: String
|
|
let title: String
|
|
let subtitle: String?
|
|
/// Full raw fields, shown in the detail panel when this node is selected.
|
|
let detail: [(key: String, value: String)]
|
|
let editTarget: EditTarget?
|
|
|
|
init(
|
|
id: String, category: Category, kind: String, title: String, subtitle: String?,
|
|
detail: [(key: String, value: String)], editTarget: EditTarget? = nil
|
|
) {
|
|
self.id = id
|
|
self.category = category
|
|
self.kind = kind
|
|
self.title = title
|
|
self.subtitle = subtitle
|
|
self.detail = detail
|
|
self.editTarget = editTarget
|
|
}
|
|
|
|
static func == (lhs: OverviewNode, rhs: OverviewNode) -> Bool {
|
|
lhs.id == rhs.id && lhs.category == rhs.category && lhs.kind == rhs.kind
|
|
&& lhs.title == rhs.title && lhs.subtitle == rhs.subtitle
|
|
&& lhs.detail.map(\.key) == rhs.detail.map(\.key) && lhs.detail.map(\.value) == rhs.detail.map(\.value)
|
|
&& lhs.editTarget == rhs.editTarget
|
|
}
|
|
}
|
|
|
|
/// What kind of dependency an edge represents — drives its color in the diagram, so different
|
|
/// relationship types (a VLAN's parent link vs. a firewall rule's interface) are visually
|
|
/// distinct at a glance, not just distinguishable by hovering.
|
|
enum OverviewEdgeKind: String, CaseIterable, Identifiable {
|
|
case vlan = "VLAN → Basis-Interface"
|
|
case bridgePort = "Bridge-Port"
|
|
case wireguardPeer = "WireGuard-Peer"
|
|
case ipAddress = "Interface → IP-Adresse"
|
|
case dhcp = "DHCP / Pool"
|
|
case route = "Route → Interface"
|
|
case firewallInterface = "Firewall/NAT → Interface"
|
|
case addressList = "Adress-Liste → Regel"
|
|
|
|
var id: String { rawValue }
|
|
}
|
|
|
|
/// One line in the diagram, always drawn from an earlier column to a later one (or within a
|
|
/// column) — direction follows the actual dependency (e.g. an interface "carries" an IP address,
|
|
/// a DHCP server "uses" a pool), not just which menu was read first.
|
|
struct OverviewEdge: Identifiable, Equatable {
|
|
var id: String { "\(from)->\(to)>\(label ?? "")" }
|
|
let from: String
|
|
let to: String
|
|
let label: String?
|
|
let kind: OverviewEdgeKind
|
|
}
|
|
|
|
struct OverviewGraph: Equatable {
|
|
var nodes: [OverviewNode] = []
|
|
var edges: [OverviewEdge] = []
|
|
|
|
/// Router areas that exist but aren't part of this topology diagram (VPN-Benutzer, WLAN-
|
|
/// Sicherheitsprofile, Queues, System, Werkzeuge — none of them wire into the interface/IP/
|
|
/// firewall dependency chain this diagram is about). Listed so nothing is silently hidden;
|
|
/// full access to all of them stays in the Experte-Tab.
|
|
static let unmappedAreas = [
|
|
"VPN: PPP-Benutzer/-Profile", "WLAN-Sicherheitsprofile", "Queues/Bandbreiten-Steuerung",
|
|
"System (Name/Uhrzeit/Scheduler/Skripte/Benutzerkonten)", "Werkzeuge (Netwatch/E-Mail)",
|
|
"Firewall: Mangle- und Raw-Regeln"
|
|
]
|
|
}
|