Übersicht: Interface-Klick hebt transitiv alle zusammenhängenden Linien hervor
OverviewGraph.highlightedNodeIDs(startingAt:) macht für Interface-Knoten eine BFS über alle Kanten statt nur 1-Hop-Matching, damit z.B. "DHCP-Server -> Pool" oder "IP-Adresse -> DHCP-Netzwerk" mit sichtbar werden. Andere Knotentypen bleiben unverändert bei 1-Hop. Logik isoliert unit-getestet (GUI selbst nicht automatisiert klickbar). 61 Tests grün. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDmUd93KxsYGr2kLTotWnG
This commit is contained in:
@@ -95,4 +95,33 @@ struct OverviewGraph: Equatable {
|
||||
"System (Name/Uhrzeit/Scheduler/Skripte/Benutzerkonten)", "Werkzeuge (Netwatch/E-Mail)",
|
||||
"Firewall: Mangle- und Raw-Regeln"
|
||||
]
|
||||
|
||||
/// Node IDs whose edges the Übersicht diagram should draw at full strength when `nodeID` is
|
||||
/// selected/hovered. Normally just `nodeID` itself (one-hop matching against `edges`) — but
|
||||
/// for an Interface node specifically, the user asked to see *every* line that traces back to
|
||||
/// it, not just the ones directly touching it (e.g. a DHCP server's own pool, or an IP
|
||||
/// address's DHCP-network options, sit a second hop away from the interface itself). Confirmed
|
||||
/// scope (2026-09-15): only interfaces expand this way — every other node kind keeps the
|
||||
/// original one-hop highlight.
|
||||
func highlightedNodeIDs(startingAt nodeID: String) -> Set<String> {
|
||||
guard let node = nodes.first(where: { $0.id == nodeID }), node.category == .interface else {
|
||||
return [nodeID]
|
||||
}
|
||||
var visited: Set<String> = [nodeID]
|
||||
var frontier = [nodeID]
|
||||
while !frontier.isEmpty {
|
||||
var next: [String] = []
|
||||
for current in frontier {
|
||||
for edge in edges {
|
||||
if edge.from == current, visited.insert(edge.to).inserted {
|
||||
next.append(edge.to)
|
||||
} else if edge.to == current, visited.insert(edge.from).inserted {
|
||||
next.append(edge.from)
|
||||
}
|
||||
}
|
||||
}
|
||||
frontier = next
|
||||
}
|
||||
return visited
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,13 @@ struct OverviewView: View {
|
||||
/// wins" order made the highlight jump to whatever card the cursor grazed en route.
|
||||
private var highlightNodeID: String? { viewModel.selectedNodeID ?? hoveredNodeID }
|
||||
|
||||
/// Node IDs whose edges should draw at full strength — see `OverviewGraph.highlightedNodeIDs`
|
||||
/// for the one-hop-vs-interface-transitive-closure logic itself.
|
||||
private var highlightedNodeIDs: Set<String> {
|
||||
guard let id = highlightNodeID else { return [] }
|
||||
return viewModel.graph.highlightedNodeIDs(startingAt: id)
|
||||
}
|
||||
|
||||
init(connectionService: ConnectionService) {
|
||||
self.connectionService = connectionService
|
||||
_viewModel = StateObject(wrappedValue: OverviewViewModel(connectionService: connectionService))
|
||||
@@ -143,7 +150,7 @@ struct OverviewView: View {
|
||||
EdgesCanvas(
|
||||
graph: viewModel.graph,
|
||||
positions: layout.positions,
|
||||
highlightNodeID: highlightNodeID,
|
||||
highlightedNodeIDs: highlightedNodeIDs,
|
||||
hoveredEdge: $hoveredEdge,
|
||||
hoverPoint: $hoverPoint,
|
||||
selectedEdge: $selectedEdge
|
||||
@@ -223,7 +230,7 @@ struct OverviewView: View {
|
||||
private struct EdgesCanvas: View {
|
||||
let graph: OverviewGraph
|
||||
let positions: [String: CGPoint]
|
||||
let highlightNodeID: String?
|
||||
let highlightedNodeIDs: Set<String>
|
||||
@Binding var hoveredEdge: OverviewEdge?
|
||||
@Binding var hoverPoint: CGPoint
|
||||
@Binding var selectedEdge: OverviewEdge?
|
||||
@@ -296,8 +303,8 @@ private struct EdgesCanvas: View {
|
||||
Canvas { context, _ in
|
||||
for geometry in geometries {
|
||||
let edge = geometry.edge
|
||||
let isConnected = highlightNodeID != nil && (edge.from == highlightNodeID || edge.to == highlightNodeID)
|
||||
let isDimmed = highlightNodeID != nil && !isConnected
|
||||
let isConnected = highlightedNodeIDs.contains(edge.from) || highlightedNodeIDs.contains(edge.to)
|
||||
let isDimmed = !highlightedNodeIDs.isEmpty && !isConnected
|
||||
let isHovered = hoveredEdge?.id == edge.id || selectedEdge?.id == edge.id
|
||||
var path = Path()
|
||||
path.move(to: geometry.start)
|
||||
@@ -335,8 +342,8 @@ private struct EdgesCanvas: View {
|
||||
/// hovering feel like it kept jumping to the wrong line. Once the user has already narrowed
|
||||
/// things down to one node's connections, hover only needs to disambiguate between those.
|
||||
private var candidateGeometries: [EdgeGeometry] {
|
||||
guard let highlightNodeID else { return geometries }
|
||||
return geometries.filter { $0.edge.from == highlightNodeID || $0.edge.to == highlightNodeID }
|
||||
guard !highlightedNodeIDs.isEmpty else { return geometries }
|
||||
return geometries.filter { highlightedNodeIDs.contains($0.edge.from) || highlightedNodeIDs.contains($0.edge.to) }
|
||||
}
|
||||
|
||||
/// Several edges often fan out from the exact same point (e.g. every edge leaving one
|
||||
|
||||
Reference in New Issue
Block a user