M28: Übersicht Fokus-Modus + einheitliches Close-Button-Layout
Klick auf einen Knoten in der Übersicht öffnet jetzt ein schwebendes Popup mit der kompletten verbundenen Kette (neu OverviewGraph. connectedChain, volle transitive Hülle unabhängig vom Knotentyp, bewusst getrennt von der bestehenden highlightedNodeIDs), sauber im selben Spalten-Layout neu angeordnet, Rest des Diagramms abgedunkelt. Nicht-modales Overlay statt .sheet, damit die rechte Seitenleiste währenddessen bedienbar bleibt (Knoten direkt aus dem Popup heraus bearbeitbar). Popup-Größe passt sich automatisch dem Inhalt an, keine Scrollbalken. Der dabei entstandene Close-Button-Header (Titel + Spacer + X, fest oben, Divider direkt darunter) wurde auf alle vier Popup-Formulare der App vereinheitlicht: Experte-Bearbeiten-Sheet und die drei Devices-Sheets (Rohdaten, Netzwerk-Test, Port-Scan) — dort ersetzt er jeweils den bisherigen einzelnen "Schließen"-Button unten. Mehrere Design-Iterationen live mit dem User durchgespielt (Trennlinie im Canvas → separates Panel → Popup → .sheet → non-modales Overlay), finale Version live bestätigt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -209,4 +209,33 @@ final class OverviewGraphTests: XCTestCase {
|
||||
XCTAssertEqual(graph.highlightedNodeIDs(startingAt: poolID), [poolID])
|
||||
XCTAssertEqual(graph.highlightedNodeIDs(startingAt: addressID), [addressID])
|
||||
}
|
||||
|
||||
/// `connectedChain` backs the Übersicht "Fokus-Modus" and, unlike `highlightedNodeIDs`, always
|
||||
/// walks the full transitive closure regardless of node category — starting from the pool
|
||||
/// (two hops from the interface) should still reach every other node in the same graph used
|
||||
/// above.
|
||||
func testConnectedChainExpandsTransitivelyForEveryNodeKind() {
|
||||
let graph = OverviewViewModel.buildGraph(
|
||||
interfaces: [item(["name": "ether1", "type": "ether"])],
|
||||
bridgePorts: [], vlans: [], wireguardPeers: [],
|
||||
addresses: [item(["address": "192.168.88.1/24", "interface": "ether1", "network": "192.168.88.0"])],
|
||||
pools: [item(["name": "pool1", "ranges": "192.168.88.10-192.168.88.254"])],
|
||||
dhcpServers: [item(["name": "dhcp1", "interface": "ether1", "address-pool": "pool1"])],
|
||||
dhcpNetworks: [item(["address": "192.168.88.0/24", "gateway": "192.168.88.1"])],
|
||||
dhcpClients: [], routes: [], filterRules: [], natRules: [], addressLists: []
|
||||
)
|
||||
|
||||
let interfaceID = "Interfaces:ether1"
|
||||
let addressID = "IP-Adressen:192.168.88.1/24"
|
||||
let dhcpServerID = "Pools & DHCP:dhcp:dhcp1"
|
||||
let poolID = "Pools & DHCP:pool:pool1"
|
||||
let dhcpNetworkID = "Pools & DHCP:dhcpnet:192.168.88.0/24"
|
||||
let fullChain: Set<String> = [interfaceID, addressID, dhcpServerID, poolID, dhcpNetworkID]
|
||||
|
||||
XCTAssertEqual(graph.connectedChain(startingAt: poolID), fullChain)
|
||||
XCTAssertEqual(graph.connectedChain(startingAt: dhcpNetworkID), fullChain)
|
||||
XCTAssertEqual(graph.connectedChain(startingAt: interfaceID), fullChain)
|
||||
|
||||
XCTAssertEqual(graph.connectedChain(startingAt: "does-not-exist"), [])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user