Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f323278f00 | ||
|
|
56a6f22bf6 | ||
|
|
b9d3cc3c4e | ||
|
|
d4833071df | ||
|
|
877d308574 | ||
|
|
83b662eac3 | ||
|
|
53a996edbc | ||
|
|
398deb5382 | ||
|
|
b5991a5f50 | ||
|
|
de92627176 | ||
|
|
6be028b9dc | ||
|
|
8997c38263 | ||
|
|
d927e40867 | ||
|
|
62c0dbddb2 |
@@ -35,7 +35,8 @@ Then run `python3 manage.py collectstatic --no-input`
|
|||||||
|
|
||||||
| netbox version | netbox-topology-views version |
|
| netbox version | netbox-topology-views version |
|
||||||
| ------------- |-------------|
|
| ------------- |-------------|
|
||||||
| >= 3.1.8 | >= v1.0.0a1 |
|
| >= 3.2.0 | >= v1.1.0 |
|
||||||
|
| >= 3.1.8 | >= v1.0.0 |
|
||||||
| >= 2.11.1 | >= v0.5.3 |
|
| >= 2.11.1 | >= v0.5.3 |
|
||||||
| >= 2.10.0 | >= v0.5.0 |
|
| >= 2.10.0 | >= v0.5.0 |
|
||||||
| < 2.10.0 | =< v0.4.10 |
|
| < 2.10.0 | =< v0.4.10 |
|
||||||
@@ -66,6 +67,7 @@ PLUGINS_CONFIG = {
|
|||||||
| ------------- |-------------| -----|
|
| ------------- |-------------| -----|
|
||||||
| 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'] | The slug of the device roles that you have a image for. |
|
| 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'] | The slug of the device roles that you have a image for. |
|
||||||
| preselected_device_roles | ['Firewall', 'Router', 'Distribution Switch', 'Core Switch', 'Internal Switch', 'Access Switch', 'Server', 'Storage', 'Backup', 'Wireless AP'] | The full name of the device roles you want to pre select in the global view. Note that this is case sensitive|
|
| preselected_device_roles | ['Firewall', 'Router', 'Distribution Switch', 'Core Switch', 'Internal Switch', 'Access Switch', 'Server', 'Storage', 'Backup', 'Wireless AP'] | The full name of the device roles you want to pre select in the global view. Note that this is case sensitive|
|
||||||
|
| preselected_intermediate_dev_roles | ['Patch-panel'] | The full name of the device roles you want to display in the global view when using end-to-end connections mode. Note that this is case sensitive|
|
||||||
| allow_coordinates_saving | False | (bool) Set to true if you use the custom coordinates fields and want to save the coordinates |
|
| allow_coordinates_saving | False | (bool) Set to true if you use the custom coordinates fields and want to save the coordinates |
|
||||||
| ignore_cable_type | ['power outlet', 'power port'] | The cable types that you want to ignore in the views |
|
| ignore_cable_type | ['power outlet', 'power port'] | The cable types that you want to ignore in the views |
|
||||||
| preselected_tags | '[]' | The name of tags you want to preload |
|
| preselected_tags | '[]' | The name of tags you want to preload |
|
||||||
|
|||||||
@@ -4,15 +4,17 @@ 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 = '2.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'
|
||||||
required_settings = []
|
required_settings = []
|
||||||
default_settings = {
|
default_settings = {
|
||||||
'preselected_device_roles': ['Firewall', 'Router', 'Distribution Switch', 'Core Switch', 'Internal Switch', 'Access Switch', 'Server', 'Storage', 'Backup', 'Wireless AP'],
|
'preselected_device_roles': ['Firewall', 'Router', 'Distribution Switch', 'Core Switch', 'Internal Switch', 'Access Switch', 'Server', 'Storage', 'Backup', 'Wireless AP'],
|
||||||
|
'preselected_intermediate_dev_roles' : [],
|
||||||
|
'end2end_connections': False,
|
||||||
'ignore_cable_type': ['power outlet','power port'],
|
'ignore_cable_type': ['power outlet','power port'],
|
||||||
'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': False,
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
#no models yet
|
|
||||||
@@ -1,18 +1,23 @@
|
|||||||
|
from cProfile import label
|
||||||
import django_filters
|
import django_filters
|
||||||
from extras.filters import TagFilter
|
|
||||||
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 django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
class DeviceFilterSet(TenancyFilterSet, django_filters.FilterSet):
|
from dcim.models import Device, DeviceRole, Region, Site, Location
|
||||||
|
from dcim.choices import DeviceStatusChoices
|
||||||
|
|
||||||
|
from netbox.filtersets import NetBoxModelFilterSet
|
||||||
|
|
||||||
|
from tenancy.models import TenantGroup, Tenant
|
||||||
|
from tenancy.filtersets import TenancyFilterSet
|
||||||
|
|
||||||
|
from utilities.filters import TreeNodeMultipleChoiceFilter
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
tag = TagFilter()
|
|
||||||
|
|
||||||
device_role_id = django_filters.ModelMultipleChoiceFilter(
|
device_role_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='device_role_id',
|
field_name='device_role_id',
|
||||||
queryset=DeviceRole.objects.all(),
|
queryset=DeviceRole.objects.all(),
|
||||||
@@ -34,10 +39,15 @@ class DeviceFilterSet(TenancyFilterSet, django_filters.FilterSet):
|
|||||||
lookup_expr='in',
|
lookup_expr='in',
|
||||||
label='Location (ID)',
|
label='Location (ID)',
|
||||||
)
|
)
|
||||||
|
status = django_filters.MultipleChoiceFilter(
|
||||||
|
choices=DeviceStatusChoices,
|
||||||
|
null_value=None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Device
|
model = Device
|
||||||
fields = ['id', 'name', ]
|
fields = ['id', 'name']
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
"""Perform the filtered search."""
|
"""Perform the filtered search."""
|
||||||
@@ -46,4 +56,4 @@ class DeviceFilterSet(TenancyFilterSet, django_filters.FilterSet):
|
|||||||
qs_filter = (
|
qs_filter = (
|
||||||
Q(name__icontains=value)
|
Q(name__icontains=value)
|
||||||
)
|
)
|
||||||
return queryset.filter(qs_filter)
|
return queryset.filter(qs_filter)
|
||||||
|
|||||||
@@ -1,26 +1,33 @@
|
|||||||
|
from cProfile import label
|
||||||
import imp
|
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 dcim.models import Device, Site, Region, DeviceRole, Location
|
from dcim.models import Device, Site, Region, DeviceRole, Location
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from dcim.choices import DeviceStatusChoices
|
||||||
from tenancy.models import TenantGroup, Tenant
|
from tenancy.models import TenantGroup, Tenant
|
||||||
from tenancy.forms import TenancyFilterForm
|
from tenancy.forms import TenancyFilterForm
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from netbox.forms import NetBoxModelFilterSetForm
|
||||||
|
from utilities.forms import (TagFilterField, DynamicModelMultipleChoiceField, MultipleChoiceField)
|
||||||
|
|
||||||
from utilities.forms import (TagFilterField, DynamicModelMultipleChoiceField, FilterForm)
|
|
||||||
allow_coordinates_saving = settings.PLUGINS_CONFIG["netbox_topology_views"]["allow_coordinates_saving"]
|
allow_coordinates_saving = settings.PLUGINS_CONFIG["netbox_topology_views"]["allow_coordinates_saving"]
|
||||||
|
end2end = settings.PLUGINS_CONFIG["netbox_topology_views"]["end2end_connections"]
|
||||||
|
|
||||||
class DeviceFilterForm(TenancyFilterForm, FilterForm):
|
|
||||||
|
class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = Device
|
model = Device
|
||||||
field_groups = [
|
fieldsets = (
|
||||||
['q', 'hide_unconnected', 'save_coords'],
|
(None, ('q', 'hide_unconnected', 'save_coords', 'end2end_connections')),
|
||||||
['tenant_group_id', 'tenant_id'],
|
(None, ('tenant_group_id', 'tenant_id')),
|
||||||
['region_id', 'site_id', 'location_id'],
|
(None, ('region_id', 'site_id', 'location_id')),
|
||||||
['device_role_id'],
|
(None, ('device_role_id', 'intermediate_dev_role_id')),
|
||||||
['tag'],
|
(None, ('tag', 'status')),
|
||||||
]
|
)
|
||||||
|
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@@ -32,6 +39,17 @@ class DeviceFilterForm(TenancyFilterForm, FilterForm):
|
|||||||
required=False,
|
required=False,
|
||||||
label=_('Device Role')
|
label=_('Device Role')
|
||||||
)
|
)
|
||||||
|
end2end_connections = forms.BooleanField(
|
||||||
|
label=_("Display end-to-end connections"),
|
||||||
|
required=False,
|
||||||
|
initial=end2end
|
||||||
|
)
|
||||||
|
intermediate_dev_role_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=DeviceRole.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Intermediate Devices Role'),
|
||||||
|
help_text='Intermediate devices to display when using end-to-end connections mode, even if they do not match the query'
|
||||||
|
)
|
||||||
site_id = DynamicModelMultipleChoiceField(
|
site_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@@ -49,16 +67,20 @@ class DeviceFilterForm(TenancyFilterForm, FilterForm):
|
|||||||
},
|
},
|
||||||
label=_('Location')
|
label=_('Location')
|
||||||
)
|
)
|
||||||
|
|
||||||
hide_unconnected = forms.BooleanField(
|
hide_unconnected = forms.BooleanField(
|
||||||
label=_("Hide Unconnected"),
|
label=_("Hide Unconnected"),
|
||||||
required=False,
|
required=False,
|
||||||
initial=False)
|
initial=False
|
||||||
|
)
|
||||||
save_coords = forms.BooleanField(
|
save_coords = forms.BooleanField(
|
||||||
label=_("Save Coordinates"),
|
label=_("Save Coordinates"),
|
||||||
required=False,
|
required=False,
|
||||||
disabled=(not allow_coordinates_saving),
|
disabled=(not allow_coordinates_saving),
|
||||||
initial=False)
|
initial=False
|
||||||
|
)
|
||||||
|
status = MultipleChoiceField(
|
||||||
|
choices=DeviceStatusChoices,
|
||||||
|
required=False,
|
||||||
|
label=_("Device Status")
|
||||||
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
|
|
||||||
# no modeles yet
|
|
||||||
@@ -4,14 +4,6 @@ from utilities.choices import ButtonColorChoices
|
|||||||
menu_items = (
|
menu_items = (
|
||||||
PluginMenuItem(
|
PluginMenuItem(
|
||||||
link='plugins:netbox_topology_views:home',
|
link='plugins:netbox_topology_views:home',
|
||||||
link_text='Topology',
|
link_text='Topology'
|
||||||
buttons=(
|
|
||||||
PluginMenuButton(
|
|
||||||
link='plugins:netbox_topology_views:home',
|
|
||||||
title='Topology View',
|
|
||||||
icon_class='mdi mdi-plus-thick',
|
|
||||||
permissions=[],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
#visgraph{height:70vh}
|
#visgraph{height:70vh}html[data-netbox-color-mode=dark] #visgraph{background-color:#212529}
|
||||||
@@ -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,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"},shadow:{enabled:!0}},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 o=document.cookie.split(";"),n=0;n<o.length;n++){var d=o[n].trim();if(d.substring(0,e.length+1)===e+"="){t=decodeURIComponent(d.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],downloadButton=document.getElementById("btnDownloadImage"),handleLoadData(),btnFullView=document.getElementById("btnFullView"),coord_save_checkbox=document.getElementById("id_save_coords")},!1)}function performGraphDownload(){var e=document.createElement("a"),t=canvas.toDataURL(MIME_TYPE);e.href=t,e.download="topology",document.body.appendChild(e),e.click(),document.body.removeChild(e)}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],downloadButton.onclick=function(e){return performGraphDownload(),!1},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,o=new XMLHttpRequest;o.open("PATCH","/api/plugins/netbox_topology_views/save-coords/save_coords/"),o.setRequestHeader("X-CSRFToken",csrftoken),o.setRequestHeader("Accept","application/json"),o.setRequestHeader("Content-Type","application/json"),o.onreadystatechange=function(){4===o.readyState&&(console.log(o.status),console.log(o.responseText))};var n=JSON.stringify({node_id:t,x:dragged[t].x,y:dragged[t].y});o.send(n)}}))}
|
||||||
@@ -2,3 +2,6 @@
|
|||||||
height: 70vh;
|
height: 70vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html[data-netbox-color-mode=dark] #visgraph {
|
||||||
|
background-color: #212529;
|
||||||
|
}
|
||||||
@@ -27,6 +27,9 @@ var options = {
|
|||||||
font: {
|
font: {
|
||||||
face: 'helvetica',
|
face: 'helvetica',
|
||||||
},
|
},
|
||||||
|
shadow: {
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
physics: {
|
physics: {
|
||||||
solver: 'forceAtlas2Based'
|
solver: 'forceAtlas2Based'
|
||||||
@@ -75,13 +78,24 @@ function iniPlotboxIndex() {
|
|||||||
csrftoken = getCookie('csrftoken');
|
csrftoken = getCookie('csrftoken');
|
||||||
container = document.getElementById('visgraph');
|
container = document.getElementById('visgraph');
|
||||||
htmlElement = document.getElementsByTagName("html")[0];
|
htmlElement = document.getElementsByTagName("html")[0];
|
||||||
handleLoadData();
|
|
||||||
downloadButton = document.getElementById('btnDownloadImage');
|
downloadButton = document.getElementById('btnDownloadImage');
|
||||||
|
handleLoadData();
|
||||||
btnFullView = document.getElementById('btnFullView');
|
btnFullView = document.getElementById('btnFullView');
|
||||||
coord_save_checkbox = document.getElementById('id_save_coords');
|
coord_save_checkbox = document.getElementById('id_save_coords');
|
||||||
}, false);
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function performGraphDownload() {
|
||||||
|
var tempDownloadLink = document.createElement('a');
|
||||||
|
var generatedImageUrl = canvas.toDataURL(MIME_TYPE);
|
||||||
|
|
||||||
|
tempDownloadLink.href = generatedImageUrl;
|
||||||
|
tempDownloadLink.download = "topology";
|
||||||
|
document.body.appendChild(tempDownloadLink);
|
||||||
|
tempDownloadLink.click();
|
||||||
|
document.body.removeChild(tempDownloadLink);
|
||||||
|
}
|
||||||
|
|
||||||
function handleLoadData() {
|
function handleLoadData() {
|
||||||
if (topology_data !== null) {
|
if (topology_data !== null) {
|
||||||
|
|
||||||
@@ -100,11 +114,7 @@ function handleLoadData() {
|
|||||||
graph.fit();
|
graph.fit();
|
||||||
canvas = document.getElementById('visgraph').getElementsByTagName('canvas')[0];
|
canvas = document.getElementById('visgraph').getElementsByTagName('canvas')[0];
|
||||||
|
|
||||||
graph.on('afterDrawing', function () {
|
downloadButton.onclick = function(e) { performGraphDownload(); return false; };
|
||||||
var image = canvas.toDataURL(MIME_TYPE);
|
|
||||||
downloadButton.href = image;
|
|
||||||
downloadButton.download = "topology";
|
|
||||||
});
|
|
||||||
|
|
||||||
graph.on("dragEnd", function (params) {
|
graph.on("dragEnd", function (params) {
|
||||||
dragged = this.getPositions(params.nodes);
|
dragged = this.getPositions(params.nodes);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
{% block extra_controls %}{% endblock %}
|
{% block extra_controls %}{% endblock %}
|
||||||
<a id="btnDownloadImage" class="btn btn-sm btn-info">
|
<a id="btnDownloadImage" class="btn btn-sm btn-info" href="#">
|
||||||
<i class="mdi mdi-download"></i>
|
<i class="mdi mdi-download"></i>
|
||||||
Download
|
Download
|
||||||
</a>
|
</a>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="filters-form-tab" data-bs-toggle="tab" data-bs-target="#filters-form" type="button" role="tab" aria-controls="object-list" aria-selected="false">
|
<button class="nav-link" id="filters-form-tab" data-bs-toggle="tab" data-bs-target="#filters-form" type="button" role="tab" aria-controls="object-list" aria-selected="false">
|
||||||
Filters
|
Filters
|
||||||
{% if filter_form %}{% badge filter_form.changed_data|length bg_class="primary" %}{% endif %}
|
{% if filter_form %}{% badge filter_form.changed_data|length bg_color="blue" %}{% endif %}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -45,30 +45,31 @@
|
|||||||
{% endblock tabs %}
|
{% endblock tabs %}
|
||||||
|
|
||||||
{% block content-wrapper %}
|
{% block content-wrapper %}
|
||||||
{% with config=settings.PLUGINS_CONFIG.netbox_topology_views %}
|
{% with config=settings.PLUGINS_CONFIG.netbox_topology_views %}
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
|
|
||||||
<div class="tab-pane show active" id="networks" role="tabpanel" aria-labelledby="network-tab">
|
<div class="tab-pane show active" id="networks" role="tabpanel" aria-labelledby="network-tab">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div id="visgraph" class=""></div>
|
<div id="visgraph" class=""></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var topology_data = {{ topology_data | safe }};
|
var topology_data = {{ topology_data | safe }};
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if filter_form %}
|
{% if filter_form %}
|
||||||
<div class="tab-pane show" id="filters-form" role="tabpanel" aria-labelledby="filters-form-tab">
|
<div class="tab-pane show" id="filters-form" role="tabpanel" aria-labelledby="filters-form-tab">
|
||||||
{% include 'inc/filter_list.html' %}
|
{% include 'inc/filter_list.html' %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endwith %}
|
||||||
<script src="{% static 'netbox_topology_views/js/vendor.js' %}"></script>
|
|
||||||
<script src="{% static 'netbox_topology_views/js/app.js' %}"></script>
|
|
||||||
|
|
||||||
<script type="application/javascript"> iniPlotboxIndex()</script>
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock content-wrapper %}
|
{% endblock content-wrapper %}
|
||||||
|
|
||||||
|
{% block javascript %}
|
||||||
|
<script src="{% static 'netbox_topology_views/js/vendor.js' %}"></script>
|
||||||
|
<script src="{% static 'netbox_topology_views/js/app.js' %}"></script>
|
||||||
|
<script type="application/javascript">iniPlotboxIndex()</script>
|
||||||
|
{% endblock javascript %}
|
||||||
|
|||||||
+227
-136
@@ -11,12 +11,84 @@ from .filters import DeviceFilterSet
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from dcim.models import Device, Cable, DeviceRole, DeviceType
|
from dcim.models import Device, Cable, DeviceRole, PathEndpoint
|
||||||
|
from circuits.models import CircuitTermination, ProviderNetwork
|
||||||
from extras.models import Tag
|
from extras.models import Tag
|
||||||
|
|
||||||
def get_topology_data(queryset, hide_unconnected):
|
|
||||||
nodes = []
|
def create_node(device):
|
||||||
nodes_ids = []
|
dev_name = device.name
|
||||||
|
if dev_name is None:
|
||||||
|
dev_name = "device name unknown"
|
||||||
|
|
||||||
|
node_content = ""
|
||||||
|
|
||||||
|
if device.device_type is not None:
|
||||||
|
node_content += "<tr><th>Type: </th><td>" + device.device_type.model + "</td></tr>"
|
||||||
|
if device.device_role.name is not None:
|
||||||
|
node_content += "<tr><th>Role: </th><td>" + device.device_role.name + "</td></tr>"
|
||||||
|
if device.serial != "":
|
||||||
|
node_content += "<tr><th>Serial: </th><td>" + device.serial + "</td></tr>"
|
||||||
|
if device.primary_ip is not None:
|
||||||
|
node_content += "<tr><th>IP Address: </th><td>" + str(device.primary_ip.address) + "</td></tr>"
|
||||||
|
if device.site is not None:
|
||||||
|
node_content += "<tr><th>Site: </th><td>" + device.site.name + "</td></tr>"
|
||||||
|
|
||||||
|
dev_title = "<table> %s </table>" % (node_content)
|
||||||
|
|
||||||
|
node = {}
|
||||||
|
node["id"] = device.id
|
||||||
|
node["name"] = dev_name
|
||||||
|
node["label"] = dev_name
|
||||||
|
node["title"] = dev_title
|
||||||
|
node["shape"] = "image"
|
||||||
|
if device.device_role.slug in settings.PLUGINS_CONFIG["netbox_topology_views"]["device_img"]:
|
||||||
|
node["image"] = "../../static/netbox_topology_views/img/" + device.device_role.slug + ".png"
|
||||||
|
else:
|
||||||
|
node["image"] = "../../static/netbox_topology_views/img/role-unknown.png"
|
||||||
|
|
||||||
|
if device.device_role.color != "":
|
||||||
|
node["color.border"] = "#" + device.device_role.color
|
||||||
|
|
||||||
|
if "coordinates" in device.custom_field_data:
|
||||||
|
if device.custom_field_data["coordinates"] is not None:
|
||||||
|
if ";" in device.custom_field_data["coordinates"]:
|
||||||
|
cords = device.custom_field_data["coordinates"].split(";")
|
||||||
|
node["x"] = int(cords[0])
|
||||||
|
node["y"] = int(cords[1])
|
||||||
|
node["physics"] = False
|
||||||
|
return node
|
||||||
|
|
||||||
|
def create_edge(edge_id, cable, termination_a, termination_b, path = None, circuit = None):
|
||||||
|
cable_a_dev_name = "device A name unknown" if termination_a.device.name is None else termination_a.device.name
|
||||||
|
cable_a_name = "device A name unknown" if termination_a.name is None else termination_a.name
|
||||||
|
cable_b_dev_name = "device A name unknown" if termination_b.device.name is None else termination_b.device.name
|
||||||
|
cable_b_name = "cable B name unknown" if termination_b.name is None else termination_b.name
|
||||||
|
|
||||||
|
edge = {}
|
||||||
|
edge["id"] = edge_id
|
||||||
|
edge["from"] = termination_a.device.id
|
||||||
|
edge["to"] = termination_b.device.id
|
||||||
|
|
||||||
|
if circuit is not None:
|
||||||
|
edge["dashes"] = True
|
||||||
|
edge["title"] = "Circuit provider: " + circuit.provider.name + "<br>"
|
||||||
|
edge["title"] += "Termination between <br>"
|
||||||
|
edge["title"] += cable_b_dev_name + " [" + cable_b_name + "]<br>"
|
||||||
|
edge["title"] += cable_a_dev_name + " [" + cable_a_name + "]"
|
||||||
|
else:
|
||||||
|
edge["title"] = "Cable between <br> " + cable_a_dev_name + " [" + cable_a_name + "]<br>" + cable_b_dev_name + " [" + cable_b_name + "]"
|
||||||
|
|
||||||
|
if path is not None:
|
||||||
|
edge["title"] += "" if len(path) <= 0 else "<br>Through " + "/".join(path)
|
||||||
|
|
||||||
|
if cable is not None and cable.color != "":
|
||||||
|
edge["color"] = "#" + cable.color
|
||||||
|
|
||||||
|
return edge
|
||||||
|
|
||||||
|
def get_topology_data(queryset, hide_unconnected, intermediate_dev_role_ids, end2end_connections):
|
||||||
|
nodes_devices = {}
|
||||||
edges = []
|
edges = []
|
||||||
edge_ids = 0
|
edge_ids = 0
|
||||||
cable_ids = []
|
cable_ids = []
|
||||||
@@ -25,184 +97,203 @@ def get_topology_data(queryset, hide_unconnected):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
ignore_cable_type = settings.PLUGINS_CONFIG["netbox_topology_views"]["ignore_cable_type"]
|
ignore_cable_type = settings.PLUGINS_CONFIG["netbox_topology_views"]["ignore_cable_type"]
|
||||||
|
enable_circuit_terminations = settings.PLUGINS_CONFIG["netbox_topology_views"]["enable_circuit_terminations"]
|
||||||
|
|
||||||
device_ids = [d.id for d in queryset]
|
device_ids = [d.id for d in queryset]
|
||||||
|
|
||||||
for qs_device in queryset:
|
links = Cable.objects.filter( Q(_termination_a_device_id__in=device_ids) | Q(_termination_b_device_id__in=device_ids) ) \
|
||||||
device_has_connections = False
|
.select_related("termination_a_type", "termination_b_type") \
|
||||||
|
.prefetch_related("termination_a", "termination_b")
|
||||||
|
|
||||||
|
for link in links:
|
||||||
|
|
||||||
|
if link.termination_a_type.name in ignore_cable_type or link.termination_b_type.name in ignore_cable_type \
|
||||||
|
or link.id in cable_ids:
|
||||||
|
continue
|
||||||
|
|
||||||
|
a_is_path_endoint = isinstance(link.termination_a, PathEndpoint)
|
||||||
|
b_is_path_endoint = isinstance(link.termination_b, PathEndpoint)
|
||||||
|
|
||||||
|
if not end2end_connections or (not a_is_path_endoint and not b_is_path_endoint):
|
||||||
|
|
||||||
|
if isinstance(link.termination_a, CircuitTermination):
|
||||||
|
if enable_circuit_terminations and link.termination_a.circuit.id not in circuit_ids:
|
||||||
|
circuit = link.termination_a.circuit
|
||||||
|
circuit_ids.append(circuit.id)
|
||||||
|
cable_ids.append(link.id)
|
||||||
|
|
||||||
|
path_destination = circuit.termination_z if link.termination_a.term_side == "A" else circuit.termination_a
|
||||||
|
|
||||||
|
if path_destination is not None and path_destination.provider_network is None:
|
||||||
|
# ProviderNetwork not supported at the moment
|
||||||
|
# $path_destination.cable would be none : there is no cable between a CircuitTermination and ProviderNetwork
|
||||||
|
origin_device = link.termination_b.device
|
||||||
|
destination_device = path_destination.cable.termination_b.device
|
||||||
|
|
||||||
|
if origin_device.id in device_ids and destination_device.id in device_ids:
|
||||||
|
if origin_device.id not in nodes_devices:
|
||||||
|
nodes_devices[origin_device.id] = origin_device
|
||||||
|
if destination_device.id not in nodes_devices:
|
||||||
|
nodes_devices[destination_device.id] = destination_device
|
||||||
|
|
||||||
links_device = Cable.objects.filter(Q(_termination_a_device_id=qs_device.id) | Q(_termination_b_device_id=qs_device.id) )
|
|
||||||
for link_from in links_device:
|
|
||||||
if link_from.termination_a_type.name != "circuit termination" and link_from.termination_b_type.name != "circuit termination":
|
|
||||||
if link_from.termination_a_type.name not in ignore_cable_type and link_from.termination_b_type.name not in ignore_cable_type:
|
|
||||||
if link_from.id not in cable_ids:
|
|
||||||
if link_from.termination_a.device.id in device_ids and link_from.termination_b.device.id in device_ids:
|
|
||||||
device_has_connections = True
|
|
||||||
cable_ids.append(link_from.id)
|
|
||||||
edge_ids += 1
|
edge_ids += 1
|
||||||
cable_a_dev_name = link_from.termination_a.device.name
|
edges.append(create_edge(edge_ids, link, link.termination_b, path_destination.cable.termination_b, [circuit.cid], circuit))
|
||||||
if cable_a_dev_name is None:
|
|
||||||
cable_a_dev_name = "device A name unknown"
|
|
||||||
cable_a_name = link_from.termination_a.name
|
|
||||||
if cable_a_name is None:
|
|
||||||
cable_a_name = "cable A name unknown"
|
|
||||||
cable_b_dev_name = link_from.termination_b.device.name
|
|
||||||
if cable_b_dev_name is None:
|
|
||||||
cable_b_dev_name = "device B name unknown"
|
|
||||||
cable_b_name = link_from.termination_b.name
|
|
||||||
if cable_b_name is None:
|
|
||||||
cable_b_name = "cable B name unknown"
|
|
||||||
|
|
||||||
edge = {}
|
elif link.termination_a.device.id in device_ids and link.termination_b.device.id in device_ids:
|
||||||
edge["id"] = edge_ids
|
|
||||||
edge["from"] = link_from.termination_a.device.id
|
if link.termination_a.device.id not in nodes_devices:
|
||||||
edge["to"] = link_from.termination_b.device.id
|
nodes_devices[link.termination_a.device.id] = link.termination_a.device
|
||||||
edge["title"] = "Cable between <br> " + cable_a_dev_name + " [" + cable_a_name + "]<br>" + cable_b_dev_name + " [" + cable_b_name + "]"
|
if link.termination_b.device.id not in nodes_devices:
|
||||||
if link_from.color != "":
|
nodes_devices[link.termination_b.device.id] = link.termination_b.device
|
||||||
edge["color"] = "#" + link_from.color
|
|
||||||
edges.append(edge)
|
cable_ids.append(link.id)
|
||||||
else:
|
edge_ids += 1
|
||||||
if link_from.termination_a.device.id in device_ids and link_from.termination_b.device.id in device_ids:
|
edges.append(create_edge(edge_ids, link, link.termination_a, link.termination_b))
|
||||||
device_has_connections = True
|
else:
|
||||||
|
# termination_a can be a CircuitTermination (no trace()) while termination_b is a PathEndpoint
|
||||||
|
# If so, we swap them so we can follow the path using PathEndpoint#trace()
|
||||||
|
path_start = link.termination_a if a_is_path_endoint else link.termination_b
|
||||||
|
if path_start.device is not None and path_start.device.id not in device_ids:
|
||||||
|
# device not in queryset, skip
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Ignore incomplete path when in end-to-end connection mode
|
||||||
|
if path_start.path is not None:
|
||||||
|
if not path_start.path.is_active or path_start.path.is_split:
|
||||||
|
continue
|
||||||
else:
|
else:
|
||||||
if settings.PLUGINS_CONFIG["netbox_topology_views"]["enable_circuit_terminations"]:
|
continue
|
||||||
if link_from.termination_a_type.name == "circuit termination":
|
|
||||||
if link_from.termination_a.circuit.id not in circuit_ids:
|
|
||||||
circuit_ids.append(link_from.termination_a.circuit.id)
|
|
||||||
edge_ids += 1
|
|
||||||
|
|
||||||
cable_b_dev_name = link_from.termination_b.device.name
|
path_destination = path_start.path.destination
|
||||||
if cable_b_dev_name is None:
|
if hasattr(path_destination, "device") and path_destination.device is not None and path_destination.device.id not in device_ids:
|
||||||
cable_b_dev_name = "device B name unknown"
|
# device not in queryset, skip
|
||||||
cable_b_name = link_from.termination_b.name
|
continue
|
||||||
if cable_b_name is None:
|
|
||||||
cable_b_name = "cable B name unknown"
|
|
||||||
|
|
||||||
edge = {}
|
valid_path = False
|
||||||
edge["id"] = edge_ids
|
|
||||||
edge["to"] = link_from.termination_b.device.id
|
|
||||||
edge["dashes"] = True
|
|
||||||
title = ""
|
|
||||||
|
|
||||||
title += "Circuit provider: " + link_from.termination_a.circuit.provider.name + "<br>"
|
# variables needed for trace() browsing
|
||||||
title += "Termination between <br>"
|
origin = None
|
||||||
title += cable_b_dev_name + " [" + cable_b_name + "]<br>"
|
circuit = None
|
||||||
|
tmp_path = []
|
||||||
|
|
||||||
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:
|
# trace() : Return the path as a list of three-tuples (A termination, cable, B termination)
|
||||||
edge["from"] = link_from.termination_a.circuit.termination_a.cable.termination_b.device.id
|
# TODO device qui ne s"affichent pas si hide_unconnected
|
||||||
|
for (termination_a, cable, termination_b) in path_start.trace():
|
||||||
|
|
||||||
|
if origin is None:
|
||||||
|
# New part of the link
|
||||||
|
origin = termination_a
|
||||||
|
circuit = None
|
||||||
|
tmp_path = []
|
||||||
|
|
||||||
cable_a_dev_name = link_from.termination_a.circuit.termination_a.cable.termination_b.device.name
|
if isinstance(termination_b, ProviderNetwork):
|
||||||
if cable_a_dev_name is None:
|
# ProviderNetwork not supported at the moment
|
||||||
cable_a_dev_name = "device B name unknown"
|
# It would need to manage several kind of nodes (Device, ProviderNetwork)
|
||||||
cable_b_name = link_from.termination_a.circuit.termination_a.cable.termination_b.name
|
# and maps javascript nodes ID with Devices IDs and ProviderNetworks IDs
|
||||||
if cable_a_name is None:
|
# When $termination_b is a ProviderNetwork instance, $cable will be None
|
||||||
cable_a_name = "cable B name unknown"
|
break
|
||||||
title += cable_a_dev_name + " [" + cable_a_name + "]<br>"
|
|
||||||
edge["title"] = title
|
|
||||||
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 cable is not None and (cable.termination_a_type in ignore_cable_type or cable.termination_b_type in ignore_cable_type):
|
||||||
edge["from"] = link_from.termination_a.circuit.termination_z.cable.termination_b.device.id
|
# Ignore this path
|
||||||
|
break
|
||||||
|
|
||||||
cable_a_dev_name = link_from.termination_a.circuit.termination_z.cable.termination_b.device.name
|
if isinstance(termination_b, CircuitTermination):
|
||||||
if cable_a_dev_name is None:
|
if enable_circuit_terminations:
|
||||||
cable_a_dev_name = "device B name unknown"
|
circuit = termination_b.circuit
|
||||||
cable_a_name = link_from.termination_a.circuit.termination_z.cable.termination_b.name
|
tmp_path.append(circuit.cid)
|
||||||
if cable_a_name is None:
|
if circuit.id not in circuit_ids:
|
||||||
cable_a_name = "cable B name unknown"
|
circuit_ids.append(circuit.id)
|
||||||
title += cable_a_dev_name + " [" + cable_a_name + "]<br>"
|
else:
|
||||||
edge["title"] = title
|
# Ignore this path
|
||||||
edges.append(edge)
|
break
|
||||||
|
elif end2end_connections and termination_b != path_destination \
|
||||||
|
and termination_b.device.device_role.id not in intermediate_dev_role_ids \
|
||||||
|
and termination_b.device.id not in device_ids:
|
||||||
|
# Skip this intermediate device, origin device is keep in $origin for next iteration
|
||||||
|
tmp_path.append(termination_b.device.name)
|
||||||
|
elif cable is not None and termination_b is not None:
|
||||||
|
if cable.id not in cable_ids:
|
||||||
|
# New part of the link we want to display
|
||||||
|
cable_ids.append(cable.id)
|
||||||
|
edge_ids += 1
|
||||||
|
edges.append(create_edge(edge_ids, cable, origin, termination_b, tmp_path, circuit))
|
||||||
|
|
||||||
if qs_device.id not in nodes_ids:
|
if not valid_path:
|
||||||
if hide_unconnected == None or (hide_unconnected is True and device_has_connections is True):
|
valid_path = True
|
||||||
nodes_ids.append(qs_device.id)
|
|
||||||
|
|
||||||
dev_name = qs_device.name
|
if termination_b.device.id not in nodes_devices and \
|
||||||
if dev_name is None:
|
(termination_b.device.device_role.id in intermediate_dev_role_ids or termination_b.device.id in device_ids) :
|
||||||
dev_name = "device name unknown"
|
nodes_devices[termination_b.device.id] = termination_b.device
|
||||||
|
# Reset for next iteration
|
||||||
|
origin = None
|
||||||
|
# endfor (trace)
|
||||||
|
|
||||||
node_content = ""
|
if valid_path and path_start.device.id not in nodes_devices:
|
||||||
|
nodes_devices[path_start.device.id] = path_start.device
|
||||||
|
|
||||||
if qs_device.device_type is not None:
|
# endfor (link)
|
||||||
node_content += "<tr><th>Type: </th><td>" + qs_device.device_type.model + "</td></tr>"
|
|
||||||
if qs_device.device_role.name is not None:
|
for qs_device in queryset:
|
||||||
node_content += "<tr><th>Role: </th><td>" + qs_device.device_role.name + "</td></tr>"
|
if qs_device.id not in nodes_devices and not hide_unconnected:
|
||||||
if qs_device.serial != "":
|
nodes_devices[qs_device.id] = qs_device
|
||||||
node_content += "<tr><th>Serial: </th><td>" + qs_device.serial + "</td></tr>"
|
|
||||||
if qs_device.primary_ip is not None:
|
|
||||||
node_content += "<tr><th>IP Address: </th><td>" + str(qs_device.primary_ip.address) + "</td></tr>"
|
|
||||||
|
|
||||||
dev_title = "<table> %s </table>" % (node_content)
|
|
||||||
|
|
||||||
node = {}
|
|
||||||
node["id"] = qs_device.id
|
|
||||||
node["name"] = dev_name
|
|
||||||
node["label"] = dev_name
|
|
||||||
node["title"] = dev_title
|
|
||||||
node["shape"] = 'image'
|
|
||||||
if qs_device.device_role.slug in settings.PLUGINS_CONFIG["netbox_topology_views"]["device_img"]:
|
|
||||||
node["image"] = '../../static/netbox_topology_views/img/' + qs_device.device_role.slug + ".png"
|
|
||||||
else:
|
|
||||||
node["image"] = "../../static/netbox_topology_views/img/role-unknown.png"
|
|
||||||
|
|
||||||
if qs_device.device_role.color != "":
|
|
||||||
node["color.border"] = "#" + qs_device.device_role.color
|
|
||||||
|
|
||||||
if "coordinates" in qs_device.custom_field_data:
|
|
||||||
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(";")
|
|
||||||
node["x"] = int(cords[0])
|
|
||||||
node["y"] = int(cords[1])
|
|
||||||
node["physics"] = False
|
|
||||||
nodes.append(node)
|
|
||||||
|
|
||||||
results = {}
|
results = {}
|
||||||
results["nodes"] = nodes
|
results["nodes"] = [create_node(d) for d in nodes_devices.values()]
|
||||||
results["edges"] = edges
|
results["edges"] = edges
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
class TopologyHomeView(PermissionRequiredMixin, View):
|
class TopologyHomeView(PermissionRequiredMixin, View):
|
||||||
permission_required = ('dcim.view_site', 'dcim.view_device')
|
permission_required = ("dcim.view_site", "dcim.view_device")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Show the home page
|
Show the home page
|
||||||
"""
|
"""
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
self.filterset = DeviceFilterSet
|
self.filterset = DeviceFilterSet
|
||||||
self.queryset = Device.objects.all()
|
self.queryset = Device.objects.all().select_related("device_type", "device_role")
|
||||||
self.queryset = self.filterset(request.GET, self.queryset).qs
|
self.queryset = self.filterset(request.GET, self.queryset).qs
|
||||||
topo_data = None
|
topo_data = None
|
||||||
|
|
||||||
if request.GET:
|
if request.GET:
|
||||||
hide_unconnected = None
|
hide_unconnected = False
|
||||||
if 'hide_unconnected' in request.GET:
|
if "hide_unconnected" in request.GET:
|
||||||
if request.GET["hide_unconnected"] == "on" :
|
if request.GET["hide_unconnected"] == "on" :
|
||||||
hide_unconnected = True
|
hide_unconnected = True
|
||||||
|
|
||||||
if 'draw_init' in request.GET:
|
if "intermediate_dev_role_id" in request.GET:
|
||||||
if request.GET["draw_init"].lower() == 'true':
|
intermediate_dev_role_ids = list(map(int, request.GET.getlist("intermediate_dev_role_id")))
|
||||||
topo_data = get_topology_data(self.queryset, hide_unconnected)
|
|
||||||
else:
|
else:
|
||||||
topo_data = get_topology_data(self.queryset, hide_unconnected)
|
intermediate_dev_role_ids = []
|
||||||
|
|
||||||
|
end2end_connections = False
|
||||||
|
if "end2end_connections" in request.GET:
|
||||||
|
if request.GET["end2end_connections"] == "on" :
|
||||||
|
end2end_connections = True
|
||||||
|
|
||||||
|
if "draw_init" in request.GET:
|
||||||
|
if request.GET["draw_init"].lower() == "true":
|
||||||
|
topo_data = get_topology_data(self.queryset, hide_unconnected, intermediate_dev_role_ids, end2end_connections)
|
||||||
|
else:
|
||||||
|
topo_data = get_topology_data(self.queryset, hide_unconnected, intermediate_dev_role_ids, end2end_connections)
|
||||||
else:
|
else:
|
||||||
preselected_device_roles = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_device_roles"]
|
preselected_device_roles = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_device_roles"]
|
||||||
|
preselected_intermediate_dev_roles = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_intermediate_dev_roles"]
|
||||||
preselected_tags = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_tags"]
|
preselected_tags = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_tags"]
|
||||||
|
|
||||||
q_device_role_id = DeviceRole.objects.filter(name__in=preselected_device_roles).values_list('id', flat=True)
|
q_device_role_id = DeviceRole.objects.filter(name__in=preselected_device_roles).values_list("id", flat=True)
|
||||||
q_tags = Tag.objects.filter(name__in=preselected_tags).values_list('name', flat=True)
|
q_intermediate_dev_role_id = DeviceRole.objects.filter(name__in=preselected_intermediate_dev_roles).values_list("id", flat=True)
|
||||||
|
q_tags = Tag.objects.filter(name__in=preselected_tags).values_list("name", flat=True)
|
||||||
|
|
||||||
q = QueryDict(mutable=True)
|
q = QueryDict(mutable=True)
|
||||||
q.setlist('device_role_id', list(q_device_role_id))
|
q.setlist("device_role_id", list(q_device_role_id))
|
||||||
q.setlist('tag', list(q_tags))
|
q.setlist("intermediate_dev_role_id", list(q_intermediate_dev_role_id))
|
||||||
q['draw_init'] = settings.PLUGINS_CONFIG["netbox_topology_views"]["draw_default_layout"]
|
q.setlist("tag", list(q_tags))
|
||||||
|
q["draw_init"] = settings.PLUGINS_CONFIG["netbox_topology_views"]["draw_default_layout"]
|
||||||
query_string = q.urlencode()
|
query_string = q.urlencode()
|
||||||
return HttpResponseRedirect(request.path + "?" + query_string)
|
return HttpResponseRedirect(request.path + "?" + query_string)
|
||||||
|
|
||||||
return render(request, 'netbox_topology_views/index.html' , {
|
return render(request, "netbox_topology_views/index.html" , {
|
||||||
'filter_form': DeviceFilterForm(request.GET, label_suffix=''),
|
"filter_form": DeviceFilterForm(request.GET, label_suffix=""),
|
||||||
'topology_data': json.dumps(topo_data)
|
"topology_data": json.dumps(topo_data)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
Generated
+28
-6096
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "netbox_topology_views",
|
"name": "netbox_topology_views",
|
||||||
"version": "1.0.3",
|
"version": "2.0.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"resources": "gulp build",
|
"resources": "gulp build",
|
||||||
"resources_dev": "gulp build_dev"
|
"resources_dev": "gulp build_dev"
|
||||||
|
|||||||
@@ -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='2.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',
|
||||||
|
|||||||
Reference in New Issue
Block a user