diff --git a/README.md b/README.md index 6812964..d8d475b 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ PLUGINS_CONFIG = { | preselected_device_roles | 'Firewall,Router,Distribution Switch,Core Switch,Internal Switch,Access Switch,Server,Storage,Backup,Wireless AP' | The full name of the device roles you want to pre select in the global view. Note that this is case sensitive| | allow_coordinates_saving | False | (bool) Set to true if you use the custom coordinates fields and want to save the coordinates | | ignore_cable_type | 'poweroutlet,powerport' | The cable types that you want to ignore in the views | +| preselected_tags | '' | The name of tags you want to preload | ### Custom Images diff --git a/netbox_topology_views/__init__.py b/netbox_topology_views/__init__.py index 969c650..ba16926 100644 --- a/netbox_topology_views/__init__.py +++ b/netbox_topology_views/__init__.py @@ -13,7 +13,8 @@ class TopologyViewsConfig(PluginConfig): 'preselected_device_roles': 'Firewall,Router,Distribution Switch,Core Switch,Internal Switch,Access Switch,Server,Storage,Backup,Wireless AP', 'ignore_cable_type': 'poweroutlet,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', - 'allow_coordinates_saving': False + 'allow_coordinates_saving': False, + 'preselected_tags' : '' } config = TopologyViewsConfig diff --git a/netbox_topology_views/api/serializers.py b/netbox_topology_views/api/serializers.py index 4b35828..f713887 100644 --- a/netbox_topology_views/api/serializers.py +++ b/netbox_topology_views/api/serializers.py @@ -1,12 +1,19 @@ from rest_framework.serializers import ModelSerializer from dcim.models import DeviceRole, Device +from extras.models import Tag class PreDeviceRoleSerializer(ModelSerializer): class Meta: model = DeviceRole fields = ('id', 'name') + +class PreTagSerializer(ModelSerializer): + + class Meta: + model = Tag + fields = ('id', 'name') class TopologyDummySerializer(ModelSerializer): diff --git a/netbox_topology_views/api/urls.py b/netbox_topology_views/api/urls.py index 13ece51..831a77b 100644 --- a/netbox_topology_views/api/urls.py +++ b/netbox_topology_views/api/urls.py @@ -1,9 +1,10 @@ from rest_framework import routers -from .views import PreSelectDeviceRolesViewSet, SearchViewSet, SaveCoordsViewSet +from .views import PreSelectDeviceRolesViewSet, SearchViewSet, SaveCoordsViewSet, PreSelectTagsViewSet router = routers.DefaultRouter() router.register('preselectdeviceroles', PreSelectDeviceRolesViewSet) +router.register('preselecttags', PreSelectTagsViewSet) router.register('search', SearchViewSet, basename='search') router.register('save-coords', SaveCoordsViewSet, basename='save_coords') diff --git a/netbox_topology_views/api/views.py b/netbox_topology_views/api/views.py index 5cfc51b..d672a39 100644 --- a/netbox_topology_views/api/views.py +++ b/netbox_topology_views/api/views.py @@ -2,12 +2,13 @@ from rest_framework.viewsets import ModelViewSet, ViewSet, ReadOnlyModelViewSet, from rest_framework.decorators import action from rest_framework.response import Response -from .serializers import PreDeviceRoleSerializer, TopologyDummySerializer +from .serializers import PreDeviceRoleSerializer, TopologyDummySerializer, PreTagSerializer from django.conf import settings from utilities.api import IsAuthenticatedOrLoginNotRequired from dcim.models import DeviceRole, Device, Cable +from extras.models import Tag ignore_cable_type_raw = settings.PLUGINS_CONFIG["netbox_topology_views"]["ignore_cable_type"] ignore_cable_type = ignore_cable_type_raw.split(",") @@ -15,11 +16,18 @@ ignore_cable_type = ignore_cable_type_raw.split(",") preselected_device_roles_raw = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_device_roles"] preselected_device_roles = preselected_device_roles_raw.split(",") +preselected_tags_raw = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_tags"] +preselected_tags = preselected_tags_raw.split(",") + class PreSelectDeviceRolesViewSet(ReadOnlyModelViewSet): queryset = DeviceRole.objects.filter(name__in=preselected_device_roles) serializer_class = PreDeviceRoleSerializer +class PreSelectTagsViewSet(ReadOnlyModelViewSet): + queryset = Tag.objects.filter(name__in=preselected_tags) + serializer_class = PreTagSerializer + class SaveCoordsViewSet(GenericViewSet): queryset = Device.objects.all() serializer_class = TopologyDummySerializer @@ -64,7 +72,7 @@ class SearchViewSet(GenericViewSet): queryset = Device.objects.all() serializer_class = TopologyDummySerializer - def _filter(self, site, role, name): + def _filter(self, site, role, name, tags): filter_devices = Device.objects.all() if name is not None: filter_devices = filter_devices.filter(name__contains=name) @@ -72,6 +80,8 @@ class SearchViewSet(GenericViewSet): filter_devices = filter_devices.filter(site__id__in=site) if role is not None: filter_devices = filter_devices.filter(device_role__id__in=role) + if tags is not None: + filter_devices = filter_devices.filter(tags__id__in=tags) return filter_devices @@ -80,6 +90,7 @@ class SearchViewSet(GenericViewSet): name = None sites = None devicerole = None + tags = None if "devicerole" in request.data: if request.data["devicerole"]: devicerole = request.data["devicerole"] @@ -89,8 +100,11 @@ class SearchViewSet(GenericViewSet): if "name" in request.data: if request.data["name"]: name = request.data["name"] + if "tags" in request.data: + if request.data["tags"]: + tags = request.data["tags"] - devices = self._filter(sites, devicerole, name) + devices = self._filter(sites, devicerole, name, tags) 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 1c22c70..aebe6df 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/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/plugins/topology-views/save-coords/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')}}))})})},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}})}}}}),$("#tags").select2({allowClear:!0,placeholder:"---------",theme:"bootstrap",multiple:!0,ajax:{url:"../../api/extras/tags/?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 s=$("#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);s.append(a).trigger("change"),s.trigger({type:"select2:select",params:{data:t}})})});var n=$("#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);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(),s=$("#sites").val(),n=$("#tags").val();$.ajax({type:"POST",url:"../../api/plugins/topology-views/search/search/",data:JSON.stringify({name:t,devicerole:a,sites:s,tags: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/plugins/topology-views/save-coords/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')}}))})})},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 52d74ce..bda9d07 100644 --- a/netbox_topology_views/static_dev/js/home.js +++ b/netbox_topology_views/static_dev/js/home.js @@ -95,6 +95,33 @@ function startLoadSearchBar() { } } }); + $('#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 = { + term: 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({ @@ -112,6 +139,23 @@ function startLoadSearchBar() { }); }); }); + + 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() { @@ -121,13 +165,15 @@ function handleButtonPress() { var value = $("#name").val(); var value2 = $("#device-roles").val(); var value3 = $("#sites").val(); + var value4 = $("#tags").val(); $.ajax({ type: "POST", url: "../../api/plugins/topology-views/search/search/", data: JSON.stringify({ 'name': value, 'devicerole': value2, - 'sites': value3 + 'sites': value3, + 'tags': value4 }), 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 d9d1f50..87cd4d0 100644 --- a/netbox_topology_views/templates/netbox_topology_views/index.html +++ b/netbox_topology_views/templates/netbox_topology_views/index.html @@ -24,7 +24,7 @@ Search