From 5bfc47775b96fe9dddfadb2b44e50a2e8610f5ee Mon Sep 17 00:00:00 2001 From: Mattijs Vanhaverbeke Date: Mon, 20 Jul 2020 12:36:38 +0200 Subject: [PATCH] #37 --- netbox_topology_views/api/views.py | 10 +- .../static/netbox_topology_views/js/app.js | 305 +++++++++++++++++- .../static/netbox_topology_views/js/vendor.js | 86 ++++- netbox_topology_views/static_dev/js/home.js | 68 +++- .../netbox_topology_views/index.html | 4 + 5 files changed, 466 insertions(+), 7 deletions(-) diff --git a/netbox_topology_views/api/views.py b/netbox_topology_views/api/views.py index 2c9b3f2..5d545d2 100644 --- a/netbox_topology_views/api/views.py +++ b/netbox_topology_views/api/views.py @@ -83,7 +83,7 @@ class SearchViewSet(ReadOnlyModelViewSet): queryset = Device.objects.all() serializer_class = TopologyDummySerializer - def _filter(self, site, role, name, tags): + def _filter(self, site, role, name, tags, region): filter_devices = Device.objects.all() if name is not None: filter_devices = filter_devices.filter(name__contains=name) @@ -93,6 +93,8 @@ class SearchViewSet(ReadOnlyModelViewSet): filter_devices = filter_devices.filter(device_role__id__in=role) if tags is not None: filter_devices = filter_devices.filter(tags__id__in=tags) + if region is not None: + filter_devices = filter_devices.filter(site__region__id__in=region) return filter_devices @@ -114,7 +116,11 @@ class SearchViewSet(ReadOnlyModelViewSet): if tags == []: tags = None - devices = self._filter(sites, devicerole, name, tags) + regions = request.query_params.getlist('regions[]', None) + if regions == []: + regions = None + + devices = self._filter(sites, devicerole, name, tags, regions) nodes = [] edges = [] 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 19d8026..39ad013 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,304 @@ -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"}};function iniPlotboxIndex(){document.addEventListener("DOMContentLoaded",function(){container=document.getElementById("visgraph"),csrftoken=jQuery("[name=csrfmiddlewaretoken]").val(),startLoadSearchBar(),handleButtonPress(),downloadButton=document.getElementById("btnDownloadImage")},!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:"../../api/dcim/sites/?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}})}}}}),$("#tags").select2({allowClear:!0,placeholder:"---------",theme:"bootstrap",multiple:!0,ajax:{url:"../../api/extras/tags/?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}})}}}});var n=$("#device-roles");$.ajax({type:"GET",url:"../../api/plugins/topology-views/preselectdeviceroles/"}).then(function(e){$.each(e.results,function(e,t){var a=new Option(t.name,t.id,!0,!0);n.append(a).trigger("change"),n.trigger({type:"select2:select",params:{data:t}})})});var s=$("#tags");$.ajax({type:"GET",url:"../../api/plugins/topology-views/preselecttags/"}).then(function(e){$.each(e.results,function(e,t){var a=new Option(t.name,t.id,!0,!0);s.append(a).trigger("change"),s.trigger({type:"select2:select",params:{data:t}})})})}function handleButtonPress(){$("#search-form").submit(function(e){$("#status").html('Loading data'),e.preventDefault();var t=$("#name").val(),a=$("#device-roles").val(),n=$("#sites").val(),s=$("#tags").val();$.ajax({type:"GET",url:"../../api/plugins/topology-views/search/search/",data:{name:t,"devicerole[]":a,"sites[]":n,"tags[]":s},headers:{"X-CSRFToken":csrftoken},contentType:"application/json; charset=utf-8",dataType:"json",success:function(e){$("#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; +var container = null; +var downloadButton = null; +var MIME_TYPE = "image/png"; +var canvas = null; +var csrftoken = null; +var nodes = new vis.DataSet(); +var edges = new vis.DataSet(); +var options = { + interaction: { + hover: true, + hoverConnectedEdges: true, + multiselect: false + }, + 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' + } +}; +var selected_regions = []; + +function iniPlotboxIndex() { + document.addEventListener('DOMContentLoaded', function () { + container = document.getElementById('visgraph'); + csrftoken = jQuery("[name=csrfmiddlewaretoken]").val(); + startLoadSearchBar(); + handleButtonPress(); + downloadButton = document.getElementById('btnDownloadImage'); + }, 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 { + selected_regions.forEach(element => { + var tmp = "®ion_id=" + element; + 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 + } + }) + }; + } + } + }); + $('#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()); + console.log(index); + if (index > -1) { + selected_regions.splice(index, 1); + } + }); + $('#regions').on('select2:clear', function (e) { + selected_regions = []; + }); + + + 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 + } + }); + }); + }); + + 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(); + $.ajax({ + type: "GET", + url: "../../api/plugins/topology-views/search/search/", + data: { + 'name': value, + 'devicerole[]': value2, + 'sites[]': value3, + 'tags[]': value4, + 'regions[]': value5 + }, + headers: { "X-CSRFToken": csrftoken }, + contentType: "application/json; charset=utf-8", + dataType: "json", + success: function (data_result) { + $("#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/static/netbox_topology_views/js/vendor.js b/netbox_topology_views/static/netbox_topology_views/js/vendor.js index 5c1e3e3..f8188ae 100644 --- a/netbox_topology_views/static/netbox_topology_views/js/vendor.js +++ b/netbox_topology_views/static/netbox_topology_views/js/vendor.js @@ -1 +1,85 @@ -!function(g,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((g=g||self).vis=g.vis||{})}(this,function(g){"use strict";var eC="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function t(g){return g&&g.__esModule&&Object.prototype.hasOwnProperty.call(g,"default")?g.default:g}function e(g,t){return g(t={exports:{}},t.exports),t.exports}function A(g){return g&&g.default||g}function I(g){return g&&g.Math==Math&&g}function a(g){try{return!!g()}catch(g){return!0}}function v(g,t){return{enumerable:!(1&g),configurable:!(2&g),writable:!(4&g),value:t}}function C(g){return k.call(g).slice(8,-1)}function o(g){if(null==g)throw TypeError("Can't call method on "+g);return g}function r(g){return O(o(g))}function d(g){return"object"==typeof g?null!==g:"function"==typeof g}function i(g,t){if(!d(g))return g;var e,A;if(t&&"function"==typeof(e=g.toString)&&!d(A=e.call(g)))return A;if("function"==typeof(e=g.valueOf)&&!d(A=e.call(g)))return A;if(!t&&"function"==typeof(e=g.toString)&&!d(A=e.call(g)))return A;throw TypeError("Can't convert object to primitive value")}function p(g,t){return N.call(g,t)}function n(g){return M?Z.createElement(g):{}}function s(g,t){var e=F[R(g)];return e==L||e!=S&&("function"==typeof t?a(t):!!t)}function l(g){if("function"!=typeof g)throw TypeError(String(g)+" is not a function");return g}function y(A,I,g){if(l(A),void 0===I)return A;switch(g){case 0:return function(){return A.call(I)};case 1:return function(g){return A.call(I,g)};case 2:return function(g,t){return A.call(I,g,t)};case 3:return function(g,t,e){return A.call(I,g,t,e)}}return function(){return A.apply(I,arguments)}}function h(g){if(!d(g))throw TypeError(String(g)+" is not an object");return g}function f(A){function g(g,t,e){if(this instanceof A){switch(arguments.length){case 0:return new A;case 1:return new A(g);case 2:return new A(g,t)}return new A(g,t,e)}return A.apply(this,arguments)}return g.prototype=A.prototype,g}function m(g,t){var e,A,I,C,i,n,o,r,s=g.target,a=g.global,d=g.stat,l=g.proto,h=a?b:d?b[s]:(b[s]||{}).prototype,c=a?Y:Y[s]||(Y[s]={}),u=c.prototype;for(I in t)e=!Q(a?I:s+(d?".":"#")+I,g.forced)&&h&&p(h,I),i=c[I],e&&(n=g.noTargetGet?(r=j(h,I))&&r.value:h[I]),C=e&&n?n:t[I],e&&typeof i==typeof C||(o=g.bind&&e?y(C,b):g.wrap&&e?f(C):l&&"function"==typeof C?y(Function.call,C):C,(g.sham||C&&C.sham||i&&i.sham)&&P(o,"sham",!0),c[I]=o,l&&(p(Y,A=s+"Prototype")||P(Y,A,{}),Y[A][I]=C,g.real&&u&&!u[I]&&P(u,I,C)))}var b=I("object"==typeof globalThis&&globalThis)||I("object"==typeof window&&window)||I("object"==typeof self&&self)||I("object"==typeof eC&&eC)||Function("return this")(),c=!a(function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}),u={}.propertyIsEnumerable,w=Object.getOwnPropertyDescriptor,x={f:w&&!u.call({1:2},1)?function(g){var t=w(this,g);return!!t&&t.enumerable}:u},k={}.toString,D="".split,O=a(function(){return!Object("z").propertyIsEnumerable(0)})?function(g){return"String"==C(g)?D.call(g,""):Object(g)}:Object,N={}.hasOwnProperty,Z=b.document,M=d(Z)&&d(Z.createElement),E=!c&&!a(function(){return 7!=Object.defineProperty(n("div"),"a",{get:function(){return 7}}).a}),G=Object.getOwnPropertyDescriptor,T={f:c?G:function(g,t){if(g=r(g),t=i(t,!0),E)try{return G(g,t)}catch(g){}if(p(g,t))return v(!x.f.call(g,t),g[t])}},B=/#|\.prototype\./,R=s.normalize=function(g){return String(g).replace(B,".").toLowerCase()},F=s.data={},S=s.NATIVE="N",L=s.POLYFILL="P",Q=s,Y={},W=Object.defineProperty,z={f:c?W:function(g,t,e){if(h(g),t=i(t,!0),h(e),E)try{return W(g,t,e)}catch(g){}if("get"in e||"set"in e)throw TypeError("Accessors not supported");return"value"in e&&(g[t]=e.value),g}},P=c?function(g,t,e){return z.f(g,t,v(1,e))}:function(g,t,e){return g[t]=e,g},j=T.f,V=[].slice,X={},U=Function.bind||function(t){var e=l(this),A=V.call(arguments,1),I=function(){var g=A.concat(V.call(arguments));return this instanceof I?function(g,t,e){if(!(t in X)){for(var A=[],I=0;Is&&(h=s),l=Math.sqrt(h*h/(1+r*r)),t+=l=n<0?-l:l,e+=r*l,!0===d?g.lineTo(t,e):g.moveTo(t,e),s-=h,d=!d}var Ag={circle:q,dashedLine:eg,database:tg,diamond:function(g,t,e,A){g.beginPath(),g.lineTo(t,e+A),g.lineTo(t+A,e),g.lineTo(t,e-A),g.lineTo(t-A,e),g.closePath()},ellipse:gg,ellipse_vis:gg,hexagon:function(g,t,e,A){g.beginPath();var I=2*Math.PI/6;g.moveTo(t+A,e);for(var C=1;C<6;C++)g.lineTo(t+A*Math.cos(I*C),e+A*Math.sin(I*C));g.closePath()},roundRect:$,square:function(g,t,e,A){g.beginPath(),g.rect(t-A,e-A,2*A,2*A),g.closePath()},star:function(g,t,e,A){g.beginPath(),e+=.1*(A*=.82);for(var I=0;I<10;I++){var C=I%2==0?1.3*A:.5*A;g.lineTo(t+C*Math.sin(2*I*Math.PI/10),e-C*Math.cos(2*I*Math.PI/10))}g.closePath()},triangle:function(g,t,e,A){g.beginPath(),e+=.275*(A*=1.15);var I=2*A,C=I/2,i=Math.sqrt(3)/6*I,n=Math.sqrt(I*I-C*C);g.moveTo(t,e-(n-i)),g.lineTo(t+C,e+i),g.lineTo(t-C,e+i),g.lineTo(t,e-(n-i)),g.closePath()},triangleDown:function(g,t,e,A){g.beginPath(),e-=.275*(A*=1.15);var I=2*A,C=I/2,i=Math.sqrt(3)/6*I,n=Math.sqrt(I*I-C*C);g.moveTo(t,e+(n-i)),g.lineTo(t+C,e-i),g.lineTo(t-C,e-i),g.lineTo(t,e+(n-i)),g.closePath()}},Ig=e(function(g){function e(g){if(g)return function(g){for(var t in e.prototype)g[t]=e.prototype[t];return g}(g)}(g.exports=e).prototype.on=e.prototype.addEventListener=function(g,t){return this._callbacks=this._callbacks||{},(this._callbacks["$"+g]=this._callbacks["$"+g]||[]).push(t),this},e.prototype.once=function(g,t){function e(){this.off(g,e),t.apply(this,arguments)}return e.fn=t,this.on(g,e),this},e.prototype.off=e.prototype.removeListener=e.prototype.removeAllListeners=e.prototype.removeEventListener=function(g,t){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var e,A=this._callbacks["$"+g];if(!A)return this;if(1==arguments.length)return delete this._callbacks["$"+g],this;for(var I=0;II;)p(A,e=t[I++])&&(~it(C,e)||C.push(e));return C}function _g(g){return"function"==typeof g?g:void 0}function Hg(g,t){return arguments.length<2?_g(Y[g])||_g(b[g]):Y[g]&&Y[g][t]||b[g]&&b[g][t]}function Jg(){}function Kg(g){return"