Skip to content

Commit

Permalink
Closes #329: Draw rectangles around Sites/Locations/Racks (#462)
Browse files Browse the repository at this point in the history
  • Loading branch information
dreng authored Mar 3, 2024
1 parent 74c2958 commit b92b012
Show file tree
Hide file tree
Showing 8 changed files with 314 additions and 16 deletions.
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", "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", "draw_default_layout")
41 changes: 39 additions & 2 deletions netbox_topology_views/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ class DeviceFilterForm(
(None, ('q', 'filter_id', 'tag')),
(_('Options'), (
'group', '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'
)),
(_('Device'), ('id',)),
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
Expand Down Expand Up @@ -258,6 +259,15 @@ class DeviceFilterForm(
show_wireless = forms.BooleanField(
label =_('Show Wireless Links'), required=False, initial=False
)
group_sites = forms.BooleanField(
label =_('Group Sites'), required=False, initial=False
)
group_locations = forms.BooleanField(
label =_('Group Locations'), required=False, initial=False
)
group_racks = forms.BooleanField(
label =_('Group Racks'), required=False, initial=False
)

class CoordinateGroupsForm(NetBoxModelForm):
fieldsets = (
Expand Down Expand Up @@ -447,6 +457,9 @@ class IndividualOptionsForm(NetBoxModelForm):
'show_circuit',
'show_power',
'show_wireless',
'group_sites',
'group_locations',
'group_racks',
'draw_default_layout',
),
),
Expand Down Expand Up @@ -548,6 +561,27 @@ class IndividualOptionsForm(NetBoxModelForm):
help_text=_('Displays wireless connections. These connections are '
'displayed as blue dotted lines.')
)
group_sites = forms.BooleanField(
label =_('Group Sites'),
required=False,
initial=False,
help_text=_('Draws a rectangle around Devices that belong to the '
'same site.')
)
group_locations = forms.BooleanField(
label =_('Group Locations'),
required=False,
initial=False,
help_text=_('Draws a rectangle around Devices that belong to the '
'same location.')
)
group_racks = forms.BooleanField(
label =_('Group Racks'),
required=False,
initial=False,
help_text=_('Draws a rectangle around Devices that belong to the '
'same rack.')
)
draw_default_layout = forms.BooleanField(
label = ('Draw Default Layout'),
required=False,
Expand All @@ -559,5 +593,8 @@ class IndividualOptionsForm(NetBoxModelForm):
class Meta:
model = IndividualOptions
fields = [
'user_id', 'ignore_cable_type', 'preselected_device_roles', 'preselected_tags', 'save_coords', 'show_unconnected', 'show_cables', 'show_logical_connections', 'show_single_cable_logical_conns', 'show_neighbors', 'show_circuit', 'show_power', 'show_wireless', 'draw_default_layout'
'user_id', 'ignore_cable_type', 'preselected_device_roles', 'preselected_tags',
'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'
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 4.2.10 on 2024-03-02 16:26

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('netbox_topology_views', '0006_powerpanelcoordinate_powerfeedcoordinate_and_more'),
]

operations = [
migrations.AddField(
model_name='individualoptions',
name='group_locations',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='individualoptions',
name='group_racks',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='individualoptions',
name='group_sites',
field=models.BooleanField(default=False),
),
]
9 changes: 9 additions & 0 deletions netbox_topology_views/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,15 @@ class IndividualOptions(NetBoxModel):
show_wireless = models.BooleanField(
default=False
)
group_sites = models.BooleanField(
default=False
)
group_locations = models.BooleanField(
default=False
)
group_racks = models.BooleanField(
default=False
)
draw_default_layout = models.BooleanField(
default=False
)
Expand Down
22 changes: 11 additions & 11 deletions netbox_topology_views/static/netbox_topology_views/js/app.js

Large diffs are not rendered by default.

188 changes: 188 additions & 0 deletions netbox_topology_views/static_dev/js/home.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ const csrftoken = getCookie('csrftoken')
// Render vis graph
let graph = null // vis graph instance

let searchParams = new URLSearchParams(window.location.search)
let group_sites = searchParams.get('group_sites')
let group_locations = searchParams.get('group_locations')
let group_racks = searchParams.get('group_racks')

const container = document.querySelector('#visgraph')
const coordSaveCheckbox = document.querySelector('#id_save_coords')
;(function handleLoadData() {
Expand Down Expand Up @@ -133,6 +138,189 @@ const coordSaveCheckbox = document.querySelector('#id_save_coords')
})
}
})

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

graph.on('click', (canvascontext) => {
allRectangles.forEach(key => {
// 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)
&& canvascontext.pointer.canvas.y > (key.y1 - key.border / 2 - 1) && canvascontext.pointer.canvas.y < (key.y2 + key.border / 2 + 1)) {
// 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)
|| canvascontext.pointer.canvas.y < (key.y1 + key.border / 2 + 1) || canvascontext.pointer.canvas.y > (key.y2 - key.border / 2 - 1)) {
// Generate an array of affected nodes in order to pass it to the select.Nodes() function
let arr = [];
if(key.category == "Site") {
groupedNodeSites.forEach(subArray => {
subArray.forEach(element => {
if (element[1] == key.id) {
arr.push(element[0]);
}
});
});
}
if(key.category == "Location") {
groupedNodeLocations.forEach(subArray => {
subArray.forEach(element => {
if (element[1] === key.id) {
arr.push(element[0]);
}
});
});
}
if(key.category == "Rack") {
groupedNodeRacks.forEach(subArray => {
subArray.forEach(element => {
if (element[1] === key.id) {
arr.push(element[0]);
}
});
});
}
graph.selectNodes(arr);
}
}
});
})

