Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closes #163: Show raster and snap icons #550

Merged
merged 2 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion netbox_topology_views/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ class Meta:
class IndividualOptionsSerializer(NetBoxModelSerializer):
class Meta:
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", "group_virtualchassis", "draw_default_layout", "straight_cables")
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")
4 changes: 3 additions & 1 deletion netbox_topology_views/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def list(self, request):

if request.GET:

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 = 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
if "filter_id" in request.GET and request.GET["filter_id"] != '':
Expand All @@ -131,6 +131,7 @@ def list(self, request):
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 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
pass
except Exception as inst:
Expand Down Expand Up @@ -162,6 +163,7 @@ def list(self, request):
group_virtualchassis=group_virtualchassis,
group_id=group_id,
straight_cables=straight_cables,
grid_size=grid_size,
)
xml_data = export_data_to_xml(topo_data).decode('utf-8')

Expand Down
23 changes: 21 additions & 2 deletions netbox_topology_views/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class DeviceFilterForm(
FieldSet(
'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',
'group_sites', 'group_locations', 'group_racks', 'group_virtualchassis', 'straight_cables', name=_("Options")
'group_sites', 'group_locations', 'group_racks', 'group_virtualchassis', 'straight_cables', 'grid_size', name=_("Options")
),
FieldSet('id', name=_("Device")),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_("Location")),
Expand Down Expand Up @@ -327,6 +327,14 @@ class DeviceFilterForm(
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):
fieldsets = (
Expand Down Expand Up @@ -521,6 +529,7 @@ class IndividualOptionsForm(NetBoxModelForm):
'group_virtualchassis',
'draw_default_layout',
'straight_cables',
'grid_size',
),
)

Expand Down Expand Up @@ -662,6 +671,16 @@ class IndividualOptionsForm(NetBoxModelForm):
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:
model = IndividualOptions
Expand All @@ -670,5 +689,5 @@ class Meta:
'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'
'straight_cables', 'grid_size'
]
Original file line number Diff line number Diff line change
@@ -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),
),
]
3 changes: 3 additions & 0 deletions netbox_topology_views/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,9 @@ class IndividualOptions(NetBoxModel):
straight_cables = models.BooleanField(
default=False
)
grid_size = models.PositiveSmallIntegerField(
default=0
)

_netbox_private = True

Expand Down
36 changes: 18 additions & 18 deletions netbox_topology_views/static/netbox_topology_views/js/app.js

Large diffs are not rendered by default.

127 changes: 127 additions & 0 deletions netbox_topology_views/static_dev/js/home.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,129 @@ const coordSaveCheckbox = document.querySelector('#id_save_coords')
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.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) => {
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

Promise.allSettled(
Expand Down Expand Up @@ -139,12 +258,20 @@ const coordSaveCheckbox = document.querySelector('#id_save_coords')
}
})

graph.on('beforeDrawing', (canvascontext) => {
if (gridSize > 0) {
drawGrid(canvascontext);
}
})

graph.on('afterDrawing', (canvascontext) => {
allRectangles = [];
if(group_sites != null && group_sites == 'on') { drawGroupRectangles(canvascontext, groupedNodeSites, siteRectParams); }
if(group_locations != null && group_locations == 'on') { drawGroupRectangles(canvascontext, groupedNodeLocations, locationRectParams); }
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) => {
Expand Down
6 changes: 5 additions & 1 deletion netbox_topology_views/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,11 @@ def get_query_settings(request):
if request.GET["straight_cables"] == "True":
straight_cables = True

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 = 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():
wireless = [2, 10, 2, 10]
Expand Down
11 changes: 10 additions & 1 deletion netbox_topology_views/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ def get_topology_data(
group_virtualchassis: bool,
group_id,
straight_cables: bool,
grid_size: list,
):

supported_termination_types = []
Expand Down Expand Up @@ -685,6 +686,10 @@ def get_topology_data(
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:
if qs_device.pk not in nodes_devices and show_unconnected:
Expand Down Expand Up @@ -724,7 +729,7 @@ def get(self, request):

if request.GET:

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 = 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
if "filter_id" in request.GET and request.GET["filter_id"] != '':
Expand All @@ -746,6 +751,7 @@ def get(self, request):
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 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
pass
except Exception as inst:
Expand Down Expand Up @@ -779,6 +785,7 @@ def get(self, request):
group_virtualchassis=group_virtualchassis,
group_id=group_id,
straight_cables=straight_cables,
grid_size=grid_size,
)

else:
Expand Down Expand Up @@ -807,6 +814,7 @@ def get(self, request):
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:
q['draw_init'] = "True"
else:
Expand Down Expand Up @@ -1167,6 +1175,7 @@ def get(self, request):
'group_virtualchassis': queryset.group_virtualchassis,
'draw_default_layout': queryset.draw_default_layout,
'straight_cables': queryset.straight_cables,
'grid_size': queryset.grid_size,
},
)

Expand Down