Compare commits

...
12 Commits
Author SHA1 Message Date
mattieserver f8c6e9e193 bump version (#558) 2024-08-26 12:21:00 +02:00
mattieserver 19adcab4a9 set BaseViewSet (#556) 2024-08-26 12:01:42 +02:00
Mario d2d2106246 Closes #163: Show raster and snap icons (#550)
* proof of concept

* add grid size to forms and individual options
2024-08-20 12:02:45 +02:00
Mario 9072faf7cf Closes #485: Add Ignore Termination Types to "regular" filters (#546)
* add ignore_cable_type filter

* deleted commented command

* corrected saved filter for ignore_cable_type
2024-08-09 15:09:16 +02:00
Mario 6f43b959bd Closes #497: Group by Virtual Chassis (#542)
* add virtual chassis to js

* add virtual chassis to py and migrations
2024-07-29 12:15:04 +02:00
Mario 1048feff52 Fixes #464: Topology not showing properly when using other language than english (#541)
* Change termination type name to model

* change CHOICES to model names

* Change termination type name to model

* change CHOICES to model names
2024-07-29 10:32:30 +02:00
Mario 7ff0a9650f Fixed another mistake (#540) 2024-07-28 00:07:00 +02:00
Mario 8d5de5c49f fix saved filter params not defined (#539) 2024-07-27 22:26:18 +02:00
Mario 96afdf8d83 fix coordinate group in saved filter (#536) 2024-07-24 22:22:13 +02:00
aba0598947 Closes #436 (and #122?): Add options to disable smooth cables and physics (#488)
* allow disabling smooth cables and render them as straight lines instead

* rename 'disable_smooth_cables' to 'straight_cables'

* missed one in renaming

* fix inverted logic

* add GET param and option to DeviceFilterForm

* fix syntax error

* rename var

* add missing query field

* netbox v4 support

---------

Co-authored-by: Daniel Bremer <daniel.bremer@mpsd.mpg.de>
Co-authored-by: mvanhaverbeke <mattijs.vanhaverbeke@ebo-enterprises.com>
2024-07-08 15:37:18 +02:00
mattieserver 142de460b9 added restrict to query (#522) 2024-07-04 21:01:40 +02:00
mattieserver 2600209724 4.0 release (#513) 2024-06-24 20:10:05 +02:00
16 changed files with 414 additions and 72 deletions
+1
View File
@@ -50,6 +50,7 @@ python3 manage.py collectstatic --no-input
| netbox version | netbox-topology-views version | | netbox version | netbox-topology-views version |
| -------------- | ----------------------------- | | -------------- | ----------------------------- |
| >= 4.0.0 | >= v4.0.0 |
| >= 3.7.0 | >= v3.9.0 | | >= 3.7.0 | >= v3.9.0 |
| >= 3.6.4 | >= v3.8.1 | | >= 3.6.4 | >= v3.8.1 |
| >= 3.6.0 | >= v3.7.X | | >= 3.6.0 | >= v3.7.X |
+1 -1
View File
@@ -5,7 +5,7 @@ class TopologyViewsConfig(PluginConfig):
name = "netbox_topology_views" name = "netbox_topology_views"
verbose_name = "Topology views" verbose_name = "Topology views"
description = "An plugin to render topology maps" description = "An plugin to render topology maps"
version = "4.0.0-beta.2" version = "4.0.1"
author = "Mattijs Vanhaverbeke" author = "Mattijs Vanhaverbeke"
author_email = "author@example.com" author_email = "author@example.com"
base_url = "netbox_topology_views" base_url = "netbox_topology_views"
+1 -1
View File
@@ -50,4 +50,4 @@ class PowerFeedCoordinateSerializer(NetBoxModelSerializer):
class IndividualOptionsSerializer(NetBoxModelSerializer): class IndividualOptionsSerializer(NetBoxModelSerializer):
class Meta: class Meta:
model = IndividualOptions model = IndividualOptions
fields = ("ignore_cable_type", "save_coords", "show_unconnected", "show_cables", "show_logical_connections", "show_single_cable_logical_conns", "show_neighbors", "show_circuit", "show_power", "show_wireless", "group_sites", "group_locations", "group_racks", "draw_default_layout") fields = ("ignore_cable_type", "save_coords", "show_unconnected", "show_cables", "show_logical_connections", "show_single_cable_logical_conns", "show_neighbors", "show_circuit", "show_power", "show_wireless", "group_sites", "group_locations", "group_racks", "group_virtualchassis", "draw_default_layout", "straight_cables", "grid_size")
+18 -7
View File
@@ -1,6 +1,8 @@
from typing import Dict from typing import Dict
import sys import sys
from netbox.api.viewsets import BaseViewSet
from circuits.models import Circuit from circuits.models import Circuit
from dcim.models import Device, DeviceRole, PowerFeed, PowerPanel from dcim.models import Device, DeviceRole, PowerFeed, PowerPanel
from extras.models import SavedFilter from extras.models import SavedFilter
@@ -22,7 +24,7 @@ from netbox_topology_views.views import get_topology_data
from netbox_topology_views.utils import get_image_from_url, export_data_to_xml, get_query_settings from netbox_topology_views.utils import get_image_from_url, export_data_to_xml, get_query_settings
from netbox_topology_views.filters import DeviceFilterSet from netbox_topology_views.filters import DeviceFilterSet
class SaveCoordsViewSet(PermissionRequiredMixin, ReadOnlyModelViewSet): class SaveCoordsViewSet(BaseViewSet, ReadOnlyModelViewSet):
permission_required = 'netbox_topology_views.change_coordinate' permission_required = 'netbox_topology_views.change_coordinate'
queryset = Device.objects.none() queryset = Device.objects.none()
@@ -89,16 +91,14 @@ class SaveCoordsViewSet(PermissionRequiredMixin, ReadOnlyModelViewSet):
return Response({"status": "saved coords"}) return Response({"status": "saved coords"})
class ExportTopoToXML(PermissionRequiredMixin, ViewSet): class ExportTopoToXML(BaseViewSet, ViewSet):
permission_required = ("dcim.view_site", "dcim.view_device")
queryset = Device.objects.none() queryset = Device.objects.none()
serializer_class = TopologyDummySerializer serializer_class = TopologyDummySerializer
def list(self, request): def list(self, request):
self.filterset = DeviceFilterSet self.filterset = DeviceFilterSet
self.queryset = Device.objects.all().select_related( self.queryset = Device.objects.restrict(request.user, 'view').select_related(
"device_type", "role" "device_type", "role"
) )
self.queryset = self.filterset(request.GET, self.queryset).qs self.queryset = self.filterset(request.GET, self.queryset).qs
@@ -109,7 +109,7 @@ class ExportTopoToXML(PermissionRequiredMixin, ViewSet):
if request.GET: if request.GET:
filter_id, save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, group_sites, group_locations, group_racks,show_neighbors = get_query_settings(request) filter_id, ignore_cable_type, save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, group_sites, group_locations, group_racks, group_virtualchassis, group, show_neighbors, straight_cables, grid_size = get_query_settings(request)
# Read options from saved filters as NetBox does not handle custom plugin filters # Read options from saved filters as NetBox does not handle custom plugin filters
if "filter_id" in request.GET and request.GET["filter_id"] != '': if "filter_id" in request.GET and request.GET["filter_id"] != '':
@@ -117,6 +117,7 @@ class ExportTopoToXML(PermissionRequiredMixin, ViewSet):
saved_filter = SavedFilter.objects.get(pk=filter_id) saved_filter = SavedFilter.objects.get(pk=filter_id)
saved_filter_params = getattr(saved_filter, 'parameters') saved_filter_params = getattr(saved_filter, 'parameters')
if ignore_cable_type == () and 'ignore_cable_type' in saved_filter_params: ignore_cable_type = saved_filter_params['ignore_cable_type']
if save_coords == False and 'save_coords' in saved_filter_params: save_coords = saved_filter_params['save_coords'] if save_coords == False and 'save_coords' in saved_filter_params: save_coords = saved_filter_params['save_coords']
if show_power == False and 'show_power' in saved_filter_params: show_power = saved_filter_params['show_power'] if show_power == False and 'show_power' in saved_filter_params: show_power = saved_filter_params['show_power']
if show_circuit == False and 'show_circuit' in saved_filter_params: show_circuit = saved_filter_params['show_circuit'] if show_circuit == False and 'show_circuit' in saved_filter_params: show_circuit = saved_filter_params['show_circuit']
@@ -127,19 +128,26 @@ class ExportTopoToXML(PermissionRequiredMixin, ViewSet):
if group_sites == False and 'group_sites' in saved_filter_params: group_sites = saved_filter_params['group_sites'] if group_sites == False and 'group_sites' in saved_filter_params: group_sites = saved_filter_params['group_sites']
if group_locations == False and 'group_locations' in saved_filter_params: group_locations = saved_filter_params['group_locations'] if group_locations == False and 'group_locations' in saved_filter_params: group_locations = saved_filter_params['group_locations']
if group_racks == False and 'group_racks' in saved_filter_params: group_racks = saved_filter_params['group_racks'] if group_racks == False and 'group_racks' in saved_filter_params: group_racks = saved_filter_params['group_racks']
if group_virtualchassis == False and 'group_virtualchassis' in saved_filter_params: group_virtualchassis = saved_filter_params['group_virtualchassis']
if show_neighbors == False and 'show_neighbors' in saved_filter_params: show_neighbors = saved_filter_params['show_neighbors'] if show_neighbors == False and 'show_neighbors' in saved_filter_params: show_neighbors = saved_filter_params['show_neighbors']
if straight_cables == False and 'straight_cables' in saved_filter_params: show_neighbors = saved_filter_params['straight_cables']
if grid_size == 0 and 'grid_size' in saved_filter_params: grid_size = saved_filter_params['grid_size']
except SavedFilter.DoesNotExist: # filter_id not found except SavedFilter.DoesNotExist: # filter_id not found
pass pass
except Exception as inst: except Exception as inst:
print(type(inst)) print(type(inst))
if 'group' not in request.query_params: if 'group' not in request.query_params:
if 'saved_filter_params' in locals() and "group" in saved_filter_params:
group_id = saved_filter_params['group'][0]
else:
group_id = "default" group_id = "default"
else: else:
group_id = request.query_params["group"] group_id = request.query_params["group"]
topo_data = get_topology_data( topo_data = get_topology_data(
queryset=self.queryset, queryset=self.queryset,
individualOptions=individualOptions, individualOptions=individualOptions,
ignore_cable_type=ignore_cable_type,
save_coords=save_coords, save_coords=save_coords,
show_unconnected=show_unconnected, show_unconnected=show_unconnected,
show_cables=show_cables, show_cables=show_cables,
@@ -152,7 +160,10 @@ class ExportTopoToXML(PermissionRequiredMixin, ViewSet):
group_sites=group_sites, group_sites=group_sites,
group_locations=group_locations, group_locations=group_locations,
group_racks=group_racks, group_racks=group_racks,
group_virtualchassis=group_virtualchassis,
group_id=group_id, group_id=group_id,
straight_cables=straight_cables,
grid_size=grid_size,
) )
xml_data = export_data_to_xml(topo_data).decode('utf-8') xml_data = export_data_to_xml(topo_data).decode('utf-8')
@@ -162,7 +173,7 @@ class ExportTopoToXML(PermissionRequiredMixin, ViewSet):
{"status": "Missing or malformed request parameters"}, status=400 {"status": "Missing or malformed request parameters"}, status=400
) )
class SaveRoleImageViewSet(PermissionRequiredMixin, ReadOnlyModelViewSet): class SaveRoleImageViewSet(BaseViewSet, ReadOnlyModelViewSet):
queryset = DeviceRole.objects.none() queryset = DeviceRole.objects.none()
serializer_class = RoleImageSerializer serializer_class = RoleImageSerializer
permission_required = ( permission_required = (
+56 -3
View File
@@ -34,9 +34,9 @@ class DeviceFilterForm(
fieldsets = ( fieldsets = (
FieldSet('q', 'filter_id', 'tag'), FieldSet('q', 'filter_id', 'tag'),
FieldSet( FieldSet(
'group', 'save_coords', 'show_unconnected', 'show_cables', 'show_logical_connections', 'group', 'ignore_cable_type', 'save_coords', 'show_unconnected', 'show_cables', 'show_logical_connections',
'show_single_cable_logical_conns', 'show_neighbors', 'show_circuit', 'show_power', 'show_wireless', 'show_single_cable_logical_conns', 'show_neighbors', 'show_circuit', 'show_power', 'show_wireless',
'group_sites', 'group_locations', 'group_racks', name=_("Options") 'group_sites', 'group_locations', 'group_racks', 'group_virtualchassis', 'straight_cables', 'grid_size', name=_("Options")
), ),
FieldSet('id', name=_("Device")), FieldSet('id', name=_("Device")),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_("Location")), FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_("Location")),
@@ -229,6 +229,11 @@ class DeviceFilterForm(
tag = TagFilterField(model) tag = TagFilterField(model)
# options # options
ignore_cable_type = forms.MultipleChoiceField(
label=_('Ignore Termination Types'),
required=False,
choices=IndividualOptions.CHOICES
)
save_coords = forms.NullBooleanField( save_coords = forms.NullBooleanField(
label=_('Save Coordinates'), label=_('Save Coordinates'),
required=False, required=False,
@@ -310,6 +315,26 @@ class DeviceFilterForm(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
) )
group_virtualchassis = forms.NullBooleanField(
label =_('Group Virtual Chassis'), required=False, initial=False,
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
straight_cables = forms.BooleanField(
label=_('Straight Cables'), required=False, initial=False,
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
grid_size = forms.IntegerField(
label=_('Grid Size'),
required=False,
initial=0,
min_value=0,
max_value=1000,
help_text=_('Show grid and snap dragged icons to grid. Set to 0 to disable grid and snapping.')
)
class CoordinateGroupsForm(NetBoxModelForm): class CoordinateGroupsForm(NetBoxModelForm):
fieldsets = ( fieldsets = (
@@ -501,7 +526,10 @@ class IndividualOptionsForm(NetBoxModelForm):
'group_sites', 'group_sites',
'group_locations', 'group_locations',
'group_racks', 'group_racks',
'group_virtualchassis',
'draw_default_layout', 'draw_default_layout',
'straight_cables',
'grid_size',
), ),
) )
@@ -622,6 +650,13 @@ class IndividualOptionsForm(NetBoxModelForm):
help_text=_('Draws a rectangle around Devices that belong to the ' help_text=_('Draws a rectangle around Devices that belong to the '
'same rack.') 'same rack.')
) )
group_virtualchassis = forms.BooleanField(
label =_('Group Virtual Chassis'),
required=False,
initial=False,
help_text=_('Draws a rectangle around Devices that belong to the '
'same virtual chassis.')
)
draw_default_layout = forms.BooleanField( draw_default_layout = forms.BooleanField(
label = ('Draw Default Layout'), label = ('Draw Default Layout'),
required=False, required=False,
@@ -629,6 +664,23 @@ class IndividualOptionsForm(NetBoxModelForm):
help_text=_('Enable this option if you want to draw the topology on ' help_text=_('Enable this option if you want to draw the topology on '
'the initial load (when you go to the topology plugin page).') 'the initial load (when you go to the topology plugin page).')
) )
straight_cables = forms.BooleanField(
label=('Use Straight Cables'),
required=False,
initial=False,
help_text=_('Enable this option if you want to draw cables as straight lines '
'instead of curves.')
)
grid_size = forms.IntegerField(
label=_('Grid Size'),
required=True,
initial=0,
min_value=0,
max_value=1000,
help_text=_('Default grid value. Set to 0 to disable grid. '
'Integers between 0 and 1000 are allowed. Snap to grid will be '
'automatically enabled for values > 0.')
)
class Meta: class Meta:
model = IndividualOptions model = IndividualOptions
@@ -636,5 +688,6 @@ class IndividualOptionsForm(NetBoxModelForm):
'user_id', 'ignore_cable_type', 'preselected_device_roles', 'preselected_tags', 'user_id', 'ignore_cable_type', 'preselected_device_roles', 'preselected_tags',
'save_coords', 'show_unconnected', 'show_cables', 'show_logical_connections', 'save_coords', 'show_unconnected', 'show_cables', 'show_logical_connections',
'show_single_cable_logical_conns', 'show_neighbors', 'show_circuit', 'show_power', 'show_single_cable_logical_conns', 'show_neighbors', 'show_circuit', 'show_power',
'show_wireless', 'group_sites', 'group_locations', 'group_racks', 'draw_default_layout' 'show_wireless', 'group_sites', 'group_locations', 'group_racks', 'group_virtualchassis', 'draw_default_layout',
'straight_cables', 'grid_size'
] ]
@@ -0,0 +1,18 @@
# Generated by Django 4.2.10 on 2024-04-29 14:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('netbox_topology_views', '0007_individualoptions_group_locations_and_more'),
]
operations = [
migrations.AddField(
model_name='individualoptions',
name='straight_cables',
field=models.BooleanField(default=False),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2024-07-29 08:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('netbox_topology_views', '0008_straight_cables'),
]
operations = [
migrations.AddField(
model_name='individualoptions',
name='group_virtualchassis',
field=models.BooleanField(default=False),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2024-08-14 09:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('netbox_topology_views', '0009_individualoptions_group_virtualchassis'),
]
operations = [
migrations.AddField(
model_name='individualoptions',
name='grid_size',
field=models.PositiveSmallIntegerField(default=0),
),
]
+15 -6
View File
@@ -331,12 +331,12 @@ class PowerFeedCoordinate(NetBoxModel):
class IndividualOptions(NetBoxModel): class IndividualOptions(NetBoxModel):
CHOICES = ( CHOICES = (
('interface', 'interface'), ('interface', 'interface'),
('front port', 'front port'), ('frontport', 'frontport'),
('rear port', 'rear port'), ('rearport', 'rearport'),
('power outlet', 'power outlet'), ('poweroutlet', 'poweroutlet'),
('power port', 'power port'), ('powerport', 'powerport'),
('console port', 'console port'), ('consoleport', 'consoleport'),
('console server port', 'console server port'), ('consoleserverport', 'consoleserverport'),
) )
user_id = models.IntegerField( user_id = models.IntegerField(
@@ -395,9 +395,18 @@ class IndividualOptions(NetBoxModel):
group_racks = models.BooleanField( group_racks = models.BooleanField(
default=False default=False
) )
group_virtualchassis = models.BooleanField(
default=False
)
draw_default_layout = models.BooleanField( draw_default_layout = models.BooleanField(
default=False default=False
) )
straight_cables = models.BooleanField(
default=False
)
grid_size = models.PositiveSmallIntegerField(
default=0
)
_netbox_private = True _netbox_private = True
File diff suppressed because one or more lines are too long
+160 -10
View File
@@ -73,11 +73,131 @@ const coordSaveCheckbox = document.querySelector('#id_save_coords')
const group_sites = topologyData.options.group_sites const group_sites = topologyData.options.group_sites
const group_locations = topologyData.options.group_locations const group_locations = topologyData.options.group_locations
const group_racks = topologyData.options.group_racks const group_racks = topologyData.options.group_racks
const group_virtualchassis = topologyData.options.group_virtualchassis
const gridSize = parseInt(topologyData.options.grid_size[0]);
var dragMode = false;
graph = new Network(container, { nodes, edges }, options) graph = new Network(container, { nodes, edges }, options)
graph.fit() graph.fit()
function getGridPosition(nodeId, gridSize) {
x = graph.getPosition(nodeId).x;
y = graph.getPosition(nodeId).y;
if(x >= 0) {
if((x % gridSize) > (gridSize / 2)) {
x += gridSize;
}
}
else {
if((-x % gridSize) > (gridSize / 2)) {
x -= gridSize;
}
}
x = x - x % gridSize;
if(y >= 0) {
if((y % gridSize) > (gridSize / 2)) {
y += gridSize;
}
}
else {
if((-y % gridSize) > (gridSize / 2)) {
y -= gridSize;
}
}
y = y - y % gridSize;
return {
x: x,
y: y
};
}
function drawGrid(canvascontext) {
// Canvas can be zoomed. It then contains more or less virtual pixels than the real number of pixels
const zoomFactor = graph.getScale() * window.devicePixelRatio;
const virtualWidth = canvascontext.canvas.width / zoomFactor;
const virtualHeight = canvascontext.canvas.height / zoomFactor;
// Canvas can be moved. Get the center of the virtual canvas. Take the grid into account
const virtualCenter = graph.getViewPosition();
const rasterizedCenterX = virtualCenter.x - virtualCenter.x % gridSize;
const rasterizedCenterY = virtualCenter.y - virtualCenter.y % gridSize;
// Calculate virtual space for the grid
const hSpace = (virtualWidth / 2) - (virtualWidth / 2) % gridSize + gridSize;
const vSpace = (virtualHeight / 2) - (virtualHeight / 2) % gridSize + gridSize;
// Calculate virtual position for the grid
const left = rasterizedCenterX - gridSize - hSpace;
const right = rasterizedCenterX + gridSize + hSpace;
const top = rasterizedCenterY - gridSize - vSpace;
const bottom = rasterizedCenterY + gridSize + vSpace;
// Draw grid
canvascontext.beginPath();
for (let x = left; x < right; x += gridSize) {
canvascontext.moveTo(x, top);
canvascontext.lineTo(x, bottom);
}
for (let y = top; y < bottom; y += gridSize) {
canvascontext.moveTo(left, y);
canvascontext.lineTo(right, y);
}
canvascontext.strokeStyle = '#777777';
canvascontext.stroke();
}
function drawGridSnapHint(canvascontext) {
// Draw grid hinting line and circle
if(gridSize > 0 && dragMode == true && graph.getSelectedNodes().length > 0) {
for(i = 0; i < graph.getSelectedNodes().length; i++) {
id = graph.getSelectedNodes()[i];
if(window.nodes.get(id).x != graph.getPosition(id).x || window.nodes.get(id).y != graph.getPosition(id).y) {
pos = getGridPosition(graph.getSelectedNodes()[i], gridSize);
canvascontext.beginPath();
canvascontext.arc(graph.getPosition(graph.getSelectedNodes()[i]).x, graph.getPosition(graph.getSelectedNodes()[i]).y, 5, 0, 2 * Math.PI);
canvascontext.fillStyle = '#FF3D3D';
canvascontext.fill();
canvascontext.beginPath();
canvascontext.moveTo(graph.getPosition(graph.getSelectedNodes()[i]).x, graph.getPosition(graph.getSelectedNodes()[i]).y);
canvascontext.lineTo(pos.x, pos.y);
canvascontext.strokeStyle = '#FF3D3D';
canvascontext.stroke();
canvascontext.beginPath();
canvascontext.arc(pos.x, pos.y, 10, 0, 2 * Math.PI);
canvascontext.fillStyle = '#9C0000';
canvascontext.fill();
}
}
}
}
graph.on('dragStart', (params) => {
dragMode = true;
})
graph.on('dragEnd', (params) => { graph.on('dragEnd', (params) => {
dragMode = false;
// Place icon on the grid
if(gridSize > 0 && graph.getSelectedNodes().length > 0) {
for(i = 0; i < graph.getSelectedNodes().length; i++) {
id = graph.getSelectedNodes()[i];
if(window.nodes.get(id).x != graph.getPosition(id).x || window.nodes.get(id).y != graph.getPosition(id).y) {
pos = getGridPosition(graph.getSelectedNodes()[i], gridSize);
window.nodes.update({id: graph.getSelectedNodes()[i], x: pos.x, y: pos.y});
}
}
}
if (coordSaveCheckbox.options[coordSaveCheckbox.selectedIndex].text != "Yes") return if (coordSaveCheckbox.options[coordSaveCheckbox.selectedIndex].text != "Yes") return
Promise.allSettled( Promise.allSettled(
@@ -138,21 +258,30 @@ const coordSaveCheckbox = document.querySelector('#id_save_coords')
} }
}) })
graph.on('beforeDrawing', (canvascontext) => {
if (gridSize > 0) {
drawGrid(canvascontext);
}
})
graph.on('afterDrawing', (canvascontext) => { graph.on('afterDrawing', (canvascontext) => {
allRectangles = []; allRectangles = [];
if(group_sites != null && group_sites == 'on') { drawGroupRectangles(canvascontext, groupedNodeSites, siteRectParams); } if(group_sites != null && group_sites == 'on') { drawGroupRectangles(canvascontext, groupedNodeSites, siteRectParams); }
if(group_locations != null && group_locations == 'on') { drawGroupRectangles(canvascontext, groupedNodeLocations, locationRectParams); } if(group_locations != null && group_locations == 'on') { drawGroupRectangles(canvascontext, groupedNodeLocations, locationRectParams); }
if(group_racks != null && group_racks == 'on') { drawGroupRectangles(canvascontext, groupedNodeRacks, rackRectParams); } if(group_racks != null && group_racks == 'on') { drawGroupRectangles(canvascontext, groupedNodeRacks, rackRectParams); }
if(group_virtualchassis != null && group_virtualchassis == 'on') { drawGroupRectangles(canvascontext, groupedNodeVirtualchassis, virtualchassisRectParams); }
drawGridSnapHint(canvascontext);
}) })
graph.on('click', (canvascontext) => { graph.on('click', (canvascontext) => {
allRectangles.forEach(key => { allRectangles.forEach(key => {
// Is the mouse pointer inside of the current rectangle? // Is the mouse pointer inside of the current rectangle?
if(canvascontext.pointer.canvas.x > (key.x1 - key.border / 2 - 1) && canvascontext.pointer.canvas.x < (key.x2 + key.border / 2 + 1) if(canvascontext.pointer.canvas.x > (key.x1 - key.border / 2 - 3) && canvascontext.pointer.canvas.x < (key.x2 + key.border / 2 + 3)
&& canvascontext.pointer.canvas.y > (key.y1 - key.border / 2 - 1) && canvascontext.pointer.canvas.y < (key.y2 + key.border / 2 + 1)) { && canvascontext.pointer.canvas.y > (key.y1 - key.border / 2 - 3) && canvascontext.pointer.canvas.y < (key.y2 + key.border / 2 + 3)) {
// We just want to react when the border has been clicked, not the whole rectangle // We just want to react when the border has been clicked, not the whole rectangle
if (canvascontext.pointer.canvas.x < (key.x1 + key.border / 2 + 1) || canvascontext.pointer.canvas.x > (key.x2 - key.border / 2 - 1) if (canvascontext.pointer.canvas.x < (key.x1 + key.border / 2 + 3) || canvascontext.pointer.canvas.x > (key.x2 - key.border / 2 - 3)
|| canvascontext.pointer.canvas.y < (key.y1 + key.border / 2 + 1) || canvascontext.pointer.canvas.y > (key.y2 - key.border / 2 - 1)) { || canvascontext.pointer.canvas.y < (key.y1 + key.border / 2 + 3) || canvascontext.pointer.canvas.y > (key.y2 - key.border / 2 - 3)) {
// Generate an array of affected nodes in order to pass it to the select.Nodes() function // Generate an array of affected nodes in order to pass it to the select.Nodes() function
let arr = []; let arr = [];
if(key.category == "Site") { if(key.category == "Site") {
@@ -182,13 +311,22 @@ const coordSaveCheckbox = document.querySelector('#id_save_coords')
}); });
}); });
} }
if(key.category == "Virtual Chassis") {
groupedNodeVirtualchassis.forEach(subArray => {
subArray.forEach(element => {
if (element[1] === key.id) {
arr.push(element[0]);
}
});
});
}
graph.selectNodes(arr); graph.selectNodes(arr);
} }
} }
}); });
}) })
// Add information on which node belongs to which group (site/location/rack). // Add information on which node belongs to which group (site/location/rack/virtualchassis).
// Create an array for each group in order to loop through that arrays later // Create an array for each group in order to loop through that arrays later
function combineNodeInfo(typeId, type) { function combineNodeInfo(typeId, type) {
let nodesArray = []; let nodesArray = [];
@@ -238,7 +376,7 @@ const coordSaveCheckbox = document.querySelector('#id_save_coords')
allRectangles.push({category: rectangle.category, id: rectangle.id, x1: rectangle.x, y1: rectangle.y, x2: rectangle.x + rectangle.width, y2: rectangle.y + rectangle.height, border: rectangle.lineWidth}) allRectangles.push({category: rectangle.category, id: rectangle.id, x1: rectangle.x, y1: rectangle.y, x2: rectangle.x + rectangle.width, y2: rectangle.y + rectangle.height, border: rectangle.lineWidth})
} }
/* Draw all rectangles of a given group (site/location/rack) /* Draw all rectangles of a given group (site/location//virtualchassis)
rectParams expects an object that consists of the following keys: rectParams expects an object that consists of the following keys:
lineWidth: border width (string) lineWidth: border width (string)
color: border color (string) color: border color (string)
@@ -303,8 +441,8 @@ const coordSaveCheckbox = document.querySelector('#id_save_coords')
color: "#337ab7", color: "#337ab7",
paddingX: 77, paddingX: 77,
paddingY: 77, paddingY: 77,
textPaddingX: 12, textPaddingX: 22,
textPaddingY: 22, textPaddingY: 29,
font: "14px helvetica", font: "14px helvetica",
category: "Location" category: "Location"
} }
@@ -315,11 +453,23 @@ const coordSaveCheckbox = document.querySelector('#id_save_coords')
color: "green", color: "green",
paddingX: 70, paddingX: 70,
paddingY: 70, paddingY: 70,
textPaddingX: 8, textPaddingX: 15,
textPaddingY: 30, textPaddingY: 36,
font: "14px helvetica", font: "14px helvetica",
category: "Rack" category: "Rack"
} }
let groupedNodeVirtualchassis = combineNodeInfo('virtual_chassis_id', 'virtual_chassis');
let virtualchassisRectParams = {
lineWidth: "5",
color: "orange",
paddingX: 63,
paddingY: 63,
textPaddingX: 8,
textPaddingY: 43,
font: "14px helvetica",
category: "Virtual Chassis"
}
})() })()
// Download Graph // Download Graph
+15 -12
View File
@@ -1,12 +1,12 @@
{ {
"name": "netbox_topology_views", "name": "netbox_topology_views",
"version": "4.0.0-beta.1", "version": "4.0.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "netbox_topology_views", "name": "netbox_topology_views",
"version": "4.0.0-beta.1", "version": "4.0.1",
"dependencies": { "dependencies": {
"vis-data": "^7.1.9", "vis-data": "^7.1.9",
"vis-network": "^9.1.9", "vis-network": "^9.1.9",
@@ -561,9 +561,9 @@
} }
}, },
"node_modules/immutable": { "node_modules/immutable": {
"version": "4.3.6", "version": "4.3.7",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
"integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==", "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==",
"dev": true "dev": true
}, },
"node_modules/is-binary-path": { "node_modules/is-binary-path": {
@@ -579,12 +579,15 @@
} }
}, },
"node_modules/is-core-module": { "node_modules/is-core-module": {
"version": "2.13.1", "version": "2.15.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"hasown": "^2.0.0" "hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@@ -682,9 +685,9 @@
} }
}, },
"node_modules/sass": { "node_modules/sass": {
"version": "1.77.4", "version": "1.77.8",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.77.4.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz",
"integrity": "sha512-vcF3Ckow6g939GMA4PeU7b2K/9FALXk2KF9J87txdHzXbUF9XRQRwSxcAs/fGaTnJeBFd7UoV22j3lzMLdM0Pw==", "integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"chokidar": ">=3.0.0 <4.0.0", "chokidar": ">=3.0.0 <4.0.0",
@@ -1,7 +1,7 @@
{ {
"private": true, "private": true,
"name": "netbox_topology_views", "name": "netbox_topology_views",
"version": "4.0.0-beta.2", "version": "4.0.1",
"scripts": { "scripts": {
"bundle": "node bundle.js", "bundle": "node bundle.js",
"bundle:styles": "node bundle.js --styles", "bundle:styles": "node bundle.js --styles",
+23 -1
View File
@@ -123,6 +123,10 @@ def get_query_settings(request):
save_coords = True save_coords = True
# Individual options # Individual options
ignore_cable_type = ()
if "ignore_cable_type" in request.GET:
ignore_cable_type = request.GET.getlist('ignore_cable_type')
show_unconnected = False show_unconnected = False
if "show_unconnected" in request.GET: if "show_unconnected" in request.GET:
if request.GET["show_unconnected"] == "True": if request.GET["show_unconnected"] == "True":
@@ -173,12 +177,30 @@ def get_query_settings(request):
if request.GET["group_racks"] == "True" : if request.GET["group_racks"] == "True" :
group_racks = True group_racks = True
group_virtualchassis = False
if "group_virtualchassis" in request.GET:
if request.GET["group_virtualchassis"] == "True" :
group_virtualchassis = True
group = False
if "group" in request.GET:
group = request.GET["group"]
show_neighbors = False show_neighbors = False
if "show_neighbors" in request.GET: if "show_neighbors" in request.GET:
if request.GET["show_neighbors"] == "True" : if request.GET["show_neighbors"] == "True" :
show_neighbors = True show_neighbors = True
return filter_id, save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, group_sites, group_locations, group_racks, show_neighbors straight_cables = False
if "straight_cables" in request.GET:
if request.GET["straight_cables"] == "True":
straight_cables = True
grid_size = 0
if "grid_size" in request.GET:
grid_size = request.GET.getlist('grid_size')
return filter_id, ignore_cable_type, save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, group_sites, group_locations, group_racks, group_virtualchassis, group, show_neighbors, straight_cables, grid_size
class LinePattern(): class LinePattern():
wireless = [2, 10, 2, 10] wireless = [2, 10, 2, 10]
+46 -7
View File
@@ -189,6 +189,9 @@ def create_node(
if device.rack is not None: if device.rack is not None:
node["rack"] = device.rack.name node["rack"] = device.rack.name
node["rack_id"] = device.rack_id node["rack_id"] = device.rack_id
if device.virtual_chassis is not None:
node["virtual_chassis"] = device.virtual_chassis.name
node["virtual_chassis_id"] = device.virtual_chassis_id
if device.role.color != "": if device.role.color != "":
node["color.border"] = "#" + device.role.color node["color.border"] = "#" + device.role.color
@@ -238,6 +241,7 @@ def create_edge(
edge_id: int, edge_id: int,
termination_a: Dict, termination_a: Dict,
termination_b: Dict, termination_b: Dict,
straight_cables: bool,
circuit: Optional[Dict] = None, circuit: Optional[Dict] = None,
cable: Optional[Cable] = None, cable: Optional[Cable] = None,
wireless: Optional[Dict] = None, wireless: Optional[Dict] = None,
@@ -300,6 +304,9 @@ def create_edge(
if hasattr(cable, 'color') and cable.color != "": if hasattr(cable, 'color') and cable.color != "":
edge["color"] = "#" + cable.color edge["color"] = "#" + cable.color
# if straight_cables == True: edge["smooth"] = False
edge["smooth"] = not straight_cables
return edge return edge
@@ -327,6 +334,7 @@ def get_topology_data(
queryset: QuerySet, queryset: QuerySet,
individualOptions: IndividualOptions, individualOptions: IndividualOptions,
show_unconnected: bool, show_unconnected: bool,
ignore_cable_type: list,
save_coords: bool, save_coords: bool,
show_cables: bool, show_cables: bool,
show_circuit: bool, show_circuit: bool,
@@ -338,7 +346,10 @@ def get_topology_data(
group_sites: bool, group_sites: bool,
group_locations: bool, group_locations: bool,
group_racks: bool, group_racks: bool,
group_virtualchassis: bool,
group_id, group_id,
straight_cables: bool,
grid_size: list,
): ):
supported_termination_types = [] supported_termination_types = []
@@ -360,8 +371,6 @@ def get_topology_data(
cable_ids = DefaultDict(dict) cable_ids = DefaultDict(dict)
interface_ids = DefaultDict(dict) interface_ids = DefaultDict(dict)
ignore_cable_type = individualOptions.ignore_cable_type
device_ids = [d.pk for d in queryset] device_ids = [d.pk for d in queryset]
site_ids = [d.site_id for d in queryset] site_ids = [d.site_id for d in queryset]
@@ -436,6 +445,7 @@ def get_topology_data(
circuit=circuit_model, circuit=circuit_model,
termination_a=termination_a, termination_a=termination_a,
termination_b=termination_b, termination_b=termination_b,
straight_cables=straight_cables,
) )
) )
@@ -505,6 +515,7 @@ def get_topology_data(
termination_a=termination_a, termination_a=termination_a,
termination_b=termination_b, termination_b=termination_b,
power=True, power=True,
straight_cables=straight_cables,
) )
) )
@@ -543,7 +554,7 @@ def get_topology_data(
edge_ids += 1 edge_ids += 1
termination_a = { "termination_name": interface.name, "termination_device_name": interface.device.name, "device_id": interface.device.id } termination_a = { "termination_name": interface.name, "termination_device_name": interface.device.name, "device_id": interface.device.id }
termination_b = { "termination_name": destination.name, "termination_device_name": destination.device.name, "device_id": destination.device.id } termination_b = { "termination_name": destination.name, "termination_device_name": destination.device.name, "device_id": destination.device.id }
edges.append(create_edge(edge_id=edge_ids, termination_a=termination_a, termination_b=termination_b, interface=interface)) edges.append(create_edge(edge_id=edge_ids, termination_a=termination_a, termination_b=termination_b, interface=interface, straight_cables=straight_cables))
nodes_devices[interface.device.id] = interface.device nodes_devices[interface.device.id] = interface.device
nodes_devices[destination.device.id] = destination.device nodes_devices[destination.device.id] = destination.device
@@ -553,11 +564,11 @@ def get_topology_data(
).select_related("termination_type") ).select_related("termination_type")
for link in links: for link in links:
if link.termination_type.name in ignore_cable_type: if link.termination_type.model in ignore_cable_type:
continue continue
# Normal device cables # Normal device cables
if link.termination_type.name in supported_termination_types: if link.termination_type.model in supported_termination_types:
complete_link = False complete_link = False
if link.cable_end == "A": if link.cable_end == "A":
if link.cable_id not in cable_ids: if link.cable_id not in cable_ids:
@@ -623,6 +634,7 @@ def get_topology_data(
cable=link.cable, cable=link.cable,
termination_a=termination_a, termination_a=termination_a,
termination_b=termination_b, termination_b=termination_b,
straight_cables=straight_cables,
) )
) )
@@ -662,6 +674,7 @@ def get_topology_data(
termination_a=termination_a, termination_a=termination_a,
termination_b=termination_b, termination_b=termination_b,
wireless=wireless, wireless=wireless,
straight_cables=straight_cables,
) )
) )
@@ -671,6 +684,12 @@ def get_topology_data(
options['group_racks'] = 'on' options['group_racks'] = 'on'
if group_sites: if group_sites:
options['group_sites'] = 'on' options['group_sites'] = 'on'
if group_virtualchassis:
options['group_virtualchassis'] = 'on'
if grid_size:
options['grid_size'] = grid_size
else:
options['grid_size'] = list('0')
for qs_device in queryset: for qs_device in queryset:
if qs_device.pk not in nodes_devices and show_unconnected: if qs_device.pk not in nodes_devices and show_unconnected:
@@ -697,7 +716,7 @@ class TopologyHomeView(PermissionRequiredMixin, View):
def get(self, request): def get(self, request):
self.filterset = DeviceFilterSet self.filterset = DeviceFilterSet
self.queryset = Device.objects.all().select_related( self.queryset = Device.objects.restrict(request.user, 'view').select_related(
"device_type", "role" "device_type", "role"
) )
self.queryset = self.filterset(request.GET, self.queryset).qs self.queryset = self.filterset(request.GET, self.queryset).qs
@@ -710,7 +729,7 @@ class TopologyHomeView(PermissionRequiredMixin, View):
if request.GET: if request.GET:
filter_id, save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, group_sites, group_locations, group_racks, show_neighbors = get_query_settings(request) filter_id, ignore_cable_type, save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, group_sites, group_locations, group_racks, group_virtualchassis, group, show_neighbors, straight_cables, grid_size = get_query_settings(request)
# Read options from saved filters as NetBox does not handle custom plugin filters # Read options from saved filters as NetBox does not handle custom plugin filters
if "filter_id" in request.GET and request.GET["filter_id"] != '': if "filter_id" in request.GET and request.GET["filter_id"] != '':
@@ -718,6 +737,7 @@ class TopologyHomeView(PermissionRequiredMixin, View):
saved_filter = SavedFilter.objects.get(pk=filter_id) saved_filter = SavedFilter.objects.get(pk=filter_id)
saved_filter_params = getattr(saved_filter, 'parameters') saved_filter_params = getattr(saved_filter, 'parameters')
if ignore_cable_type == () and 'ignore_cable_type' in saved_filter_params: ignore_cable_type = saved_filter_params['ignore_cable_type']
if save_coords == False and 'save_coords' in saved_filter_params: save_coords = saved_filter_params['save_coords'] if save_coords == False and 'save_coords' in saved_filter_params: save_coords = saved_filter_params['save_coords']
if show_power == False and 'show_power' in saved_filter_params: show_power = saved_filter_params['show_power'] if show_power == False and 'show_power' in saved_filter_params: show_power = saved_filter_params['show_power']
if show_circuit == False and 'show_circuit' in saved_filter_params: show_circuit = saved_filter_params['show_circuit'] if show_circuit == False and 'show_circuit' in saved_filter_params: show_circuit = saved_filter_params['show_circuit']
@@ -728,13 +748,19 @@ class TopologyHomeView(PermissionRequiredMixin, View):
if group_sites == False and 'group_sites' in saved_filter_params: group_sites = saved_filter_params['group_sites'] if group_sites == False and 'group_sites' in saved_filter_params: group_sites = saved_filter_params['group_sites']
if group_locations == False and 'group_locations' in saved_filter_params: group_locations = saved_filter_params['group_locations'] if group_locations == False and 'group_locations' in saved_filter_params: group_locations = saved_filter_params['group_locations']
if group_racks == False and 'group_racks' in saved_filter_params: group_racks = saved_filter_params['group_racks'] if group_racks == False and 'group_racks' in saved_filter_params: group_racks = saved_filter_params['group_racks']
if group_virtualchassis == False and 'group_virtualchassis' in saved_filter_params: group_virtualchassis = saved_filter_params['group_virtualchassis']
if show_neighbors == False and 'show_neighbors' in saved_filter_params: show_neighbors = saved_filter_params['show_neighbors'] if show_neighbors == False and 'show_neighbors' in saved_filter_params: show_neighbors = saved_filter_params['show_neighbors']
if straight_cables == False and 'straight_cables' in saved_filter_params: straight_cables = saved_filter_params['straight_cables']
if grid_size == 0 and 'grid_size' in saved_filter_params: grid_size = saved_filter_params['grid_size']
except SavedFilter.DoesNotExist: # filter_id not found except SavedFilter.DoesNotExist: # filter_id not found
pass pass
except Exception as inst: except Exception as inst:
print(type(inst)) print(type(inst))
if "group" not in request.GET: if "group" not in request.GET:
if 'saved_filter_params' in locals() and "group" in saved_filter_params:
group_id = saved_filter_params['group'][0]
else:
group_id = "default" group_id = "default"
else: else:
group_id = request.GET["group"] group_id = request.GET["group"]
@@ -743,6 +769,7 @@ class TopologyHomeView(PermissionRequiredMixin, View):
topo_data = get_topology_data( topo_data = get_topology_data(
queryset=self.queryset, queryset=self.queryset,
individualOptions=individualOptions, individualOptions=individualOptions,
ignore_cable_type=ignore_cable_type,
save_coords=save_coords, save_coords=save_coords,
show_unconnected=show_unconnected, show_unconnected=show_unconnected,
show_cables=show_cables, show_cables=show_cables,
@@ -755,17 +782,23 @@ class TopologyHomeView(PermissionRequiredMixin, View):
group_sites=group_sites, group_sites=group_sites,
group_locations=group_locations, group_locations=group_locations,
group_racks=group_racks, group_racks=group_racks,
group_virtualchassis=group_virtualchassis,
group_id=group_id, group_id=group_id,
straight_cables=straight_cables,
grid_size=grid_size,
) )
else: else:
# No GET-Request in URL. We most likely came here from the navigation menu. # No GET-Request in URL. We most likely came here from the navigation menu.
preselected_device_roles = IndividualOptions.objects.get(id=individualOptions.id).preselected_device_roles.all().values_list('id', flat=True) preselected_device_roles = IndividualOptions.objects.get(id=individualOptions.id).preselected_device_roles.all().values_list('id', flat=True)
preselected_tags = IndividualOptions.objects.get(id=individualOptions.id).preselected_tags.all().values_list(Lower('name'), flat=True) preselected_tags = IndividualOptions.objects.get(id=individualOptions.id).preselected_tags.all().values_list(Lower('name'), flat=True)
ignore_cable_type = IndividualOptions.objects.get(id=individualOptions.id).ignore_cable_type.translate({ord(i): None for i in '[]\''}).split(', ')
if ignore_cable_type == ['']: ignore_cable_type = []
q = QueryDict(mutable=True) q = QueryDict(mutable=True)
q.setlist("role_id", list(preselected_device_roles)) q.setlist("role_id", list(preselected_device_roles))
q.setlist("tag", list(preselected_tags)) q.setlist("tag", list(preselected_tags))
q.setlist("ignore_cable_type", ignore_cable_type)
if individualOptions.save_coords: q['save_coords'] = "True" if individualOptions.save_coords: q['save_coords'] = "True"
if individualOptions.show_unconnected: q['show_unconnected'] = "True" if individualOptions.show_unconnected: q['show_unconnected'] = "True"
@@ -779,6 +812,9 @@ class TopologyHomeView(PermissionRequiredMixin, View):
if individualOptions.group_sites: q['group_sites'] = "True" if individualOptions.group_sites: q['group_sites'] = "True"
if individualOptions.group_locations: q['group_locations'] = "True" if individualOptions.group_locations: q['group_locations'] = "True"
if individualOptions.group_racks: q['group_racks'] = "True" if individualOptions.group_racks: q['group_racks'] = "True"
if individualOptions.group_virtualchassis: q['group_virtualchassis'] = "True"
if individualOptions.straight_cables: q['straight_cables'] = "True"
if individualOptions.grid_size: q['grid_size'] = individualOptions.grid_size
if individualOptions.draw_default_layout: if individualOptions.draw_default_layout:
q['draw_init'] = "True" q['draw_init'] = "True"
else: else:
@@ -1136,7 +1172,10 @@ class TopologyIndividualOptionsView(PermissionRequiredMixin, View):
'group_sites': queryset.group_sites, 'group_sites': queryset.group_sites,
'group_locations': queryset.group_locations, 'group_locations': queryset.group_locations,
'group_racks': queryset.group_racks, 'group_racks': queryset.group_racks,
'group_virtualchassis': queryset.group_virtualchassis,
'draw_default_layout': queryset.draw_default_layout, 'draw_default_layout': queryset.draw_default_layout,
'straight_cables': queryset.straight_cables,
'grid_size': queryset.grid_size,
}, },
) )
+1 -1
View File
@@ -7,7 +7,7 @@ long_description = readme.read_text()
setup( setup(
name="netbox-topology-views", name="netbox-topology-views",
version="4.0.0-beta.2", version="4.0.1",
description="An NetBox plugin to create Topology maps", description="An NetBox plugin to create Topology maps",
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",