diff --git a/netbox_topology_views/__init__.py b/netbox_topology_views/__init__.py index f1ac006..132b675 100644 --- a/netbox_topology_views/__init__.py +++ b/netbox_topology_views/__init__.py @@ -7,7 +7,7 @@ class TopologyViewsConfig(PluginConfig): version = '0.5.3' author = 'Mattijs Vanhaverbeke' author_email = 'author@example.com' - base_url = 'topology-views' + base_url = 'netbox_topology_views' required_settings = [] default_settings = { 'preselected_device_roles': 'Firewall,Router,Distribution Switch,Core Switch,Internal Switch,Access Switch,Server,Storage,Backup,Wireless AP', diff --git a/netbox_topology_views/api/urls.py b/netbox_topology_views/api/urls.py index 239e6cb..506e2ff 100644 --- a/netbox_topology_views/api/urls.py +++ b/netbox_topology_views/api/urls.py @@ -1,9 +1,7 @@ -from netbox.api import OrderedDefaultRouter +from rest_framework import routers from . import views -router = OrderedDefaultRouter() -router.APIRootView = views.TopologyViewsRootView - +router = routers.DefaultRouter() router.register('preselectdeviceroles', views.PreSelectDeviceRolesViewSet) router.register('preselecttags', views.PreSelectTagsViewSet) diff --git a/netbox_topology_views/filters.py b/netbox_topology_views/filters.py new file mode 100644 index 0000000..85d2aba --- /dev/null +++ b/netbox_topology_views/filters.py @@ -0,0 +1,47 @@ +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 + +class DeviceFilterSet(django_filters.FilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) + tag = TagFilter() + + role_id = django_filters.ModelMultipleChoiceFilter( + field_name='device_role_id', + queryset=DeviceRole.objects.all(), + label='Role (ID)', + ) + region_id = TreeNodeMultipleChoiceFilter( + queryset=Region.objects.all(), + field_name='site__region', + lookup_expr='in', + label='Region (ID)', + ) + site_id = django_filters.ModelMultipleChoiceFilter( + queryset=Site.objects.all(), + label='Site (ID)', + ) + location_id = TreeNodeMultipleChoiceFilter( + queryset=Location.objects.all(), + field_name='location', + lookup_expr='in', + label='Location (ID)', + ) + + class Meta: + model = Device + fields = ['id', 'name', ] + + def search(self, queryset, name, value): + """Perform the filtered search.""" + if not value.strip(): + return queryset + qs_filter = ( + Q(name__icontains=value) + ) + return queryset.filter(qs_filter) \ No newline at end of file diff --git a/netbox_topology_views/forms.py b/netbox_topology_views/forms.py new file mode 100644 index 0000000..ef941d4 --- /dev/null +++ b/netbox_topology_views/forms.py @@ -0,0 +1,49 @@ +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 extras.forms import CustomFieldModelFilterForm + +from utilities.forms import (TagFilterField, DynamicModelMultipleChoiceField, BootstrapMixin) + +class DeviceFilterForm(CustomFieldModelFilterForm): + model = Device + field_groups = [ + ['q'], + ['region_id', 'site_id', 'location_id'], + ['device_role_id'], + ['tag'], + ] + + region_id = DynamicModelMultipleChoiceField( + queryset=Region.objects.all(), + required=False, + label=_('Region') + ) + device_role_id = DynamicModelMultipleChoiceField( + queryset=DeviceRole.objects.all(), + required=False, + label=_('Device Role') + ) + site_id = DynamicModelMultipleChoiceField( + queryset=Site.objects.all(), + required=False, + query_params={ + 'region_id': '$region_id', + }, + label=_('Site') + ) + location_id = DynamicModelMultipleChoiceField( + queryset=Location.objects.all(), + required=False, + query_params={ + 'region_id': '$region_id', + 'site_id': '$site_id', + }, + label=_('Location') + ) + + tag = TagFilterField(model) + diff --git a/netbox_topology_views/navigation.py b/netbox_topology_views/navigation.py index c549a79..d39c1d2 100644 --- a/netbox_topology_views/navigation.py +++ b/netbox_topology_views/navigation.py @@ -5,6 +5,13 @@ menu_items = ( PluginMenuItem( link='plugins:netbox_topology_views:home', link_text='Topology', - buttons=() + buttons=( + PluginMenuButton( + link='plugins:netbox_topology_views:home', + title='AS Numbers', + icon_class='mdi mdi-plus-thick', + permissions=[], + ), + ) ), ) \ No newline at end of file diff --git a/netbox_topology_views/static/netbox_topology_views/js/app.js b/netbox_topology_views/static/netbox_topology_views/js/app.js index 12693ef..dedb7d7 100644 --- a/netbox_topology_views/static/netbox_topology_views/js/app.js +++ b/netbox_topology_views/static/netbox_topology_views/js/app.js @@ -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,t,n){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,t){nodes.add(t)}),$.each(e.edges,function(e,t){edges.add(t)}),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 iniPlotboxIndex(){document.addEventListener("DOMContentLoaded",function(){container=document.getElementById("visgraph"),csrftoken=jQuery("[name=csrfmiddlewaretoken]").val(),startLoadSearchBar(),handleButtonPress(),downloadButton=document.getElementById("btnDownloadImage"),btnFullView=document.getElementById("btnFullView")},!1)}function startLoadSearchBar(){$("#device-roles").select2({allowClear:!0,placeholder:"---------",theme:"bootstrap",multiple:!0,ajax:{url:"../../api/dcim/device-roles/?brief=true",dataType:"json",type:"GET",data:function(e){return{q:e.term}},processResults:function(e){return{results:$.map(e.results,function(e){return{text:e.name,id:e.id}})}}}}),$("#sites").select2({allowClear:!0,placeholder:"---------",theme:"bootstrap",multiple:!0,ajax:{url:function(e){var t="../../api/dcim/sites/?brief=true";if(0==selected_regions.length)return t;for(var n=0;nLoading data'),e.preventDefault();var t=$("#name").val(),n=$("#device-roles").val(),s=$("#sites").val(),a=$("#tags").val(),o=$("#regions").val(),r=$("#checkHideUnconnected").is(":checked"),l=$("#locations").val();$.ajax({type:"GET",url:"../../api/plugins/topology-views/search/search/",data:{name:t,"devicerole[]":n,"sites[]":s,"tags[]":a,"regions[]":o,hide_unconnected:r,"locations[]":l},headers:{"X-CSRFToken":csrftoken},contentType:"application/json; charset=utf-8",dataType:"json",beforeSend:function(){new_url=this.url.split("../../api/plugins/topology-views/search/search/?"),new_url=new_url[1],new_url="../../plugins/topology-views/full?"+new_url,btnFullView.classList.remove("disabled"),btnFullView.href=new_url},success:function(e,t,n){$("#status").html('Drawing network'),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,t){nodes.add(t)}),$.each(e.edges,function(e,t){edges.add(t)}),graph.fit(),canvas=document.getElementById("visgraph").getElementsByTagName("canvas")[0],graph.on("afterDrawing",function(){$("#status").html('Ready');var e=canvas.toDataURL(MIME_TYPE);downloadButton.href=e,downloadButton.download="topology"}),graph.on("dragEnd",function(e){dragged=this.getPositions(e.nodes),$.each(dragged,function(e,t){$("#coordstatus").html(""),$("#checkSaveCoordinates").is(":checked")&&$.ajax({url:"../../api/plugins/topology-views/save-coords/save_coords/",type:"PATCH",dataType:"json",headers:{"X-CSRFToken":csrftoken},contentType:"application/json; charset=utf-8",processData:!1,data:JSON.stringify({node_id:e,x:t.x,y:t.y}),error:function(e){$("#coordstatus").html('Failed to update coordinates')},success:function(e){$("#coordstatus").html('Updated coordinates')}})})})},error:function(e){$("#status").html('Something went wrong')}})})} \ No newline at end of file +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 addEdge(e){edges.add(e)}function addNode(e){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"})} \ No newline at end of file diff --git a/netbox_topology_views/static_dev/js/home.js b/netbox_topology_views/static_dev/js/home.js index 845b5eb..0e0198f 100644 --- a/netbox_topology_views/static_dev/js/home.js +++ b/netbox_topology_views/static_dev/js/home.js @@ -35,343 +35,40 @@ var options = { var selected_regions = []; var selected_sites = []; +function addEdge(item) { + edges.add(item); +} + +function addNode(item) { + nodes.add(item); +} + function iniPlotboxIndex() { document.addEventListener('DOMContentLoaded', function () { container = document.getElementById('visgraph'); - csrftoken = jQuery("[name=csrfmiddlewaretoken]").val(); - startLoadSearchBar(); - handleButtonPress(); + handleLoadData(); downloadButton = document.getElementById('btnDownloadImage'); btnFullView = document.getElementById('btnFullView'); }, false); } -function startLoadSearchBar() { - $('#device-roles').select2({ - allowClear: true, - placeholder: "---------", - theme: "bootstrap", - multiple: true, - ajax: { - url: "../../api/dcim/device-roles/?brief=true", - dataType: "json", - type: "GET", - data: function (params) { - var queryParameters = { - q: params.term - } - return queryParameters; - }, - processResults: function (data) { - return { - results: $.map(data.results, function (item) { - return { - text: item.name, - id: item.id - } - }) - }; - } - } - }); - $('#sites').select2({ - allowClear: true, - placeholder: "---------", - theme: "bootstrap", - multiple: true, - ajax: { - url: function (params) { - var base_url = "../../api/dcim/sites/?brief=true"; - if (selected_regions.length == 0) { - return base_url; - } - else { - for (var i = 0; i < selected_regions.length; i++) { - var tmp = "®ion_id=" + selected_regions[i]; - base_url = base_url + tmp; - } - return base_url; - } +function handleLoadData() { - }, - dataType: "json", - type: "GET", - data: function (params) { - var queryParameters = { - q: params.term - } - return queryParameters; - }, - processResults: function (data) { - return { - results: $.map(data.results, function (item) { - return { - text: item.name, - id: item.id - } - }) - }; - } - } - }); + 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); - $('#sites').on('select2:select', function (e) { - var data = e.params.data; - var index = selected_sites.indexOf(data.id.toString()); - if (index == -1) { - selected_sites.push(data.id.toString()); - } - }); - $('#sites').on('select2:unselect', function (e) { - var data = e.params.data; - var index = selected_sites.indexOf(data.id.toString()); - if (index > -1) { - selected_sites.splice(index, 1); - } - }); - $('#sites').on('select2:clear', function (e) { - selected_sites = []; - }); + graph.fit(); + canvas = document.getElementById('visgraph').getElementsByTagName('canvas')[0]; - $('#tags').select2({ - allowClear: true, - placeholder: "---------", - theme: "bootstrap", - multiple: true, - ajax: { - url: "../../api/extras/tags/?brief=true", - dataType: "json", - type: "GET", - data: function (params) { - var queryParameters = { - q: params.term - } - return queryParameters; - }, - processResults: function (data) { - return { - results: $.map(data.results, function (item) { - return { - text: item.name, - id: item.id - } - }) - }; - } - } - }); - $('#regions').select2({ - allowClear: true, - placeholder: "---------", - theme: "bootstrap", - multiple: true, - ajax: { - url: "../../api/dcim/regions/?brief=true", - dataType: "json", - type: "GET", - data: function (params) { - var queryParameters = { - q: params.term - } - return queryParameters; - }, - processResults: function (data) { - return { - results: $.map(data.results, function (item) { - return { - text: item.name, - id: item.id - } - }) - }; - } - } - }); - - $('#regions').on('select2:select', function (e) { - var data = e.params.data; - var index = selected_regions.indexOf(data.id.toString()); - if (index == -1) { - selected_regions.push(data.id.toString()); - } - }); - $('#regions').on('select2:unselect', function (e) { - var data = e.params.data; - var index = selected_regions.indexOf(data.id.toString()); - if (index > -1) { - selected_regions.splice(index, 1); - } - }); - $('#regions').on('select2:clear', function (e) { - selected_regions = []; - }); - - $('#locations').select2({ - allowClear: true, - placeholder: "---------", - theme: "bootstrap", - multiple: true, - ajax: { - url: function (params) { - var base_url = "../../api/dcim/locations/?brief=true"; - if (selected_sites.length == 0) { - return base_url; - } - else { - for (var i = 0; i < selected_sites.length; i++) { - var tmp = "&site_id=" + selected_sites[i]; - base_url = base_url + tmp; - } - return base_url; - } - - }, - dataType: "json", - type: "GET", - data: function (params) { - var queryParameters = { - q: params.term - } - return queryParameters; - }, - processResults: function (data) { - return { - results: $.map(data.results, function (item) { - return { - text: item.name, - id: item.id - } - }) - }; - } - } - }); - - - var deviceRolesSelect = $('#device-roles'); - $.ajax({ - type: 'GET', - url: '../../api/plugins/topology-views/preselectdeviceroles/' - }).then(function (data) { - $.each(data.results, function (index, device_role_to_preload) { - var option = new Option(device_role_to_preload.name, device_role_to_preload.id, true, true); - deviceRolesSelect.append(option).trigger('change'); - deviceRolesSelect.trigger({ - type: 'select2:select', - params: { - data: device_role_to_preload - } - }); + graph.on('afterDrawing', function () { + var image = canvas.toDataURL(MIME_TYPE); + downloadButton.href = image; + downloadButton.download = "topology"; }); - }); - var tagsSelect = $('#tags'); - $.ajax({ - type: 'GET', - url: '../../api/plugins/topology-views/preselecttags/' - }).then(function (data) { - $.each(data.results, function (index, tags_to_preload) { - var option = new Option(tags_to_preload.name, tags_to_preload.id, true, true); - tagsSelect.append(option).trigger('change'); - tagsSelect.trigger({ - type: 'select2:select', - params: { - data: tags_to_preload - } - }); - }); - }); -} - -function handleButtonPress() { - $("#search-form").submit(function (event) { - $("#status").html('Loading data'); - event.preventDefault(); - var value = $("#name").val(); - var value2 = $("#device-roles").val(); - var value3 = $("#sites").val(); - var value4 = $("#tags").val(); - var value5 = $("#regions").val(); - var value6 = $('#checkHideUnconnected').is(":checked"); - var value7 = $("#locations").val(); - $.ajax({ - type: "GET", - url: "../../api/plugins/topology-views/search/search/", - data: { - 'name': value, - 'devicerole[]': value2, - 'sites[]': value3, - 'tags[]': value4, - 'regions[]': value5, - 'hide_unconnected': value6, - 'locations[]': value7 - }, - headers: { "X-CSRFToken": csrftoken }, - contentType: "application/json; charset=utf-8", - dataType: "json", - beforeSend: function(){ - new_url = this.url.split("../../api/plugins/topology-views/search/search/?"); - new_url = new_url[1]; - new_url = "../../plugins/topology-views/full?" + new_url - - btnFullView.classList.remove("disabled"); - btnFullView.href = new_url; - }, - success: function (data_result, status, xhr) { - $("#status").html('Drawing network'); - graph = null; - nodes = new vis.DataSet(); - edges = new vis.DataSet(); - graph = new vis.Network(container, { nodes: nodes, edges: edges }, options); - $.each(data_result["nodes"], function (index, device) { - nodes.add(device); - }); - $.each(data_result["edges"], function (index, edge) { - edges.add(edge); - }); - graph.fit(); - canvas = document.getElementById('visgraph').getElementsByTagName('canvas')[0]; - - graph.on('afterDrawing', function () { - $("#status").html('Ready'); - var image = canvas.toDataURL(MIME_TYPE); - downloadButton.href = image; - downloadButton.download = "topology"; - }); - - graph.on("dragEnd", function (params) { - dragged = this.getPositions(params.nodes); - $.each(dragged, function (node_id, coordinates) { - $("#coordstatus").html(''); - if ($('#checkSaveCoordinates').is(":checked")) { - //nodes.update({ id: node_id, physics: false }); - $.ajax({ - url: "../../api/plugins/topology-views/save-coords/save_coords/", - type: 'PATCH', - dataType: 'json', - headers: { "X-CSRFToken": csrftoken }, - contentType: "application/json; charset=utf-8", - processData: false, - data: JSON.stringify({ - 'node_id': node_id, - 'x': coordinates.x, - 'y': coordinates.y - }), - error: function (error_result) { - $("#coordstatus").html('Failed to update coordinates'); - }, - success: function (data_result) { - $("#coordstatus").html('Updated coordinates'); - }, - }); - } - }); - }); - - }, - error: function (error_result) { - $("#status").html('Something went wrong'); - }, - }); - }); } diff --git a/netbox_topology_views/templates/netbox_topology_views/index.html b/netbox_topology_views/templates/netbox_topology_views/index.html index d1b703c..874ac36 100644 --- a/netbox_topology_views/templates/netbox_topology_views/index.html +++ b/netbox_topology_views/templates/netbox_topology_views/index.html @@ -1,103 +1,78 @@ -{% extends 'base.html' %} +{% extends 'base/layout.html' %} +{% load buttons %} +{% load render_table from django_tables2 %} {% load static %} +{% load perms %} {% load helpers %} -{% block content %} -{% with config=settings.PLUGINS_CONFIG.netbox_topology_views %} +{% block title %}Topology Views{% endblock %} + +{% block head %} +{% endblock %} -
-
-
-
- Network - -
+{% block controls %} +
+
+ {% block extra_controls %}{% endblock %} + + + Download + + + + Share + +
+
+{% endblock controls %} + +{% block tabs %} + +{% endblock tabs %} + +{% block content-wrapper %} +{% with config=settings.PLUGINS_CONFIG.netbox_topology_views %} +
+ +
+ +
+ + {% if filter_form %} +
+ {% include 'inc/filter_list.html' %} +
+ {% endif %} +
-
-
-
- Search -
-
-
- {% csrf_token %} -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- - -
-
-
- -
-
-
- - {% if config.allow_coordinates_saving == True %} -
-
- Settings -
-
-
- - -
-
- -
-
-
- {% endif %} -
-
+ + + {% endwith %} -{% endblock %} - -{% block javascript %} - - - - - - -{% endblock %} \ No newline at end of file +{% endblock content-wrapper %} diff --git a/netbox_topology_views/urls.py b/netbox_topology_views/urls.py index 3c8a09e..5a1c715 100644 --- a/netbox_topology_views/urls.py +++ b/netbox_topology_views/urls.py @@ -7,5 +7,4 @@ from . import views # a specific view so that it can be accessed by users. urlpatterns = ( path('', views.TopologyHomeView.as_view(), name='home'), - path('full', views.TopologyFullView.as_view(), name='full'), ) diff --git a/netbox_topology_views/views.py b/netbox_topology_views/views.py index 830c205..b5f3c02 100644 --- a/netbox_topology_views/views.py +++ b/netbox_topology_views/views.py @@ -1,6 +1,143 @@ from django.shortcuts import get_object_or_404, render +from django.db.models import Q from django.views.generic import View from django.contrib.auth.mixins import PermissionRequiredMixin +from django.conf import settings + +from .forms import DeviceFilterForm +from .filters import DeviceFilterSet +import json + +from dcim.models import Device, Cable, DeviceRole, DeviceType + +def get_topology_data(queryset): + nodes = [] + nodes_ids = [] + edges = [] + edge_ids = 0 + cable_ids = [] + circuit_ids = [] + if not queryset: + return None + + for qs_device in queryset: + device_has_connections = False + links_from_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_from_device: + device_has_connections = True + if link_from.termination_a_type.name != "circuit termination" and link_from.termination_b_type.name != "circuit termination": + if link_from.id not in cable_ids: + cable_ids.append(link_from.id) + edge_ids += 1 + cable_a_dev_name = link_from.termination_a.device.name + 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 = {} + edge["id"] = edge_ids + edge["from"] = link_from.termination_a.device.id + edge["to"] = link_from.termination_b.device.id + edge["title"] = "Cable between
" + cable_a_dev_name + " [" + cable_a_name + "]
" + cable_b_dev_name + " [" + cable_b_name + "]" + if link_from.color != "": + edge["color"] = "#" + link_from.color + edges.append(edge) + else: + 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" + + 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 + "
" + title += "Termination between
" + title += cable_b_dev_name + " [" + cable_b_name + "]
" + + 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 + "]
" + 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 + + 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 + "]
" + edge["title"] = title + edges.append(edge) + + if qs_device.id not in nodes_ids: + nodes_ids.append(qs_device.id) + + dev_name = qs_device.name + if dev_name is None: + dev_name = "device name unknown" + + node_content = "" + + if qs_device.device_type is not None: + node_content += "Type: " + qs_device.device_type.model + "" + if qs_device.device_role.name is not None: + node_content += "Role: " + qs_device.device_role.name + "" + if qs_device.serial != "": + node_content += "Serial: " + qs_device.serial + "" + if qs_device.primary_ip is not None: + node_content += "IP Address: " + str(qs_device.primary_ip.address) + "" + + dev_title = " %s
" % (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 + nodes.append(node) + + results = {} + results["nodes"] = nodes + results["edges"] = edges + return results + class TopologyHomeView(PermissionRequiredMixin, View): permission_required = ('dcim.view_site', 'dcim.view_device') @@ -8,14 +145,16 @@ class TopologyHomeView(PermissionRequiredMixin, View): """ Show the home page """ - def get(self, request): - return render(request, 'netbox_topology_views/index.html') + def get(self, request): + self.filterset = DeviceFilterSet + self.queryset = Device.objects.all() + self.queryset = self.filterset(request.GET, self.queryset).qs + topo_data = None + if request.GET: + topo_data = get_topology_data(self.queryset) -class TopologyFullView(PermissionRequiredMixin, View): - permission_required = ('dcim.view_site', 'dcim.view_device') - - """ - Show the full view page - """ - def get(self, request): - return render(request, 'netbox_topology_views/full.html') \ No newline at end of file + return render(request, 'netbox_topology_views/index.html' , { + 'filter_form': DeviceFilterForm(request.GET, label_suffix=''), + 'topology_data': json.dumps(topo_data) + } + )