Fixes #377: Non-devices do not save coordinates (#387)

* corrected nodeId handling

* add models

* import models, migrate db

* correct migration deps

* load coordinates from db

* add serializers, correct imports

* add views and templates

* fix X-Y swap in help text

* add non-devices to coordinate group view
This commit is contained in:
Mario
2023-09-27 09:13:17 +02:00
committed by GitHub
parent dbe331afb8
commit 987c7ef34c
26 changed files with 1405 additions and 34 deletions
+16 -1
View File
@@ -2,7 +2,7 @@ from dcim.models import Device, DeviceRole
from rest_framework.serializers import ModelSerializer
from netbox.api.serializers import NetBoxModelSerializer
from netbox_topology_views.models import RoleImage, IndividualOptions, CoordinateGroup, Coordinate
from netbox_topology_views.models import RoleImage, IndividualOptions, CoordinateGroup, Coordinate, CircuitCoordinate, PowerPanelCoordinate, PowerFeedCoordinate
class TopologyDummySerializer(ModelSerializer):
@@ -32,6 +32,21 @@ class CoordinateSerializer(NetBoxModelSerializer):
model = Coordinate
fields = ("x", "y")
class CircuitCoordinateSerializer(NetBoxModelSerializer):
class Meta:
model = CircuitCoordinate
fields = ("x", "y")
class PowerPanelCoordinateSerializer(NetBoxModelSerializer):
class Meta:
model = PowerPanelCoordinate
fields = ("x", "y")
class PowerFeedCoordinateSerializer(NetBoxModelSerializer):
class Meta:
model = PowerFeedCoordinate
fields = ("x", "y")
class IndividualOptionsSerializer(NetBoxModelSerializer):
class Meta:
model = IndividualOptions
+12 -5
View File
@@ -14,7 +14,8 @@ from netbox_topology_views.api.serializers import (
RoleImageSerializer,
TopologyDummySerializer,
)
from netbox_topology_views.models import RoleImage, IndividualOptions, CoordinateGroup, Coordinate
import netbox_topology_views.models
from netbox_topology_views.models import RoleImage, IndividualOptions, CoordinateGroup, Coordinate, CircuitCoordinate, PowerPanelCoordinate, PowerFeedCoordinate
from netbox_topology_views.views import get_topology_data
from netbox_topology_views.utils import get_image_from_url, export_data_to_xml, get_query_settings
from netbox_topology_views.filters import DeviceFilterSet
@@ -41,20 +42,26 @@ class SaveCoordsViewSet(PermissionRequiredMixin, ReadOnlyModelViewSet):
if device_id.startswith("c"):
device_id = device_id.lstrip("c")
actual_device = Circuit.objects.get(id=device_id)
model_name = 'CircuitCoordinate'
elif device_id.startswith("p"):
device_id = device_id.lstrip("p")
actual_device = PowerPanel.objects.get(id=device_id)
model_name = 'PowerPanelCoordinate'
elif device_id.startswith("f"):
device_id = device_id.lstrip("f")
actual_device = PowerFeed.objects.get(id=device_id)
model_name = 'PowerFeedCoordinate'
elif device_id.isnumeric():
actual_device = Device.objects.get(id=device_id)
model_name = 'Coordinate'
if not actual_device:
return Response({"status": "invalid node_id in body"}, status=400)
model_class = getattr(netbox_topology_views.models, model_name)
if group_id is None or group_id == "default":
group_id = Coordinate.get_or_create_default_group(group_id)
group_id = model_class.get_or_create_default_group(group_id)
if not group_id:
return Response(
{"status": "Error while creating default group."}, status=500
@@ -66,12 +73,12 @@ class SaveCoordsViewSet(PermissionRequiredMixin, ReadOnlyModelViewSet):
# Hen-and-egg-problem. Thanks, Django! By default, Django updates records that
# already exist and inserts otherwise. This does not work with our
# unique_together key if no pk is given. But: No record, no pk.
if not Coordinate.objects.filter(group=group, device=actual_device):
if not model_class.objects.filter(group=group, device=actual_device):
# Unique group/device pair does not exist. Prepare new data set
coords = Coordinate(group=group, device=actual_device, x=x_coord, y=y_coord)
coords = model_class(group=group, device=actual_device, x=x_coord, y=y_coord)
else:
# Unique group/device pair already exists. Update data
coords = Coordinate(pk=Coordinate.objects.get(group=group, device=actual_device).pk, group=group, device=actual_device, x=x_coord, y=y_coord)
coords = model_class(pk=model_class.objects.get(group=group, device=actual_device).pk, group=group, device=actual_device, x=x_coord, y=y_coord)
coords.save()
except:
return Response(
+69 -2
View File
@@ -1,13 +1,14 @@
import django_filters
from circuits.models import Circuit
from dcim.choices import DeviceStatusChoices
from dcim.models import Device, DeviceRole, Location, Rack, Region, Site, SiteGroup, Manufacturer, DeviceType, Platform
from dcim.models import Device, DeviceRole, Location, Rack, Region, Site, SiteGroup, Manufacturer, DeviceType, Platform, PowerPanel, PowerFeed
from django.db.models import Q
from extras.filtersets import LocalConfigContextFilterSet
from extras.models import ConfigTemplate
from netbox.filtersets import NetBoxModelFilterSet
from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet
from utilities.filters import TreeNodeMultipleChoiceFilter, MultiValueCharFilter, MultiValueMACAddressFilter
from netbox_topology_views.models import CoordinateGroup, Coordinate
from netbox_topology_views.models import CoordinateGroup, Coordinate, CircuitCoordinate, PowerPanelCoordinate, PowerFeedCoordinate
class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet, LocalConfigContextFilterSet):
q = django_filters.CharFilter(
@@ -164,6 +165,72 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter
def _virtual_chassis_member(self, queryset, name, value):
return queryset.exclude(virtual_chassis__isnull=value)
class CircuitCoordinatesFilterSet(NetBoxModelFilterSet):
group = django_filters.ModelMultipleChoiceFilter(
queryset = CoordinateGroup.objects.all(),
)
device = django_filters.ModelMultipleChoiceFilter(
queryset = Circuit.objects.all(),
)
class Meta:
model = CircuitCoordinate
fields = ['id', 'group', 'device', 'x', 'y']
def search(self, queryset, name, value):
"""Perform the filtered search."""
if not value.strip():
return queryset
return queryset.filter(
Q(group__name__icontains=value) |
Q(device__name__icontains=value)
)
class PowerPanelCoordinatesFilterSet(NetBoxModelFilterSet):
group = django_filters.ModelMultipleChoiceFilter(
queryset = CoordinateGroup.objects.all(),
)
device = django_filters.ModelMultipleChoiceFilter(
queryset = PowerPanel.objects.all(),
)
class Meta:
model = PowerPanelCoordinate
fields = ['id', 'group', 'device', 'x', 'y']
def search(self, queryset, name, value):
"""Perform the filtered search."""
if not value.strip():
return queryset
return queryset.filter(
Q(group__name__icontains=value) |
Q(device__name__icontains=value)
)
class PowerFeedCoordinatesFilterSet(NetBoxModelFilterSet):
group = django_filters.ModelMultipleChoiceFilter(
queryset = CoordinateGroup.objects.all(),
)
device = django_filters.ModelMultipleChoiceFilter(
queryset = PowerFeed.objects.all(),
)
class Meta:
model = PowerFeedCoordinate
fields = ['id', 'group', 'device', 'x', 'y']
def search(self, queryset, name, value):
"""Perform the filtered search."""
if not value.strip():
return queryset
return queryset.filter(
Q(group__name__icontains=value) |
Q(device__name__icontains=value)
)
class CoordinatesFilterSet(NetBoxModelFilterSet):
group = django_filters.ModelMultipleChoiceFilter(
queryset = CoordinateGroup.objects.all(),
+120 -2
View File
@@ -5,7 +5,8 @@ from django.conf import settings
from django.utils.translation import gettext as _
from dcim.models import Device, Site, SiteGroup, Region, DeviceRole, Location, Rack, Manufacturer, DeviceType, Platform
from circuits.models import Circuit
from dcim.models import Device, Site, SiteGroup, Region, DeviceRole, Location, Rack, Manufacturer, DeviceType, Platform, PowerPanel, PowerFeed
from django import forms
from dcim.choices import DeviceStatusChoices, DeviceAirflowChoices
@@ -21,7 +22,7 @@ from utilities.forms.fields import (
DynamicModelMultipleChoiceField
)
from netbox_topology_views.models import IndividualOptions, CoordinateGroup, Coordinate
from netbox_topology_views.models import IndividualOptions, CoordinateGroup, Coordinate, CircuitCoordinate, PowerPanelCoordinate, PowerFeedCoordinate
class DeviceFilterForm(
LocalConfigContextFilterForm,
@@ -272,6 +273,33 @@ class CoordinateGroupsImportForm(NetBoxModelImportForm):
model = CoordinateGroup
fields = ('name', 'description')
class CircuitCoordinatesForm(NetBoxModelForm):
fieldsets = (
('CircuitCoordinate', ('group', 'device', 'x', 'y')),
)
class Meta:
model = CircuitCoordinate
fields = ('group', 'device', 'x', 'y')
class PowerPanelCoordinatesForm(NetBoxModelForm):
fieldsets = (
('PowerPanel', ('group', 'device', 'x', 'y')),
)
class Meta:
model = PowerPanelCoordinate
fields = ('group', 'device', 'x', 'y')
class PowerFeedCoordinatesForm(NetBoxModelForm):
fieldsets = (
('PowerFeedCoordinate', ('group', 'device', 'x', 'y')),
)
class Meta:
model = PowerFeedCoordinate
fields = ('group', 'device', 'x', 'y')
class CoordinatesForm(NetBoxModelForm):
fieldsets = (
('Coordinate', ('group', 'device', 'x', 'y')),
@@ -281,11 +309,101 @@ class CoordinatesForm(NetBoxModelForm):
model = Coordinate
fields = ('group', 'device', 'x', 'y')
class CircuitCoordinatesImportForm(NetBoxModelImportForm):
class Meta:
model = CircuitCoordinate
fields = ('group', 'device', 'x', 'y')
class PowerPanelCoordinatesImportForm(NetBoxModelImportForm):
class Meta:
model = PowerPanelCoordinate
fields = ('group', 'device', 'x', 'y')
class PowerFeedCoordinatesImportForm(NetBoxModelImportForm):
class Meta:
model = PowerFeedCoordinate
fields = ('group', 'device', 'x', 'y')
class CoordinatesImportForm(NetBoxModelImportForm):
class Meta:
model = Coordinate
fields = ('group', 'device', 'x', 'y')
class CircuitCoordinatesFilterForm(NetBoxModelFilterSetForm):
model = CircuitCoordinate
fieldsets = (
(None, ('q', 'filter_id')),
('CircuitCoordinates', ('group', 'device', 'x', 'y'))
)
group = forms.ModelMultipleChoiceField(
queryset=CoordinateGroup.objects.all(),
required=False
)
device = DynamicModelMultipleChoiceField(
queryset=Circuit.objects.all(),
required=False
)
x = forms.IntegerField(
required=False
)
y = forms.IntegerField(
required=False
)
class PowerPanelCoordinatesFilterForm(NetBoxModelFilterSetForm):
model = PowerPanelCoordinate
fieldsets = (
(None, ('q', 'filter_id')),
('PowerPanelCoordinates', ('group', 'device', 'x', 'y'))
)
group = forms.ModelMultipleChoiceField(
queryset=CoordinateGroup.objects.all(),
required=False
)
device = DynamicModelMultipleChoiceField(
queryset=PowerPanel.objects.all(),
required=False
)
x = forms.IntegerField(
required=False
)
y = forms.IntegerField(
required=False
)
class PowerFeedCoordinatesFilterForm(NetBoxModelFilterSetForm):
model = Coordinate
fieldsets = (
(None, ('q', 'filter_id')),
('PowerFeedCoordinates', ('group', 'device', 'x', 'y'))
)
group = forms.ModelMultipleChoiceField(
queryset=CoordinateGroup.objects.all(),
required=False
)
device = DynamicModelMultipleChoiceField(
queryset=PowerFeed.objects.all(),
required=False
)
x = forms.IntegerField(
required=False
)
y = forms.IntegerField(
required=False
)
class CoordinatesFilterForm(NetBoxModelFilterSetForm):
model = Coordinate
fieldsets = (
@@ -0,0 +1,70 @@
# Generated by Django 4.1.8 on 2023-09-25 21:18
from django.db import migrations, models
import django.db.models.deletion
import taggit.managers
import utilities.json
class Migration(migrations.Migration):
dependencies = [
('netbox_topology_views', '0005_individualoptions_save_coords'),
]
operations = [
migrations.CreateModel(
name='PowerPanelCoordinate',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
('x', models.IntegerField()),
('y', models.IntegerField()),
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dcim.powerpanel')),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='netbox_topology_views.coordinategroup')),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
],
options={
'ordering': ['group', 'device'],
'unique_together': {('device', 'group')},
},
),
migrations.CreateModel(
name='PowerFeedCoordinate',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
('x', models.IntegerField()),
('y', models.IntegerField()),
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dcim.powerfeed')),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='netbox_topology_views.coordinategroup')),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
],
options={
'ordering': ['group', 'device'],
'unique_together': {('device', 'group')},
},
),
migrations.CreateModel(
name='CircuitCoordinate',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
('x', models.IntegerField()),
('y', models.IntegerField()),
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='circuits.circuit')),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='netbox_topology_views.coordinategroup')),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
],
options={
'ordering': ['group', 'device'],
'unique_together': {('device', 'group')},
},
),
]
+151 -3
View File
@@ -1,7 +1,8 @@
from pathlib import Path
from typing import Optional
from dcim.models import Device, DeviceRole
from circuits.models import Circuit
from dcim.models import Device, DeviceRole, PowerPanel, PowerFeed
from extras.models import Tag
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
@@ -130,11 +131,11 @@ class Coordinate(NetBoxModel):
x = models.IntegerField(
help_text='X-coordinate of the device (horizontal) on the canvas. '
'Smaller values correspond to a position further up on the monitor.',
'Smaller values correspond to a position further to the left on the monitor.',
)
y = models.IntegerField(
help_text='Y-coordinate of the device (vertical) on the canvas. '
'Smaller values correspond to a position further to the left on the monitor.',
'Smaller values correspond to a position further up on the monitor.',
)
def get_or_create_default_group(group_id):
@@ -168,6 +169,153 @@ class Coordinate(NetBoxModel):
def get_absolute_url(self):
return reverse('plugins:netbox_topology_views:coordinate', args=[self.pk])
class CircuitCoordinate(NetBoxModel):
"""
Coordinates are being used to place devices in a topology view onto a certain
position. Devices belong to one or more coordinate groups. They have to
be unique together.
"""
device = models.ForeignKey(Circuit, on_delete=models.CASCADE)
group = models.ForeignKey(CoordinateGroup, on_delete=models.CASCADE)
x = models.IntegerField(
help_text='X-coordinate of the device (horizontal) on the canvas. '
'Smaller values correspond to a position further to the left on the monitor.',
)
y = models.IntegerField(
help_text='Y-coordinate of the device (vertical) on the canvas. '
'Smaller values correspond to a position further up on the monitor.',
)
def get_or_create_default_group(group_id):
# Default group named "default" must always exist in order to make sure
# that coordinate values can be stored even if no coordinate group has been
# selected. The default group will be added automatically if it does not exist.
try:
if CoordinateGroup.objects.filter(name="default"):
group = CoordinateGroup.objects.get(name="default")
group_id = group.pk
else:
group = CoordinateGroup(
name="default",
description="Automatically generated default group. If you delete "
"this group, all default coordinates are gone for good but "
"the group itself will be re-created."
)
group.save()
group_id = group.pk
except:
return False
return group_id
class Meta:
ordering = ['group', 'device']
unique_together = ('device', 'group')
def __str__(self):
return f'{self.x};{self.y}'
def get_absolute_url(self):
return reverse('plugins:netbox_topology_views:circuitcoordinate', args=[self.pk])
class PowerPanelCoordinate(NetBoxModel):
"""
Coordinates are being used to place devices in a topology view onto a certain
position. Devices belong to one or more coordinate groups. They have to
be unique together.
"""
device = models.ForeignKey(PowerPanel, on_delete=models.CASCADE)
group = models.ForeignKey(CoordinateGroup, on_delete=models.CASCADE)
x = models.IntegerField(
help_text='X-coordinate of the device (horizontal) on the canvas. '
'Smaller values correspond to a position further to the left on the monitor.',
)
y = models.IntegerField(
help_text='Y-coordinate of the device (vertical) on the canvas. '
'Smaller values correspond to a position further up on the monitor.',
)
def get_or_create_default_group(group_id):
# Default group named "default" must always exist in order to make sure
# that coordinate values can be stored even if no coordinate group has been
# selected. The default group will be added automatically if it does not exist.
try:
if CoordinateGroup.objects.filter(name="default"):
group = CoordinateGroup.objects.get(name="default")
group_id = group.pk
else:
group = CoordinateGroup(
name="default",
description="Automatically generated default group. If you delete "
"this group, all default coordinates are gone for good but "
"the group itself will be re-created."
)
group.save()
group_id = group.pk
except:
return False
return group_id
class Meta:
ordering = ['group', 'device']
unique_together = ('device', 'group')
def __str__(self):
return f'{self.x};{self.y}'
def get_absolute_url(self):
return reverse('plugins:netbox_topology_views:powerpanelcoordinate', args=[self.pk])
class PowerFeedCoordinate(NetBoxModel):
"""
Coordinates are being used to place devices in a topology view onto a certain
position. Devices belong to one or more coordinate groups. They have to
be unique together.
"""
device = models.ForeignKey(PowerFeed, on_delete=models.CASCADE)
group = models.ForeignKey(CoordinateGroup, on_delete=models.CASCADE)
x = models.IntegerField(
help_text='X-coordinate of the device (horizontal) on the canvas. '
'Smaller values correspond to a position further to the left on the monitor.',
)
y = models.IntegerField(
help_text='Y-coordinate of the device (vertical) on the canvas. '
'Smaller values correspond to a position further up on the monitor.',
)
def get_or_create_default_group(group_id):
# Default group named "default" must always exist in order to make sure
# that coordinate values can be stored even if no coordinate group has been
# selected. The default group will be added automatically if it does not exist.
try:
if CoordinateGroup.objects.filter(name="default"):
group = CoordinateGroup.objects.get(name="default")
group_id = group.pk
else:
group = CoordinateGroup(
name="default",
description="Automatically generated default group. If you delete "
"this group, all default coordinates are gone for good but "
"the group itself will be re-created."
)
group.save()
group_id = group.pk
except:
return False
return group_id
class Meta:
ordering = ['group', 'device']
unique_together = ('device', 'group')
def __str__(self):
return f'{self.x};{self.y}'
def get_absolute_url(self):
return reverse('plugins:netbox_topology_views:powerfeedcoordinate', args=[self.pk])
class IndividualOptions(NetBoxModel):
CHOICES = (
('interface', 'interface'),
+59 -1
View File
@@ -18,6 +18,57 @@ coordinategroup_buttons = (
)
)
circuitcoordinate_buttons = (
PluginMenuButton(
link='plugins:netbox_topology_views:circuitcoordinate_add',
title='Add',
icon_class='mdi mdi-plus-thick',
color=ButtonColorChoices.GREEN,
permissions=['netbox_topology_views.add_coordinate']
),
PluginMenuButton(
link='plugins:netbox_topology_views:circuitcoordinate_import',
title='Import',
icon_class='mdi mdi-upload',
color=ButtonColorChoices.CYAN,
permissions=['netbox_topology_views.add_coordinate']
)
)
powerpanelcoordinate_buttons = (
PluginMenuButton(
link='plugins:netbox_topology_views:powerpanelcoordinate_add',
title='Add',
icon_class='mdi mdi-plus-thick',
color=ButtonColorChoices.GREEN,
permissions=['netbox_topology_views.add_coordinate']
),
PluginMenuButton(
link='plugins:netbox_topology_views:powerpanelcoordinate_import',
title='Import',
icon_class='mdi mdi-upload',
color=ButtonColorChoices.CYAN,
permissions=['netbox_topology_views.add_coordinate']
)
)
powerfeedcoordinate_buttons = (
PluginMenuButton(
link='plugins:netbox_topology_views:powerfeedcoordinate_add',
title='Add',
icon_class='mdi mdi-plus-thick',
color=ButtonColorChoices.GREEN,
permissions=['netbox_topology_views.add_coordinate']
),
PluginMenuButton(
link='plugins:netbox_topology_views:powerfeedcoordinate_import',
title='Import',
icon_class='mdi mdi-upload',
color=ButtonColorChoices.CYAN,
permissions=['netbox_topology_views.add_coordinate']
)
)
coordinate_buttons = (
PluginMenuButton(
link='plugins:netbox_topology_views:coordinate_add',
@@ -42,8 +93,15 @@ menu = PluginMenu(
('TOPOLOGY',
(
PluginMenuItem(link="plugins:netbox_topology_views:home", link_text="Topology", permissions=["dcim.view_site", "dcim.view_device"]),
),
),
('COORDINATES',
(
PluginMenuItem(link="plugins:netbox_topology_views:coordinategroup_list", link_text="Coordinate Groups", buttons=coordinategroup_buttons, permissions=['netbox_topology_views.view_coordinategroup']),
PluginMenuItem(link="plugins:netbox_topology_views:coordinate_list", link_text="Coordinates", buttons=coordinate_buttons, permissions=['netbox_topology_views.view_coordinate']),
PluginMenuItem(link="plugins:netbox_topology_views:coordinate_list", link_text="Device Coordinates", buttons=coordinate_buttons, permissions=['netbox_topology_views.view_coordinate']),
PluginMenuItem(link="plugins:netbox_topology_views:powerfeedcoordinate_list", link_text="Power Feed Coords", buttons=powerfeedcoordinate_buttons, permissions=['netbox_topology_views.view_coordinate']),
PluginMenuItem(link="plugins:netbox_topology_views:powerpanelcoordinate_list", link_text="Power Panel Coords", buttons=powerpanelcoordinate_buttons, permissions=['netbox_topology_views.view_coordinate']),
PluginMenuItem(link="plugins:netbox_topology_views:circuitcoordinate_list", link_text="Circuit Coordinates", buttons=circuitcoordinate_buttons, permissions=['netbox_topology_views.view_coordinate']),
),
),
('PREFERENCES',
File diff suppressed because one or more lines are too long
+20 -3
View File
@@ -79,7 +79,26 @@ const coordSaveCheckbox = document.querySelector('#id_save_coords')
Promise.allSettled(
Object.entries(graph.getPositions(params.nodes)).map(
async ([nodeId, nodePosition]) => {
window.nodes.update({id: parseInt(nodeId), physics: false, x: nodePosition.x, y: nodePosition.y});
if(!isNaN(parseInt(nodeId))) {
nodeKey = parseInt(nodeId);
}
else {
nodeKey = nodeId;
}
try {
window.nodes.update({id: nodeKey, physics: false, x: nodePosition.x, y: nodePosition.y});
}
catch (e) {
console.log([
'Error while executing window.nodes.update()',
'nodeId: ' + nodeId,
'nodeKey: ' + nodeKey,
'x: ' + nodePosition.x,
'y: ' + nodePosition.y
]);
console.log(e);
}
const res = await fetch(
'/' + basePath + 'api/plugins/netbox_topology_views/save-coords/save_coords/',
{
@@ -97,8 +116,6 @@ const coordSaveCheckbox = document.querySelector('#id_save_coords')
})
}
)
console.log(nodeId, res.status, res.statusText)
}
)
)
+472 -3
View File
@@ -1,12 +1,12 @@
{
"name": "netbox_topology_views",
"version": "3.6.2",
"lockfileVersion": 3,
"version": "3.7.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "netbox_topology_views",
"version": "3.6.2",
"version": "3.7.0",
"dependencies": {
"vis-data": "^7.1.6",
"vis-network": "^9.1.6",
@@ -102,6 +102,7 @@
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"fsevents": "~2.3.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
@@ -404,6 +405,29 @@
"integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"@esbuild/linux-loong64": "0.14.54",
"esbuild-android-64": "0.14.54",
"esbuild-android-arm64": "0.14.54",
"esbuild-darwin-64": "0.14.54",
"esbuild-darwin-arm64": "0.14.54",
"esbuild-freebsd-64": "0.14.54",
"esbuild-freebsd-arm64": "0.14.54",
"esbuild-linux-32": "0.14.54",
"esbuild-linux-64": "0.14.54",
"esbuild-linux-arm": "0.14.54",
"esbuild-linux-arm64": "0.14.54",
"esbuild-linux-mips64le": "0.14.54",
"esbuild-linux-ppc64le": "0.14.54",
"esbuild-linux-riscv64": "0.14.54",
"esbuild-linux-s390x": "0.14.54",
"esbuild-netbsd-64": "0.14.54",
"esbuild-openbsd-64": "0.14.54",
"esbuild-sunos-64": "0.14.54",
"esbuild-windows-32": "0.14.54",
"esbuild-windows-64": "0.14.54",
"esbuild-windows-arm64": "0.14.54"
},
"bin": {
"esbuild": "bin/esbuild"
},
@@ -787,5 +811,450 @@
"component-emitter": "^1.3.0"
}
}
},
"dependencies": {
"@egjs/hammerjs": {
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
"integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==",
"requires": {
"@types/hammerjs": "^2.0.36"
}
},
"@esbuild/linux-loong64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz",
"integrity": "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==",
"dev": true,
"optional": true
},
"@types/hammerjs": {
"version": "2.0.41",
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz",
"integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA=="
},
"anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
}
},
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true
},
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
}
},
"chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"dev": true,
"requires": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"fsevents": "~2.3.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
}
},
"component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
},
"esbuild": {
"version": "0.12.29",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.29.tgz",
"integrity": "sha512-w/XuoBCSwepyiZtIRsKsetiLDUVGPVw1E/R3VTFSecIy8UR7Cq3SOtwKHJMFoVqqVG36aGkzh4e8BvpO1Fdc7g==",
"dev": true
},
"esbuild-android-64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz",
"integrity": "sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==",
"dev": true,
"optional": true
},
"esbuild-android-arm64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz",
"integrity": "sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==",
"dev": true,
"optional": true
},
"esbuild-darwin-64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz",
"integrity": "sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==",
"dev": true,
"optional": true
},
"esbuild-darwin-arm64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz",
"integrity": "sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==",
"dev": true,
"optional": true
},
"esbuild-freebsd-64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz",
"integrity": "sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==",
"dev": true,
"optional": true
},
"esbuild-freebsd-arm64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz",
"integrity": "sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==",
"dev": true,
"optional": true
},
"esbuild-linux-32": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz",
"integrity": "sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==",
"dev": true,
"optional": true
},
"esbuild-linux-64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz",
"integrity": "sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==",
"dev": true,
"optional": true
},
"esbuild-linux-arm": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz",
"integrity": "sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==",
"dev": true,
"optional": true
},
"esbuild-linux-arm64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz",
"integrity": "sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==",
"dev": true,
"optional": true
},
"esbuild-linux-mips64le": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz",
"integrity": "sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==",
"dev": true,
"optional": true
},
"esbuild-linux-ppc64le": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz",
"integrity": "sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==",
"dev": true,
"optional": true
},
"esbuild-linux-riscv64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz",
"integrity": "sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==",
"dev": true,
"optional": true
},
"esbuild-linux-s390x": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz",
"integrity": "sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==",
"dev": true,
"optional": true
},
"esbuild-netbsd-64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz",
"integrity": "sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==",
"dev": true,
"optional": true
},
"esbuild-openbsd-64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz",
"integrity": "sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==",
"dev": true,
"optional": true
},
"esbuild-sass-plugin": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-1.8.2.tgz",
"integrity": "sha512-ZBjONsRSpmzMKvxSNohnNXCNaBBVlYLqYhyZtN8leGGkJktCvVMKC6g9V5TWIemk1SEaW2XK+YFxz3Whw3+YYw==",
"dev": true,
"requires": {
"esbuild": "^0.14.5",
"picomatch": "^2.3.0",
"resolve": "^1.20.0",
"sass": "^1.45.0"
},
"dependencies": {
"esbuild": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.54.tgz",
"integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==",
"dev": true,
"requires": {
"@esbuild/linux-loong64": "0.14.54",
"esbuild-android-64": "0.14.54",
"esbuild-android-arm64": "0.14.54",
"esbuild-darwin-64": "0.14.54",
"esbuild-darwin-arm64": "0.14.54",
"esbuild-freebsd-64": "0.14.54",
"esbuild-freebsd-arm64": "0.14.54",
"esbuild-linux-32": "0.14.54",
"esbuild-linux-64": "0.14.54",
"esbuild-linux-arm": "0.14.54",
"esbuild-linux-arm64": "0.14.54",
"esbuild-linux-mips64le": "0.14.54",
"esbuild-linux-ppc64le": "0.14.54",
"esbuild-linux-riscv64": "0.14.54",
"esbuild-linux-s390x": "0.14.54",
"esbuild-netbsd-64": "0.14.54",
"esbuild-openbsd-64": "0.14.54",
"esbuild-sunos-64": "0.14.54",
"esbuild-windows-32": "0.14.54",
"esbuild-windows-64": "0.14.54",
"esbuild-windows-arm64": "0.14.54"
}
}
}
},
"esbuild-sunos-64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz",
"integrity": "sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==",
"dev": true,
"optional": true
},
"esbuild-windows-32": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz",
"integrity": "sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==",
"dev": true,
"optional": true
},
"esbuild-windows-64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz",
"integrity": "sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==",
"dev": true,
"optional": true
},
"esbuild-windows-arm64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz",
"integrity": "sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==",
"dev": true,
"optional": true
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
"function-bind": "^1.1.1"
}
},
"immutable": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz",
"integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==",
"dev": true
},
"is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"requires": {
"binary-extensions": "^2.0.0"
}
},
"is-core-module": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz",
"integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==",
"dev": true,
"requires": {
"has": "^1.0.3"
}
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true
},
"is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
"keycharm": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.4.0.tgz",
"integrity": "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ=="
},
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true
},
"path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true
},
"readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"requires": {
"picomatch": "^2.2.1"
}
},
"resolve": {
"version": "1.22.2",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
"integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
"dev": true,
"requires": {
"is-core-module": "^2.11.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
}
},
"sass": {
"version": "1.63.6",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.63.6.tgz",
"integrity": "sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==",
"dev": true,
"requires": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
}
},
"source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true
},
"supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true
},
"timsort": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
"integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A=="
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"requires": {
"is-number": "^7.0.0"
}
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"vis-data": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/vis-data/-/vis-data-7.1.6.tgz",
"integrity": "sha512-lG7LJdkawlKSXsdcEkxe/zRDyW29a4r7N7PMwxCPxK12/QIdqxJwcMxwjVj9ozdisRhP5TyWDHZwsgjmj0g6Dg==",
"requires": {}
},
"vis-network": {
"version": "9.1.6",
"resolved": "https://registry.npmjs.org/vis-network/-/vis-network-9.1.6.tgz",
"integrity": "sha512-Eiwx1JleAsUqfy4pzcsFngCVlCEdjAtRPB/OwCV7PHBm+o2jtE4IZPcPITAEGUlxvL4Fdw7/lZsfD32dL+IL6g==",
"requires": {}
},
"vis-util": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/vis-util/-/vis-util-5.0.3.tgz",
"integrity": "sha512-Wf9STUcFrDzK4/Zr7B6epW2Kvm3ORNWF+WiwEz2dpf5RdWkLUXFSbLcuB88n1W6tCdFwVN+v3V4/Xmn9PeL39g==",
"requires": {}
}
}
}
+43 -2
View File
@@ -1,7 +1,7 @@
import django_tables2 as tables
from netbox.tables import NetBoxTable, ChoiceFieldColumn
from netbox_topology_views.models import CoordinateGroup, Coordinate
from netbox_topology_views.models import CoordinateGroup, Coordinate, CircuitCoordinate, PowerPanelCoordinate, PowerFeedCoordinate
class CoordinateGroupListTable(NetBoxTable):
name = tables.Column(
@@ -14,6 +14,48 @@ class CoordinateGroupListTable(NetBoxTable):
fields = ('pk', 'id', 'name', 'description', 'devices')
default_columns = ('name', 'description', 'devices')
class CircuitCoordinateListTable(NetBoxTable):
group = tables.Column(
linkify=True
)
device = tables.Column(
linkify=True
)
class Meta(NetBoxTable.Meta):
model = CircuitCoordinate
fields = ('pk', 'id', 'group', 'device', 'x', 'y')
default_columns = ('id', 'group', 'device', 'x', 'y')
class PowerPanelCoordinateListTable(NetBoxTable):
group = tables.Column(
linkify=True
)
device = tables.Column(
linkify=True
)
class Meta(NetBoxTable.Meta):
model = PowerPanelCoordinate
fields = ('pk', 'id', 'group', 'device', 'x', 'y')
default_columns = ('id', 'group', 'device', 'x', 'y')
class PowerFeedCoordinateListTable(NetBoxTable):
group = tables.Column(
linkify=True
)
device = tables.Column(
linkify=True
)
class Meta(NetBoxTable.Meta):
model = PowerFeedCoordinate
fields = ('pk', 'id', 'group', 'device', 'x', 'y')
default_columns = ('id', 'group', 'device', 'x', 'y')
class CoordinateListTable(NetBoxTable):
group = tables.Column(
linkify=True
@@ -27,4 +69,3 @@ class CoordinateListTable(NetBoxTable):
model = Coordinate
fields = ('pk', 'id', 'group', 'device', 'x', 'y')
default_columns = ('id', 'group', 'device', 'x', 'y')
@@ -0,0 +1,42 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block title %}Topology Views Coordinates{% endblock title %}
{% block content %}
<div class="row mb-3">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">
Coordinates
</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">Group</th>
<td>{{ object.group|linkify }}</td>
</tr>
<tr>
<th scope="row">Circuit</th>
<td>{{ object.device|linkify }}</td>
</tr>
<tr>
<th scope="row">X-Coordinate</th>
<td>{{ object.x }}</td>
</tr>
<tr>
<th scope="row">Y-Coordinate</th>
<td>{{ object.y }}</td>
</tr>
</table>
</div>
</div>
{% plugin_left_page object %}
</div>
<div class="col col-md-6">
{% include 'inc/panels/custom_fields.html' %}
{% plugin_right_page object %}
</div>
</div>
{% endblock content %}
@@ -0,0 +1,3 @@
{% extends 'generic/object_edit.html' %}
{% block title %}Add new Circuit Coordinates{% endblock title %}
@@ -0,0 +1,3 @@
{% extends 'generic/object_edit.html' %}
{% block title %}Edit Circuit Coordinates{% endblock title %}
@@ -0,0 +1,3 @@
{% extends 'generic/object_list.html' %}
{% block title %}Circuit Coordinates{% endblock title %}
@@ -32,10 +32,34 @@
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="row mb-3">
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Coordinates</h5>
<h5 class="card-header">Circuit Coordinates</h5>
<div class="card-body table-responsive">
{% render_table circuitcoordinates_table %}
</div>
</div>
</div>
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Power Panel Coordinates</h5>
<div class="card-body table-responsive">
{% render_table powerpanelcoordinates_table %}
</div>
</div>
</div>
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Power Feed Coordinates</h5>
<div class="card-body table-responsive">
{% render_table powerfeedcoordinates_table %}
</div>
</div>
</div>
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Device Coordinates</h5>
<div class="card-body table-responsive">
{% render_table coordinates_table %}
</div>
@@ -0,0 +1,42 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block title %}Topology Views Coordinates{% endblock title %}
{% block content %}
<div class="row mb-3">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">
Coordinates
</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">Group</th>
<td>{{ object.group|linkify }}</td>
</tr>
<tr>
<th scope="row">Power Feed</th>
<td>{{ object.device|linkify }}</td>
</tr>
<tr>
<th scope="row">X-Coordinate</th>
<td>{{ object.x }}</td>
</tr>
<tr>
<th scope="row">Y-Coordinate</th>
<td>{{ object.y }}</td>
</tr>
</table>
</div>
</div>
{% plugin_left_page object %}
</div>
<div class="col col-md-6">
{% include 'inc/panels/custom_fields.html' %}
{% plugin_right_page object %}
</div>
</div>
{% endblock content %}
@@ -0,0 +1,3 @@
{% extends 'generic/object_edit.html' %}
{% block title %}Add new Power Feed Coordinates{% endblock title %}
@@ -0,0 +1,3 @@
{% extends 'generic/object_edit.html' %}
{% block title %}Edit Power Feed Coordinates{% endblock title %}
@@ -0,0 +1,3 @@
{% extends 'generic/object_list.html' %}
{% block title %}Power Feed Coordinates{% endblock title %}
@@ -0,0 +1,42 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block title %}Topology Views Coordinates{% endblock title %}
{% block content %}
<div class="row mb-3">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">
Coordinates
</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">Group</th>
<td>{{ object.group|linkify }}</td>
</tr>
<tr>
<th scope="row">Power Panel</th>
<td>{{ object.device|linkify }}</td>
</tr>
<tr>
<th scope="row">X-Coordinate</th>
<td>{{ object.x }}</td>
</tr>
<tr>
<th scope="row">Y-Coordinate</th>
<td>{{ object.y }}</td>
</tr>
</table>
</div>
</div>
{% plugin_left_page object %}
</div>
<div class="col col-md-6">
{% include 'inc/panels/custom_fields.html' %}
{% plugin_right_page object %}
</div>
</div>
{% endblock content %}
@@ -0,0 +1,3 @@
{% extends 'generic/object_edit.html' %}
{% block title %}Add new Power Panel Coordinates{% endblock title %}
@@ -0,0 +1,3 @@
{% extends 'generic/object_edit.html' %}
{% block title %}Edit Power Panel Coordinates{% endblock title %}
@@ -0,0 +1,3 @@
{% extends 'generic/object_list.html' %}
{% block title %}Power Panel Coordinates{% endblock title %}
+27
View File
@@ -20,6 +20,33 @@ urlpatterns = (
path("coordinate-groups/<int:pk>/delete/", views.CoordinateGroupDeleteView.as_view(), name="coordinategroup_delete"),
path("coordinate-groups/<int:pk>/changelog/", ObjectChangeLogView.as_view(), name="coordinategroup_changelog", kwargs={'model': models.CoordinateGroup}),
# Circuit Coordinate
path("circuitcoordinate/", views.CircuitCoordinateListView.as_view(), name="circuitcoordinate_list"),
path("circuitcoordinate/add/", views.CircuitCoordinateAddView.as_view(), name="circuitcoordinate_add"),
path('circuitcoordinate/import/', views.CircuitCoordinateBulkImportView.as_view(), name='circuitcoordinate_import'),
path("circuitcoordinate/<int:pk>/", views.CircuitCoordinateView.as_view(), name="circuitcoordinate"),
path("circuitcoordinate/<int:pk>/edit/", views.CircuitCoordinateEditView.as_view(), name="circuitcoordinate_edit"),
path("circuitcoordinate/<int:pk>/delete/", views.CircuitCoordinateDeleteView.as_view(), name="circuitcoordinate_delete"),
path("circuitcoordinate/<int:pk>/changelog/", ObjectChangeLogView.as_view(), name="circuitcoordinate_changelog", kwargs={'model': models.CircuitCoordinate}),
# Power Panel Coordinate
path("powerpanelcoordinate/", views.PowerPanelCoordinateListView.as_view(), name="powerpanelcoordinate_list"),
path("powerpanelcoordinate/add/", views.PowerPanelCoordinateAddView.as_view(), name="powerpanelcoordinate_add"),
path('powerpanelcoordinate/import/', views.PowerPanelCoordinateBulkImportView.as_view(), name='powerpanelcoordinate_import'),
path("powerpanelcoordinate/<int:pk>/", views.PowerPanelCoordinateView.as_view(), name="powerpanelcoordinate"),
path("powerpanelcoordinate/<int:pk>/edit/", views.PowerPanelCoordinateEditView.as_view(), name="powerpanelcoordinate_edit"),
path("powerpanelcoordinate/<int:pk>/delete/", views.PowerPanelCoordinateDeleteView.as_view(), name="powerpanelcoordinate_delete"),
path("powerpanelcoordinate/<int:pk>/changelog/", ObjectChangeLogView.as_view(), name="powerpanelcoordinate_changelog", kwargs={'model': models.PowerPanelCoordinate}),
# Power Feed Coordinate
path("powerfeedcoordinate/", views.PowerFeedCoordinateListView.as_view(), name="powerfeedcoordinate_list"),
path("powerfeedcoordinate/add/", views.PowerFeedCoordinateAddView.as_view(), name="powerfeedcoordinate_add"),
path('powerfeedcoordinate/import/', views.PowerFeedCoordinateBulkImportView.as_view(), name='powerfeedcoordinate_import'),
path("powerfeedcoordinate/<int:pk>/", views.PowerFeedCoordinateView.as_view(), name="powerfeedcoordinate"),
path("powerfeedcoordinate/<int:pk>/edit/", views.PowerFeedCoordinateEditView.as_view(), name="powerfeedcoordinate_edit"),
path("powerfeedcoordinate/<int:pk>/delete/", views.PowerFeedCoordinateDeleteView.as_view(), name="powerfeedcoordinate_delete"),
path("powerfeedcoordinate/<int:pk>/changelog/", ObjectChangeLogView.as_view(), name="powerfeedcoordinate_changelog", kwargs={'model': models.PowerFeedCoordinate}),
# Coordinate
path("coordinate/", views.CoordinateListView.as_view(), name="coordinate_list"),
path("coordinate/add/", views.CoordinateAddView.as_view(), name="coordinate_add"),
+166 -9
View File
@@ -37,20 +37,36 @@ from netbox.views.generic import (
ObjectChangeLogView,
BulkImportView
)
from netbox_topology_views.filters import DeviceFilterSet, CoordinatesFilterSet
from netbox_topology_views.filters import DeviceFilterSet, CoordinatesFilterSet, CircuitCoordinatesFilterSet, PowerPanelCoordinatesFilterSet, PowerFeedCoordinatesFilterSet
from netbox_topology_views.forms import (
DeviceFilterForm,
IndividualOptionsForm,
CoordinateGroupsForm,
CircuitCoordinatesForm,
CircuitCoordinatesFilterForm,
CircuitCoordinatesImportForm,
PowerPanelCoordinatesForm,
PowerPanelCoordinatesFilterForm,
PowerPanelCoordinatesImportForm,
PowerFeedCoordinatesForm,
PowerFeedCoordinatesFilterForm,
PowerFeedCoordinatesImportForm,
CoordinatesForm,
CoordinatesFilterForm,
CoordinateGroupsImportForm,
CoordinatesImportForm
)
from netbox_topology_views.models import RoleImage, CoordinateGroup, Coordinate, IndividualOptions
from netbox_topology_views.tables import CoordinateGroupListTable, CoordinateListTable
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,
@@ -88,6 +104,7 @@ def create_node(
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 += (
@@ -98,6 +115,7 @@ def create_node(
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>"
@@ -108,6 +126,7 @@ def create_node(
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 += (
@@ -124,6 +143,7 @@ def create_node(
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.get_full_name
@@ -163,8 +183,10 @@ def create_node(
if device.device_role.color != "":
node["color.border"] = "#" + device.device_role.color
model_class = getattr(netbox_topology_views.models, model_name)
if group_id is None or group_id == "default":
group_id = Coordinate.get_or_create_default_group(group_id)
group_id = model_class.get_or_create_default_group(group_id)
if not group_id:
print('Exception occured while handling default group.')
return node
@@ -176,10 +198,10 @@ def create_node(
# will not be placed correctly by vis-network.
node["x"] = 0
node["y"] = 0
if Coordinate.objects.filter(group=group, device=device.pk).values('x') and Coordinate.objects.filter(group=group, device=device.pk).values('y'):
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"] = Coordinate.objects.get(group=group, device=device.pk).x
node["y"] = Coordinate.objects.get(group=group, device=device.pk).y
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
@@ -799,6 +821,132 @@ class TopologyImagesView(PermissionRequiredMixin, View):
},
)
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'
@@ -847,10 +995,19 @@ class CoordinateGroupView(PermissionRequiredMixin, ObjectView):
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,
}