From 5c58f2a9c2b8275bafc29d5940fd7d7dbea159b9 Mon Sep 17 00:00:00 2001 From: Mario Date: Sun, 30 Apr 2023 19:09:36 +0200 Subject: [PATCH] Closes #64: Show neighbors option (#286) * Add option to show neighbors * Updated documentation and help text * Added newlines at end of files where they where missing * Updated options screenshot * fix function vars --------- --- README.md | 8 +-- netbox_topology_views/api/serializers.py | 3 +- netbox_topology_views/api/views.py | 36 +++++++------ netbox_topology_views/forms.py | 32 ++++++++---- .../0003_individualoptions_show_neighbors.py | 18 +++++++ netbox_topology_views/models.py | 3 ++ netbox_topology_views/utils.py | 7 ++- netbox_topology_views/views.py | 52 +++++++++++++++---- 8 files changed, 119 insertions(+), 40 deletions(-) create mode 100644 netbox_topology_views/migrations/0003_individualoptions_show_neighbors.py diff --git a/README.md b/README.md index f37c33c..ac5c326 100644 --- a/README.md +++ b/README.md @@ -99,15 +99,13 @@ Go to the plugins tab in the navbar and click topology or go to `$NETBOX_URL/plu Select your options for the topology view: -![selection_options](https://user-images.githubusercontent.com/20901110/220410144-5633d24c-6383-4cba-8d6d-ba77ef3d3cdd.png) +![topology_options](https://user-images.githubusercontent.com/20901110/235302073-234c3ef7-2c2b-4d0c-a674-9909d490d7ea.png)
Save Coordinates
Save the coordinates of devices in the topology view.
Show Unconnected
Show devices that have no connections or for which no connection is displayed. This option depends on other parameters like 'Show Cables' and 'Show Logical Connections'.
-
Show Circuit Terminations
-
Show connections which end at a circuit termination in the topology view. These connections are displayed as blue dashed lines.
Show Cables
Show connections between interfaces, front / rear ports, etc., that are connected with one or more cables. These connections are displayed as solid lines in the color of the cable.
Show Logical Connections
@@ -116,6 +114,10 @@ Select your options for the topology view: intermediate front / rear port connections, etc. This is similar to what was referred to as 'end-to-end' connections in previous versions. These connections are displayed as yellow dotted lines.
Show redundant Cable and Locigal Connection
Shows a logical connection (in addition to a cable), even if a cable is directly connected. Leaving this option disabled prevents that redundant display. This option only has an effect if 'Show Logical Connections' is activated.
+
Show Neighbors
+
Adds neighbors to the filter result set automatically. Link peers will be added if 'Show Cables' is ticked, far-end terminations will be added if 'Show Logical Connections' is ticked.
+
Show Circuit Terminations
+
Show connections which end at a circuit termination in the topology view. These connections are displayed as blue dashed lines.
Show Power Feeds
Displays connections between power outlets and power ports. These connections are displayed as solid lines in the color of the cable. This option depends on 'Show Cables'.
Show Wireless Links
diff --git a/netbox_topology_views/api/serializers.py b/netbox_topology_views/api/serializers.py index b8608aa..82963eb 100644 --- a/netbox_topology_views/api/serializers.py +++ b/netbox_topology_views/api/serializers.py @@ -25,4 +25,5 @@ class DeviceRoleSerializer(ModelSerializer): class IndividualOptionsSerializer(NetBoxModelSerializer): class Meta: model = IndividualOptions - fields = ("ignore_cable_type", "show_unconnected", "show_cables", "show_logical_connections", "show_single_cable_logical_conns", "show_circuit", "show_power", "show_wireless", "draw_default_layout") \ No newline at end of file + fields = ("ignore_cable_type", "show_unconnected", "show_cables", "show_logical_connections", "show_single_cable_logical_conns", "show_neighbors", "show_circuit", "show_power", "show_wireless", "draw_default_layout") + \ No newline at end of file diff --git a/netbox_topology_views/api/views.py b/netbox_topology_views/api/views.py index 736f86c..026b8be 100644 --- a/netbox_topology_views/api/views.py +++ b/netbox_topology_views/api/views.py @@ -80,23 +80,29 @@ class ExportTopoToXML(PermissionRequiredMixin, ViewSet): user_id=request.user.id, ) - save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless = get_query_settings(request) - topo_data = get_topology_data( - self.queryset, - individualOptions, - show_unconnected, - save_coords, - show_cables, - show_circuit, - show_logical_connections, - show_single_cable_logical_conns, - show_power, - show_wireless, - ) - xml_data = export_data_to_xml(topo_data).decode('utf-8') + if request.GET: - return HttpResponse(xml_data, content_type="application/xml; charset=utf-8") + save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, show_neighbors = get_query_settings(request) + topo_data = get_topology_data( + queryset=self.queryset, + individualOptions=individualOptions, + save_coords=save_coords, + show_unconnected=show_unconnected, + show_cables=show_cables, + show_logical_connections=show_logical_connections, + show_single_cable_logical_conns=show_single_cable_logical_conns, + show_neighbors=show_neighbors, + show_circuit=show_circuit, + show_power=show_power, + show_wireless=show_wireless, + ) + xml_data = export_data_to_xml(topo_data).decode('utf-8') + return HttpResponse(xml_data, content_type="application/xml; charset=utf-8") + else: + return JsonResponse( + {"status": "Missing or malformed request parameters"}, status=400 + ) class SaveRoleImageViewSet(PermissionRequiredMixin, ViewSet): queryset = DeviceRole.objects.none() diff --git a/netbox_topology_views/forms.py b/netbox_topology_views/forms.py index d6dfc99..af1ab06 100644 --- a/netbox_topology_views/forms.py +++ b/netbox_topology_views/forms.py @@ -33,9 +33,10 @@ class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): "save_coords", "show_unconnected", "show_cables", - "show_circuit", "show_logical_connections", "show_single_cable_logical_conns", + "show_neighbors", + "show_circuit", "show_power", "show_wireless", ), @@ -118,17 +119,17 @@ class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): show_unconnected = forms.BooleanField( label=_("Show Unconnected"), required=False, initial=False ) + show_cables = forms.BooleanField( + label =_("Show Cables"), required=False, initial=False + ) show_logical_connections = forms.BooleanField( label =_("Show Logical Connections"), required=False, initial=False ) show_single_cable_logical_conns = forms.BooleanField( label =_("Show redundant Cable and Locigal Connection"), required=False, initial=False ) - show_cables = forms.BooleanField( - label =_("Show Cables"), required=False, initial=False - ) - show_wireless = forms.BooleanField( - label =_("Show Wireless Links"), required=False, initial=False + show_neighbors = forms.BooleanField( + label =_("Show Neighbors"), required=False, initial=False ) show_circuit = forms.BooleanField( label=_("Show Circuit Terminations"), required=False, initial=False @@ -136,6 +137,9 @@ class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): show_power = forms.BooleanField( label=_("Show Power Feeds"), required=False, initial=False ) + show_wireless = forms.BooleanField( + label =_("Show Wireless Links"), required=False, initial=False + ) class IndividualOptionsForm(NetBoxModelForm): fieldsets = ( @@ -148,9 +152,10 @@ class IndividualOptionsForm(NetBoxModelForm): "preselected_tags", "show_unconnected", "show_cables", - "show_circuit", "show_logical_connections", "show_single_cable_logical_conns", + "show_neighbors", + "show_circuit", "show_power", "show_wireless", "draw_default_layout", @@ -215,6 +220,14 @@ class IndividualOptionsForm(NetBoxModelForm): "disabled prevents that redundant display. This option only " "has an effect if 'Show Logical Connections' is activated.") ) + show_neighbors = forms.BooleanField( + label =_("Show Neighbors"), + required=False, + initial=False, + help_text=_("Adds neighbors to the filter result set automatically. " + "Link peers will be added if 'Show Cables' is ticked, far-end " + "terminations will be added if 'Show Logical Connections' is ticked.") + ) show_circuit = forms.BooleanField( label=_("Show Circuit Terminations"), required=False, @@ -248,5 +261,6 @@ class IndividualOptionsForm(NetBoxModelForm): class Meta: model = IndividualOptions fields = [ - 'user_id', 'ignore_cable_type', 'preselected_device_roles', 'preselected_tags', 'show_unconnected', 'show_cables', 'show_logical_connections', 'show_single_cable_logical_conns', 'show_circuit', 'show_power', 'show_wireless', 'draw_default_layout' - ] \ No newline at end of file + 'user_id', 'ignore_cable_type', 'preselected_device_roles', 'preselected_tags', 'show_unconnected', 'show_cables', 'show_logical_connections', 'show_single_cable_logical_conns', 'show_neighbors', 'show_circuit', 'show_power', 'show_wireless', 'draw_default_layout' + ] + \ No newline at end of file diff --git a/netbox_topology_views/migrations/0003_individualoptions_show_neighbors.py b/netbox_topology_views/migrations/0003_individualoptions_show_neighbors.py new file mode 100644 index 0000000..080dd9b --- /dev/null +++ b/netbox_topology_views/migrations/0003_individualoptions_show_neighbors.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.8 on 2023-04-29 09:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('netbox_topology_views', '0002_individualoptions'), + ] + + operations = [ + migrations.AddField( + model_name='individualoptions', + name='show_neighbors', + field=models.BooleanField(default=False), + ), + ] diff --git a/netbox_topology_views/models.py b/netbox_topology_views/models.py index e95d020..941ef34 100644 --- a/netbox_topology_views/models.py +++ b/netbox_topology_views/models.py @@ -137,6 +137,9 @@ class IndividualOptions(NetBoxModel): show_single_cable_logical_conns = models.BooleanField( default=False ) + show_neighbors = models.BooleanField( + default=False + ) show_circuit = models.BooleanField( default=False ) diff --git a/netbox_topology_views/utils.py b/netbox_topology_views/utils.py index d802035..b427cea 100644 --- a/netbox_topology_views/utils.py +++ b/netbox_topology_views/utils.py @@ -154,8 +154,13 @@ def get_query_settings(request): if "show_wireless" in request.GET: if request.GET["show_wireless"] == "on" : show_wireless = True + + show_neighbors = False + if "show_neighbors" in request.GET: + if request.GET["show_neighbors"] == "on" : + show_neighbors = True - return save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless + return save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, show_neighbors class LinePattern(): wireless = [2, 10, 2, 10] diff --git a/netbox_topology_views/views.py b/netbox_topology_views/views.py index 65a21c6..890999e 100644 --- a/netbox_topology_views/views.py +++ b/netbox_topology_views/views.py @@ -2,6 +2,7 @@ import json from functools import reduce from typing import DefaultDict, Dict, Optional, Union import time +from itertools import chain from utilities.htmx import is_htmx from circuits.models import Circuit, CircuitTermination @@ -263,6 +264,7 @@ def get_topology_data( show_circuit: bool, show_logical_connections: bool, show_single_cable_logical_conns: bool, + show_neighbors: bool, show_power: bool, show_wireless: bool, ): @@ -290,6 +292,31 @@ def get_topology_data( device_ids = [d.pk for d in queryset] site_ids = [d.site_id for d in queryset] + if show_neighbors: + interfaces = Interface.objects.filter( + Q(device_id__in=device_ids) + ) + frontports = FrontPort.objects.filter( + Q(device_id__in=device_ids) + ) + rearports = RearPort.objects.filter( + Q(device_id__in=device_ids) + ) + + ports = chain(interfaces, frontports, rearports) + for port in ports: + for link_peer in port.link_peers: + if link_peer.device.id not in device_ids: + device_ids.append(link_peer.device.id) + + if show_logical_connections: + path_complete_interfaces = Interface.objects.filter( + Q(_path__is_complete=True) & Q(device_id__in=device_ids) + ) + for path_complete_interface in path_complete_interfaces: + for connected_endpoint in path_complete_interface.connected_endpoints: + device_ids.append(connected_endpoint.device.id) + if show_circuit: circuit_terminations = CircuitTermination.objects.filter( Q(site_id__in=site_ids) | Q(provider_network__isnull=False) @@ -600,20 +627,21 @@ class TopologyHomeView(PermissionRequiredMixin, View): if request.GET: - save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless = get_query_settings(request) + save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, show_neighbors = get_query_settings(request) if not "draw_init" in request.GET or "draw_init" in request.GET and request.GET["draw_init"].lower() == "true": topo_data = get_topology_data( - self.queryset, - individualOptions, - show_unconnected, - save_coords, - show_cables, - show_circuit, - show_logical_connections, - show_single_cable_logical_conns, - show_power, - show_wireless, + queryset=self.queryset, + individualOptions=individualOptions, + save_coords=save_coords, + show_unconnected=show_unconnected, + show_cables=show_cables, + show_logical_connections=show_logical_connections, + show_single_cable_logical_conns=show_single_cable_logical_conns, + show_neighbors=show_neighbors, + show_circuit=show_circuit, + show_power=show_power, + show_wireless=show_wireless, ) else: @@ -629,6 +657,7 @@ class TopologyHomeView(PermissionRequiredMixin, View): if individualOptions.show_cables: q['show_cables'] = "on" if individualOptions.show_logical_connections: q['show_logical_connections'] = "on" if individualOptions.show_single_cable_logical_conns: q['show_single_cable_logical_conns'] = "on" + if individualOptions.show_neighbors: q['show_neighbors'] = "on" if individualOptions.show_circuit: q['show_circuit'] = "on" if individualOptions.show_power: q['show_power'] = "on" if individualOptions.show_wireless: q['show_wireless'] = "on" @@ -751,6 +780,7 @@ class TopologyIndividualOptionsView(PermissionRequiredMixin, View): 'show_cables': queryset.show_cables, 'show_logical_connections': queryset.show_logical_connections, 'show_single_cable_logical_conns': queryset.show_single_cable_logical_conns, + 'show_neighbors': queryset.show_neighbors, 'show_circuit': queryset.show_circuit, 'show_power': queryset.show_power, 'show_wireless': queryset.show_wireless,