Skip to content

Commit

Permalink
Update to support netbox v3.1 (#82)
Browse files Browse the repository at this point in the history
* init for v3.1

* added coords + bump version

* fix filter + added preselected roles

* added draw_default_layout

* upgraded packages

* fixed edge/node labels

* fixed enable_circuit_terminations

* added filter for unconnected devices

* added ignore_cable_type

* fixed old settings using a string instead of array

* removed distutils

* fixed coords check

* added coor saving

* added doc img

* updated docs

* bump version

* bump version
  • Loading branch information
mattieserver authored Mar 8, 2022
1 parent 3b42092 commit 3e5b28f
Show file tree
Hide file tree
Showing 22 changed files with 6,602 additions and 820 deletions.
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Support to filter on name, site, tag and device role.

## Preview

![preview image](doc/img/preview.png?raw=true "preview")
![preview image](doc/img/preview_3.1.jpeg?raw=true "preview")

## Install

Expand All @@ -35,6 +35,7 @@ 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 |
Expand All @@ -55,20 +56,21 @@ 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

Expand All @@ -78,7 +80,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/topology-views/` to view your topologies
Go to the plugins tab in the navbar and click topology or go to `$NETBOX_URL/plugins/netbox_topology_views/` to view your topologies

### Update

Expand Down
Binary file added doc/img/preview_3.1.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 8 additions & 7 deletions netbox_topology_views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ class TopologyViewsConfig(PluginConfig):
name = 'netbox_topology_views'
verbose_name = 'Topology views'
description = 'An plugin to render topology maps'
version = '0.5.3'
version = '1.0.0'
author = 'Mattijs Vanhaverbeke'
author_email = 'author@example.com'
base_url = 'topology-views'
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': False
'preselected_tags' : [],
'enable_circuit_terminations': True,
'draw_default_layout': False
}

config = TopologyViewsConfig
12 changes: 0 additions & 12 deletions netbox_topology_views/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,6 @@
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):

Expand Down
9 changes: 2 additions & 7 deletions netbox_topology_views/api/urls.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
from netbox.api import OrderedDefaultRouter
from rest_framework import routers
from . import views

router = OrderedDefaultRouter()
router.APIRootView = views.TopologyViewsRootView
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
215 changes: 1 addition & 214 deletions netbox_topology_views/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,18 @@
from django.contrib.contenttypes.models import ContentType
from rest_framework.routers import APIRootView

from .serializers import PreDeviceRoleSerializer, TopologyDummySerializer, PreTagSerializer
from .serializers import TopologyDummySerializer
from django.conf import settings

from dcim.models import DeviceRole, Device, Cable
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()
Expand Down Expand Up @@ -74,200 +58,3 @@ def save_coords(self, request):
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)

Loading

0 comments on commit 3e5b28f

Please sign in to comment.