Compare commits

..
12 changed files with 66 additions and 130 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_3.1.jpeg?raw=true "preview") ![preview image](doc/img/preview.png?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/netbox_topology_views/` to view your topologies Go to the plugins tab in the navbar and click topology or go to `$NETBOX_URL/plugins/topology-views/` to view your topologies
### Update ### Update
Binary file not shown.

Before

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.3' version = '1.0.0-alpha.3'
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': False, 'enable_circuit_terminations': True,
'draw_default_layout': False 'draw_default_layout': False
} }
+9
View File
@@ -12,6 +12,15 @@ from circuits.models import Circuit
from extras.models import Tag from extras.models import Tag
ignore_cable_type_raw = settings.PLUGINS_CONFIG["netbox_topology_views"]["ignore_cable_type"]
ignore_cable_type = ignore_cable_type_raw.split(",")
preselected_device_roles_raw = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_device_roles"]
preselected_device_roles = preselected_device_roles_raw.split(",")
preselected_tags_raw = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_tags"]
preselected_tags = preselected_tags_raw.split(",")
class TopologyViewsRootView(APIRootView): class TopologyViewsRootView(APIRootView):
def get_view_name(self): def get_view_name(self):
return 'TopologyViews' return 'TopologyViews'
+1 -3
View File
@@ -1,12 +1,10 @@
import django_filters import django_filters
from extras.filters import TagFilter from extras.filters import TagFilter
from dcim.models import Device, DeviceRole, Region, Site, Location from dcim.models import Device, DeviceRole, Region, Site, Location
from tenancy.models import TenantGroup, Tenant
from tenancy.filtersets import TenancyFilterSet
from utilities.filters import TreeNodeMultipleChoiceFilter from utilities.filters import TreeNodeMultipleChoiceFilter
from django.db.models import Q from django.db.models import Q
class DeviceFilterSet(TenancyFilterSet, django_filters.FilterSet): class DeviceFilterSet(django_filters.FilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label='Search',
+3 -14
View File
@@ -1,22 +1,17 @@
import imp
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.utils.translation import gettext as _ 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 tenancy.models import TenantGroup, Tenant
from tenancy.forms import TenancyFilterForm
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(TenancyFilterForm, FilterForm): class DeviceFilterForm(FilterForm):
model = Device model = Device
field_groups = [ field_groups = [
['q', 'hide_unconnected', 'save_coords'], ['q', 'hide_unconnected'],
['tenant_group_id', 'tenant_id'],
['region_id', 'site_id', 'location_id'], ['region_id', 'site_id', 'location_id'],
['device_role_id'], ['device_role_id'],
['tag'], ['tag'],
@@ -55,10 +50,4 @@ class DeviceFilterForm(TenancyFilterForm, 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,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,htmlElement=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"),htmlElement=document.getElementsByTagName("html")[0],handleLoadData(),downloadButton=document.getElementById("btnDownloadImage"),btnFullView=document.getElementById("btnFullView"),coord_save_checkbox=document.getElementById("id_save_coords")},!1)}function handleLoadData(){null!==topology_data&&("dark"==htmlElement.dataset.netboxColorMode&&(options.nodes.font.color="#fff"),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)}}))} 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"})}
@@ -34,25 +34,6 @@ var options = {
}; };
var selected_regions = []; var selected_regions = [];
var selected_sites = []; var selected_sites = [];
var coord_save_checkbox = null;
var htmlElement = 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");
@@ -72,22 +53,14 @@ 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');
htmlElement = document.getElementsByTagName("html")[0];
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) {
if (htmlElement.dataset.netboxColorMode == "dark") {
options.nodes.font.color = "#fff";
}
graph = null; graph = null;
nodes = new vis.DataSet(); nodes = new vis.DataSet();
@@ -106,36 +79,4 @@ 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);
}
}
}
});
}
} }
+2 -3
View File
@@ -10,6 +10,7 @@ from .forms import DeviceFilterForm
from .filters import DeviceFilterSet from .filters import DeviceFilterSet
import json import json
import distutils
from dcim.models import Device, Cable, DeviceRole, DeviceType from dcim.models import Device, Cable, DeviceRole, DeviceType
from extras.models import Tag from extras.models import Tag
@@ -66,7 +67,6 @@ 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_type.name == "circuit termination":
if link_from.termination_a.circuit.id not in circuit_ids: if link_from.termination_a.circuit.id not in circuit_ids:
circuit_ids.append(link_from.termination_a.circuit.id) circuit_ids.append(link_from.termination_a.circuit.id)
edge_ids += 1 edge_ids += 1
@@ -151,7 +151,6 @@ 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:
if ';' in qs_device.custom_field_data["coordinates"]:
cords = qs_device.custom_field_data["coordinates"].split(";") cords = qs_device.custom_field_data["coordinates"].split(";")
node["x"] = int(cords[0]) node["x"] = int(cords[0])
node["y"] = int(cords[1]) node["y"] = int(cords[1])
@@ -183,7 +182,7 @@ class TopologyHomeView(PermissionRequiredMixin, View):
hide_unconnected = True hide_unconnected = True
if 'draw_init' in request.GET: if 'draw_init' in request.GET:
if request.GET["draw_init"].lower() == 'true': if bool(distutils.util.strtobool(request.GET["draw_init"])):
topo_data = get_topology_data(self.queryset, hide_unconnected) topo_data = get_topology_data(self.queryset, hide_unconnected)
else: else:
topo_data = get_topology_data(self.queryset, hide_unconnected) topo_data = get_topology_data(self.queryset, hide_unconnected)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "netbox_topology_views", "name": "netbox_topology_views",
"version": "1.0.3", "version": "1.0.0-alpha.3",
"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.3", "version": "1.0.0-alpha.3",
"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.3', version='1.0.0-alpha.3',
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',