Neuer Tab zeigt die komplette aktuelle Router-Konfiguration als grafisches Diagramm: Interfaces, IP-Adressen, DHCP/Pools, Routen und Firewall/NAT in Spalten, verbunden durch Linien, die RouterOS' eigene Referenzfelder abbilden (VLAN->Basis-Interface, DHCP-Server->Pool, Firewall-Regel-> Interface/Adress-Liste usw.), nicht geraten. Rein lesend, kein apply(). Verbindungsarten sind farblich getrennt (8 Kategorien), Hover/Klick auf eine Karte hebt ihre Linien hervor und blendet den Rest ab. Detail-Panel zeigt Rohfelder + Verbindungen; Legende erklärt Spalten, Farben und listet bewusst nicht gegraphte Bereiche (VPN, WLAN-Sicherheitsprofile, Queues, System, Werkzeuge, Mangle/Raw), die weiterhin im Experte-Tab erreichbar bleiben. OverviewViewModel.buildGraph ist eine reine, nonisolated Funktion, getestet in OverviewGraphTests (6 Fälle: IP/Interface, VLAN-Parent, DHCP->Pool, DHCP-Netzwerk->passende IP-Adresse über RouterOS' eigenes "network"-Feld, Route nur bei Interface-Gateway, Firewall->Interface/ Adress-Liste). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CTgRxJTzaQwaRkngbaE1GJ
42 lines
1.9 KiB
Swift
42 lines
1.9 KiB
Swift
import CoreGraphics
|
|
|
|
/// Fixed column/row grid layout for the Übersicht diagram — pure geometry, no SwiftUI. Columns
|
|
/// follow the dependency order left-to-right (interfaces are the foundation everything else
|
|
/// attaches to); nodes within a column are stacked top-to-bottom, sorted so repeated runs render
|
|
/// in a stable order across reloads.
|
|
struct OverviewLayoutResult {
|
|
var positions: [String: CGPoint] = [:]
|
|
var canvasSize: CGSize = .zero
|
|
}
|
|
|
|
enum OverviewLayout {
|
|
static let columnOrder: [OverviewNode.Category] = [.interface, .ipAddress, .service, .route, .firewall]
|
|
static let columnWidth: CGFloat = 220
|
|
static let rowHeight: CGFloat = 64
|
|
static let nodeWidth: CGFloat = 180
|
|
static let nodeHeight: CGFloat = 50
|
|
static let topInset: CGFloat = 44
|
|
static let leftInset: CGFloat = 40
|
|
|
|
static func layout(for graph: OverviewGraph) -> OverviewLayoutResult {
|
|
var positions: [String: CGPoint] = [:]
|
|
var maxRows = 0
|
|
for (columnIndex, category) in columnOrder.enumerated() {
|
|
let nodesInColumn = graph.nodes
|
|
.filter { $0.category == category }
|
|
.sorted { lhs, rhs in
|
|
lhs.kind == rhs.kind ? lhs.title < rhs.title : lhs.kind < rhs.kind
|
|
}
|
|
maxRows = max(maxRows, nodesInColumn.count)
|
|
let x = leftInset + CGFloat(columnIndex) * columnWidth + nodeWidth / 2
|
|
for (row, node) in nodesInColumn.enumerated() {
|
|
let y = topInset + CGFloat(row) * rowHeight + nodeHeight / 2
|
|
positions[node.id] = CGPoint(x: x, y: y)
|
|
}
|
|
}
|
|
let width = leftInset * 2 + CGFloat(columnOrder.count) * columnWidth
|
|
let height = topInset * 2 + CGFloat(max(maxRows, 1)) * rowHeight
|
|
return OverviewLayoutResult(positions: positions, canvasSize: CGSize(width: width, height: height))
|
|
}
|
|
}
|