fixed auth, added save-coords

This commit is contained in:
Mattijs Vanhaverbeke
2020-05-14 22:20:01 +02:00
parent a1d155fdbc
commit 5e53066ba7
12 changed files with 99 additions and 41 deletions
+7 -1
View File
@@ -18,8 +18,11 @@ Then run `python3 manage.py collectstatic --no-input`
### Custom field: coordinates ### Custom field: coordinates
There is also support for custom fiels. 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. If you create a custom field "coordinates" for "dcim > device" with type "text" and name "coordinates" you will see the same layout every time.
The coordinates can then be provided as: "X;Y"
## Configure ## Configure
If you want to override the default values configure the `PLUGINS_CONFIG` in your `netbox configuration.py`. If you want to override the default values configure the `PLUGINS_CONFIG` in your `netbox configuration.py`.
@@ -37,7 +40,10 @@ PLUGINS_CONFIG = {
| Setting | Default value | Description | | Setting | Default value | Description |
| ------------- |-------------| -----| | ------------- |-------------| -----|
| 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' | The slug of the device roles that you have a image for. | | 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' | The slug of the device roles that you have a image for. |
| 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. | | 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 |
### Custom Images ### Custom Images
+4 -3
View File
@@ -4,15 +4,16 @@ class TopologyViewsConfig(PluginConfig):
name = 'netbox_topology_views' name = 'netbox_topology_views'
verbose_name = 'Topology views' verbose_name = 'Topology views'
description = 'An plugin to render toplogoy maps' description = 'An plugin to render toplogoy maps'
version = '0.3' version = '0.4.0'
author = 'Mattijs Vanhaverbeke' author = 'Mattijs Vanhaverbeke'
author_email = 'author@example.com' author_email = 'author@example.com'
base_url = 'topology-views' base_url = 'topology-views'
required_settings = [] required_settings = []
default_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', '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
} }
config = TopologyViewsConfig config = TopologyViewsConfig
+8 -1
View File
@@ -1,6 +1,6 @@
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from dcim.models import DeviceRole from dcim.models import DeviceRole, Device
class PreDeviceRoleSerializer(ModelSerializer): class PreDeviceRoleSerializer(ModelSerializer):
@@ -8,3 +8,10 @@ class PreDeviceRoleSerializer(ModelSerializer):
model = DeviceRole model = DeviceRole
fields = ('id', 'name') fields = ('id', 'name')
class TopologyDummySerializer(ModelSerializer):
class Meta:
model = Device
fields = ('id', 'name')
+2 -1
View File
@@ -1,9 +1,10 @@
from rest_framework import routers from rest_framework import routers
from .views import PreSelectDeviceRolesViewSet, SearchViewSet from .views import PreSelectDeviceRolesViewSet, SearchViewSet, SaveCoordsViewSet
router = routers.DefaultRouter() router = routers.DefaultRouter()
router.register('preselectdeviceroles', PreSelectDeviceRolesViewSet) router.register('preselectdeviceroles', PreSelectDeviceRolesViewSet)
router.register('search', SearchViewSet, basename='search') router.register('search', SearchViewSet, basename='search')
router.register('save-coords', SaveCoordsViewSet, basename='save_coords')
urlpatterns = router.urls urlpatterns = router.urls
+52 -6
View File
@@ -1,24 +1,68 @@
from rest_framework.viewsets import ModelViewSet, ViewSet, ReadOnlyModelViewSet from rest_framework.viewsets import ModelViewSet, ViewSet, ReadOnlyModelViewSet, GenericViewSet
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 from .serializers import PreDeviceRoleSerializer, TopologyDummySerializer
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
ignore_cable_type_raw = settings.PLUGINS_CONFIG["netbox_topology_views"]["ignore_cable_type"]
ignore_cable_type = ignore_cable_type_raw.split(",")
class PreSelectDeviceRolesViewSet(ReadOnlyModelViewSet):
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(",")
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 SearchViewSet(ViewSet): class SaveCoordsViewSet(GenericViewSet):
_ignore_model_permissions = True queryset = Device.objects.all()
permission_classes = [IsAuthenticatedOrLoginNotRequired] serializer_class = TopologyDummySerializer
@action(detail=False, methods=['post'])
def save_coords(self, request):
if settings.PLUGINS_CONFIG["netbox_topology_views"]["allow_coordinates_saving"]:
device_id = None
x_coord = None
y_coord = None
if "node_id" in request.data:
if request.data["node_id"]:
device_id = request.data["node_id"]
if "x" in request.data:
if request.data["x"]:
x_coord = request.data["x"]
if "y" in request.data:
if request.data["y"]:
y_coord = request.data["y"]
actual_device= Device.objects.get(id=device_id)
for device_custom_field in actual_device.custom_field_values.all():
if device_custom_field.field.name == "coordinates":
old_cords = device_custom_field.serialized_value.split(";")
device_custom_field.value = "%s;%s" % (x_coord,y_coord)
device_custom_field.save()
actual_device.save()
results = {}
results["status"] = "ok"
return Response(results)
print('notok')
#TODO
return Response(status=500)
else:
return Response(status=500)
class SearchViewSet(GenericViewSet):
#_ignore_model_permissions = True
#permission_classes = [IsAuthenticatedOrLoginNotRequired]
queryset = Device.objects.all()
serializer_class = TopologyDummySerializer
def _filter(self, site, role, name): def _filter(self, site, role, name):
filter_devices = Device.objects.all() filter_devices = Device.objects.all()
@@ -47,6 +91,7 @@ class SearchViewSet(ViewSet):
name = request.data["name"] name = request.data["name"]
devices = self._filter(sites, devicerole, name) devices = self._filter(sites, devicerole, name)
nodes = [] nodes = []
edges = [] edges = []
edge_ids = 0 edge_ids = 0
@@ -55,6 +100,7 @@ class SearchViewSet(ViewSet):
cables = device.get_cables() cables = device.get_cables()
for cable in cables: for cable in cables:
if cable.id not in cable_ids: if cable.id not in cable_ids:
if cable.termination_a_type.name not in ignore_cable_type:
cable_ids.append(cable.id) cable_ids.append(cable.id)
edge_ids += 1 edge_ids += 1
edge = {} edge = {}
@@ -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="badge badge-pill badge-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="badge badge-pill badge-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="badge badge-pill badge-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/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="badge badge-pill badge-warning">Failed to update coordinates</span>')},success:function(e){$("#coordstatus").html('<span class="badge badge-pill badge-success">Updated coordinates</span>'),$.ajax({url:"/api/reload_devices",type:"GET",headers:{"X-CSRFToken":csrftoken},success:function(e){}})}}))})})},error:function(e){$("#status").html('<span class="badge badge-pill badge-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}})}}}});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>')}})})}
File diff suppressed because one or more lines are too long
+7 -15
View File
@@ -116,7 +116,7 @@ function startLoadSearchBar() {
function handleButtonPress() { function handleButtonPress() {
$("#search-form").submit(function (event) { $("#search-form").submit(function (event) {
$("#status").html('<span class="badge badge-pill badge-info">Loading data</span>'); $("#status").html('<span class="label label-info">Loading data</span>');
event.preventDefault(); event.preventDefault();
var value = $("#name").val(); var value = $("#name").val();
var value2 = $("#device-roles").val(); var value2 = $("#device-roles").val();
@@ -133,7 +133,7 @@ function handleButtonPress() {
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
dataType: "json", dataType: "json",
success: function (data_result) { success: function (data_result) {
$("#status").html('<span class="badge badge-pill badge-info">Drawing network</span>'); $("#status").html('<span class="label label-info">Drawing network</span>');
graph = null; graph = null;
nodes = new vis.DataSet(); nodes = new vis.DataSet();
edges = new vis.DataSet(); edges = new vis.DataSet();
@@ -147,7 +147,7 @@ function handleButtonPress() {
graph.fit(); graph.fit();
graph.on('afterDrawing', function () { graph.on('afterDrawing', function () {
$("#status").html('<span class="badge badge-pill badge-success">Ready</span>'); $("#status").html('<span class="label label-success">Ready</span>');
}); });
graph.on("dragEnd", function (params) { graph.on("dragEnd", function (params) {
@@ -157,7 +157,7 @@ function handleButtonPress() {
if ($('#checkSaveCoordinates').is(":checked")) { if ($('#checkSaveCoordinates').is(":checked")) {
nodes.update({ id: node_id, physics: false }); nodes.update({ id: node_id, physics: false });
$.ajax({ $.ajax({
url: "../../api/save_coords", url: "../../api/plugins/topology-views/save-coords/save_coords/",
type: 'POST', type: 'POST',
dataType: 'json', dataType: 'json',
headers: { "X-CSRFToken": csrftoken }, headers: { "X-CSRFToken": csrftoken },
@@ -168,18 +168,10 @@ function handleButtonPress() {
'y': coordinates.y 'y': coordinates.y
}), }),
error: function (error_result) { error: function (error_result) {
$("#coordstatus").html('<span class="badge badge-pill badge-warning">Failed to update coordinates</span>'); $("#coordstatus").html('<span class="label label-warning">Failed to update coordinates</span>');
}, },
success: function (data_result) { success: function (data_result) {
$("#coordstatus").html('<span class="badge badge-pill badge-success">Updated coordinates</span>'); $("#coordstatus").html('<span class="label label-success">Updated coordinates</span>');
$.ajax({
url: '/api/reload_devices',
type: 'GET',
headers: { "X-CSRFToken": csrftoken },
success: function (data) {
//nothing
},
});
}, },
}); });
} }
@@ -187,7 +179,7 @@ function handleButtonPress() {
}); });
}, },
error: function (error_result) { error: function (error_result) {
$("#status").html('<span class="badge badge-pill badge-warning">Something went wrong</span>'); $("#status").html('<span class="label label-warning">Something went wrong</span>');
}, },
}); });
}); });
@@ -45,6 +45,7 @@
</div> </div>
{% if config.allow_coordinates_saving == True %}
<div class="panel panel-default mt-3"> <div class="panel panel-default mt-3">
<div class="panel-heading"> <div class="panel-heading">
<strong>Settings</strong> <strong>Settings</strong>
@@ -59,6 +60,7 @@
</div> </div>
</div> </div>
</div> </div>
{% endif %}
</div> </div>
</div> </div>
+4 -1
View File
@@ -1,7 +1,10 @@
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
from django.views.generic import View from django.views.generic import View
from django.contrib.auth.mixins import PermissionRequiredMixin
class TopologyHomeView(PermissionRequiredMixin, View):
permission_required = ('dcim.view_site', 'dcim.view_device')
class TopologyHomeView(View):
""" """
Show the home page Show the home page
""" """
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"private": true, "private": true,
"name": "netbox_topology_views", "name": "netbox_topology_views",
"version": "0.2", "version": "0.4.0",
"scripts": { "scripts": {
"resources": "gulp build", "resources": "gulp build",
"resources_dev": "gulp build_dev" "resources_dev": "gulp build_dev"
+1 -1
View File
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
setup( setup(
name='netbox-topology-views', name='netbox-topology-views',
version='0.3', version='0.4.0',
description='An NetBox plugin to create Topology maps', description='An NetBox plugin to create Topology maps',
url='https://github.com/mattieserver/netbox-topology-views', url='https://github.com/mattieserver/netbox-topology-views',
author='Mattijs Vanhaverbeke', author='Mattijs Vanhaverbeke',