Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
deedee78bd | ||
|
|
683e125cd5 |
@@ -8,7 +8,7 @@ Support to filter on name, site, tag and device role.
|
||||
|
||||
## Preview
|
||||
|
||||

|
||||

|
||||
|
||||
## Install
|
||||
|
||||
@@ -35,7 +35,6 @@ Then run `python3 manage.py collectstatic --no-input`
|
||||
|
||||
| netbox version | netbox-topology-views version |
|
||||
| ------------- |-------------|
|
||||
| >= 3.1.8 | >= v1.0.0a1 |
|
||||
| >= 2.11.1 | >= v0.5.3 |
|
||||
| >= 2.10.0 | >= v0.5.0 |
|
||||
| < 2.10.0 | =< v0.4.10 |
|
||||
@@ -56,21 +55,20 @@ Example:
|
||||
```
|
||||
PLUGINS_CONFIG = {
|
||||
'netbox_topology_views': {
|
||||
'device_img': ['router','switch', 'firewall'],
|
||||
'preselected_device_roles': ['Router', 'Firewall']
|
||||
'device_img': 'router,switch,firewall',
|
||||
'preselected_device_roles': 'Router, Firewall'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 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. |
|
||||
| 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|
|
||||
| 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. 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 | ['power outlet', 'power port'] | The cable types that you want to ignore in the views |
|
||||
| preselected_tags | '[]' | The name of tags you want to preload |
|
||||
| ignore_cable_type | 'power outlet,power port' | The cable types that you want to ignore in the views |
|
||||
| preselected_tags | '' | The name of tags you want to preload |
|
||||
| enable_circuit_terminations | False | (bool) Set to true if you want to see circuit terminations in the topology |
|
||||
| draw_default_layout | False | (bool) Set to True if you want to load draw the topology on the initial load (when you go to the topology plugin page) |
|
||||
|
||||
### Custom Images
|
||||
|
||||
@@ -80,7 +78,7 @@ If you add your own image you also need to add the slug to the `device_img` sett
|
||||
|
||||
## Use
|
||||
|
||||
Go to the plugins tab in the navbar and click topology or go to `$NETBOX_URL/plugins/netbox_topology_views/` to view your topologies
|
||||
Go to the plugins tab in the navbar and click topology or go to `$NETBOX_URL/plugins/topology-views/` to view your topologies
|
||||
|
||||
### Update
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 72 KiB |
@@ -4,19 +4,18 @@ class TopologyViewsConfig(PluginConfig):
|
||||
name = 'netbox_topology_views'
|
||||
verbose_name = 'Topology views'
|
||||
description = 'An plugin to render topology maps'
|
||||
version = '1.0.0'
|
||||
version = '1.0.0-alpha.1'
|
||||
author = 'Mattijs Vanhaverbeke'
|
||||
author_email = 'author@example.com'
|
||||
base_url = 'netbox_topology_views'
|
||||
required_settings = []
|
||||
default_settings = {
|
||||
'preselected_device_roles': ['Firewall', 'Router', 'Distribution Switch', 'Core Switch', 'Internal Switch', 'Access Switch', 'Server', 'Storage', 'Backup', 'Wireless AP'],
|
||||
'ignore_cable_type': ['power outlet','power port'],
|
||||
'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'],
|
||||
'preselected_device_roles': 'Firewall,Router,Distribution Switch,Core Switch,Internal Switch,Access Switch,Server,Storage,Backup,Wireless AP',
|
||||
'ignore_cable_type': 'power outlet,power port',
|
||||
'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,
|
||||
'preselected_tags' : [],
|
||||
'enable_circuit_terminations': True,
|
||||
'draw_default_layout': False
|
||||
'preselected_tags' : '',
|
||||
'enable_circuit_terminations': False
|
||||
}
|
||||
|
||||
config = TopologyViewsConfig
|
||||
|
||||
@@ -3,6 +3,18 @@ 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):
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ from . import views
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
|
||||
router.register('preselectdeviceroles', views.PreSelectDeviceRolesViewSet)
|
||||
router.register('preselecttags', views.PreSelectTagsViewSet)
|
||||
router.register('search', views.SearchViewSet, basename='search')
|
||||
router.register('save-coords', views.SaveCoordsViewSet, basename='save_coords')
|
||||
|
||||
urlpatterns = router.urls
|
||||
@@ -4,7 +4,7 @@ from rest_framework.response import Response
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from rest_framework.routers import APIRootView
|
||||
|
||||
from .serializers import TopologyDummySerializer
|
||||
from .serializers import PreDeviceRoleSerializer, TopologyDummySerializer, PreTagSerializer
|
||||
from django.conf import settings
|
||||
|
||||
from dcim.models import DeviceRole, Device, Cable
|
||||
@@ -12,10 +12,26 @@ from circuits.models import Circuit
|
||||
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(",")
|
||||
|
||||
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 TopologyViewsRootView(APIRootView):
|
||||
def get_view_name(self):
|
||||
return 'TopologyViews'
|
||||
|
||||
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(ReadOnlyModelViewSet):
|
||||
queryset = Device.objects.all()
|
||||
@@ -58,3 +74,200 @@ class SaveCoordsViewSet(ReadOnlyModelViewSet):
|
||||
results["status"] = "not allowed to save coords"
|
||||
return Response(results, status=500)
|
||||
|
||||
class SearchViewSet(ReadOnlyModelViewSet):
|
||||
queryset = Device.objects.all()
|
||||
serializer_class = TopologyDummySerializer
|
||||
|
||||
def _filter(self, site, role, name, tags, region, location):
|
||||
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)
|
||||
if tags is not None:
|
||||
filter_devices = filter_devices.filter(tags__id__in=tags)
|
||||
if region is not None:
|
||||
filter_devices = filter_devices.filter(site__region__id__in=region)
|
||||
if location is not None:
|
||||
filter_devices = filter_devices.filter(location__id__in=location)
|
||||
return filter_devices
|
||||
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def search(self, request):
|
||||
name = request.query_params.get('name', None)
|
||||
if name == "":
|
||||
name = None
|
||||
|
||||
sites = request.query_params.getlist('sites[]', None)
|
||||
if sites == []:
|
||||
sites = None
|
||||
|
||||
devicerole = request.query_params.getlist('devicerole[]', None)
|
||||
if devicerole == []:
|
||||
devicerole = None
|
||||
|
||||
tags = request.query_params.getlist('tags[]', None)
|
||||
if tags == []:
|
||||
tags = None
|
||||
|
||||
regions = request.query_params.getlist('regions[]', None)
|
||||
if regions == []:
|
||||
regions = None
|
||||
|
||||
locations = request.query_params.getlist('locations[]', None)
|
||||
if locations == []:
|
||||
locations = None
|
||||
|
||||
hide_unconnected = request.query_params.get('hide_unconnected', None)
|
||||
if hide_unconnected == "":
|
||||
hide_unconnected = None
|
||||
|
||||
devices = self._filter(sites, devicerole, name, tags, regions, locations)
|
||||
|
||||
nodes = []
|
||||
edges = []
|
||||
edge_ids = 0
|
||||
cable_ids = []
|
||||
circuit_ids = []
|
||||
for device in devices:
|
||||
cables = device.get_cables()
|
||||
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"
|
||||
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 <br> " + cable_a_dev_name + " [" + cable_a_name + "]<br>" + 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
|
||||
|
||||
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["to"] = cable.termination_b.device.id
|
||||
edge["dashes"] = True
|
||||
title = ""
|
||||
|
||||
title += "Circuit provider: " + cable.termination_a.circuit.provider.name + "<br>"
|
||||
title += "Termination between <br>"
|
||||
title += cable_b_dev_name + " [" + cable_b_name + "]<br>"
|
||||
# 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_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 + "]<br>"
|
||||
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
|
||||
|
||||
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 + "]<br>"
|
||||
edge["title"] = title
|
||||
edges.append(edge)
|
||||
else:
|
||||
device_has_connections = False
|
||||
|
||||
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"
|
||||
|
||||
node_content = ""
|
||||
|
||||
if device.device_type.display_name is not None:
|
||||
node_content += "<tr><th>Type: </th><td>" + device.device_type.display_name + "</td></tr>"
|
||||
if device.device_role.name is not None:
|
||||
node_content += "<tr><th>Role: </th><td>" + device.device_role.name + "</td></tr>"
|
||||
if device.serial != "":
|
||||
node_content += "<tr><th>Serial: </th><td>" + device.serial + "</td></tr>"
|
||||
if device.primary_ip is not None:
|
||||
node_content += "<tr><th>IP Address: </th><td>" + str(device.primary_ip.address) + "</td></tr>"
|
||||
|
||||
dev_title = "<table> %s </table>" % (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
|
||||
results["edges"] = edges
|
||||
|
||||
return Response(results)
|
||||
|
||||
@@ -11,7 +11,7 @@ class DeviceFilterSet(django_filters.FilterSet):
|
||||
)
|
||||
tag = TagFilter()
|
||||
|
||||
device_role_id = django_filters.ModelMultipleChoiceFilter(
|
||||
role_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='device_role_id',
|
||||
queryset=DeviceRole.objects.all(),
|
||||
label='Role (ID)',
|
||||
|
||||
@@ -4,15 +4,14 @@ from django.utils.translation import gettext as _
|
||||
|
||||
from extras.models import Tag
|
||||
from dcim.models import Device, Site, Region, DeviceRole, Location
|
||||
from django.conf import settings
|
||||
from extras.forms import CustomFieldModelFilterForm
|
||||
|
||||
from utilities.forms import (TagFilterField, DynamicModelMultipleChoiceField, FilterForm)
|
||||
allow_coordinates_saving = settings.PLUGINS_CONFIG["netbox_topology_views"]["allow_coordinates_saving"]
|
||||
from utilities.forms import (TagFilterField, DynamicModelMultipleChoiceField, BootstrapMixin)
|
||||
|
||||
class DeviceFilterForm(FilterForm):
|
||||
class DeviceFilterForm(CustomFieldModelFilterForm):
|
||||
model = Device
|
||||
field_groups = [
|
||||
['q', 'hide_unconnected', 'save_coords'],
|
||||
['q'],
|
||||
['region_id', 'site_id', 'location_id'],
|
||||
['device_role_id'],
|
||||
['tag'],
|
||||
@@ -46,15 +45,5 @@ class DeviceFilterForm(FilterForm):
|
||||
label=_('Location')
|
||||
)
|
||||
|
||||
hide_unconnected = forms.BooleanField(
|
||||
label=_("Hide Unconnected"),
|
||||
required=False,
|
||||
initial=False)
|
||||
|
||||
save_coords = forms.BooleanField(
|
||||
label=_("Save Coordinates"),
|
||||
required=False,
|
||||
disabled=(not allow_coordinates_saving),
|
||||
initial=False)
|
||||
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
var graph=null,container=null,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=[],selected_sites=[],coord_save_checkbox=null;function getCookie(e){var t=null;if(document.cookie&&""!==document.cookie)for(var n=document.cookie.split(";"),o=0;o<n.length;o++){var a=n[o].trim();if(a.substring(0,e.length+1)===e+"="){t=decodeURIComponent(a.substring(e.length+1));break}}return t}function htmlTitle(e){return(container=document.createElement("div")).innerHTML=e,container}function addEdge(e){e.title=htmlTitle(e.title),edges.add(e)}function addNode(e){e.title=htmlTitle(e.title),nodes.add(e)}function iniPlotboxIndex(){document.addEventListener("DOMContentLoaded",function(){csrftoken=getCookie("csrftoken"),container=document.getElementById("visgraph"),handleLoadData(),downloadButton=document.getElementById("btnDownloadImage"),btnFullView=document.getElementById("btnFullView"),coord_save_checkbox=document.getElementById("id_save_coords")},!1)}function handleLoadData(){null!==topology_data&&(graph=null,nodes=new vis.DataSet,edges=new vis.DataSet,graph=new vis.Network(container,{nodes:nodes,edges:edges},options),topology_data.edges.forEach(addEdge),topology_data.nodes.forEach(addNode),graph.fit(),canvas=document.getElementById("visgraph").getElementsByTagName("canvas")[0],graph.on("afterDrawing",function(){var e=canvas.toDataURL(MIME_TYPE);downloadButton.href=e,downloadButton.download="topology"}),graph.on("dragEnd",function(e){if(dragged=this.getPositions(e.nodes),coord_save_checkbox.checked&&0!==Object.keys(dragged).length)for(dragged_device in dragged){var t=dragged_device,n=new XMLHttpRequest;n.open("PATCH","/api/plugins/netbox_topology_views/save-coords/save_coords/"),n.setRequestHeader("X-CSRFToken",csrftoken),n.setRequestHeader("Accept","application/json"),n.setRequestHeader("Content-Type","application/json"),n.onreadystatechange=function(){4===n.readyState&&(console.log(n.status),console.log(n.responseText))};var o=JSON.stringify({node_id:t,x:dragged[t].x,y:dragged[t].y});n.send(o)}}))}
|
||||
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,n,t){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,n){nodes.add(n)}),$.each(e.edges,function(e,n){edges.add(n)}),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=[],selected_sites=[];function addEdge(e){edges.add(e)}function addNode(e){nodes.add(e)}function iniPlotboxIndex(){document.addEventListener("DOMContentLoaded",function(){container=document.getElementById("visgraph"),handleLoadData(),downloadButton=document.getElementById("btnDownloadImage"),btnFullView=document.getElementById("btnFullView")},!1)}function handleLoadData(){graph=null,nodes=new vis.DataSet,edges=new vis.DataSet,graph=new vis.Network(container,{nodes:nodes,edges:edges},options),topology_data.edges.forEach(addEdge),topology_data.nodes.forEach(addNode),graph.fit(),canvas=document.getElementById("visgraph").getElementsByTagName("canvas")[0],graph.on("afterDrawing",function(){var e=canvas.toDataURL(MIME_TYPE);downloadButton.href=e,downloadButton.download="topology"})}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,4 +1,3 @@
|
||||
#visgraph {
|
||||
height: 70vh;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
var graph = null;
|
||||
var container = null;
|
||||
|
||||
function iniPlotboxFull() {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
container = document.getElementById('fullvisgraph');
|
||||
startRender();
|
||||
}, false);
|
||||
}
|
||||
|
||||
function startRender() {
|
||||
var url = location.search;
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "../../api/plugins/topology-views/search/search/" + url,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
success: function (data_result, status, xhr) {
|
||||
graph = null;
|
||||
nodes = new vis.DataSet();
|
||||
edges = new vis.DataSet();
|
||||
graph = new vis.Network(container, { nodes: nodes, edges: edges }, options);
|
||||
$.each(data_result["nodes"], function (index, device) {
|
||||
nodes.add(device);
|
||||
});
|
||||
$.each(data_result["edges"], function (index, edge) {
|
||||
edges.add(edge);
|
||||
});
|
||||
graph.fit();
|
||||
canvas = document.getElementById('fullvisgraph').getElementsByTagName('canvas')[0];
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -34,54 +34,26 @@ var options = {
|
||||
};
|
||||
var selected_regions = [];
|
||||
var selected_sites = [];
|
||||
var coord_save_checkbox = null;
|
||||
|
||||
function getCookie(name) {
|
||||
var cookieValue = null;
|
||||
if (document.cookie && document.cookie !== '') {
|
||||
var cookies = document.cookie.split(';');
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
var cookie = cookies[i].trim();
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
|
||||
function htmlTitle(html) {
|
||||
container = document.createElement("div");
|
||||
container.innerHTML = html;
|
||||
return container;
|
||||
}
|
||||
|
||||
function addEdge(item) {
|
||||
item.title = htmlTitle( item.title );
|
||||
edges.add(item);
|
||||
}
|
||||
|
||||
function addNode(item) {
|
||||
item.title = htmlTitle( item.title );
|
||||
nodes.add(item);
|
||||
}
|
||||
|
||||
function iniPlotboxIndex() {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
csrftoken = getCookie('csrftoken');
|
||||
container = document.getElementById('visgraph');
|
||||
handleLoadData();
|
||||
downloadButton = document.getElementById('btnDownloadImage');
|
||||
btnFullView = document.getElementById('btnFullView');
|
||||
coord_save_checkbox = document.getElementById('id_save_coords');
|
||||
}, false);
|
||||
}
|
||||
|
||||
function handleLoadData() {
|
||||
if (topology_data !== null) {
|
||||
|
||||
graph = null;
|
||||
nodes = new vis.DataSet();
|
||||
edges = new vis.DataSet();
|
||||
@@ -99,36 +71,4 @@ function handleLoadData() {
|
||||
downloadButton.download = "topology";
|
||||
});
|
||||
|
||||
graph.on("dragEnd", function (params) {
|
||||
dragged = this.getPositions(params.nodes);
|
||||
|
||||
if (coord_save_checkbox.checked) {
|
||||
if (Object.keys(dragged).length !== 0) {
|
||||
for (dragged_device in dragged) {
|
||||
var node_id = dragged_device;
|
||||
|
||||
var url = "/api/plugins/netbox_topology_views/save-coords/save_coords/";
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("PATCH", url);
|
||||
xhr.setRequestHeader('X-CSRFToken', csrftoken );
|
||||
xhr.setRequestHeader("Accept", "application/json");
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
console.log(xhr.status);
|
||||
console.log(xhr.responseText);
|
||||
}};
|
||||
|
||||
var data = JSON.stringify({
|
||||
'node_id': node_id,
|
||||
'x': dragged[node_id].x,
|
||||
'y': dragged[node_id].y});
|
||||
|
||||
xhr.send(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{% load static %}
|
||||
{% load helpers %}
|
||||
|
||||
{% block content %}
|
||||
{% with config=settings.PLUGINS_CONFIG.netbox_topology_views %}
|
||||
<link rel="stylesheet" href="{% static 'netbox_topology_views/css/vendor.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'netbox_topology_views/css/app.css' %}">
|
||||
|
||||
|
||||
<div class="panel-body">
|
||||
<div id="fullvisgraph" class=""></div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
<script src="{% static 'jquery/jquery-3.5.1.min.js' %}"></script>
|
||||
<script src="{% static 'netbox_topology_views/js/vendor.js' %}"></script>
|
||||
<script src="{% static 'netbox_topology_views/js/app.js' %}"></script>
|
||||
|
||||
<script type="application/javascript"> iniPlotboxFull()</script>
|
||||
{% endblock %}
|
||||
@@ -20,6 +20,10 @@
|
||||
<i class="mdi mdi-download"></i>
|
||||
Download
|
||||
</a>
|
||||
<a id="btnFullView" class="btn btn-sm btn-info disabled" target="_blank">
|
||||
<i class="mdi mdi-share"></i>
|
||||
Share
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock controls %}
|
||||
|
||||
+73
-112
@@ -3,18 +3,14 @@ from django.db.models import Q
|
||||
from django.views.generic import View
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.conf import settings
|
||||
from django.http import QueryDict
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
from .forms import DeviceFilterForm
|
||||
from .filters import DeviceFilterSet
|
||||
|
||||
import json
|
||||
|
||||
from dcim.models import Device, Cable, DeviceRole, DeviceType
|
||||
from extras.models import Tag
|
||||
|
||||
def get_topology_data(queryset, hide_unconnected):
|
||||
def get_topology_data(queryset):
|
||||
nodes = []
|
||||
nodes_ids = []
|
||||
edges = []
|
||||
@@ -24,97 +20,85 @@ def get_topology_data(queryset, hide_unconnected):
|
||||
if not queryset:
|
||||
return None
|
||||
|
||||
ignore_cable_type = settings.PLUGINS_CONFIG["netbox_topology_views"]["ignore_cable_type"]
|
||||
|
||||
device_ids = [d.id for d in queryset]
|
||||
|
||||
for qs_device in queryset:
|
||||
device_has_connections = False
|
||||
|
||||
links_device = Cable.objects.filter(Q(_termination_a_device_id=qs_device.id) | Q(_termination_b_device_id=qs_device.id) )
|
||||
for link_from in links_device:
|
||||
links_from_device = Cable.objects.filter(Q(_termination_a_device_id=qs_device.id) | Q(_termination_b_device_id=qs_device.id) )
|
||||
for link_from in links_from_device:
|
||||
device_has_connections = True
|
||||
if link_from.termination_a_type.name != "circuit termination" and link_from.termination_b_type.name != "circuit termination":
|
||||
if link_from.termination_a_type.name not in ignore_cable_type and link_from.termination_b_type.name not in ignore_cable_type:
|
||||
if link_from.id not in cable_ids:
|
||||
if link_from.termination_a.device.id in device_ids and link_from.termination_b.device.id in device_ids:
|
||||
device_has_connections = True
|
||||
cable_ids.append(link_from.id)
|
||||
edge_ids += 1
|
||||
cable_a_dev_name = link_from.termination_a.device.name
|
||||
if cable_a_dev_name is None:
|
||||
cable_a_dev_name = "device A name unknown"
|
||||
cable_a_name = link_from.termination_a.name
|
||||
if cable_a_name is None:
|
||||
cable_a_name = "cable A name unknown"
|
||||
cable_b_dev_name = link_from.termination_b.device.name
|
||||
if cable_b_dev_name is None:
|
||||
cable_b_dev_name = "device B name unknown"
|
||||
cable_b_name = link_from.termination_b.name
|
||||
if cable_b_name is None:
|
||||
cable_b_name = "cable B name unknown"
|
||||
if link_from.id not in cable_ids:
|
||||
cable_ids.append(link_from.id)
|
||||
edge_ids += 1
|
||||
cable_a_dev_name = link_from.termination_a.device.name
|
||||
if cable_a_dev_name is None:
|
||||
cable_a_dev_name = "device A name unknown"
|
||||
cable_a_name = link_from.termination_a.name
|
||||
if cable_a_name is None:
|
||||
cable_a_name = "cable A name unknown"
|
||||
cable_b_dev_name = link_from.termination_b.device.name
|
||||
if cable_b_dev_name is None:
|
||||
cable_b_dev_name = "device B name unknown"
|
||||
cable_b_name = link_from.termination_b.name
|
||||
if cable_b_name is None:
|
||||
cable_b_name = "cable B name unknown"
|
||||
|
||||
edge = {}
|
||||
edge["id"] = edge_ids
|
||||
edge["from"] = link_from.termination_a.device.id
|
||||
edge["to"] = link_from.termination_b.device.id
|
||||
edge["title"] = "Cable between <br> " + cable_a_dev_name + " [" + cable_a_name + "]<br>" + cable_b_dev_name + " [" + cable_b_name + "]"
|
||||
if link_from.color != "":
|
||||
edge["color"] = "#" + link_from.color
|
||||
edges.append(edge)
|
||||
else:
|
||||
if link_from.termination_a.device.id in device_ids and link_from.termination_b.device.id in device_ids:
|
||||
device_has_connections = True
|
||||
edge = {}
|
||||
edge["id"] = edge_ids
|
||||
edge["from"] = link_from.termination_a.device.id
|
||||
edge["to"] = link_from.termination_b.device.id
|
||||
edge["title"] = "Cable between <br> " + cable_a_dev_name + " [" + cable_a_name + "]<br>" + cable_b_dev_name + " [" + cable_b_name + "]"
|
||||
if link_from.color != "":
|
||||
edge["color"] = "#" + link_from.color
|
||||
edges.append(edge)
|
||||
else:
|
||||
if settings.PLUGINS_CONFIG["netbox_topology_views"]["enable_circuit_terminations"]:
|
||||
if link_from.termination_a.circuit.id not in circuit_ids:
|
||||
circuit_ids.append(link_from.termination_a.circuit.id)
|
||||
edge_ids += 1
|
||||
if link_from.termination_a.circuit.id not in circuit_ids:
|
||||
circuit_ids.append(link_from.termination_a.circuit.id)
|
||||
edge_ids += 1
|
||||
|
||||
cable_b_dev_name = link_from.termination_b.device.name
|
||||
if cable_b_dev_name is None:
|
||||
cable_b_dev_name = "device B name unknown"
|
||||
cable_b_name = link_from.termination_b.name
|
||||
if cable_b_name is None:
|
||||
cable_b_name = "cable B name unknown"
|
||||
cable_b_dev_name = link_from.termination_b.device.name
|
||||
if cable_b_dev_name is None:
|
||||
cable_b_dev_name = "device B name unknown"
|
||||
cable_b_name = link_from.termination_b.name
|
||||
if cable_b_name is None:
|
||||
cable_b_name = "cable B name unknown"
|
||||
|
||||
edge = {}
|
||||
edge["id"] = edge_ids
|
||||
edge["to"] = link_from.termination_b.device.id
|
||||
edge["dashes"] = True
|
||||
title = ""
|
||||
edge = {}
|
||||
edge["id"] = edge_ids
|
||||
edge["to"] = link_from.termination_b.device.id
|
||||
edge["dashes"] = True
|
||||
title = ""
|
||||
|
||||
title += "Circuit provider: " + link_from.termination_a.circuit.provider.name + "<br>"
|
||||
title += "Termination between <br>"
|
||||
title += cable_b_dev_name + " [" + cable_b_name + "]<br>"
|
||||
title += "Circuit provider: " + link_from.termination_a.circuit.provider.name + "<br>"
|
||||
title += "Termination between <br>"
|
||||
title += cable_b_dev_name + " [" + cable_b_name + "]<br>"
|
||||
|
||||
if link_from.termination_a.circuit.termination_a is not None and link_from.termination_a.circuit.termination_a.cable is not None and link_from.termination_a.circuit.termination_a.cable.id != link_from.id and link_from.termination_a.circuit.termination_a.cable.termination_b is not None and link_from.termination_a.circuit.termination_a.cable.termination_b.device is not None:
|
||||
edge["from"] = link_from.termination_a.circuit.termination_a.cable.termination_b.device.id
|
||||
if link_from.termination_a.circuit.termination_a is not None and link_from.termination_a.circuit.termination_a.cable is not None and link_from.termination_a.circuit.termination_a.cable.id != link_from.id and link_from.termination_a.circuit.termination_a.cable.termination_b is not None and link_from.termination_a.circuit.termination_a.cable.termination_b.device is not None:
|
||||
edge["from"] = link_from.termination_a.circuit.termination_a.cable.termination_b.device.id
|
||||
|
||||
cable_a_dev_name = link_from.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 = link_from.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 + "]<br>"
|
||||
edge["title"] = title
|
||||
edges.append(edge)
|
||||
cable_a_dev_name = link_from.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 = link_from.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 + "]<br>"
|
||||
edge["title"] = title
|
||||
edges.append(edge)
|
||||
|
||||
if link_from.termination_a.circuit.termination_z is not None and link_from.termination_a.circuit.termination_z.cable is not None and link_from.termination_a.circuit.termination_z.cable.id != link_from.id and link_from.termination_a.circuit.termination_z.cable.termination_b is not None and link_from.termination_a.circuit.termination_z.cable.termination_b.device is not None:
|
||||
edge["from"] = link_from.termination_a.circuit.termination_z.cable.termination_b.device.id
|
||||
if link_from.termination_a.circuit.termination_z is not None and link_from.termination_a.circuit.termination_z.cable is not None and link_from.termination_a.circuit.termination_z.cable.id != link_from.id and link_from.termination_a.circuit.termination_z.cable.termination_b is not None and link_from.termination_a.circuit.termination_z.cable.termination_b.device is not None:
|
||||
edge["from"] = link_from.termination_a.circuit.termination_z.cable.termination_b.device.id
|
||||
|
||||
cable_a_dev_name = link_from.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 = link_from.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 + "]<br>"
|
||||
edge["title"] = title
|
||||
edges.append(edge)
|
||||
cable_a_dev_name = link_from.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 = link_from.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 + "]<br>"
|
||||
edge["title"] = title
|
||||
edges.append(edge)
|
||||
|
||||
if qs_device.id not in nodes_ids:
|
||||
if hide_unconnected == None or (hide_unconnected is True and device_has_connections is True):
|
||||
if qs_device.id not in nodes_ids:
|
||||
nodes_ids.append(qs_device.id)
|
||||
|
||||
dev_name = qs_device.name
|
||||
@@ -149,12 +133,12 @@ def get_topology_data(queryset, hide_unconnected):
|
||||
node["color.border"] = "#" + qs_device.device_role.color
|
||||
|
||||
if "coordinates" in qs_device.custom_field_data:
|
||||
print(qs_device.custom_field_data)
|
||||
if qs_device.custom_field_data["coordinates"] is not None:
|
||||
if ';' in qs_device.custom_field_data["coordinates"]:
|
||||
cords = qs_device.custom_field_data["coordinates"].split(";")
|
||||
node["x"] = int(cords[0])
|
||||
node["y"] = int(cords[1])
|
||||
node["physics"] = False
|
||||
cords = qs_device.custom_field_data["coordinates"].split(";")
|
||||
node["x"] = int(cords[0])
|
||||
node["y"] = int(cords[1])
|
||||
node["physics"] = False
|
||||
nodes.append(node)
|
||||
|
||||
results = {}
|
||||
@@ -174,31 +158,8 @@ class TopologyHomeView(PermissionRequiredMixin, View):
|
||||
self.queryset = Device.objects.all()
|
||||
self.queryset = self.filterset(request.GET, self.queryset).qs
|
||||
topo_data = None
|
||||
|
||||
if request.GET:
|
||||
hide_unconnected = None
|
||||
if 'hide_unconnected' in request.GET:
|
||||
if request.GET["hide_unconnected"] == "on" :
|
||||
hide_unconnected = True
|
||||
|
||||
if 'draw_init' in request.GET:
|
||||
if request.GET["draw_init"].lower() == 'true':
|
||||
topo_data = get_topology_data(self.queryset, hide_unconnected)
|
||||
else:
|
||||
topo_data = get_topology_data(self.queryset, hide_unconnected)
|
||||
else:
|
||||
preselected_device_roles = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_device_roles"]
|
||||
preselected_tags = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_tags"]
|
||||
|
||||
q_device_role_id = DeviceRole.objects.filter(name__in=preselected_device_roles).values_list('id', flat=True)
|
||||
q_tags = Tag.objects.filter(name__in=preselected_tags).values_list('name', flat=True)
|
||||
|
||||
q = QueryDict(mutable=True)
|
||||
q.setlist('device_role_id', list(q_device_role_id))
|
||||
q.setlist('tag', list(q_tags))
|
||||
q['draw_init'] = settings.PLUGINS_CONFIG["netbox_topology_views"]["draw_default_layout"]
|
||||
query_string = q.urlencode()
|
||||
return HttpResponseRedirect(request.path + "?" + query_string)
|
||||
topo_data = get_topology_data(self.queryset)
|
||||
|
||||
return render(request, 'netbox_topology_views/index.html' , {
|
||||
'filter_form': DeviceFilterForm(request.GET, label_suffix=''),
|
||||
|
||||
Generated
+76
-6113
File diff suppressed because it is too large
Load Diff
+12
-13
@@ -1,27 +1,26 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "netbox_topology_views",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.0-alpha.1",
|
||||
"scripts": {
|
||||
"resources": "gulp build",
|
||||
"resources_dev": "gulp build_dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"vis-data": "^7.1.2",
|
||||
"vis-network": "^9.1.0",
|
||||
"vis-util": "^5.0.2"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@egjs/hammerjs": "^2.0.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-clean-css": "^4.3.0",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-sass": "^4.1.0",
|
||||
"gulp-uglify": "^3.0.2"
|
||||
"gulp-uglify": "^3.0.2",
|
||||
"keycharm": "^0.3.0",
|
||||
"moment": "^2.24.0",
|
||||
"timsort": "^0.3.0",
|
||||
"uuid": "7.0.0",
|
||||
"vis-data": "^6.2.1",
|
||||
"vis-network": "^7.10.2",
|
||||
"vis-util": "^4.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@egjs/hammerjs": "^2.0.0",
|
||||
"uuid": "^8.0.0",
|
||||
"keycharm": "^0.4.0",
|
||||
"timsort": "^0.3.0"
|
||||
}
|
||||
"peerDependencies": {}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name='netbox-topology-views',
|
||||
version='1.0.0',
|
||||
version='1.0.0-alpha.1',
|
||||
description='An NetBox plugin to create Topology maps',
|
||||
url='https://github.com/mattieserver/netbox-topology-views',
|
||||
author='Mattijs Vanhaverbeke',
|
||||
|
||||
Reference in New Issue
Block a user