Files
tasq/lib/models/network/topology_graph.dart
2026-05-21 06:29:14 +08:00

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,
);
}
}