Compare commits

..
Author SHA1 Message Date
mattieserver f5d9f589bf Fix circuit error (#87)
* fix 'Interface' object has no attribute 'circuit'
2022-03-09 21:00:55 +01:00
mattieserver 3e5b28f208 Update to support netbox v3.1 (#82)
* init for v3.1

* added coords + bump version

* fix filter + added preselected roles

* added draw_default_layout

* upgraded packages

* fixed edge/node labels

* fixed enable_circuit_terminations

* added filter for unconnected devices

* added ignore_cable_type

* fixed old settings using a string instead of array

* removed distutils

* fixed coords check

* added coor saving

* added doc img

* updated docs

* bump version

* bump version
2022-03-08 17:03:47 +01:00
10 changed files with 115 additions and 54 deletions
+2 -2
View File
@@ -8,7 +8,7 @@ Support to filter on name, site, tag and device role.
## Preview ## Preview
![preview image](doc/img/preview.png?raw=true "preview") ![preview image](doc/img/preview_3.1.jpeg?raw=true "preview")
## Install ## Install
@@ -80,7 +80,7 @@ If you add your own image you also need to add the slug to the `device_img` sett
## Use ## Use
Go to the plugins tab in the navbar and click topology or go to `$NETBOX_URL/plugins/topology-views/` to view your topologies Go to the plugins tab in the navbar and click topology or go to `$NETBOX_URL/plugins/netbox_topology_views/` to view your topologies
### Update ### Update
Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

+2 -2
View File
@@ -4,7 +4,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 = '1.0.0-alpha.5' version = '1.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'
@@ -15,7 +15,7 @@ class TopologyViewsConfig(PluginConfig):
'device_img': ['access-switch', 'core-switch', 'firewall', 'router', 'distribution-switch', 'backup', 'storage,wan-network', 'wireless-ap', 'server', 'internal-switch', 'isp-cpe-material', 'non-racked-devices', 'power-units'], 'device_img': ['access-switch', 'core-switch', 'firewall', 'router', 'distribution-switch', 'backup', 'storage,wan-network', 'wireless-ap', 'server', 'internal-switch', 'isp-cpe-material', 'non-racked-devices', 'power-units'],
'allow_coordinates_saving': False, 'allow_coordinates_saving': False,
'preselected_tags' : [], 'preselected_tags' : [],
'enable_circuit_terminations': True, 'enable_circuit_terminations': False,
'draw_default_layout': False 'draw_default_layout': False
} }
+9 -2
View File
@@ -4,14 +4,15 @@ from django.utils.translation import gettext as _
from extras.models import Tag from extras.models import Tag
from dcim.models import Device, Site, Region, DeviceRole, Location from dcim.models import Device, Site, Region, DeviceRole, Location
from django.conf import settings
from utilities.forms import (TagFilterField, DynamicModelMultipleChoiceField, FilterForm) from utilities.forms import (TagFilterField, DynamicModelMultipleChoiceField, FilterForm)
allow_coordinates_saving = settings.PLUGINS_CONFIG["netbox_topology_views"]["allow_coordinates_saving"]
class DeviceFilterForm(FilterForm): class DeviceFilterForm(FilterForm):
model = Device model = Device
field_groups = [ field_groups = [
['q', 'hide_unconnected'], ['q', 'hide_unconnected', 'save_coords'],
['region_id', 'site_id', 'location_id'], ['region_id', 'site_id', 'location_id'],
['device_role_id'], ['device_role_id'],
['tag'], ['tag'],
@@ -50,4 +51,10 @@ class DeviceFilterForm(FilterForm):
required=False, required=False,
initial=False) initial=False)
save_coords = forms.BooleanField(
label=_("Save Coordinates"),
required=False,
disabled=(not allow_coordinates_saving),
initial=False)
tag = TagFilterField(model) tag = TagFilterField(model)
@@ -1 +1 @@
var graph=null,container=null;function iniPlotboxFull(){document.addEventListener("DOMContentLoaded",function(){container=document.getElementById("fullvisgraph"),startRender()},!1)}function startRender(){var e=location.search;$.ajax({type:"GET",url:"../../api/plugins/topology-views/search/search/"+e,contentType:"application/json; charset=utf-8",success:function(e,n,t){graph=null,nodes=new vis.DataSet,edges=new vis.DataSet,graph=new vis.Network(container,{nodes:nodes,edges:edges},options),$.each(e.nodes,function(e,n){nodes.add(n)}),$.each(e.edges,function(e,n){edges.add(n)}),graph.fit(),canvas=document.getElementById("fullvisgraph").getElementsByTagName("canvas")[0]}})}graph=null,container=null;var downloadButton=null,MIME_TYPE="image/png",canvas=null,csrftoken=null,nodes=new vis.DataSet,edges=new vis.DataSet,options={interaction:{hover:!0,hoverConnectedEdges:!0,multiselect:!1},nodes:{shape:"image",brokenImage:"../../static/netbox_topology_views/img/role-unknown.png",size:35,font:{multi:"md",face:"helvetica"}},edges:{length:100,width:2,font:{face:"helvetica"}},physics:{solver:"forceAtlas2Based"}},selected_regions=[],selected_sites=[];function htmlTitle(e){return(container=document.createElement("div")).innerHTML=e,container}function addEdge(e){e.title=htmlTitle(e.title),edges.add(e)}function addNode(e){e.title=htmlTitle(e.title),nodes.add(e)}function iniPlotboxIndex(){document.addEventListener("DOMContentLoaded",function(){container=document.getElementById("visgraph"),handleLoadData(),downloadButton=document.getElementById("btnDownloadImage"),btnFullView=document.getElementById("btnFullView")},!1)}function handleLoadData(){graph=null,nodes=new vis.DataSet,edges=new vis.DataSet,graph=new vis.Network(container,{nodes:nodes,edges:edges},options),topology_data.edges.forEach(addEdge),topology_data.nodes.forEach(addNode),graph.fit(),canvas=document.getElementById("visgraph").getElementsByTagName("canvas")[0],graph.on("afterDrawing",function(){var e=canvas.toDataURL(MIME_TYPE);downloadButton.href=e,downloadButton.download="topology"})} var graph=null,container=null,downloadButton=null,MIME_TYPE="image/png",canvas=null,csrftoken=null,nodes=new vis.DataSet,edges=new vis.DataSet,options={interaction:{hover:!0,hoverConnectedEdges:!0,multiselect:!1},nodes:{shape:"image",brokenImage:"../../static/netbox_topology_views/img/role-unknown.png",size:35,font:{multi:"md",face:"helvetica"}},edges:{length:100,width:2,font:{face:"helvetica"}},physics:{solver:"forceAtlas2Based"}},selected_regions=[],selected_sites=[],coord_save_checkbox=null;function getCookie(e){var t=null;if(document.cookie&&""!==document.cookie)for(var n=document.cookie.split(";"),o=0;o<n.length;o++){var a=n[o].trim();if(a.substring(0,e.length+1)===e+"="){t=decodeURIComponent(a.substring(e.length+1));break}}return t}function htmlTitle(e){return(container=document.createElement("div")).innerHTML=e,container}function addEdge(e){e.title=htmlTitle(e.title),edges.add(e)}function addNode(e){e.title=htmlTitle(e.title),nodes.add(e)}function iniPlotboxIndex(){document.addEventListener("DOMContentLoaded",function(){csrftoken=getCookie("csrftoken"),container=document.getElementById("visgraph"),handleLoadData(),downloadButton=document.getElementById("btnDownloadImage"),btnFullView=document.getElementById("btnFullView"),coord_save_checkbox=document.getElementById("id_save_coords")},!1)}function handleLoadData(){null!==topology_data&&(graph=null,nodes=new vis.DataSet,edges=new vis.DataSet,graph=new vis.Network(container,{nodes:nodes,edges:edges},options),topology_data.edges.forEach(addEdge),topology_data.nodes.forEach(addNode),graph.fit(),canvas=document.getElementById("visgraph").getElementsByTagName("canvas")[0],graph.on("afterDrawing",function(){var e=canvas.toDataURL(MIME_TYPE);downloadButton.href=e,downloadButton.download="topology"}),graph.on("dragEnd",function(e){if(dragged=this.getPositions(e.nodes),coord_save_checkbox.checked&&0!==Object.keys(dragged).length)for(dragged_device in dragged){var t=dragged_device,n=new XMLHttpRequest;n.open("PATCH","/api/plugins/netbox_topology_views/save-coords/save_coords/"),n.setRequestHeader("X-CSRFToken",csrftoken),n.setRequestHeader("Accept","application/json"),n.setRequestHeader("Content-Type","application/json"),n.onreadystatechange=function(){4===n.readyState&&(console.log(n.status),console.log(n.responseText))};var o=JSON.stringify({node_id:t,x:dragged[t].x,y:dragged[t].y});n.send(o)}}))}
+53 -1
View File
@@ -34,6 +34,24 @@ var options = {
}; };
var selected_regions = []; var selected_regions = [];
var selected_sites = []; var selected_sites = [];
var coord_save_checkbox = null;
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function htmlTitle(html) { function htmlTitle(html) {
container = document.createElement("div"); container = document.createElement("div");
@@ -53,15 +71,17 @@ function addNode(item) {
function iniPlotboxIndex() { function iniPlotboxIndex() {
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
csrftoken = getCookie('csrftoken');
container = document.getElementById('visgraph'); container = document.getElementById('visgraph');
handleLoadData(); handleLoadData();
downloadButton = document.getElementById('btnDownloadImage'); downloadButton = document.getElementById('btnDownloadImage');
btnFullView = document.getElementById('btnFullView'); btnFullView = document.getElementById('btnFullView');
coord_save_checkbox = document.getElementById('id_save_coords');
}, false); }, false);
} }
function handleLoadData() { function handleLoadData() {
if (topology_data !== null) {
graph = null; graph = null;
nodes = new vis.DataSet(); nodes = new vis.DataSet();
edges = new vis.DataSet(); edges = new vis.DataSet();
@@ -79,4 +99,36 @@ function handleLoadData() {
downloadButton.download = "topology"; downloadButton.download = "topology";
}); });
graph.on("dragEnd", function (params) {
dragged = this.getPositions(params.nodes);
if (coord_save_checkbox.checked) {
if (Object.keys(dragged).length !== 0) {
for (dragged_device in dragged) {
var node_id = dragged_device;
var url = "/api/plugins/netbox_topology_views/save-coords/save_coords/";
var xhr = new XMLHttpRequest();
xhr.open("PATCH", url);
xhr.setRequestHeader('X-CSRFToken', csrftoken );
xhr.setRequestHeader("Accept", "application/json");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
console.log(xhr.status);
console.log(xhr.responseText);
}};
var data = JSON.stringify({
'node_id': node_id,
'x': dragged[node_id].x,
'y': dragged[node_id].y});
xhr.send(data);
}
}
}
});
}
} }
+45 -43
View File
@@ -66,52 +66,53 @@ def get_topology_data(queryset, hide_unconnected):
device_has_connections = True device_has_connections = True
else: else:
if settings.PLUGINS_CONFIG["netbox_topology_views"]["enable_circuit_terminations"]: if settings.PLUGINS_CONFIG["netbox_topology_views"]["enable_circuit_terminations"]:
if link_from.termination_a.circuit.id not in circuit_ids: if link_from.termination_a_type.name == "circuit termination":
circuit_ids.append(link_from.termination_a.circuit.id) if link_from.termination_a.circuit.id not in circuit_ids:
edge_ids += 1 circuit_ids.append(link_from.termination_a.circuit.id)
edge_ids += 1
cable_b_dev_name = link_from.termination_b.device.name cable_b_dev_name = link_from.termination_b.device.name
if cable_b_dev_name is None: if cable_b_dev_name is None:
cable_b_dev_name = "device B name unknown" cable_b_dev_name = "device B name unknown"
cable_b_name = link_from.termination_b.name cable_b_name = link_from.termination_b.name
if cable_b_name is None: if cable_b_name is None:
cable_b_name = "cable B name unknown" cable_b_name = "cable B name unknown"
edge = {} edge = {}
edge["id"] = edge_ids edge["id"] = edge_ids
edge["to"] = link_from.termination_b.device.id edge["to"] = link_from.termination_b.device.id
edge["dashes"] = True edge["dashes"] = True
title = "" title = ""
title += "Circuit provider: " + link_from.termination_a.circuit.provider.name + "<br>" title += "Circuit provider: " + link_from.termination_a.circuit.provider.name + "<br>"
title += "Termination between <br>" title += "Termination between <br>"
title += cable_b_dev_name + " [" + cable_b_name + "]<br>" title += cable_b_dev_name + " [" + cable_b_name + "]<br>"
if link_from.termination_a.circuit.termination_a is not None and link_from.termination_a.circuit.termination_a.cable is not None and link_from.termination_a.circuit.termination_a.cable.id != link_from.id and link_from.termination_a.circuit.termination_a.cable.termination_b is not None and link_from.termination_a.circuit.termination_a.cable.termination_b.device is not None: if link_from.termination_a.circuit.termination_a is not None and link_from.termination_a.circuit.termination_a.cable is not None and link_from.termination_a.circuit.termination_a.cable.id != link_from.id and link_from.termination_a.circuit.termination_a.cable.termination_b is not None and link_from.termination_a.circuit.termination_a.cable.termination_b.device is not None:
edge["from"] = link_from.termination_a.circuit.termination_a.cable.termination_b.device.id edge["from"] = link_from.termination_a.circuit.termination_a.cable.termination_b.device.id
cable_a_dev_name = link_from.termination_a.circuit.termination_a.cable.termination_b.device.name cable_a_dev_name = link_from.termination_a.circuit.termination_a.cable.termination_b.device.name
if cable_a_dev_name is None: if cable_a_dev_name is None:
cable_a_dev_name = "device B name unknown" cable_a_dev_name = "device B name unknown"
cable_b_name = link_from.termination_a.circuit.termination_a.cable.termination_b.name cable_b_name = link_from.termination_a.circuit.termination_a.cable.termination_b.name
if cable_a_name is None: if cable_a_name is None:
cable_a_name = "cable B name unknown" cable_a_name = "cable B name unknown"
title += cable_a_dev_name + " [" + cable_a_name + "]<br>" title += cable_a_dev_name + " [" + cable_a_name + "]<br>"
edge["title"] = title edge["title"] = title
edges.append(edge) edges.append(edge)
if link_from.termination_a.circuit.termination_z is not None and link_from.termination_a.circuit.termination_z.cable is not None and link_from.termination_a.circuit.termination_z.cable.id != link_from.id and link_from.termination_a.circuit.termination_z.cable.termination_b is not None and link_from.termination_a.circuit.termination_z.cable.termination_b.device is not None: if link_from.termination_a.circuit.termination_z is not None and link_from.termination_a.circuit.termination_z.cable is not None and link_from.termination_a.circuit.termination_z.cable.id != link_from.id and link_from.termination_a.circuit.termination_z.cable.termination_b is not None and link_from.termination_a.circuit.termination_z.cable.termination_b.device is not None:
edge["from"] = link_from.termination_a.circuit.termination_z.cable.termination_b.device.id edge["from"] = link_from.termination_a.circuit.termination_z.cable.termination_b.device.id
cable_a_dev_name = link_from.termination_a.circuit.termination_z.cable.termination_b.device.name cable_a_dev_name = link_from.termination_a.circuit.termination_z.cable.termination_b.device.name
if cable_a_dev_name is None: if cable_a_dev_name is None:
cable_a_dev_name = "device B name unknown" cable_a_dev_name = "device B name unknown"
cable_a_name = link_from.termination_a.circuit.termination_z.cable.termination_b.name cable_a_name = link_from.termination_a.circuit.termination_z.cable.termination_b.name
if cable_a_name is None: if cable_a_name is None:
cable_a_name = "cable B name unknown" cable_a_name = "cable B name unknown"
title += cable_a_dev_name + " [" + cable_a_name + "]<br>" title += cable_a_dev_name + " [" + cable_a_name + "]<br>"
edge["title"] = title edge["title"] = title
edges.append(edge) edges.append(edge)
if qs_device.id not in nodes_ids: if qs_device.id not in nodes_ids:
if hide_unconnected == None or (hide_unconnected is True and device_has_connections is True): if hide_unconnected == None or (hide_unconnected is True and device_has_connections is True):
@@ -150,10 +151,11 @@ def get_topology_data(queryset, hide_unconnected):
if "coordinates" in qs_device.custom_field_data: if "coordinates" in qs_device.custom_field_data:
if qs_device.custom_field_data["coordinates"] is not None: if qs_device.custom_field_data["coordinates"] is not None:
cords = qs_device.custom_field_data["coordinates"].split(";") if ';' in qs_device.custom_field_data["coordinates"]:
node["x"] = int(cords[0]) cords = qs_device.custom_field_data["coordinates"].split(";")
node["y"] = int(cords[1]) node["x"] = int(cords[0])
node["physics"] = False node["y"] = int(cords[1])
node["physics"] = False
nodes.append(node) nodes.append(node)
results = {} results = {}
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "netbox_topology_views", "name": "netbox_topology_views",
"version": "1.0.0-alpha.45", "version": "1.0.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"private": true, "private": true,
"name": "netbox_topology_views", "name": "netbox_topology_views",
"version": "1.0.0-alpha.45", "version": "1.0.1",
"scripts": { "scripts": {
"resources": "gulp build", "resources": "gulp build",
"resources_dev": "gulp build_dev" "resources_dev": "gulp build_dev"
+1 -1
View File
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
setup( setup(
name='netbox-topology-views', name='netbox-topology-views',
version='1.0.0-alpha.5', version='1.0.1',
description='An NetBox plugin to create Topology maps', description='An NetBox plugin to create Topology maps',
url='https://github.com/mattieserver/netbox-topology-views', url='https://github.com/mattieserver/netbox-topology-views',
author='Mattijs Vanhaverbeke', author='Mattijs Vanhaverbeke',