diff --git a/dev_setup/README.md b/dev_setup/README.md index 2021676..b44aef7 100644 --- a/dev_setup/README.md +++ b/dev_setup/README.md @@ -1,6 +1,7 @@ # Setup Dev + install docker + install docker compose + + install npm + clone netbox repo + clone netbox_topology_views repo + create venv: `/usr/bin/python3 -m venv venv` @@ -8,9 +9,10 @@ + install deps: `pip3 install -r requirements.txt` + install build tools: `python3 -m pip install --upgrade build setuptools wheel` + # Build - + in the root of the netbox_topology_views folder run `python3 -m build` - + install the package: `python3 -m pip install ../netbox-topology-views/dist/netbox_topology_views-X.Y.Z-py3-none-any.whl` where X,Y & Z are the version + + build the package: `python3 setup.py develop` + + build the resources `npm run resources` # Test + copy configuration.py from dev_setup to the netbox/netbox/ folder diff --git a/netbox_topology_views/__init__.py b/netbox_topology_views/__init__.py index 8247616..9aa2c13 100644 --- a/netbox_topology_views/__init__.py +++ b/netbox_topology_views/__init__.py @@ -4,7 +4,7 @@ class TopologyViewsConfig(PluginConfig): name = 'netbox_topology_views' verbose_name = 'Topology views' description = 'An plugin to render topology maps' - version = '0.5.0' + version = '0.5.1' author = 'Mattijs Vanhaverbeke' author_email = 'author@example.com' base_url = 'topology-views' diff --git a/netbox_topology_views/api/views.py b/netbox_topology_views/api/views.py index 9e673b5..07ca7b5 100644 --- a/netbox_topology_views/api/views.py +++ b/netbox_topology_views/api/views.py @@ -115,6 +115,13 @@ class SearchViewSet(ReadOnlyModelViewSet): if regions == []: regions = None + hide_unconnected = request.query_params.get('hide_unconnected', None) + if hide_unconnected == "": + hide_unconnected = None + print(hide_unconnected) + if hide_unconnected == 'false': + print("ok") + devices = self._filter(sites, devicerole, name, tags, regions) nodes = [] @@ -124,41 +131,21 @@ class SearchViewSet(ReadOnlyModelViewSet): circuit_ids = [] for device in devices: cables = device.get_cables() - for cable in cables: - if cable.termination_a_type.name != "circuit termination" and cable.termination_b_type.name != "circuit termination": - if cable.id not in cable_ids: - if cable.termination_a_type.name not in ignore_cable_type and cable.termination_b_type.name not in ignore_cable_type: - cable_ids.append(cable.id) - edge_ids += 1 - - cable_a_dev_name = cable.termination_a.device.name - if cable_a_dev_name is None: - cable_a_dev_name = "device A name unknown" - cable_a_name = cable.termination_a.name - if cable_a_name is None: - cable_a_name = "cable A name unknown" - cable_b_dev_name = cable.termination_b.device.name - if cable_b_dev_name is None: - cable_b_dev_name = "device B name unknown" - cable_b_name = cable.termination_b.name - if cable_b_name is None: - cable_b_name = "cable B name unknown" - - edge = {} - edge["id"] = edge_ids - edge["from"] = cable.termination_a.device.id - edge["to"] = cable.termination_b.device.id - edge["title"] = "Cable between
" + cable_a_dev_name + " [" + cable_a_name + "]
" + cable_b_dev_name + " [" + cable_b_name + "]" - if cable.color != "": - edge["color"] = "#" + cable.color - edges.append(edge) - else: - if cable.termination_a_type.name == "circuit termination": - if settings.PLUGINS_CONFIG["netbox_topology_views"]["enable_circuit_terminations"]: - if cable.termination_a.circuit.id not in circuit_ids: - circuit_ids.append(cable.termination_a.circuit.id) + if cables.exists(): + device_has_connections = True + for cable in cables: + if cable.termination_a_type.name != "circuit termination" and cable.termination_b_type.name != "circuit termination": + if cable.id not in cable_ids: + if cable.termination_a_type.name not in ignore_cable_type and cable.termination_b_type.name not in ignore_cable_type: + cable_ids.append(cable.id) edge_ids += 1 + cable_a_dev_name = cable.termination_a.device.name + if cable_a_dev_name is None: + cable_a_dev_name = "device A name unknown" + cable_a_name = cable.termination_a.name + if cable_a_name is None: + cable_a_name = "cable A name unknown" cable_b_dev_name = cable.termination_b.device.name if cable_b_dev_name is None: cable_b_dev_name = "device B name unknown" @@ -168,86 +155,112 @@ class SearchViewSet(ReadOnlyModelViewSet): edge = {} edge["id"] = edge_ids + edge["from"] = cable.termination_a.device.id edge["to"] = cable.termination_b.device.id - edge["dashes"] = True - title = "" + edge["title"] = "Cable between
" + cable_a_dev_name + " [" + cable_a_name + "]
" + cable_b_dev_name + " [" + cable_b_name + "]" + if cable.color != "": + edge["color"] = "#" + cable.color + edges.append(edge) + else: + if cable.termination_a_type.name == "circuit termination": + if settings.PLUGINS_CONFIG["netbox_topology_views"]["enable_circuit_terminations"]: + if cable.termination_a.circuit.id not in circuit_ids: + circuit_ids.append(cable.termination_a.circuit.id) + edge_ids += 1 - title += "Circuit provider: " + cable.termination_a.circuit.provider.name + "
" - title += "Termination between
" - title += cable_b_dev_name + " [" + cable_b_name + "]
" - # To Many if's - if cable.termination_a.circuit.termination_a is not None: - if cable.termination_a.circuit.termination_a.cable is not None: - if cable.termination_a.circuit.termination_a.cable.id != cable.id: - if cable.termination_a.circuit.termination_a.cable.termination_b is not None: - if cable.termination_a.circuit.termination_a.cable.termination_b.device is not None: - edge["from"] = cable.termination_a.circuit.termination_a.cable.termination_b.device.id + cable_b_dev_name = cable.termination_b.device.name + if cable_b_dev_name is None: + cable_b_dev_name = "device B name unknown" + cable_b_name = cable.termination_b.name + if cable_b_name is None: + cable_b_name = "cable B name unknown" - cable_a_dev_name = cable.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 = cable.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) - # To Many if's - if cable.termination_a.circuit.termination_z is not None: - if cable.termination_a.circuit.termination_z.cable is not None: - if cable.termination_a.circuit.termination_z.cable.id != cable.id: - if cable.termination_a.circuit.termination_z.cable.termination_b is not None: - if cable.termination_a.circuit.termination_z.cable.termination_b.device is not None: - edge["from"] = cable.termination_a.circuit.termination_z.cable.termination_b.device.id + edge = {} + edge["id"] = edge_ids + edge["to"] = cable.termination_b.device.id + edge["dashes"] = True + title = "" - cable_a_dev_name = cable.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 = cable.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) - dev_name = device.name - if dev_name is None: - dev_name = "device name unknown" + title += "Circuit provider: " + cable.termination_a.circuit.provider.name + "
" + title += "Termination between
" + title += cable_b_dev_name + " [" + cable_b_name + "]
" + # To Many if's + if cable.termination_a.circuit.termination_a is not None: + if cable.termination_a.circuit.termination_a.cable is not None: + if cable.termination_a.circuit.termination_a.cable.id != cable.id: + if cable.termination_a.circuit.termination_a.cable.termination_b is not None: + if cable.termination_a.circuit.termination_a.cable.termination_b.device is not None: + edge["from"] = cable.termination_a.circuit.termination_a.cable.termination_b.device.id - node_content = "" + cable_a_dev_name = cable.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 = cable.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) + # To Many if's + if cable.termination_a.circuit.termination_z is not None: + if cable.termination_a.circuit.termination_z.cable is not None: + if cable.termination_a.circuit.termination_z.cable.id != cable.id: + if cable.termination_a.circuit.termination_z.cable.termination_b is not None: + if cable.termination_a.circuit.termination_z.cable.termination_b.device is not None: + edge["from"] = cable.termination_a.circuit.termination_z.cable.termination_b.device.id - if device.device_type.display_name is not None: - node_content += "Type: " + device.device_type.display_name + "" - if device.device_role.name is not None: - node_content += "Role: " + device.device_role.name + "" - if device.serial != "": - node_content += "Serial: " + device.serial + "" - if device.primary_ip is not None: - node_content += "IP Address: " + str(device.primary_ip.address) + "" - - dev_title = " %s
" % (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" + cable_a_dev_name = cable.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 = cable.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) else: - node["image"] = "../../static/netbox_topology_views/img/role-unknown.png" + device_has_connections = False - if device.device_role.color != "": - node["color.border"] = "#" + device.device_role.color + if hide_unconnected == 'false' or (hide_unconnected == 'true' and device_has_connections is True): + dev_name = device.name + if dev_name is None: + dev_name = "device name unknown" - if "coordinates" in device.custom_field_data: - if 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 - - nodes.append(node) + node_content = "" + + if device.device_type.display_name is not None: + node_content += "Type: " + device.device_type.display_name + "" + if device.device_role.name is not None: + node_content += "Role: " + device.device_role.name + "" + if device.serial != "": + node_content += "Serial: " + device.serial + "" + if device.primary_ip is not None: + node_content += "IP Address: " + str(device.primary_ip.address) + "" + + dev_title = " %s
" % (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"] != "": + cords = device.custom_field_data["coordinates"].split(";") + node["x"] = int(cords[0]) + node["y"] = int(cords[1]) + node["physics"] = False + + nodes.append(node) results = {} results["nodes"] = nodes 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 9fbae8d..c81ebea 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=[];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(),a=$("#sites").val(),s=$("#tags").val(),o=$("#regions").val();$.ajax({type:"GET",url:"../../api/plugins/topology-views/search/search/",data:{name:t,"devicerole[]":n,"sites[]":a,"tags[]":s,"regions[]":o},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,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=[];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(),a=$("#sites").val(),s=$("#tags").val(),o=$("#regions").val(),r=$("#checkHideUnconnected").is(":checked");$.ajax({type:"GET",url:"../../api/plugins/topology-views/search/search/",data:{name:t,"devicerole[]":n,"sites[]":a,"tags[]":s,"regions[]":o,hide_unconnected:r},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 diff --git a/netbox_topology_views/static_dev/js/home.js b/netbox_topology_views/static_dev/js/home.js index 3ffba5b..9233339 100644 --- a/netbox_topology_views/static_dev/js/home.js +++ b/netbox_topology_views/static_dev/js/home.js @@ -232,6 +232,7 @@ function handleButtonPress() { var value3 = $("#sites").val(); var value4 = $("#tags").val(); var value5 = $("#regions").val(); + var value6 = $('#checkHideUnconnected').is(":checked"); $.ajax({ type: "GET", url: "../../api/plugins/topology-views/search/search/", @@ -240,7 +241,8 @@ function handleButtonPress() { 'devicerole[]': value2, 'sites[]': value3, 'tags[]': value4, - 'regions[]': value5 + 'regions[]': value5, + 'hide_unconnected': value6 }, headers: { "X-CSRFToken": csrftoken }, contentType: "application/json; charset=utf-8", diff --git a/netbox_topology_views/templates/netbox_topology_views/index.html b/netbox_topology_views/templates/netbox_topology_views/index.html index eaca05f..d1fd1e5 100644 --- a/netbox_topology_views/templates/netbox_topology_views/index.html +++ b/netbox_topology_views/templates/netbox_topology_views/index.html @@ -53,6 +53,12 @@
+
+
+
+ + +

diff --git a/package-lock.json b/package-lock.json index b6a1cd5..4753fa8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "netbox_topology_views", - "version": "0.5.0", + "version": "0.5.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -4772,9 +4772,9 @@ "dev": true }, "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", "dev": true }, "yallist": { diff --git a/package.json b/package.json index 193a91a..43cef2b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "netbox_topology_views", - "version": "0.5.0", + "version": "0.5.1", "scripts": { "resources": "gulp build", "resources_dev": "gulp build_dev" diff --git a/setup.py b/setup.py index 23a3161..aac297f 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup, find_packages setup( name='netbox-topology-views', - version='0.5.0', + version='0.5.1', description='An NetBox plugin to create Topology maps', url='https://github.com/mattieserver/netbox-topology-views', author='Mattijs Vanhaverbeke',