Compare commits

..
16 Commits
16 changed files with 6203 additions and 160 deletions
+1 -2
View File
@@ -35,8 +35,7 @@ Then run `python3 manage.py collectstatic --no-input`
| netbox version | netbox-topology-views version |
| ------------- |-------------|
| >= 3.2.0 | >= v1.1.0 |
| >= 3.1.8 | >= v1.0.0 |
| >= 3.1.8 | >= v1.0.0a1 |
| >= 2.11.1 | >= v0.5.3 |
| >= 2.10.0 | >= v0.5.0 |
| < 2.10.0 | =< v0.4.10 |
+3 -3
View File
@@ -4,7 +4,7 @@ class TopologyViewsConfig(PluginConfig):
name = 'netbox_topology_views'
verbose_name = 'Topology views'
description = 'An plugin to render topology maps'
version = '1.2.0'
version = '1.0.0-beta.1'
author = 'Mattijs Vanhaverbeke'
author_email = 'author@example.com'
base_url = 'netbox_topology_views'
@@ -12,10 +12,10 @@ class TopologyViewsConfig(PluginConfig):
default_settings = {
'preselected_device_roles': ['Firewall', 'Router', 'Distribution Switch', 'Core Switch', 'Internal Switch', 'Access Switch', 'Server', 'Storage', 'Backup', 'Wireless AP'],
'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,
'preselected_tags' : [],
'enable_circuit_terminations': False,
'enable_circuit_terminations': True,
'draw_default_layout': False
}
+3
View File
@@ -0,0 +1,3 @@
from django.contrib import admin
#no models yet
+8 -13
View File
@@ -1,21 +1,16 @@
import django_filters
from extras.filters import TagFilter
from dcim.models import Device, DeviceRole, Region, Site, Location
from utilities.filters import TreeNodeMultipleChoiceFilter
from django.db.models import Q
from dcim.models import Device, DeviceRole, Region, Site, Location
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):
class DeviceFilterSet(django_filters.FilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
)
tag = TagFilter()
device_role_id = django_filters.ModelMultipleChoiceFilter(
field_name='device_role_id',
queryset=DeviceRole.objects.all(),
@@ -40,7 +35,7 @@ class DeviceFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
class Meta:
model = Device
fields = ['id', 'name']
fields = ['id', 'name', ]
def search(self, queryset, name, value):
"""Perform the filtered search."""
@@ -49,4 +44,4 @@ class DeviceFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
qs_filter = (
Q(name__icontains=value)
)
return queryset.filter(qs_filter)
return queryset.filter(qs_filter)
+11 -17
View File
@@ -1,30 +1,22 @@
import imp
from django import forms
from django.conf import settings
from django.utils.translation import gettext as _
from extras.models import Tag
from dcim.models import Device, Site, Region, DeviceRole, Location
from django import forms
from tenancy.models import TenantGroup, Tenant
from tenancy.forms import TenancyFilterForm
from django.conf import settings
from netbox.forms import NetBoxModelFilterSetForm
from utilities.forms import (TagFilterField, DynamicModelMultipleChoiceField)
from utilities.forms import (TagFilterField, DynamicModelMultipleChoiceField, FilterForm)
allow_coordinates_saving = settings.PLUGINS_CONFIG["netbox_topology_views"]["allow_coordinates_saving"]
class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
class DeviceFilterForm(FilterForm):
model = Device
fieldsets = (
(None, ('q', 'hide_unconnected', 'save_coords')),
(None, ('tenant_group_id', 'tenant_id')),
(None, ('region_id', 'site_id', 'location_id')),
(None, ('device_role_id',)),
(None, ('tag',)),
)
field_groups = [
['q', 'hide_unconnected', 'save_coords'],
['region_id', 'site_id', 'location_id'],
['device_role_id'],
['tag'],
]
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
@@ -53,6 +45,7 @@ class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
},
label=_('Location')
)
hide_unconnected = forms.BooleanField(
label=_("Hide Unconnected"),
required=False,
@@ -63,4 +56,5 @@ class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
required=False,
disabled=(not allow_coordinates_saving),
initial=False)
tag = TagFilterField(model)
+3
View File
@@ -0,0 +1,3 @@
from django.db import models
# no modeles yet
+10 -2
View File
@@ -4,6 +4,14 @@ from utilities.choices import ButtonColorChoices
menu_items = (
PluginMenuItem(
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}html[data-netbox-color-mode=dark] #visgraph{background-color:#212529}
#visgraph{height:70vh}
@@ -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"},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)}}))}
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)}}))}
@@ -2,6 +2,3 @@
height: 70vh;
}
html[data-netbox-color-mode=dark] #visgraph {
background-color: #212529;
}
+6 -23
View File
@@ -27,9 +27,6 @@ var options = {
font: {
face: 'helvetica',
},
shadow: {
enabled: true
}
},
physics: {
solver: 'forceAtlas2Based'
@@ -38,7 +35,6 @@ var options = {
var selected_regions = [];
var selected_sites = [];
var coord_save_checkbox = null;
var htmlElement = null;
function getCookie(name) {
var cookieValue = null;
@@ -77,32 +73,15 @@ function iniPlotboxIndex() {
document.addEventListener('DOMContentLoaded', function () {
csrftoken = getCookie('csrftoken');
container = document.getElementById('visgraph');
htmlElement = document.getElementsByTagName("html")[0];
downloadButton = document.getElementById('btnDownloadImage');
handleLoadData();
downloadButton = document.getElementById('btnDownloadImage');
btnFullView = document.getElementById('btnFullView');
coord_save_checkbox = document.getElementById('id_save_coords');
}, 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() {
if (topology_data !== null) {
if (htmlElement.dataset.netboxColorMode == "dark") {
options.nodes.font.color = "#fff";
}
graph = null;
nodes = new vis.DataSet();
edges = new vis.DataSet();
@@ -114,7 +93,11 @@ function handleLoadData() {
graph.fit();
canvas = document.getElementById('visgraph').getElementsByTagName('canvas')[0];
downloadButton.onclick = function(e) { performGraphDownload(); return false; };
graph.on('afterDrawing', function () {
var image = canvas.toDataURL(MIME_TYPE);
downloadButton.href = image;
downloadButton.download = "topology";
});
graph.on("dragEnd", function (params) {
dragged = this.getPositions(params.nodes);
@@ -16,7 +16,7 @@
<div class="controls">
<div class="control-group">
{% block extra_controls %}{% endblock %}
<a id="btnDownloadImage" class="btn btn-sm btn-info" href="#">
<a id="btnDownloadImage" class="btn btn-sm btn-info">
<i class="mdi mdi-download"></i>
Download
</a>
@@ -36,7 +36,7 @@
<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">
Filters
{% if filter_form %}{% badge filter_form.changed_data|length bg_color="blue" %}{% endif %}
{% if filter_form %}{% badge filter_form.changed_data|length bg_class="primary" %}{% endif %}
</button>
</li>
{% endif %}
@@ -45,31 +45,30 @@
{% endblock tabs %}
{% block content-wrapper %}
{% with config=settings.PLUGINS_CONFIG.netbox_topology_views %}
<div class="tab-content">
{% with config=settings.PLUGINS_CONFIG.netbox_topology_views %}
<div class="tab-content">
<div class="tab-pane show active" id="networks" role="tabpanel" aria-labelledby="network-tab">
<div class="panel-body">
<div id="visgraph" class=""></div>
</div>
<div class="tab-pane show active" id="networks" role="tabpanel" aria-labelledby="network-tab">
<div class="panel-body">
<div id="visgraph" class=""></div>
</div>
<script type="text/javascript">
var topology_data = {{ topology_data | safe }};
</script>
</div>
<script type="text/javascript">
var topology_data = {{ topology_data | safe }};
</script>
</div>
{% if filter_form %}
<div class="tab-pane show" id="filters-form" role="tabpanel" aria-labelledby="filters-form-tab">
{% include 'inc/filter_list.html' %}
</div>
{% endif %}
{% if filter_form %}
<div class="tab-pane show" id="filters-form" role="tabpanel" aria-labelledby="filters-form-tab">
{% include 'inc/filter_list.html' %}
</div>
{% endif %}
</div>
{% endwith %}
{% endblock content-wrapper %}
</div>
{% block javascript %}
<script src="{% static 'netbox_topology_views/js/vendor.js' %}"></script>
<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 %}
<script type="application/javascript"> iniPlotboxIndex()</script>
{% endwith %}
{% endblock content-wrapper %}
+39 -40
View File
@@ -66,53 +66,52 @@ def get_topology_data(queryset, hide_unconnected):
device_has_connections = True
else:
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:
circuit_ids.append(link_from.termination_a.circuit.id)
edge_ids += 1
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
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"
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 = {}
edge["id"] = edge_ids
edge["to"] = link_from.termination_b.device.id
edge["dashes"] = True
title = ""
edge = {}
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>"
title += "Termination between <br>"
title += cable_b_dev_name + " [" + cable_b_name + "]<br>"
title += "Circuit provider: " + link_from.termination_a.circuit.provider.name + "<br>"
title += "Termination between <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:
edge["from"] = link_from.termination_a.circuit.termination_a.cable.termination_b.device.id
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
cable_a_dev_name = link_from.termination_a.circuit.termination_a.cable.termination_b.device.name
if cable_a_dev_name is None:
cable_a_dev_name = "device B name unknown"
cable_b_name = link_from.termination_a.circuit.termination_a.cable.termination_b.name
if cable_a_name is None:
cable_a_name = "cable B name unknown"
title += cable_a_dev_name + " [" + cable_a_name + "]<br>"
edge["title"] = title
edges.append(edge)
cable_a_dev_name = link_from.termination_a.circuit.termination_a.cable.termination_b.device.name
if cable_a_dev_name is None:
cable_a_dev_name = "device B name unknown"
cable_b_name = link_from.termination_a.circuit.termination_a.cable.termination_b.name
if cable_a_name is None:
cable_a_name = "cable B name unknown"
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:
edge["from"] = link_from.termination_a.circuit.termination_z.cable.termination_b.device.id
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
cable_a_dev_name = link_from.termination_a.circuit.termination_z.cable.termination_b.device.name
if cable_a_dev_name is None:
cable_a_dev_name = "device B name unknown"
cable_a_name = link_from.termination_a.circuit.termination_z.cable.termination_b.name
if cable_a_name is None:
cable_a_name = "cable B name unknown"
title += cable_a_dev_name + " [" + cable_a_name + "]<br>"
edge["title"] = title
edges.append(edge)
cable_a_dev_name = link_from.termination_a.circuit.termination_z.cable.termination_b.device.name
if cable_a_dev_name is None:
cable_a_dev_name = "device B name unknown"
cable_a_name = link_from.termination_a.circuit.termination_z.cable.termination_b.name
if cable_a_name is None:
cable_a_name = "cable B name unknown"
title += cable_a_dev_name + " [" + cable_a_name + "]<br>"
edge["title"] = title
edges.append(edge)
if qs_device.id not in nodes_ids:
if hide_unconnected == None or (hide_unconnected is True and device_has_connections is True):
+6092 -29
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,7 +1,7 @@
{
"private": true,
"name": "netbox_topology_views",
"version": "1.2.0",
"version": "1.0.0-beta.1",
"scripts": {
"resources": "gulp build",
"resources_dev": "gulp build_dev"
+1 -1
View File
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
setup(
name='netbox-topology-views',
version='1.2.0',
version='1.0.0-beta.1',
description='An NetBox plugin to create Topology maps',
url='https://github.com/mattieserver/netbox-topology-views',
author='Mattijs Vanhaverbeke',