144 lines
3.9 KiB
Dart
144 lines
3.9 KiB
Dart
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<NetworkPort> 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<NetworkLocation> locations;
|
|
final List<TopologyNode> nodes;
|
|
final List<TopologyEdge> edges;
|
|
final List<NetworkVlan> 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<NetworkLocation> allLocations,
|
|
required List<NetworkDevice> allDevices,
|
|
required List<NetworkPort> allPorts,
|
|
required List<NetworkLink> allLinks,
|
|
required List<NetworkVlan> allVlans,
|
|
}) {
|
|
final siteLocations = site == null
|
|
? <NetworkLocation>[]
|
|
: 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 = <String, List<NetworkPort>>{};
|
|
for (final port in allPorts) {
|
|
if (!siteDeviceIds.contains(port.deviceId)) continue;
|
|
portsByDevice.putIfAbsent(port.deviceId, () => []).add(port);
|
|
}
|
|
|
|
final portToDevice = <String, String>{};
|
|
final portLabel = <String, String>{};
|
|
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 = <TopologyEdge>[];
|
|
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,
|
|
);
|
|
}
|
|
}
|