added search by tag (#8)

This commit is contained in:
Mattijs Vanhaverbeke
2020-05-16 14:24:38 +02:00
parent 5c4c6be745
commit 33d7157b12
8 changed files with 82 additions and 8 deletions
+1
View File
@@ -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| | 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 | | 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 | | 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 ### Custom Images
+2 -1
View File
@@ -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', 'preselected_device_roles': 'Firewall,Router,Distribution Switch,Core Switch,Internal Switch,Access Switch,Server,Storage,Backup,Wireless AP',
'ignore_cable_type': 'poweroutlet,powerport', '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', '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 config = TopologyViewsConfig
+7
View File
@@ -1,6 +1,7 @@
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from dcim.models import DeviceRole, Device from dcim.models import DeviceRole, Device
from extras.models import Tag
class PreDeviceRoleSerializer(ModelSerializer): class PreDeviceRoleSerializer(ModelSerializer):
@@ -8,6 +9,12 @@ class PreDeviceRoleSerializer(ModelSerializer):
model = DeviceRole model = DeviceRole
fields = ('id', 'name') fields = ('id', 'name')
class PreTagSerializer(ModelSerializer):
class Meta:
model = Tag
fields = ('id', 'name')
class TopologyDummySerializer(ModelSerializer): class TopologyDummySerializer(ModelSerializer):
+2 -1
View File
@@ -1,9 +1,10 @@
from rest_framework import routers from rest_framework import routers
from .views import PreSelectDeviceRolesViewSet, SearchViewSet, SaveCoordsViewSet from .views import PreSelectDeviceRolesViewSet, SearchViewSet, SaveCoordsViewSet, PreSelectTagsViewSet
router = routers.DefaultRouter() router = routers.DefaultRouter()
router.register('preselectdeviceroles', PreSelectDeviceRolesViewSet) router.register('preselectdeviceroles', PreSelectDeviceRolesViewSet)
router.register('preselecttags', PreSelectTagsViewSet)
router.register('search', SearchViewSet, basename='search') router.register('search', SearchViewSet, basename='search')
router.register('save-coords', SaveCoordsViewSet, basename='save_coords') router.register('save-coords', SaveCoordsViewSet, basename='save_coords')
+17 -3
View File
@@ -2,12 +2,13 @@ from rest_framework.viewsets import ModelViewSet, ViewSet, ReadOnlyModelViewSet,
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from .serializers import PreDeviceRoleSerializer, TopologyDummySerializer from .serializers import PreDeviceRoleSerializer, TopologyDummySerializer, PreTagSerializer
from django.conf import settings from django.conf import settings
from utilities.api import IsAuthenticatedOrLoginNotRequired from utilities.api import IsAuthenticatedOrLoginNotRequired
from dcim.models import DeviceRole, Device, Cable 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_raw = settings.PLUGINS_CONFIG["netbox_topology_views"]["ignore_cable_type"]
ignore_cable_type = ignore_cable_type_raw.split(",") 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_raw = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_device_roles"]
preselected_device_roles = preselected_device_roles_raw.split(",") 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): class PreSelectDeviceRolesViewSet(ReadOnlyModelViewSet):
queryset = DeviceRole.objects.filter(name__in=preselected_device_roles) queryset = DeviceRole.objects.filter(name__in=preselected_device_roles)
serializer_class = PreDeviceRoleSerializer serializer_class = PreDeviceRoleSerializer
class PreSelectTagsViewSet(ReadOnlyModelViewSet):
queryset = Tag.objects.filter(name__in=preselected_tags)
serializer_class = PreTagSerializer
class SaveCoordsViewSet(GenericViewSet): class SaveCoordsViewSet(GenericViewSet):
queryset = Device.objects.all() queryset = Device.objects.all()
serializer_class = TopologyDummySerializer serializer_class = TopologyDummySerializer
@@ -64,7 +72,7 @@ class SearchViewSet(GenericViewSet):
queryset = Device.objects.all() queryset = Device.objects.all()
serializer_class = TopologyDummySerializer serializer_class = TopologyDummySerializer
def _filter(self, site, role, name): def _filter(self, site, role, name, tags):
filter_devices = Device.objects.all() filter_devices = Device.objects.all()
if name is not None: if name is not None:
filter_devices = filter_devices.filter(name__contains=name) filter_devices = filter_devices.filter(name__contains=name)
@@ -72,6 +80,8 @@ class SearchViewSet(GenericViewSet):
filter_devices = filter_devices.filter(site__id__in=site) filter_devices = filter_devices.filter(site__id__in=site)
if role is not None: if role is not None:
filter_devices = filter_devices.filter(device_role__id__in=role) 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 return filter_devices
@@ -80,6 +90,7 @@ class SearchViewSet(GenericViewSet):
name = None name = None
sites = None sites = None
devicerole = None devicerole = None
tags = None
if "devicerole" in request.data: if "devicerole" in request.data:
if request.data["devicerole"]: if request.data["devicerole"]:
devicerole = request.data["devicerole"] devicerole = request.data["devicerole"]
@@ -89,8 +100,11 @@ class SearchViewSet(GenericViewSet):
if "name" in request.data: if "name" in request.data:
if request.data["name"]: if request.data["name"]:
name = 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 = [] nodes = []
edges = [] edges = []
@@ -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('<span class="label label-info">Loading data</span>'),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('<span class="label label-info">Drawing network</span>'),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('<span class="label label-success">Ready</span>')}),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('<span class="label label-warning">Failed to update coordinates</span>')},success:function(e){$("#coordstatus").html('<span class="label label-success">Updated coordinates</span>')}}))})})},error:function(e){$("#status").html('<span class="label label-warning">Something went wrong</span>')}})})} 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('<span class="label label-info">Loading data</span>'),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('<span class="label label-info">Drawing network</span>'),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('<span class="label label-success">Ready</span>')}),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('<span class="label label-warning">Failed to update coordinates</span>')},success:function(e){$("#coordstatus").html('<span class="label label-success">Updated coordinates</span>')}}))})})},error:function(e){$("#status").html('<span class="label label-warning">Something went wrong</span>')}})})}
+47 -1
View File
@@ -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'); var deviceRolesSelect = $('#device-roles');
$.ajax({ $.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() { function handleButtonPress() {
@@ -121,13 +165,15 @@ function handleButtonPress() {
var value = $("#name").val(); var value = $("#name").val();
var value2 = $("#device-roles").val(); var value2 = $("#device-roles").val();
var value3 = $("#sites").val(); var value3 = $("#sites").val();
var value4 = $("#tags").val();
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: "../../api/plugins/topology-views/search/search/", url: "../../api/plugins/topology-views/search/search/",
data: JSON.stringify({ data: JSON.stringify({
'name': value, 'name': value,
'devicerole': value2, 'devicerole': value2,
'sites': value3 'sites': value3,
'tags': value4
}), }),
headers: { "X-CSRFToken": csrftoken }, headers: { "X-CSRFToken": csrftoken },
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
@@ -24,7 +24,7 @@
<strong>Search</strong> <strong>Search</strong>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<form id="search-form" class="form"> <form id="search-form" class="form" autocomplete="off">
{% csrf_token %} {% csrf_token %}
<div class="form-group"> <div class="form-group">
<label for="name">Name</label> <label for="name">Name</label>
@@ -37,6 +37,10 @@
<div class="form-group"> <div class="form-group">
<label for="sites">Sites</label> <label for="sites">Sites</label>
<select class="form-control" multiple="multiple" id="sites"></select> <select class="form-control" multiple="multiple" id="sites"></select>
</div>
<div class="form-group">
<label for="tags">Tags</label>
<select class="form-control" multiple="multiple" id="tags"></select>
</div> </div>
<hr class="mb-4"> <hr class="mb-4">
<button class="btn btn-primary btn-lg btn-block" type="submit" id="start-search">Search</button> <button class="btn btn-primary btn-lg btn-block" type="submit" id="start-search">Search</button>