* 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:
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
+70
@@ -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')},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -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'),
|
||||
|
||||
@@ -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
@@ -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
@@ -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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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"),
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user