From ac76d70c57f4fa88310b746dcced8625e858da54 Mon Sep 17 00:00:00 2001 From: mattijs vanhaverbeke Date: Thu, 16 Apr 2020 14:22:42 +0200 Subject: [PATCH] added base functions and api --- README.md | 11 ++- doc/img/preview.png | Bin 0 -> 54276 bytes netbox_topology_views/__init__.py | 4 +- netbox_topology_views/api/__init__.py | 0 netbox_topology_views/api/serializers.py | 6 +- netbox_topology_views/api/urls.py | 7 +- netbox_topology_views/api/views.py | 88 +++++++++++++++++- .../static/netbox_topology_views/js/app.js | 2 +- netbox_topology_views/static_dev/js/home.js | 16 ++-- .../netbox_topology_views/index.html | 1 + netbox_topology_views/views.py | 1 - 11 files changed, 111 insertions(+), 25 deletions(-) create mode 100644 doc/img/preview.png delete mode 100644 netbox_topology_views/api/__init__.py diff --git a/README.md b/README.md index 795069f..496efbe 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,17 @@ # netbox-topology-views +## preview + +![preview image](doc/img/preview.png?raw=true "preview") + ## install +Add 'netbox_topology_views' to the PLUGINS array in your django configuration file. +Then run python3 manage.py collectstatic --no-input + +There is also support for custom fiels. +If you create a custom field "coordinates" for "dcim > device" with type "text" and name "coordinates" you will see the same layout every time. ## use -go to /plugins/topology-views/ \ No newline at end of file +go to /plugins/topology-views/ view your topologies \ No newline at end of file diff --git a/doc/img/preview.png b/doc/img/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..83a411c4d628d4f897f1f3bae1134a1b065f5b36 GIT binary patch literal 54276 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 literal 0 HcmV?d00001 diff --git a/netbox_topology_views/__init__.py b/netbox_topology_views/__init__.py index 334709e..843bbdc 100644 --- a/netbox_topology_views/__init__.py +++ b/netbox_topology_views/__init__.py @@ -10,9 +10,9 @@ class TopologyViewsConfig(PluginConfig): base_url = 'topology-views' required_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', 'IGNORE_CABLE_TYPE': 'dcim.poweroutlet,dcim.powerport', - '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' } config = TopologyViewsConfig diff --git a/netbox_topology_views/api/__init__.py b/netbox_topology_views/api/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/netbox_topology_views/api/serializers.py b/netbox_topology_views/api/serializers.py index 5dc8463..76f571d 100644 --- a/netbox_topology_views/api/serializers.py +++ b/netbox_topology_views/api/serializers.py @@ -1,10 +1,10 @@ -from rest_framework.serializers import ModelSerializer, ValidatedModelSerializer +from rest_framework.serializers import ModelSerializer from dcim.models import DeviceRole -class PreDeviceRoleSerializer(ValidatedModelSerializer): +class PreDeviceRoleSerializer(ModelSerializer): class Meta: model = DeviceRole - fields = ('id', 'name', 'slug', 'color', 'vm_role', 'description') + fields = ('id', 'name') \ No newline at end of file diff --git a/netbox_topology_views/api/urls.py b/netbox_topology_views/api/urls.py index 21fe127..7fac369 100644 --- a/netbox_topology_views/api/urls.py +++ b/netbox_topology_views/api/urls.py @@ -1,12 +1,9 @@ from rest_framework import routers -from .views import PreSelectDeviceRolesViewSet +from .views import PreSelectDeviceRolesViewSet, SearchViewSet router = routers.DefaultRouter() -#router.register('device_roles', AnimalViewSet) -#router.register('sites', AnimalViewSet) -#router.register('search', AnimalViewSet) -#router.register('save_coords', AnimalViewSet) router.register('preselectdeviceroles', PreSelectDeviceRolesViewSet) +router.register('search', SearchViewSet, basename='search') urlpatterns = router.urls \ No newline at end of file diff --git a/netbox_topology_views/api/views.py b/netbox_topology_views/api/views.py index 0e57673..8aed3ba 100644 --- a/netbox_topology_views/api/views.py +++ b/netbox_topology_views/api/views.py @@ -1,9 +1,89 @@ -from rest_framework.viewsets import ModelViewSet +from rest_framework.viewsets import ModelViewSet, ViewSet, ReadOnlyModelViewSet +from rest_framework.decorators import action +from rest_framework.response import Response from .serializers import PreDeviceRoleSerializer +from django.conf import settings -from dcim.models import DeviceRole +from utilities.api import IsAuthenticatedOrLoginNotRequired -class PreSelectDeviceRolesViewSet(ModelViewSet): - queryset = DeviceRole.objects.all() +from dcim.models import DeviceRole, Device, Cable + + +class PreSelectDeviceRolesViewSet(ReadOnlyModelViewSet): + preselected_device_roles_raw = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_device_roles"] + preselected_device_roles = preselected_device_roles_raw.split(",") + queryset = DeviceRole.objects.filter(name__in=preselected_device_roles) serializer_class = PreDeviceRoleSerializer + +class SearchViewSet(ViewSet): + _ignore_model_permissions = True + permission_classes = [IsAuthenticatedOrLoginNotRequired] + + def _filter(self, site, role, name): + filter_devices = Device.objects.all() + if name is not None: + filter_devices = filter_devices.filter(name__contains=name) + if site is not None: + filter_devices = filter_devices.filter(site__id__in=site) + if role is not None: + filter_devices = filter_devices.filter(device_role__id__in=role) + return filter_devices + + + @action(detail=False, methods=['post']) + def search(self, request): + name = None + sites = None + devicerole = None + if "devicerole" in request.data: + if request.data["devicerole"]: + devicerole = request.data["devicerole"] + if "sites" in request.data: + if request.data["sites"]: + sites = request.data["sites"] + if "name" in request.data: + if request.data["name"]: + name = request.data["name"] + + devices = self._filter(sites, devicerole, name) + nodes = [] + edges = [] + edge_ids = 0 + cable_ids = [] + for device in devices: + cables = device.get_cables() + for cable in cables: + if cable.id not in cable_ids: + cable_ids.append(cable.id) + edge_ids += 1 + edge = {} + edge["id"] = edge_ids + edge["from"] = cable.termination_a.device.id + edge["to"] = cable.termination_b.device.id + edge["title"] = "Connection between
" + cable.termination_a.device.name + " [" + cable.termination_a.name + "]
" + cable.termination_b.device.name + " [" + cable.termination_b.name + "]" + edges.append(edge) + node = {} + node["id"] = device.id + node["name"] = device.name + node["label"] = device.name + device.device_type.display_name + 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" + + for device_custom_field in device.custom_field_values.all(): + if device_custom_field.field.name == "coordinates": + cords = device_custom_field.serialized_value.split(";") + node["x"] = int(cords[0]) + node["y"] = int(cords[1]) + node["physics"] = False + + nodes.append(node) + + results = {} + results["nodes"] = nodes + results["edges"] = edges + + return Response(results) \ 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 67d5d83..c5aef68 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,csrftoken=null,nodes=new vis.DataSet,edges=new vis.DataSet,options={interaction:{hover:!0,hoverConnectedEdges:!0,multiselect:!0},nodes:{shape:"image",brokenImage:"/static/assets/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()},!1)}function startLoadSearchBar(){$("#device-roles").select2({allowClear:!0,placeholder:"---------",theme:"bootstrap",multiple:!0,ajax:{url:"/api/device_roles",dataType:"json",type:"GET",data:function(e){return{term:e.term}},processResults:function(e){return{results:$.map(e,function(e){return{text:e.name,id:e.id}})}}}}),$("#sites").select2({allowClear:!0,placeholder:"---------",theme:"bootstrap",multiple:!0,ajax:{url:"/api/sites",dataType:"json",type:"GET",data:function(e){return{term:e.term}},processResults:function(e){return{results:$.map(e,function(e){return{text:e.name,id:e.id}})}}}});var n=$("#device-roles");$.ajax({type:"GET",url:"/api/preselect_device_roles"}).then(function(e){$.each(e.data,function(e,a){var t=new Option(a.name,a.id,!0,!0);n.append(t).trigger("change"),n.trigger({type:"select2:select",params:{data:a}})})})}function handleButtonPress(){$("#search-form").submit(function(e){$("#status").html('Loading data'),e.preventDefault();var a=$("#name").val(),t=$("#device-roles").val(),n=$("#sites").val();$.ajax({type:"POST",url:"/api/search",data:JSON.stringify({name:a,devicerole:t,sites:n}),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,a){nodes.add(a)}),$.each(e.edges,function(e,a){edges.add(a)}),graph.fit(),graph.on("afterDrawing",function(){$("#status").html('Ready')}),graph.on("dragEnd",function(e){dragged=this.getPositions(e.nodes),$.each(dragged,function(e,a){$("#coordstatus").html(""),$("#checkSaveCoordinates").is(":checked")&&(nodes.update({id:e,physics:!1}),$.ajax({url:"/api/save_coords",type:"POST",dataType:"json",headers:{"X-CSRFToken":csrftoken},contentType:"application/json; charset=utf-8",data:JSON.stringify({node_id:e,x:a.x,y:a.y}),error:function(e){$("#coordstatus").html('Failed to update coordinates')},success:function(e){$("#coordstatus").html('Updated coordinates'),$.ajax({url:"/api/reload_devices",type:"GET",headers:{"X-CSRFToken":csrftoken},success:function(e){}})}}))})})},error:function(e){$("#status").html('Something went wrong')}})})} \ No newline at end of file +var graph=null,container=null,csrftoken=null,nodes=new vis.DataSet,edges=new vis.DataSet,options={interaction:{hover:!0,hoverConnectedEdges:!0,multiselect:!0},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()},!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{term: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{term: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}})})})}function handleButtonPress(){$("#search-form").submit(function(e){$("#status").html('Loading data'),e.preventDefault();var t=$("#name").val(),a=$("#device-roles").val(),n=$("#sites").val();$.ajax({type:"POST",url:"/api/plugins/topology-views/search/search/",data:JSON.stringify({name:t,devicerole:a,sites:n}),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(),graph.on("afterDrawing",function(){$("#status").html('Ready')}),graph.on("dragEnd",function(e){dragged=this.getPositions(e.nodes),$.each(dragged,function(e,t){$("#coordstatus").html(""),$("#checkSaveCoordinates").is(":checked")&&(nodes.update({id:e,physics:!1}),$.ajax({url:"/api/save_coords",type:"POST",dataType:"json",headers:{"X-CSRFToken":csrftoken},contentType:"application/json; charset=utf-8",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'),$.ajax({url:"/api/reload_devices",type:"GET",headers:{"X-CSRFToken":csrftoken},success:function(e){}})}}))})})},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 ecabb37..83f4d6a 100644 --- a/netbox_topology_views/static_dev/js/home.js +++ b/netbox_topology_views/static_dev/js/home.js @@ -11,7 +11,7 @@ var options = { }, nodes: { shape: 'image', - brokenImage: '/static/assets/img/role-unknown.png', + brokenImage: '/static/netbox_topology_views/img/role-unknown.png', size: 35, font: { multi: 'md', @@ -46,7 +46,7 @@ function startLoadSearchBar() { theme: "bootstrap", multiple: true, ajax: { - url: "/api/device_roles", + url: "/api/dcim/device-roles/?brief=true", dataType: "json", type: "GET", data: function (params) { @@ -57,7 +57,7 @@ function startLoadSearchBar() { }, processResults: function (data) { return { - results: $.map(data, function (item) { + results: $.map(data.results, function (item) { return { text: item.name, id: item.id @@ -73,7 +73,7 @@ function startLoadSearchBar() { theme: "bootstrap", multiple: true, ajax: { - url: "/api/sites", + url: "/api/dcim/sites/?brief=true", dataType: "json", type: "GET", data: function (params) { @@ -85,7 +85,7 @@ function startLoadSearchBar() { }, processResults: function (data) { return { - results: $.map(data, function (item) { + results: $.map(data.results, function (item) { return { text: item.name, id: item.id @@ -99,9 +99,9 @@ function startLoadSearchBar() { var deviceRolesSelect = $('#device-roles'); $.ajax({ type: 'GET', - url: '/api/preselect_device_roles' + url: '/api/plugins/topology-views/preselectdeviceroles/' }).then(function (data) { - $.each(data.data, function (index, device_role_to_preload) { + $.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({ @@ -123,7 +123,7 @@ function handleButtonPress() { var value3 = $("#sites").val(); $.ajax({ type: "POST", - url: "/api/search", + url: "/api/plugins/topology-views/search/search/", data: JSON.stringify({ 'name': value, 'devicerole': value2, diff --git a/netbox_topology_views/templates/netbox_topology_views/index.html b/netbox_topology_views/templates/netbox_topology_views/index.html index 02a52c1..79e91e8 100644 --- a/netbox_topology_views/templates/netbox_topology_views/index.html +++ b/netbox_topology_views/templates/netbox_topology_views/index.html @@ -62,6 +62,7 @@ +{% endwith %} {% endblock %} {% block javascript %} diff --git a/netbox_topology_views/views.py b/netbox_topology_views/views.py index 27b9e00..bf1c275 100644 --- a/netbox_topology_views/views.py +++ b/netbox_topology_views/views.py @@ -1,7 +1,6 @@ from django.shortcuts import get_object_or_404, render from django.views.generic import View - class TopologyHomeView(View): """ Show the home page