1394 lines
60 KiB
Python
1394 lines
60 KiB
Python
import json
|
|
from functools import reduce
|
|
from typing import DefaultDict, Dict, Optional, Union
|
|
from itertools import chain
|
|
|
|
from circuits.models import Circuit, CircuitTermination, ProviderNetwork
|
|
from dcim.models import (
|
|
Cable,
|
|
CableTermination,
|
|
Device,
|
|
device_components,
|
|
DeviceRole,
|
|
FrontPort,
|
|
Interface,
|
|
PowerFeed,
|
|
PowerPanel,
|
|
RearPort,
|
|
Rack,
|
|
)
|
|
from django.conf import settings
|
|
from django.contrib import messages
|
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.db.models import Q, QuerySet, Count
|
|
from django.db.models.functions import Lower
|
|
from django.http import HttpRequest, HttpResponseRedirect, QueryDict
|
|
from django.shortcuts import render, get_object_or_404
|
|
from django.views.generic import View
|
|
from extras.models import Tag, SavedFilter
|
|
from wireless.models import WirelessLink
|
|
from netbox.views.generic import (
|
|
ObjectView,
|
|
ObjectListView,
|
|
ObjectEditView,
|
|
ObjectDeleteView,
|
|
ObjectChangeLogView,
|
|
BulkImportView
|
|
)
|
|
from netbox_topology_views.filters import DeviceFilterSet, CoordinatesFilterSet, CircuitCoordinatesFilterSet, PowerPanelCoordinatesFilterSet, PowerFeedCoordinatesFilterSet
|
|
from netbox_topology_views.forms import (
|
|
DeviceFilterForm,
|
|
RackElevationFilterForm,
|
|
IndividualOptionsForm,
|
|
CoordinateGroupsForm,
|
|
CircuitCoordinatesForm,
|
|
CircuitCoordinatesFilterForm,
|
|
CircuitCoordinatesImportForm,
|
|
PowerPanelCoordinatesForm,
|
|
PowerPanelCoordinatesFilterForm,
|
|
PowerPanelCoordinatesImportForm,
|
|
PowerFeedCoordinatesForm,
|
|
PowerFeedCoordinatesFilterForm,
|
|
PowerFeedCoordinatesImportForm,
|
|
CoordinatesForm,
|
|
CoordinatesFilterForm,
|
|
CoordinateGroupsImportForm,
|
|
CoordinatesImportForm
|
|
)
|
|
import netbox_topology_views.models
|
|
from netbox_topology_views.models import (
|
|
RoleImage,
|
|
IndividualOptions,
|
|
CoordinateGroup,
|
|
Coordinate,
|
|
CircuitCoordinate,
|
|
PowerPanelCoordinate,
|
|
PowerFeedCoordinate,
|
|
)
|
|
from netbox_topology_views.tables import CoordinateGroupListTable, CoordinateListTable, CircuitCoordinateListTable, PowerPanelCoordinateListTable, PowerFeedCoordinateListTable
|
|
from netbox_topology_views.utils import (
|
|
CONF_IMAGE_DIR,
|
|
find_image_url,
|
|
get_model_role,
|
|
get_model_slug,
|
|
image_static_url,
|
|
LinePattern,
|
|
get_query_settings,
|
|
IMAGE_FILETYPES
|
|
)
|
|
|
|
from netbox_topology_views.choices import NodeLabelItems
|
|
|
|
def get_image_for_entity(entity: Union[Device, Circuit, PowerPanel, PowerFeed]):
|
|
is_device = isinstance(entity, Device)
|
|
query = (
|
|
{"object_id": entity.role_id}
|
|
if is_device
|
|
else {"content_type_id": ContentType.objects.get_for_model(entity).pk}
|
|
)
|
|
|
|
try:
|
|
return RoleImage.objects.get(**query).get_image_url()
|
|
except RoleImage.DoesNotExist:
|
|
return find_image_url(
|
|
entity.role.slug if is_device else get_model_slug(entity.__class__)
|
|
)
|
|
|
|
|
|
def create_node(
|
|
device: Union[Device, Circuit, PowerPanel, PowerFeed],
|
|
save_coords: bool,
|
|
node_label_items: list,
|
|
group_id="default"
|
|
):
|
|
node = {}
|
|
node_content = ""
|
|
if isinstance(device, Circuit):
|
|
dev_name = device.cid
|
|
node["id"] = f"c{device.pk}"
|
|
model_name = 'CircuitCoordinate'
|
|
|
|
if device.provider is not None:
|
|
node_content += (
|
|
f"<tr><th>Provider: </th><td>{device.provider.name}</td></tr>"
|
|
)
|
|
if device.type is not None:
|
|
node_content += f"<tr><th>Type: </th><td>{device.type.name}</td></tr>"
|
|
elif isinstance(device, PowerPanel):
|
|
dev_name = device.name
|
|
node["id"] = f"p{device.pk}"
|
|
model_name = 'PowerPanelCoordinate'
|
|
|
|
if device.site is not None:
|
|
node_content += f"<tr><th>Site: </th><td>{device.site.name}</td></tr>"
|
|
if device.location is not None:
|
|
node_content += (
|
|
f"<tr><th>Location: </th><td>{device.location.name}</td></tr>"
|
|
)
|
|
elif isinstance(device, PowerFeed):
|
|
dev_name = device.name
|
|
node["id"] = f"f{device.pk}"
|
|
model_name = 'PowerFeedCoordinate'
|
|
|
|
if device.power_panel is not None:
|
|
node_content += (
|
|
f"<tr><th>Power Panel: </th><td>{device.power_panel.name}</td></tr>"
|
|
)
|
|
if device.type is not None:
|
|
node_content += f"<tr><th>Type: </th><td>{device.type}</td></tr>"
|
|
if device.supply is not None:
|
|
node_content += f"<tr><th>Supply: </th><td>{device.supply}</td></tr>"
|
|
if device.phase is not None:
|
|
node_content += f"<tr><th>Phase: </th><td>{device.phase}</td></tr>"
|
|
if device.amperage is not None:
|
|
node_content += f"<tr><th>Amperage: </th><td>{device.amperage}</td></tr>"
|
|
if device.voltage is not None:
|
|
node_content += f"<tr><th>Voltage: </th><td>{device.voltage}</td></tr>"
|
|
else:
|
|
model_name = 'Coordinate'
|
|
dev_name = device.name
|
|
if dev_name is None:
|
|
dev_name = device.device_type.full_name
|
|
|
|
if device.device_type is not None:
|
|
node_content += (
|
|
f"<tr><th>Type: </th><td>{device.device_type.model}</td></tr>"
|
|
)
|
|
if device.role.name is not None:
|
|
node_content += (
|
|
f"<tr><th>Role: </th><td>{device.role.name}</td></tr>"
|
|
)
|
|
if device.serial != "":
|
|
node_content += f"<tr><th>Serial: </th><td>{device.serial}</td></tr>"
|
|
if device.primary_ip is not None:
|
|
node_content += (
|
|
f"<tr><th>IP Address: </th><td>{device.primary_ip.address}</td></tr>"
|
|
)
|
|
if device.site is not None:
|
|
node_content += f"<tr><th>Site: </th><td>{device.site.name}</td></tr>"
|
|
if device.location is not None:
|
|
node_content += (
|
|
f"<tr><th>Location: </th><td>{device.location.name}</td></tr>"
|
|
)
|
|
if device.rack is not None:
|
|
node_content += f"<tr><th>Rack: </th><td>{device.rack.name}</td></tr>"
|
|
if device.position is not None:
|
|
if device.face is not None:
|
|
node_content += f"<tr><th>Position: </th><td>{device.position} ({device.face})</td></tr>"
|
|
else:
|
|
node_content += (
|
|
f"<tr><th>Position: </th><td>{device.position}</td></tr>"
|
|
)
|
|
|
|
node["id"] = device.pk
|
|
|
|
if device.site is not None:
|
|
node["site"] = device.site.name
|
|
node["site_id"] = device.site_id
|
|
if device.location is not None:
|
|
node["location"] = device.location.name
|
|
node["location_id"] = device.location_id
|
|
if device.rack is not None:
|
|
node["rack"] = device.rack.name
|
|
node["rack_id"] = device.rack_id
|
|
if device.virtual_chassis is not None:
|
|
node["virtual_chassis"] = device.virtual_chassis.name
|
|
node["virtual_chassis_id"] = device.virtual_chassis_id
|
|
|
|
if device.role.color != "":
|
|
node["color.border"] = "#" + device.role.color
|
|
|
|
model_class = getattr(netbox_topology_views.models, model_name)
|
|
|
|
if group_id is None or group_id == "default":
|
|
group_id = model_class.get_or_create_default_group(group_id)
|
|
if not group_id:
|
|
print('Exception occured while handling default group.')
|
|
return node
|
|
|
|
group = get_object_or_404(CoordinateGroup, pk=group_id)
|
|
|
|
node["physics"] = True
|
|
# Coords must be set even if no coords have been stored. Otherwise nodes with coords
|
|
# will not be placed correctly by vis-network.
|
|
node["x"] = 0
|
|
node["y"] = 0
|
|
if model_class.objects.filter(group=group, device=device.pk).values('x') and model_class.objects.filter(group=group, device=device.pk).values('y'):
|
|
# Coordinates data for the device exists in Coordinates Group. Let's assign them
|
|
node["x"] = model_class.objects.get(group=group, device=device.pk).x
|
|
node["y"] = model_class.objects.get(group=group, device=device.pk).y
|
|
node["physics"] = False
|
|
elif "coordinates" in device.custom_field_data:
|
|
# We prefer the new Coordinate model but leave the deprecated method
|
|
# for now as fallback for compatibility reasons
|
|
if device.custom_field_data["coordinates"] is not None:
|
|
if ";" in 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
|
|
|
|
dev_title = "<table><tbody> %s</tbody></table>" % (node_content)
|
|
node["title"] = dev_title
|
|
node["name"] = dev_name
|
|
|
|
# Create a list of possible label items. Omit None types
|
|
label_mapping = {
|
|
NodeLabelItems.DEVICE_NAME: dev_name,
|
|
NodeLabelItems.DEVICE_TYPE: getattr(device, 'device_type', None),
|
|
NodeLabelItems.ROLE: getattr(device, 'role', None),
|
|
NodeLabelItems.DESCRIPTION: getattr(device, 'description', None),
|
|
NodeLabelItems.PRIMARY_IPV4: getattr(device, 'primary_ip4', None),
|
|
NodeLabelItems.PRIMARY_IPV6: getattr(device, 'primary_ip6', None),
|
|
NodeLabelItems.OUT_OF_BAND_IP: getattr(device, 'oob_ip', None),
|
|
NodeLabelItems.PLATFORM: getattr(device, 'platform', None),
|
|
NodeLabelItems.SERIAL: getattr(device, 'serial', None),
|
|
NodeLabelItems.TENANT: getattr(device, 'tenant', None),
|
|
NodeLabelItems.SITE: getattr(device, 'site', None),
|
|
NodeLabelItems.LOCATION: getattr(device, 'location', None),
|
|
NodeLabelItems.RACK: getattr(device, 'rack', None),
|
|
NodeLabelItems.VIRTUAL_CHASSIS: getattr(device, 'virtual_chassis', None),
|
|
NodeLabelItems.ASSET_TAG: getattr(device, 'asset_tag', None),
|
|
}
|
|
|
|
label_items = []
|
|
for item in node_label_items:
|
|
if label_mapping[item] and label_mapping[item] is not None:
|
|
label_items.append(str(label_mapping[item]))
|
|
node_label = '\n'.join(label_items)
|
|
|
|
node["label"] = node_label
|
|
node["shape"] = "image"
|
|
node["href"] = device.get_absolute_url()
|
|
node["image"] = get_image_for_entity(device)
|
|
node["label_num_lines"] = len(label_items)
|
|
|
|
return node
|
|
|
|
|
|
def create_edge(
|
|
edge_id: int,
|
|
termination_a: Dict,
|
|
termination_b: Dict,
|
|
straight_cables: bool,
|
|
draw_termination_labels: bool,
|
|
draw_cable_labels: bool,
|
|
circuit: Optional[Dict] = None,
|
|
cable: Optional[Cable] = None,
|
|
wireless: Optional[Dict] = None,
|
|
power: Optional[bool] = None,
|
|
interface: Optional[Interface] = None,
|
|
):
|
|
cable_a_name = (
|
|
"device A name unknown"
|
|
if termination_a["termination_name"] is None
|
|
else termination_a["termination_name"]
|
|
)
|
|
cable_a_dev_name = (
|
|
"device A name unknown"
|
|
if termination_a["termination_device_name"] is None
|
|
else termination_a["termination_device_name"]
|
|
)
|
|
cable_b_name = (
|
|
"device A name unknown"
|
|
if termination_b["termination_name"] is None
|
|
else termination_b["termination_name"]
|
|
)
|
|
cable_b_dev_name = (
|
|
"cable B name unknown"
|
|
if termination_b["termination_device_name"] is None
|
|
else termination_b["termination_device_name"]
|
|
)
|
|
|
|
edge = {}
|
|
edge["id"] = edge_id
|
|
edge["from"] = termination_a["device_id"]
|
|
edge["to"] = termination_b["device_id"]
|
|
edge["color"] = '#2b7ce9'
|
|
title = "Cable"
|
|
|
|
if circuit is not None:
|
|
edge["dashes"] = True
|
|
title = f"Circuit provider: {circuit['provider_name']}<br>Termination"
|
|
|
|
elif wireless is not None:
|
|
edge["dashes"] = LinePattern().wireless
|
|
title = "Wireless Connection"
|
|
|
|
elif power is not None:
|
|
edge["dashes"] = LinePattern().power
|
|
title = "Power Connection"
|
|
|
|
elif interface is not None:
|
|
title = "Interface Connection"
|
|
edge["width"] = 3
|
|
edge["dashes"] = LinePattern().logical
|
|
edge["color"] = '#f1c232'
|
|
edge["href"] = interface.get_absolute_url() + "trace"
|
|
|
|
if cable is not None and hasattr(cable, "label") and cable.label:
|
|
cable_label = "<br>Label: " + cable.label
|
|
if draw_cable_labels is True:
|
|
edge["label"] = cable.label
|
|
else:
|
|
cable_label = ""
|
|
|
|
edge[
|
|
"title"
|
|
] = f"{title} between<br>{cable_a_dev_name} [{cable_a_name}]<br>{cable_b_dev_name} [{cable_b_name}] {cable_label}"
|
|
|
|
if cable is not None:
|
|
edge["href"] = cable.get_absolute_url()
|
|
if hasattr(cable, 'color') and cable.color != "":
|
|
edge["color"] = "#" + cable.color
|
|
|
|
# if straight_cables == True: edge["smooth"] = False
|
|
edge["smooth"] = not straight_cables
|
|
|
|
if draw_termination_labels is True:
|
|
edge["drawTerminationLabel"] = True
|
|
edge["cable_a_name"] = cable_a_name
|
|
edge["cable_b_name"] = cable_b_name
|
|
|
|
|
|
return edge
|
|
|
|
|
|
def create_circuit_termination(termination):
|
|
if isinstance(termination, CircuitTermination):
|
|
return {
|
|
"termination_name": termination.circuit.provider.name,
|
|
"termination_device_name": termination.circuit.cid,
|
|
"device_id": "c{}".format(termination.circuit.pk),
|
|
}
|
|
if (
|
|
isinstance(termination, Interface)
|
|
or isinstance(termination, FrontPort)
|
|
or isinstance(termination, RearPort)
|
|
):
|
|
return {
|
|
"termination_name": termination.name,
|
|
"termination_device_name": termination.device.name,
|
|
"device_id": termination.device.pk,
|
|
}
|
|
return None
|
|
|
|
|
|
def get_topology_data(
|
|
queryset: QuerySet,
|
|
individualOptions: IndividualOptions,
|
|
show_unconnected: bool,
|
|
ignore_cable_type: list,
|
|
save_coords: bool,
|
|
show_cables: bool,
|
|
show_circuit: bool,
|
|
show_logical_connections: bool,
|
|
show_single_cable_logical_conns: bool,
|
|
show_neighbors: bool,
|
|
show_power: bool,
|
|
show_wireless: bool,
|
|
group_sites: bool,
|
|
group_locations: bool,
|
|
group_racks: bool,
|
|
group_virtualchassis: bool,
|
|
group_id,
|
|
straight_cables: bool,
|
|
draw_termination_labels: bool,
|
|
draw_cable_labels: bool,
|
|
grid_size: list,
|
|
node_label_items: list,
|
|
):
|
|
|
|
supported_termination_types = []
|
|
for t in IndividualOptions.CHOICES:
|
|
supported_termination_types.append(t[1])
|
|
|
|
if not queryset:
|
|
return None
|
|
|
|
nodes_devices = {}
|
|
edges = []
|
|
nodes = []
|
|
options = {}
|
|
edge_ids = 0
|
|
nodes_circuits: Dict[int, Circuit] = {}
|
|
nodes_powerpanel: Dict[int, PowerPanel] = {}
|
|
nodes_powerfeed: Dict[int, PowerFeed] = {}
|
|
nodes_provider_networks = {}
|
|
cable_ids = DefaultDict(dict)
|
|
interface_ids = DefaultDict(dict)
|
|
|
|
device_ids = [d.pk for d in queryset]
|
|
site_ids = [d.site_id for d in queryset]
|
|
|
|
if show_neighbors:
|
|
interfaces = Interface.objects.filter(
|
|
Q(device_id__in=device_ids)
|
|
)
|
|
frontports = FrontPort.objects.filter(
|
|
Q(device_id__in=device_ids)
|
|
)
|
|
rearports = RearPort.objects.filter(
|
|
Q(device_id__in=device_ids)
|
|
)
|
|
|
|
ports = chain(interfaces, frontports, rearports)
|
|
for port in ports:
|
|
for link_peer in port.link_peers:
|
|
if hasattr(link_peer, 'device') and link_peer.device.id not in device_ids:
|
|
device_ids.append(link_peer.device.id)
|
|
|
|
if show_logical_connections:
|
|
path_complete_interfaces = Interface.objects.filter(
|
|
Q(_path__is_complete=True) & Q(device_id__in=device_ids)
|
|
)
|
|
for path_complete_interface in path_complete_interfaces:
|
|
for connected_endpoint in path_complete_interface.connected_endpoints:
|
|
if type(connected_endpoint) != ProviderNetwork:
|
|
device_ids.append(connected_endpoint.device.id)
|
|
|
|
if show_circuit:
|
|
circuit_terminations = CircuitTermination.objects.filter(
|
|
Q(_site_id__in=site_ids) | Q(_provider_network__isnull=False)
|
|
)
|
|
for circuit_termination in circuit_terminations:
|
|
circuit_termination: CircuitTermination
|
|
if (
|
|
show_unconnected
|
|
and circuit_termination.circuit_id not in nodes_circuits
|
|
):
|
|
nodes_circuits[
|
|
circuit_termination.circuit.pk
|
|
] = circuit_termination.circuit
|
|
|
|
termination_a = {}
|
|
termination_b = {}
|
|
circuit_model = {}
|
|
if circuit_termination.cable is not None and bool(circuit_termination.cable.a_terminations) and bool(circuit_termination.cable.b_terminations):
|
|
termination_a = create_circuit_termination(
|
|
circuit_termination.cable.a_terminations[0]
|
|
)
|
|
termination_b = create_circuit_termination(
|
|
circuit_termination.cable.b_terminations[0]
|
|
)
|
|
elif circuit_termination.termination is not None:
|
|
if (
|
|
circuit_termination.termination_id
|
|
not in nodes_provider_networks
|
|
):
|
|
nodes_provider_networks[
|
|
circuit_termination.termination.pk
|
|
] = circuit_termination.termination
|
|
|
|
if bool(termination_a) and bool(termination_b):
|
|
circuit_model = {
|
|
"provider_name": circuit_termination.circuit.provider.name
|
|
}
|
|
edge_ids += 1
|
|
edges.append(
|
|
create_edge(
|
|
edge_id=edge_ids,
|
|
cable=circuit_termination.cable,
|
|
circuit=circuit_model,
|
|
termination_a=termination_a,
|
|
termination_b=termination_b,
|
|
straight_cables=straight_cables,
|
|
draw_termination_labels=draw_termination_labels,
|
|
draw_cable_labels=draw_cable_labels,
|
|
)
|
|
)
|
|
|
|
circuit_has_connections = False
|
|
for termination in [
|
|
circuit_termination.cable.a_terminations[0],
|
|
circuit_termination.cable.b_terminations[0],
|
|
]:
|
|
if not isinstance(termination, CircuitTermination):
|
|
if (
|
|
termination.device_id not in nodes_devices
|
|
and termination.device_id in device_ids
|
|
):
|
|
nodes_devices[termination.device_id] = termination.device
|
|
circuit_has_connections = True
|
|
else:
|
|
if termination.device_id in device_ids:
|
|
circuit_has_connections = True
|
|
|
|
if circuit_has_connections and not show_unconnected:
|
|
if circuit_termination.circuit_id not in nodes_circuits:
|
|
nodes_circuits[
|
|
circuit_termination.circuit.pk
|
|
] = circuit_termination.circuit
|
|
|
|
for d in nodes_circuits.values():
|
|
nodes.append(create_node(d, save_coords, node_label_items, group_id))
|
|
|
|
if show_power:
|
|
power_panels_ids = PowerPanel.objects.filter(
|
|
Q(site_id__in=site_ids)
|
|
).values_list("pk", flat=True)
|
|
power_feeds: QuerySet[PowerFeed] = PowerFeed.objects.filter(
|
|
Q(power_panel_id__in=power_panels_ids)
|
|
)
|
|
|
|
for power_feed in power_feeds:
|
|
if show_unconnected or (
|
|
not show_unconnected and power_feed.cable_id is not None
|
|
):
|
|
if power_feed.power_panel_id not in nodes_powerpanel:
|
|
nodes_powerpanel[power_feed.power_panel.pk] = power_feed.power_panel
|
|
|
|
power_link_name = ""
|
|
if power_feed.pk not in nodes_powerfeed:
|
|
if not show_unconnected:
|
|
if power_feed.link_peers[0].device_id in device_ids:
|
|
nodes_powerfeed[power_feed.pk] = power_feed
|
|
power_link_name = power_feed.link_peers[0].name
|
|
else:
|
|
nodes_powerfeed[power_feed.pk] = power_feed
|
|
|
|
edge_ids += 1
|
|
termination_a = {
|
|
"termination_name": power_feed.power_panel.name,
|
|
"termination_device_name": "",
|
|
"device_id": f"p{power_feed.power_panel_id}",
|
|
}
|
|
termination_b = {
|
|
"termination_name": power_feed.name,
|
|
"termination_device_name": power_link_name,
|
|
"device_id": f"f{power_feed.pk}",
|
|
}
|
|
edges.append(
|
|
create_edge(
|
|
edge_id=edge_ids,
|
|
termination_a=termination_a,
|
|
termination_b=termination_b,
|
|
power=True,
|
|
straight_cables=straight_cables,
|
|
draw_termination_labels=draw_termination_labels,
|
|
draw_cable_labels=draw_cable_labels,
|
|
)
|
|
)
|
|
|
|
if power_feed.cable_id is not None:
|
|
cable_ids[power_feed.cable_id][power_feed.cable_end] = termination_b
|
|
|
|
for d in nodes_powerfeed.values():
|
|
nodes.append(create_node(d, save_coords, node_label_items ,group_id))
|
|
|
|
for d in nodes_powerpanel.values():
|
|
nodes.append(create_node(d, save_coords, node_label_items, group_id))
|
|
|
|
if show_logical_connections:
|
|
interfaces = Interface.objects.filter(
|
|
Q(_path__is_complete=True) & Q(device_id__in=device_ids)
|
|
)
|
|
|
|
for interface in interfaces:
|
|
# print('{} {} {} {}'.format(interface.device.name, interface.name, interface._path.destinations[0].device.name, interface._path.destinations[0].name))
|
|
for destination in interface._path.destinations:
|
|
if isinstance(destination, device_components.Interface):
|
|
if destination.device.id not in device_ids:
|
|
# print('Destination interface not in device queryset, ignoring')
|
|
continue
|
|
|
|
if destination.id in interface_ids:
|
|
# we've already captured the destination interface, ignore this connection
|
|
# print('Destination interface already exists, ignoring')
|
|
continue
|
|
|
|
if not show_single_cable_logical_conns and interface.cable_id==destination.cable_id and show_cables:
|
|
# interface connection is the same as the cable connection, ignore this connection
|
|
continue
|
|
|
|
interface_ids[interface.id]=interface
|
|
edge_ids += 1
|
|
termination_a = { "termination_name": interface.name, "termination_device_name": interface.device.name, "device_id": interface.device.id }
|
|
termination_b = { "termination_name": destination.name, "termination_device_name": destination.device.name, "device_id": destination.device.id }
|
|
edges.append(create_edge(edge_id=edge_ids, termination_a=termination_a, termination_b=termination_b, interface=interface, straight_cables=straight_cables, draw_termination_labels=draw_termination_labels, draw_cable_labels=draw_cable_labels))
|
|
nodes_devices[interface.device.id] = interface.device
|
|
nodes_devices[destination.device.id] = destination.device
|
|
|
|
if show_cables:
|
|
links: QuerySet[CableTermination] = CableTermination.objects.filter(
|
|
Q(_device_id__in=device_ids)
|
|
).select_related("termination_type")
|
|
|
|
for link in links:
|
|
if link.termination_type.model in ignore_cable_type:
|
|
continue
|
|
|
|
# Normal device cables
|
|
if link.termination_type.model in supported_termination_types:
|
|
complete_link = False
|
|
if link.cable_end == "A":
|
|
if link.cable_id not in cable_ids:
|
|
cable_ids[link.cable_id] = {}
|
|
else:
|
|
if "B" in cable_ids[link.cable_id]:
|
|
if cable_ids[link.cable_id]["B"] is not None:
|
|
complete_link = True
|
|
elif link.cable_end == "B":
|
|
if link.cable_id not in cable_ids:
|
|
cable_ids[link.cable_id] = {}
|
|
else:
|
|
if "A" in cable_ids[link.cable_id]:
|
|
if cable_ids[link.cable_id]["A"] is not None:
|
|
complete_link = True
|
|
else:
|
|
print("Unkown cable end")
|
|
cable_ids[link.cable_id][link.cable_end] = link
|
|
|
|
if complete_link:
|
|
edge_ids += 1
|
|
if isinstance(cable_ids[link.cable_id]["B"], CableTermination):
|
|
if cable_ids[link.cable_id]["B"]._device_id not in nodes_devices:
|
|
nodes_devices[
|
|
cable_ids[link.cable_id]["B"]._device_id
|
|
] = cable_ids[link.cable_id]["B"].termination.device
|
|
termination_b = {
|
|
"termination_name": cable_ids[link.cable_id][
|
|
"B"
|
|
].termination.name,
|
|
"termination_device_name": cable_ids[link.cable_id][
|
|
"B"
|
|
].termination.device.name,
|
|
"device_id": cable_ids[link.cable_id][
|
|
"B"
|
|
].termination.device_id,
|
|
}
|
|
else:
|
|
termination_b = cable_ids[link.cable_id]["B"]
|
|
|
|
if isinstance(cable_ids[link.cable_id]["A"], CableTermination):
|
|
if cable_ids[link.cable_id]["A"]._device_id not in nodes_devices:
|
|
nodes_devices[
|
|
cable_ids[link.cable_id]["A"]._device_id
|
|
] = cable_ids[link.cable_id]["A"].termination.device
|
|
termination_a = {
|
|
"termination_name": cable_ids[link.cable_id][
|
|
"A"
|
|
].termination.name,
|
|
"termination_device_name": cable_ids[link.cable_id][
|
|
"A"
|
|
].termination.device.name,
|
|
"device_id": cable_ids[link.cable_id][
|
|
"A"
|
|
].termination.device_id,
|
|
}
|
|
else:
|
|
termination_a = cable_ids[link.cable_id]["A"]
|
|
|
|
edges.append(
|
|
create_edge(
|
|
edge_id=edge_ids,
|
|
cable=link.cable,
|
|
termination_a=termination_a,
|
|
termination_b=termination_b,
|
|
straight_cables=straight_cables,
|
|
draw_termination_labels=draw_termination_labels,
|
|
draw_cable_labels=draw_cable_labels,
|
|
)
|
|
)
|
|
|
|
if show_wireless:
|
|
wlan_links: QuerySet[WirelessLink] = WirelessLink.objects.filter(
|
|
Q(_interface_a_device_id__in=device_ids)
|
|
& Q(_interface_b_device_id__in=device_ids)
|
|
)
|
|
|
|
for wlan_link in wlan_links:
|
|
if wlan_link.interface_a.device_id not in nodes_devices:
|
|
nodes_devices[
|
|
wlan_link.interface_a.device.pk
|
|
] = wlan_link.interface_a.device
|
|
if wlan_link.interface_b.device_id not in nodes_devices:
|
|
nodes_devices[
|
|
wlan_link.interface_b.device.pk
|
|
] = wlan_link.interface_b.device
|
|
|
|
termination_a = {
|
|
"termination_name": wlan_link.interface_a.name,
|
|
"termination_device_name": wlan_link.interface_a.device.name,
|
|
"device_id": wlan_link.interface_a.device_id,
|
|
}
|
|
termination_b = {
|
|
"termination_name": wlan_link.interface_b.name,
|
|
"termination_device_name": wlan_link.interface_b.device.name,
|
|
"device_id": wlan_link.interface_b.device_id,
|
|
}
|
|
wireless = {"ssid": wlan_link.ssid}
|
|
|
|
edge_ids += 1
|
|
edges.append(
|
|
create_edge(
|
|
edge_id=edge_ids,
|
|
cable=wlan_link,
|
|
termination_a=termination_a,
|
|
termination_b=termination_b,
|
|
wireless=wireless,
|
|
straight_cables=straight_cables,
|
|
draw_termination_labels=draw_termination_labels,
|
|
draw_cable_labels=draw_cable_labels,
|
|
)
|
|
)
|
|
|
|
if group_locations:
|
|
options['group_locations'] = 'on'
|
|
if group_racks:
|
|
options['group_racks'] = 'on'
|
|
if group_sites:
|
|
options['group_sites'] = 'on'
|
|
if group_virtualchassis:
|
|
options['group_virtualchassis'] = 'on'
|
|
if grid_size:
|
|
options['grid_size'] = grid_size
|
|
else:
|
|
options['grid_size'] = list('0')
|
|
|
|
for qs_device in queryset:
|
|
if qs_device.pk not in nodes_devices and show_unconnected:
|
|
nodes_devices[qs_device.pk] = qs_device
|
|
|
|
results = {}
|
|
|
|
for d in nodes_devices.values():
|
|
nodes.append(create_node(d, save_coords, node_label_items, group_id))
|
|
|
|
results["nodes"] = nodes
|
|
results["edges"] = edges
|
|
results["group"] = group_id
|
|
results["options"] = options
|
|
return results
|
|
|
|
|
|
class TopologyHomeView(PermissionRequiredMixin, View):
|
|
permission_required = ("dcim.view_site", "dcim.view_device")
|
|
|
|
"""
|
|
Show the home page
|
|
"""
|
|
|
|
def get(self, request):
|
|
self.filterset = DeviceFilterSet
|
|
self.queryset = Device.objects.restrict(request.user, 'view').select_related(
|
|
"device_type", "role"
|
|
)
|
|
self.queryset = self.filterset(request.GET, self.queryset).qs
|
|
self.model = self.queryset.model
|
|
topo_data = None
|
|
|
|
individualOptions, created = IndividualOptions.objects.get_or_create(
|
|
user_id=request.user.id,
|
|
)
|
|
|
|
if request.GET:
|
|
|
|
filter_id, ignore_cable_type, save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, group_sites, group_locations, group_racks, group_virtualchassis, group, show_neighbors, straight_cables, draw_termination_labels, draw_cable_labels, grid_size, node_label_items = get_query_settings(request)
|
|
|
|
filter_required = True
|
|
empty_result = False
|
|
|
|
# Read options from saved filters as NetBox does not handle custom plugin filters
|
|
if "filter_id" in request.GET and request.GET["filter_id"] != '':
|
|
try:
|
|
saved_filter = SavedFilter.objects.get(pk=filter_id)
|
|
saved_filter_params = getattr(saved_filter, 'parameters')
|
|
|
|
if ignore_cable_type == () and 'ignore_cable_type' in saved_filter_params: ignore_cable_type = saved_filter_params['ignore_cable_type']
|
|
if save_coords == False and 'save_coords' in saved_filter_params: save_coords = saved_filter_params['save_coords']
|
|
if show_unconnected == False and 'show_unconnected' in saved_filter_params: show_unconnected = saved_filter_params['show_unconnected']
|
|
if show_power == False and 'show_power' in saved_filter_params: show_power = saved_filter_params['show_power']
|
|
if show_circuit == False and 'show_circuit' in saved_filter_params: show_circuit = saved_filter_params['show_circuit']
|
|
if show_logical_connections == False and 'show_logical_connections' in saved_filter_params: show_logical_connections = saved_filter_params['show_logical_connections']
|
|
if show_single_cable_logical_conns == False and 'show_single_cable_logical_conns' in saved_filter_params: show_single_cable_logical_conns = saved_filter_params['show_single_cable_logical_conns']
|
|
if show_cables == False and 'show_cables' in saved_filter_params: show_cables = saved_filter_params['show_cables']
|
|
if show_wireless == False and 'show_wireless' in saved_filter_params: show_wireless = saved_filter_params['show_wireless']
|
|
if group_sites == False and 'group_sites' in saved_filter_params: group_sites = saved_filter_params['group_sites']
|
|
if group_locations == False and 'group_locations' in saved_filter_params: group_locations = saved_filter_params['group_locations']
|
|
if group_racks == False and 'group_racks' in saved_filter_params: group_racks = saved_filter_params['group_racks']
|
|
if group_virtualchassis == False and 'group_virtualchassis' in saved_filter_params: group_virtualchassis = saved_filter_params['group_virtualchassis']
|
|
if show_neighbors == False and 'show_neighbors' in saved_filter_params: show_neighbors = saved_filter_params['show_neighbors']
|
|
if straight_cables == False and 'straight_cables' in saved_filter_params: straight_cables = saved_filter_params['straight_cables']
|
|
if draw_termination_labels == False and 'draw_termination_labels' in saved_filter_params: draw_termination_labels = saved_filter_params['draw_termination_labels']
|
|
if draw_cable_labels == False and 'draw_cable_labels' in saved_filter_params: draw_cable_labels = saved_filter_params['draw_cable_labels']
|
|
if grid_size == 0 and 'grid_size' in saved_filter_params: grid_size = saved_filter_params['grid_size']
|
|
if node_label_items == () and 'node_label_items' in saved_filter_params: node_label_items = saved_filter_params['node_label_items']
|
|
except SavedFilter.DoesNotExist: # filter_id not found
|
|
pass
|
|
except Exception as inst:
|
|
print(type(inst))
|
|
|
|
if "group" not in request.GET:
|
|
if 'saved_filter_params' in locals() and "group" in saved_filter_params:
|
|
group_id = saved_filter_params['group'][0]
|
|
else:
|
|
group_id = "default"
|
|
else:
|
|
group_id = request.GET["group"]
|
|
|
|
if not "draw_init" in request.GET or "draw_init" in request.GET and request.GET["draw_init"].lower() == "true":
|
|
filter_required = False
|
|
|
|
topo_data = get_topology_data(
|
|
queryset=self.queryset,
|
|
individualOptions=individualOptions,
|
|
ignore_cable_type=ignore_cable_type,
|
|
save_coords=save_coords,
|
|
show_unconnected=show_unconnected,
|
|
show_cables=show_cables,
|
|
show_logical_connections=show_logical_connections,
|
|
show_single_cable_logical_conns=show_single_cable_logical_conns,
|
|
show_neighbors=show_neighbors,
|
|
show_circuit=show_circuit,
|
|
show_power=show_power,
|
|
show_wireless=show_wireless,
|
|
group_sites=group_sites,
|
|
group_locations=group_locations,
|
|
group_racks=group_racks,
|
|
group_virtualchassis=group_virtualchassis,
|
|
group_id=group_id,
|
|
straight_cables=straight_cables,
|
|
draw_termination_labels=draw_termination_labels,
|
|
draw_cable_labels=draw_cable_labels,
|
|
grid_size=grid_size,
|
|
node_label_items=node_label_items,
|
|
)
|
|
|
|
if topo_data is None or not topo_data["nodes"]:
|
|
empty_result = True
|
|
|
|
else:
|
|
# No GET-Request in URL. We most likely came here from the navigation menu.
|
|
preselected_device_roles = IndividualOptions.objects.get(id=individualOptions.id).preselected_device_roles.all().values_list('id', flat=True)
|
|
preselected_tags = IndividualOptions.objects.get(id=individualOptions.id).preselected_tags.all().values_list('slug', flat=True)
|
|
ignore_cable_type = IndividualOptions.objects.get(id=individualOptions.id).ignore_cable_type.translate({ord(i): None for i in '[]\''}).split(', ')
|
|
if ignore_cable_type == ['']: ignore_cable_type = []
|
|
|
|
q = QueryDict(mutable=True)
|
|
q.setlist("role_id", list(preselected_device_roles))
|
|
q.setlist("tag", list(preselected_tags))
|
|
q.setlist("ignore_cable_type", ignore_cable_type)
|
|
|
|
if individualOptions.save_coords: q['save_coords'] = "True"
|
|
if individualOptions.show_unconnected: q['show_unconnected'] = "True"
|
|
if individualOptions.show_cables: q['show_cables'] = "True"
|
|
if individualOptions.show_logical_connections: q['show_logical_connections'] = "True"
|
|
if individualOptions.show_single_cable_logical_conns: q['show_single_cable_logical_conns'] = "True"
|
|
if individualOptions.show_neighbors: q['show_neighbors'] = "True"
|
|
if individualOptions.show_circuit: q['show_circuit'] = "True"
|
|
if individualOptions.show_power: q['show_power'] = "True"
|
|
if individualOptions.show_wireless: q['show_wireless'] = "True"
|
|
if individualOptions.group_sites: q['group_sites'] = "True"
|
|
if individualOptions.group_locations: q['group_locations'] = "True"
|
|
if individualOptions.group_racks: q['group_racks'] = "True"
|
|
if individualOptions.group_virtualchassis: q['group_virtualchassis'] = "True"
|
|
if individualOptions.straight_cables: q['straight_cables'] = "True"
|
|
if individualOptions.draw_termination_labels: q['draw_termination_labels'] = "True"
|
|
if individualOptions.draw_cable_labels: q['draw_cable_labels'] = "True"
|
|
if individualOptions.grid_size: q['grid_size'] = individualOptions.grid_size
|
|
node_label_items = IndividualOptions.objects.get(id=individualOptions.id).node_label_items.translate({ord(i): None for i in '[]\''}).split(', ')
|
|
if node_label_items == ['']: node_label_items = []
|
|
q.setlist("node_label_items", node_label_items)
|
|
|
|
if individualOptions.draw_default_layout:
|
|
q['draw_init'] = "True"
|
|
else:
|
|
q['draw_init'] = "False"
|
|
|
|
query_string = q.urlencode()
|
|
return HttpResponseRedirect(f"{request.path}?{query_string}")
|
|
|
|
return render(
|
|
request,
|
|
"netbox_topology_views/index.html",
|
|
{
|
|
"filter_form": DeviceFilterForm(request.GET, label_suffix=""),
|
|
"topology_data": json.dumps(topo_data),
|
|
"broken_image": find_image_url("role-unknown"),
|
|
"model": self.model,
|
|
"basepath": settings.BASE_PATH,
|
|
"filter_required": filter_required,
|
|
"empty_result": empty_result,
|
|
},
|
|
)
|
|
|
|
|
|
def _rack_endpoint(termination):
|
|
"""Return a template-friendly representation of any cable termination."""
|
|
device = getattr(termination, "device", None)
|
|
return {
|
|
"node_id": f"device-{device.pk}" if device else f"external-{termination._meta.model_name}-{termination.pk}",
|
|
"device_id": getattr(device, "pk", None),
|
|
"device_name": getattr(device, "name", None) or "External",
|
|
"name": getattr(termination, "name", str(termination)),
|
|
"rack_id": getattr(device, "rack_id", None),
|
|
"rack_name": getattr(getattr(device, "rack", None), "name", None) or "Extern",
|
|
"url": device.get_absolute_url() if device else None,
|
|
"color": getattr(getattr(device, "role", None), "color", None) or "1685fc",
|
|
"position": float(device.position or 1) if device else None,
|
|
"height": max(getattr(getattr(device, "device_type", None), "u_height", 1) or 1, 1) if device else 1,
|
|
"face": str(device.face or "front").lower() if device else "front",
|
|
"rack_u_height": getattr(getattr(device, "rack", None), "u_height", None),
|
|
"subtitle": str(device.device_type) if device else getattr(termination, "name", str(termination)),
|
|
}
|
|
|
|
|
|
class RackElevationView(PermissionRequiredMixin, View):
|
|
"""Render one or more complete rack elevations and their physical cabling."""
|
|
|
|
permission_required = ("dcim.view_rack", "dcim.view_device", "dcim.view_cable")
|
|
|
|
def get(self, request):
|
|
racks = Rack.objects.restrict(request.user, "view").select_related("site", "location")
|
|
if not request.GET:
|
|
racks = racks.none()
|
|
selected_racks = request.GET.getlist("rack_id")
|
|
if selected_racks:
|
|
racks = racks.filter(pk__in=selected_racks)
|
|
else:
|
|
if request.GET.getlist("site_id"):
|
|
racks = racks.filter(site_id__in=request.GET.getlist("site_id"))
|
|
if request.GET.getlist("location_id"):
|
|
racks = racks.filter(location_id__in=request.GET.getlist("location_id"))
|
|
|
|
devices = Device.objects.restrict(request.user, "view").filter(
|
|
rack_id__in=racks.values("pk")
|
|
).select_related("device_type", "role", "rack")
|
|
if request.GET.get("include_empty") not in ("on", "True", "true", "1"):
|
|
racks = racks.filter(devices__in=devices).distinct()
|
|
|
|
rack_list = list(racks.order_by("site__name", "location__name", "name"))
|
|
rack_ids = {rack.pk for rack in rack_list}
|
|
rack_devices = {rack_id: [] for rack_id in rack_ids}
|
|
device_ids = []
|
|
for device in devices.filter(rack_id__in=rack_ids).order_by("rack_id", "position", "name"):
|
|
device_ids.append(device.pk)
|
|
height = max(getattr(device.device_type, "u_height", 1) or 1, 1)
|
|
rack_height = max(device.rack.u_height or 1, 1)
|
|
device.elevation_bottom = f"{(float(device.position or 1) - 1) / rack_height * 100:.4f}"
|
|
device.elevation_height = f"{min(height / rack_height * 100, 100):.4f}"
|
|
device.elevation_face = str(device.face or "front").lower()
|
|
rack_devices[device.rack_id].append(device)
|
|
|
|
for rack in rack_list:
|
|
rack.elevation_devices = rack_devices[rack.pk]
|
|
rack.elevation_unit_labels = range(rack.u_height or 0, 0, -1)
|
|
|
|
rack_export = []
|
|
for rack in rack_list:
|
|
rack_export.append({
|
|
"id": rack.pk, "name": rack.name, "u_height": rack.u_height,
|
|
"site": str(rack.site), "location": str(rack.location or ""),
|
|
"devices": [{
|
|
"name": device.name or str(device.device_type),
|
|
"type": str(device.device_type),
|
|
"position": float(device.position or 1),
|
|
"height": max(device.device_type.u_height or 1, 1),
|
|
"face": device.elevation_face,
|
|
"color": f"#{device.role.color}" if device.role.color else "#1685fc",
|
|
} for device in rack.elevation_devices],
|
|
})
|
|
|
|
cable_rows = []
|
|
topology_nodes = {}
|
|
topology_edges = []
|
|
if device_ids:
|
|
cable_ids = CableTermination.objects.filter(
|
|
_device_id__in=device_ids
|
|
).values_list("cable_id", flat=True).distinct()
|
|
cables = Cable.objects.restrict(request.user, "view").filter(pk__in=cable_ids)
|
|
for cable in cables.order_by("label", "pk"):
|
|
a_terms, b_terms = list(cable.a_terminations), list(cable.b_terminations)
|
|
for index in range(max(len(a_terms), len(b_terms), 1)):
|
|
a = _rack_endpoint(a_terms[index]) if index < len(a_terms) else None
|
|
b = _rack_endpoint(b_terms[index]) if index < len(b_terms) else None
|
|
if (a and a["rack_id"] in rack_ids) or (b and b["rack_id"] in rack_ids):
|
|
cable_rows.append({"cable": cable, "a": a, "b": b})
|
|
for endpoint in (a, b):
|
|
if endpoint:
|
|
topology_nodes[endpoint["node_id"]] = endpoint
|
|
if a and b:
|
|
topology_edges.append({
|
|
"from": a["node_id"], "to": b["node_id"],
|
|
"from_device": a["device_name"], "to_device": b["device_name"],
|
|
"from_port": a["name"], "to_port": b["name"],
|
|
"label": cable.label or str(cable),
|
|
"color": f"#{cable.color}" if cable.color else "#1685fc",
|
|
"url": cable.get_absolute_url(),
|
|
"kind": "cable",
|
|
})
|
|
|
|
wireless_links = WirelessLink.objects.restrict(request.user, "view").filter(
|
|
Q(_interface_a_device_id__in=device_ids) |
|
|
Q(_interface_b_device_id__in=device_ids)
|
|
).select_related(
|
|
"interface_a__device__rack", "interface_a__device__role", "interface_a__device__device_type",
|
|
"interface_b__device__rack", "interface_b__device__role", "interface_b__device__device_type",
|
|
)
|
|
for wireless_link in wireless_links:
|
|
a = _rack_endpoint(wireless_link.interface_a)
|
|
b = _rack_endpoint(wireless_link.interface_b)
|
|
topology_nodes[a["node_id"]] = a
|
|
topology_nodes[b["node_id"]] = b
|
|
topology_edges.append({
|
|
"from": a["node_id"], "to": b["node_id"],
|
|
"from_device": a["device_name"], "to_device": b["device_name"],
|
|
"from_port": a["name"], "to_port": b["name"],
|
|
"label": wireless_link.ssid or str(wireless_link),
|
|
"color": "#8b5cf6", "url": wireless_link.get_absolute_url(),
|
|
"kind": "wireless",
|
|
})
|
|
|
|
return render(request, "netbox_topology_views/rack_elevation.html", {
|
|
"filter_form": RackElevationFilterForm(request.GET or None),
|
|
"racks": rack_list, "cables": cable_rows,
|
|
"rack_topology": {"nodes": list(topology_nodes.values()), "edges": topology_edges},
|
|
"rack_export": rack_export,
|
|
"faces": (("front", "Vorderseite"), ("rear", "Rückseite")),
|
|
"has_selection": bool(request.GET),
|
|
})
|
|
|
|
|
|
CONFIG = settings.PLUGINS_CONFIG["netbox_topology_views"]
|
|
ADDITIONAL_ROLES = (PowerPanel, PowerFeed, Circuit)
|
|
|
|
|
|
class TopologyImagesView(PermissionRequiredMixin, View):
|
|
permission_required = (
|
|
"dcim.view_site",
|
|
"dcim.view_devicerole",
|
|
"dcim.add_devicerole",
|
|
"dcim.change_devicerole",
|
|
)
|
|
|
|
def get(self, request: HttpRequest):
|
|
images = [
|
|
{"url": image_static_url(image), "title": image.stem}
|
|
for image in CONF_IMAGE_DIR.iterdir() if image.name.lower().endswith(IMAGE_FILETYPES)
|
|
]
|
|
|
|
roles = reduce(
|
|
lambda acc, cur: {
|
|
**acc,
|
|
cur.name: {
|
|
"id": cur.pk,
|
|
"name": cur.name,
|
|
"slug": cur.slug,
|
|
"image": find_image_url(cur.slug),
|
|
},
|
|
},
|
|
DeviceRole.objects.all(),
|
|
dict(),
|
|
)
|
|
|
|
for additional_role in ADDITIONAL_ROLES:
|
|
cur = get_model_role(additional_role)
|
|
ct = ContentType.objects.get_for_model(additional_role).pk
|
|
|
|
roles[cur.name] = {
|
|
"id": f"ct{ct}",
|
|
"name": cur.name,
|
|
"slug": cur.slug,
|
|
"image": find_image_url(cur.slug),
|
|
}
|
|
|
|
role_images = RoleImage.objects.all()
|
|
|
|
for role_image in role_images:
|
|
roles[role_image.role.name]["image"] = role_image.get_image_url()
|
|
|
|
return render(
|
|
request,
|
|
"netbox_topology_views/images.html",
|
|
{
|
|
"roles": sorted(list(roles.values()), key=lambda r: r["name"]),
|
|
"images": images,
|
|
"basepath": settings.BASE_PATH,
|
|
},
|
|
)
|
|
|
|
class CircuitCoordinateView(PermissionRequiredMixin, ObjectView):
|
|
permission_required = 'netbox_topology_views.view_coordinate'
|
|
|
|
queryset = CircuitCoordinate.objects.all()
|
|
|
|
class CircuitCoordinateAddView(PermissionRequiredMixin, ObjectEditView):
|
|
permission_required = 'netbox_topology_views.add_coordinate'
|
|
|
|
queryset = CircuitCoordinate.objects.all()
|
|
form = CircuitCoordinatesForm
|
|
template_name = 'netbox_topology_views/circuitcoordinate_add.html'
|
|
|
|
class CircuitCoordinateBulkImportView(BulkImportView):
|
|
queryset = CircuitCoordinate.objects.all()
|
|
model_form = CircuitCoordinatesImportForm
|
|
|
|
class CircuitCoordinateListView(PermissionRequiredMixin, ObjectListView):
|
|
permission_required = 'netbox_topology_views.view_coordinate'
|
|
|
|
queryset = CircuitCoordinate.objects.all()
|
|
table = CircuitCoordinateListTable
|
|
template_name = 'netbox_topology_views/circuitcoordinate_list.html'
|
|
filterset = CircuitCoordinatesFilterSet
|
|
filterset_form = CircuitCoordinatesFilterForm
|
|
|
|
class CircuitCoordinateEditView(PermissionRequiredMixin, ObjectEditView):
|
|
permission_required = 'netbox_topology_views.change_coordinate'
|
|
|
|
queryset = CircuitCoordinate.objects.all()
|
|
form = CircuitCoordinatesForm
|
|
template_name = 'netbox_topology_views/circuitcoordinate_edit.html'
|
|
|
|
class CircuitCoordinateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
|
permission_required = 'netbox_topology_views.delete_coordinate'
|
|
|
|
queryset = CircuitCoordinate.objects.all()
|
|
|
|
class CircuitCoordinateChangeLogView(PermissionRequiredMixin, ObjectChangeLogView):
|
|
permission_required = 'netbox_topology_views.view_coordinate'
|
|
|
|
queryset = CircuitCoordinate.objects.all()
|
|
|
|
class PowerPanelCoordinateView(PermissionRequiredMixin, ObjectView):
|
|
permission_required = 'netbox_topology_views.view_coordinate'
|
|
|
|
queryset = PowerPanelCoordinate.objects.all()
|
|
|
|
class PowerPanelCoordinateAddView(PermissionRequiredMixin, ObjectEditView):
|
|
permission_required = 'netbox_topology_views.add_coordinate'
|
|
|
|
queryset = PowerPanelCoordinate.objects.all()
|
|
form = PowerPanelCoordinatesForm
|
|
template_name = 'netbox_topology_views/powerpanelcoordinate_add.html'
|
|
|
|
class PowerPanelCoordinateBulkImportView(BulkImportView):
|
|
queryset = PowerPanelCoordinate.objects.all()
|
|
model_form = PowerPanelCoordinatesImportForm
|
|
|
|
class PowerPanelCoordinateListView(PermissionRequiredMixin, ObjectListView):
|
|
permission_required = 'netbox_topology_views.view_coordinate'
|
|
|
|
queryset = PowerPanelCoordinate.objects.all()
|
|
table = PowerPanelCoordinateListTable
|
|
template_name = 'netbox_topology_views/powerpanelcoordinate_list.html'
|
|
filterset = PowerPanelCoordinatesFilterSet
|
|
filterset_form = PowerPanelCoordinatesFilterForm
|
|
|
|
class PowerPanelCoordinateEditView(PermissionRequiredMixin, ObjectEditView):
|
|
permission_required = 'netbox_topology_views.change_coordinate'
|
|
|
|
queryset = PowerPanelCoordinate.objects.all()
|
|
form = PowerPanelCoordinatesForm
|
|
template_name = 'netbox_topology_views/powerpanelcoordinate_edit.html'
|
|
|
|
class PowerPanelCoordinateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
|
permission_required = 'netbox_topology_views.delete_coordinate'
|
|
|
|
queryset = PowerPanelCoordinate.objects.all()
|
|
|
|
class PowerPanelCoordinateChangeLogView(PermissionRequiredMixin, ObjectChangeLogView):
|
|
permission_required = 'netbox_topology_views.view_coordinate'
|
|
|
|
queryset = PowerPanelCoordinate.objects.all()
|
|
|
|
class PowerFeedCoordinateView(PermissionRequiredMixin, ObjectView):
|
|
permission_required = 'netbox_topology_views.view_coordinate'
|
|
|
|
queryset = PowerFeedCoordinate.objects.all()
|
|
|
|
class PowerFeedCoordinateAddView(PermissionRequiredMixin, ObjectEditView):
|
|
permission_required = 'netbox_topology_views.add_coordinate'
|
|
|
|
queryset = PowerFeedCoordinate.objects.all()
|
|
form = PowerFeedCoordinatesForm
|
|
template_name = 'netbox_topology_views/powerfeedcoordinate_add.html'
|
|
|
|
class PowerFeedCoordinateBulkImportView(BulkImportView):
|
|
queryset = PowerFeedCoordinate.objects.all()
|
|
model_form = PowerFeedCoordinatesImportForm
|
|
|
|
class PowerFeedCoordinateListView(PermissionRequiredMixin, ObjectListView):
|
|
permission_required = 'netbox_topology_views.view_coordinate'
|
|
|
|
queryset = PowerFeedCoordinate.objects.all()
|
|
table = PowerFeedCoordinateListTable
|
|
template_name = 'netbox_topology_views/powerfeedcoordinate_list.html'
|
|
filterset = PowerFeedCoordinatesFilterSet
|
|
filterset_form = PowerFeedCoordinatesFilterForm
|
|
|
|
class PowerFeedCoordinateEditView(PermissionRequiredMixin, ObjectEditView):
|
|
permission_required = 'netbox_topology_views.change_coordinate'
|
|
|
|
queryset = PowerFeedCoordinate.objects.all()
|
|
form = PowerFeedCoordinatesForm
|
|
template_name = 'netbox_topology_views/powerfeedcoordinate_edit.html'
|
|
|
|
class PowerFeedCoordinateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
|
permission_required = 'netbox_topology_views.delete_coordinate'
|
|
|
|
queryset = PowerFeedCoordinate.objects.all()
|
|
|
|
class PowerFeedCoordinateChangeLogView(PermissionRequiredMixin, ObjectChangeLogView):
|
|
permission_required = 'netbox_topology_views.view_coordinate'
|
|
|
|
queryset = PowerFeedCoordinate.objects.all()
|
|
|
|
class CoordinateView(PermissionRequiredMixin, ObjectView):
|
|
permission_required = 'netbox_topology_views.view_coordinate'
|
|
|
|
queryset = Coordinate.objects.all()
|
|
|
|
class CoordinateAddView(PermissionRequiredMixin, ObjectEditView):
|
|
permission_required = 'netbox_topology_views.add_coordinate'
|
|
|
|
queryset = Coordinate.objects.all()
|
|
form = CoordinatesForm
|
|
template_name = 'netbox_topology_views/coordinate_add.html'
|
|
|
|
class CoordinateBulkImportView(BulkImportView):
|
|
queryset = Coordinate.objects.all()
|
|
model_form = CoordinatesImportForm
|
|
|
|
class CoordinateListView(PermissionRequiredMixin, ObjectListView):
|
|
permission_required = 'netbox_topology_views.view_coordinate'
|
|
|
|
queryset = Coordinate.objects.all()
|
|
table = CoordinateListTable
|
|
template_name = 'netbox_topology_views/coordinate_list.html'
|
|
filterset = CoordinatesFilterSet
|
|
filterset_form = CoordinatesFilterForm
|
|
|
|
class CoordinateEditView(PermissionRequiredMixin, ObjectEditView):
|
|
permission_required = 'netbox_topology_views.change_coordinate'
|
|
|
|
queryset = Coordinate.objects.all()
|
|
form = CoordinatesForm
|
|
template_name = 'netbox_topology_views/coordinate_edit.html'
|
|
|
|
class CoordinateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
|
permission_required = 'netbox_topology_views.delete_coordinate'
|
|
|
|
queryset = Coordinate.objects.all()
|
|
|
|
class CoordinateChangeLogView(PermissionRequiredMixin, ObjectChangeLogView):
|
|
permission_required = 'netbox_topology_views.view_coordinate'
|
|
|
|
queryset = Coordinate.objects.all()
|
|
|
|
class CoordinateGroupView(PermissionRequiredMixin, ObjectView):
|
|
permission_required = 'netbox_topology_views.view_coordinategroup'
|
|
|
|
queryset = CoordinateGroup.objects.all()
|
|
|
|
def get_extra_context(self, request, instance):
|
|
circuittable = CircuitCoordinateListTable(instance.circuitcoordinate_set.all())
|
|
circuittable.configure(request)
|
|
powerpaneltable = PowerPanelCoordinateListTable(instance.powerpanelcoordinate_set.all())
|
|
powerpaneltable.configure(request)
|
|
powerfeedtable = PowerFeedCoordinateListTable(instance.powerfeedcoordinate_set.all())
|
|
powerfeedtable.configure(request)
|
|
table = CoordinateListTable(instance.coordinate_set.all())
|
|
table.configure(request)
|
|
|
|
return {
|
|
'circuitcoordinates_table': circuittable,
|
|
'powerpanelcoordinates_table': powerpaneltable,
|
|
'powerfeedcoordinates_table': powerfeedtable,
|
|
'coordinates_table': table,
|
|
}
|
|
|
|
class CoordinateGroupAddView(PermissionRequiredMixin, ObjectEditView):
|
|
permission_required = 'netbox_topology_views.add_coordinategroup'
|
|
|
|
queryset = CoordinateGroup.objects.all()
|
|
form = CoordinateGroupsForm
|
|
template_name = 'netbox_topology_views/coordinategroup_add.html'
|
|
|
|
class CoordinateGroupBulkImportView(BulkImportView):
|
|
queryset = CoordinateGroup.objects.all()
|
|
model_form = CoordinateGroupsImportForm
|
|
|
|
class CoordinateGroupListView(PermissionRequiredMixin, ObjectListView):
|
|
permission_required = 'netbox_topology_views.view_coordinategroup'
|
|
|
|
queryset = CoordinateGroup.objects.annotate(
|
|
devices = Count('coordinate')
|
|
)
|
|
table = CoordinateGroupListTable
|
|
template_name = 'netbox_topology_views/coordinategroup_list.html'
|
|
|
|
class CoordinateGroupEditView(PermissionRequiredMixin, ObjectEditView):
|
|
permission_required = 'netbox_topology_views.change_coordinategroup'
|
|
|
|
queryset = CoordinateGroup.objects.all()
|
|
form = CoordinateGroupsForm
|
|
template_name = 'netbox_topology_views/coordinategroup_edit.html'
|
|
|
|
class CoordinateGroupDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
|
permission_required = 'netbox_topology_views.delete_coordinategroup'
|
|
|
|
queryset = CoordinateGroup.objects.all()
|
|
|
|
class CoordinateGroupChangeLogView(PermissionRequiredMixin, ObjectChangeLogView):
|
|
permission_required = 'netbox_topology_views.view_coordinategroup'
|
|
|
|
queryset = CoordinateGroup.objects.all()
|
|
|
|
class TopologyIndividualOptionsView(PermissionRequiredMixin, View):
|
|
permission_required = 'netbox_topology_views.change_individualoptions'
|
|
|
|
def post(self, request):
|
|
instance = IndividualOptions.objects.get(user_id=request.user.id)
|
|
form = IndividualOptionsForm(request.POST, instance=instance)
|
|
if form.is_valid():
|
|
form.save()
|
|
messages.success(request, "Options have been sucessfully saved")
|
|
else:
|
|
messages.error(request, form.errors)
|
|
|
|
return HttpResponseRedirect("./")
|
|
|
|
def get(self, request):
|
|
queryset, created = IndividualOptions.objects.get_or_create(
|
|
user_id=request.user.id,
|
|
)
|
|
|
|
form = IndividualOptionsForm(
|
|
initial={
|
|
'user_id': request.user.id,
|
|
'ignore_cable_type': tuple(queryset.ignore_cable_type.translate({ord(i): None for i in '[]\''}).split(', ')),
|
|
'preselected_device_roles': IndividualOptions.objects.get(id=queryset.id).preselected_device_roles.all(),
|
|
'preselected_tags': IndividualOptions.objects.get(id=queryset.id).preselected_tags.all(),
|
|
'save_coords': queryset.save_coords,
|
|
'show_unconnected': queryset.show_unconnected,
|
|
'show_cables': queryset.show_cables,
|
|
'show_logical_connections': queryset.show_logical_connections,
|
|
'show_single_cable_logical_conns': queryset.show_single_cable_logical_conns,
|
|
'show_neighbors': queryset.show_neighbors,
|
|
'show_circuit': queryset.show_circuit,
|
|
'show_power': queryset.show_power,
|
|
'show_wireless': queryset.show_wireless,
|
|
'group_sites': queryset.group_sites,
|
|
'group_locations': queryset.group_locations,
|
|
'group_racks': queryset.group_racks,
|
|
'group_virtualchassis': queryset.group_virtualchassis,
|
|
'draw_default_layout': queryset.draw_default_layout,
|
|
'straight_cables': queryset.straight_cables,
|
|
'draw_termination_labels': queryset.draw_termination_labels,
|
|
'draw_cable_labels': queryset.draw_cable_labels,
|
|
'grid_size': queryset.grid_size,
|
|
'node_label_items': tuple(queryset.node_label_items.translate({ord(i): None for i in '[]\''}).split(', ')),
|
|
},
|
|
)
|
|
|
|
return render(
|
|
request,
|
|
"netbox_topology_views/individual_options.html",
|
|
{
|
|
"form": form,
|
|
"object": queryset,
|
|
},
|
|
)
|