import 'network_device.dart'; import 'network_link.dart'; import 'network_location.dart'; import 'network_port.dart'; import 'network_site.dart'; import 'network_vlan.dart'; /// Client-side aggregate view-model assembled from raw network_* rows. /// /// The canvas consumes this directly rather than re-querying tables for every /// pan/zoom event. Built once per site by [TopologyGraph.build] and cached in /// the topology provider. class TopologyNode { TopologyNode({ required this.device, required this.ports, }); final NetworkDevice device; final List ports; String get id => device.id; } class TopologyEdge { TopologyEdge({ required this.link, required this.fromDeviceId, required this.toDeviceId, required this.fromPortLabel, required this.toPortLabel, }); final NetworkLink link; final String fromDeviceId; final String toDeviceId; final String fromPortLabel; final String toPortLabel; } enum TopologyViewMode { physical, logical; String get label { switch (this) { case TopologyViewMode.physical: return 'Physical'; case TopologyViewMode.logical: return 'Logical'; } } } class TopologyGraph { TopologyGraph({ required this.site, required this.locations, required this.nodes, required this.edges, required this.vlans, }); final NetworkSite? site; final List locations; final List nodes; final List edges; final List vlans; bool get isEmpty => nodes.isEmpty; /// Build a graph for a specific site from raw tables. /// /// Filters devices to those in the site's location subtree. If [site] is /// null, all devices with no location are included (orphan view). static TopologyGraph build({ required NetworkSite? site, required List allLocations, required List allDevices, required List allPorts, required List allLinks, required List allVlans, }) { final siteLocations = site == null ? [] : allLocations.where((l) => l.siteId == site.id).toList(); final siteLocationIds = siteLocations.map((l) => l.id).toSet(); final siteDevices = site == null ? allDevices.where((d) => d.locationId == null).toList() : allDevices .where((d) => d.locationId != null && siteLocationIds.contains(d.locationId)) .toList(); final siteDeviceIds = siteDevices.map((d) => d.id).toSet(); final portsByDevice = >{}; for (final port in allPorts) { if (!siteDeviceIds.contains(port.deviceId)) continue; portsByDevice.putIfAbsent(port.deviceId, () => []).add(port); } final portToDevice = {}; final portLabel = {}; for (final port in allPorts) { portToDevice[port.id] = port.deviceId; portLabel[port.id] = port.portNumber; } final nodes = siteDevices .map((d) => TopologyNode( device: d, ports: portsByDevice[d.id] ?? const [], )) .toList(); final edges = []; for (final link in allLinks) { final fromDeviceId = portToDevice[link.portA]; final toDeviceId = portToDevice[link.portB]; if (fromDeviceId == null || toDeviceId == null) continue; if (!siteDeviceIds.contains(fromDeviceId) || !siteDeviceIds.contains(toDeviceId)) { continue; } edges.add(TopologyEdge( link: link, fromDeviceId: fromDeviceId, toDeviceId: toDeviceId, fromPortLabel: portLabel[link.portA] ?? '', toPortLabel: portLabel[link.portB] ?? '', )); } return TopologyGraph( site: site, locations: siteLocations, nodes: nodes, edges: edges, vlans: allVlans, ); } }