// Add information on which node belongs to which group (site/location/rack).
// Create an array for each group in order to loop through that arrays later
function combineNodeInfo(typeId, type) {
let nodesArray = [];
// Extract node ids and node type ids from all nodes
for (let [key, value] of nodes._data) {
if (value[typeId] != undefined) {
nodesArray.push([value.id, value[typeId], value[type]]);
}
}
// Split single array above into arrays grouped by node id
let groupedNodeArray = nodesArray.reduce((acc, value) => {
let key = value[1]; // node id
acc[key] = acc[key] || [];
acc[key].push(value);
return acc;
}, {});

return Object.values(groupedNodeArray);
}

var allRectangles = [];
/* Draw a single rectangle with given parameters
rectangle expects an object that consists of the following keys:
ctx: canvas context on which the rectangle should be drawn
x: x-coordinate of top left point of the rectangle
y: y-coordinate of top left point of the rectangle
width: width of rectangle
height: height of rectangle
lineWidth: border width
color: border color
text: a string to be placed where you want it to be
textPaddingX: x-position of the text
textPaddingY: y-position of the text
font: text font */
function drawGroupRectangle(rectangle) {
// Draw rectangle
rectangle.ctx.beginPath();
rectangle.ctx.lineWidth = rectangle.lineWidth;
rectangle.ctx.strokeStyle = rectangle.color;
rectangle.ctx.rect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
rectangle.ctx.stroke();
// Draw text
rectangle.ctx.font = rectangle.font;
rectangle.ctx.fillStyle = rectangle.color;
rectangle.ctx.fillText(rectangle.text, rectangle.x + rectangle.textPaddingX, rectangle.y + rectangle.textPaddingY);

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)
rectParams expects an object that consists of the following keys:
lineWidth: border width (string)
color: border color (string)
paddingX: rectangle x-padding, calculated from the center of a node (int)
paddingY: rectangle y-padding, calculated from the center of a node (int)
textPaddingX: text x-padding, calculated from the lower left point of the text (int)
textPaddingY: text y-padding, calculated from the lower left point of the text (int)
font: css-like font size and font (string) */
function drawGroupRectangles(canvascontext, groupedNodes, rectParams) {
for(let value of Object.entries(groupedNodes)) {
const rectangles = [];
const xValues = [];
const yValues = [];

for(let val of value[1]) {
xValues.push(graph.getPosition(val[0]).x);
yValues.push(graph.getPosition(val[0]).y);
}

const rectX = Math.min(...xValues) - rectParams.paddingX;
const rectY = Math.min(...yValues) - rectParams.paddingY;
const rectSizeX = Math.max(...xValues) - Math.min(...xValues) + 2*rectParams.paddingX;
const rectSizeY = Math.max(...yValues) - Math.min(...yValues) + 2*rectParams.paddingY;

rectangles.push({
ctx: canvascontext,
x: rectX,
y: rectY,
width: rectSizeX,
height: rectSizeY,
lineWidth: rectParams.lineWidth,
color: rectParams.color,
text: value[1][0][2],
textPaddingX: rectParams.textPaddingX,
textPaddingY: rectParams.textPaddingY,
font: rectParams.font,
id: value[1][0][1],
category: rectParams.category
});

rectangles.forEach(function(rectangle) {
drawGroupRectangle(rectangle);
});
}
}

let groupedNodeSites = combineNodeInfo('site_id', 'site');
let siteRectParams = {
lineWidth: "5",
color: "red",
paddingX: 84,
paddingY: 84,
textPaddingX: 8,
textPaddingY: -8,
font: "14px helvetica",
category: "Site"
}

let groupedNodeLocations = combineNodeInfo('location_id', 'location');
let locationRectParams = {
lineWidth: "5",
color: "yellow",
paddingX: 77,
paddingY: 77,
textPaddingX: 12,
textPaddingY: 22,
font: "14px helvetica",
category: "Location"
}

let groupedNodeRacks = combineNodeInfo('rack_id', 'rack');
let rackRectParams = {
lineWidth: "5",
color: "green",
paddingX: 70,
paddingY: 70,
textPaddingX: 8,
textPaddingY: 30,
font: "14px helvetica",
category: "Rack"
}
})()

// Download Graph
Expand Down
17 changes: 16 additions & 1 deletion netbox_topology_views/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,27 @@ def get_query_settings(request):
if request.GET["show_wireless"] == "on" :
show_wireless = True

group_sites = False
if "group_sites" in request.GET:
if request.GET["group_sites"] == "on" :
group_sites = True

group_locations = False
if "group_locations" in request.GET:
if request.GET["group_locations"] == "on" :
group_locations = True

group_racks = False
if "group_racks" in request.GET:
if request.GET["group_racks"] == "on" :
group_racks = 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, show_neighbors
return 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

class LinePattern():
wireless = [2, 10, 2, 10]
Expand Down
Loading

0 comments on commit b92b012

Please sign in to comment.