* 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 rest_framework.serializers import ModelSerializer
|
||||||
from netbox.api.serializers import NetBoxModelSerializer
|
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):
|
class TopologyDummySerializer(ModelSerializer):
|
||||||
@@ -32,6 +32,21 @@ class CoordinateSerializer(NetBoxModelSerializer):
|
|||||||
model = Coordinate
|
model = Coordinate
|
||||||
fields = ("x", "y")
|
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 IndividualOptionsSerializer(NetBoxModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IndividualOptions
|
model = IndividualOptions
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ from netbox_topology_views.api.serializers import (
|
|||||||
RoleImageSerializer,
|
RoleImageSerializer,
|
||||||
TopologyDummySerializer,
|
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.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.utils import get_image_from_url, export_data_to_xml, get_query_settings
|
||||||
from netbox_topology_views.filters import DeviceFilterSet
|
from netbox_topology_views.filters import DeviceFilterSet
|
||||||
@@ -41,20 +42,26 @@ class SaveCoordsViewSet(PermissionRequiredMixin, ReadOnlyModelViewSet):
|
|||||||
if device_id.startswith("c"):
|
if device_id.startswith("c"):
|
||||||
device_id = device_id.lstrip("c")
|
device_id = device_id.lstrip("c")
|
||||||
actual_device = Circuit.objects.get(id=device_id)
|
actual_device = Circuit.objects.get(id=device_id)
|
||||||
|
model_name = 'CircuitCoordinate'
|
||||||
elif device_id.startswith("p"):
|
elif device_id.startswith("p"):
|
||||||
device_id = device_id.lstrip("p")
|
device_id = device_id.lstrip("p")
|
||||||
actual_device = PowerPanel.objects.get(id=device_id)
|
actual_device = PowerPanel.objects.get(id=device_id)
|
||||||
|
model_name = 'PowerPanelCoordinate'
|
||||||
elif device_id.startswith("f"):
|
elif device_id.startswith("f"):
|
||||||
device_id = device_id.lstrip("f")
|
device_id = device_id.lstrip("f")
|
||||||
actual_device = PowerFeed.objects.get(id=device_id)
|
actual_device = PowerFeed.objects.get(id=device_id)
|
||||||
|
model_name = 'PowerFeedCoordinate'
|
||||||
elif device_id.isnumeric():
|
elif device_id.isnumeric():
|
||||||
actual_device = Device.objects.get(id=device_id)
|
actual_device = Device.objects.get(id=device_id)
|
||||||
|
model_name = 'Coordinate'
|
||||||
|
|
||||||
if not actual_device:
|
if not actual_device:
|
||||||
return Response({"status": "invalid node_id in body"}, status=400)
|
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":
|
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:
|
if not group_id:
|
||||||
return Response(
|
return Response(
|
||||||
{"status": "Error while creating default group."}, status=500
|
{"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
|
# Hen-and-egg-problem. Thanks, Django! By default, Django updates records that
|
||||||
# already exist and inserts otherwise. This does not work with our
|
# already exist and inserts otherwise. This does not work with our
|
||||||
# unique_together key if no pk is given. But: No record, no pk.
|
# 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
|
# 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:
|
else:
|
||||||
# Unique group/device pair already exists. Update data
|
# 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()
|
coords.save()
|
||||||
except:
|
except:
|
||||||
return Response(
|
return Response(
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import django_filters
|
import django_filters
|
||||||
|
from circuits.models import Circuit
|
||||||
from dcim.choices import DeviceStatusChoices
|
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 django.db.models import Q
|
||||||
from extras.filtersets import LocalConfigContextFilterSet
|
from extras.filtersets import LocalConfigContextFilterSet
|
||||||
from extras.models import ConfigTemplate
|
from extras.models import ConfigTemplate
|
||||||
from netbox.filtersets import NetBoxModelFilterSet
|
from netbox.filtersets import NetBoxModelFilterSet
|
||||||
from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet
|
from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet
|
||||||
from utilities.filters import TreeNodeMultipleChoiceFilter, MultiValueCharFilter, MultiValueMACAddressFilter
|
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):
|
class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet, LocalConfigContextFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
@@ -164,6 +165,72 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter
|
|||||||
def _virtual_chassis_member(self, queryset, name, value):
|
def _virtual_chassis_member(self, queryset, name, value):
|
||||||
return queryset.exclude(virtual_chassis__isnull=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):
|
class CoordinatesFilterSet(NetBoxModelFilterSet):
|
||||||
group = django_filters.ModelMultipleChoiceFilter(
|
group = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset = CoordinateGroup.objects.all(),
|
queryset = CoordinateGroup.objects.all(),
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ from django.conf import settings
|
|||||||
|
|
||||||
from django.utils.translation import gettext as _
|
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 django import forms
|
||||||
from dcim.choices import DeviceStatusChoices, DeviceAirflowChoices
|
from dcim.choices import DeviceStatusChoices, DeviceAirflowChoices
|
||||||
@@ -21,7 +22,7 @@ from utilities.forms.fields import (
|
|||||||
DynamicModelMultipleChoiceField
|
DynamicModelMultipleChoiceField
|
||||||
)
|
)
|
||||||
|
|
||||||
from netbox_topology_views.models import IndividualOptions, CoordinateGroup, Coordinate
|
from netbox_topology_views.models import IndividualOptions, CoordinateGroup, Coordinate, CircuitCoordinate, PowerPanelCoordinate, PowerFeedCoordinate
|
||||||
|
|
||||||
class DeviceFilterForm(
|
class DeviceFilterForm(
|
||||||
LocalConfigContextFilterForm,
|
LocalConfigContextFilterForm,
|
||||||
@@ -272,6 +273,33 @@ class CoordinateGroupsImportForm(NetBoxModelImportForm):
|
|||||||
model = CoordinateGroup
|
model = CoordinateGroup
|
||||||
fields = ('name', 'description')
|
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):
|
class CoordinatesForm(NetBoxModelForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Coordinate', ('group', 'device', 'x', 'y')),
|
('Coordinate', ('group', 'device', 'x', 'y')),
|
||||||
@@ -281,11 +309,101 @@ class CoordinatesForm(NetBoxModelForm):
|
|||||||
model = Coordinate
|
model = Coordinate
|
||||||
fields = ('group', 'device', 'x', 'y')
|
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 CoordinatesImportForm(NetBoxModelImportForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Coordinate
|
model = Coordinate
|
||||||
fields = ('group', 'device', 'x', 'y')
|
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):
|
class CoordinatesFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = Coordinate
|
model = Coordinate
|
||||||
fieldsets = (
|
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 pathlib import Path
|
||||||
from typing import Optional
|
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 extras.models import Tag
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
@@ -130,11 +131,11 @@ class Coordinate(NetBoxModel):
|
|||||||
|
|
||||||
x = models.IntegerField(
|
x = models.IntegerField(
|
||||||
help_text='X-coordinate of the device (horizontal) on the canvas. '
|
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(
|
y = models.IntegerField(
|
||||||
help_text='Y-coordinate of the device (vertical) on the canvas. '
|
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):
|
def get_or_create_default_group(group_id):
|
||||||
@@ -168,6 +169,153 @@ class Coordinate(NetBoxModel):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('plugins:netbox_topology_views:coordinate', args=[self.pk])
|
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):
|
class IndividualOptions(NetBoxModel):
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
('interface', 'interface'),
|
('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 = (
|
coordinate_buttons = (
|
||||||
PluginMenuButton(
|
PluginMenuButton(
|
||||||
link='plugins:netbox_topology_views:coordinate_add',
|
link='plugins:netbox_topology_views:coordinate_add',
|
||||||
@@ -42,8 +93,15 @@ menu = PluginMenu(
|
|||||||
('TOPOLOGY',
|
('TOPOLOGY',
|
||||||
(
|
(
|
||||||
PluginMenuItem(link="plugins:netbox_topology_views:home", link_text="Topology", permissions=["dcim.view_site", "dcim.view_device"]),
|
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: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',
|
('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(
|
Promise.allSettled(
|
||||||
Object.entries(graph.getPositions(params.nodes)).map(
|
Object.entries(graph.getPositions(params.nodes)).map(
|
||||||
async ([nodeId, nodePosition]) => {
|
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(
|
const res = await fetch(
|
||||||
'/' + basePath + 'api/plugins/netbox_topology_views/save-coords/save_coords/',
|
'/' + 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",
|
"name": "netbox_topology_views",
|
||||||
"version": "3.6.2",
|
"version": "3.7.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "netbox_topology_views",
|
"name": "netbox_topology_views",
|
||||||
"version": "3.6.2",
|
"version": "3.7.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vis-data": "^7.1.6",
|
"vis-data": "^7.1.6",
|
||||||
"vis-network": "^9.1.6",
|
"vis-network": "^9.1.6",
|
||||||
@@ -102,6 +102,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"anymatch": "~3.1.2",
|
"anymatch": "~3.1.2",
|
||||||
"braces": "~3.0.2",
|
"braces": "~3.0.2",
|
||||||
|
"fsevents": "~2.3.2",
|
||||||
"glob-parent": "~5.1.2",
|
"glob-parent": "~5.1.2",
|
||||||
"is-binary-path": "~2.1.0",
|
"is-binary-path": "~2.1.0",
|
||||||
"is-glob": "~4.0.1",
|
"is-glob": "~4.0.1",
|
||||||
@@ -404,6 +405,29 @@
|
|||||||
"integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==",
|
"integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": 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": {
|
"bin": {
|
||||||
"esbuild": "bin/esbuild"
|
"esbuild": "bin/esbuild"
|
||||||
},
|
},
|
||||||
@@ -787,5 +811,450 @@
|
|||||||
"component-emitter": "^1.3.0"
|
"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
|
import django_tables2 as tables
|
||||||
|
|
||||||
from netbox.tables import NetBoxTable, ChoiceFieldColumn
|
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):
|
class CoordinateGroupListTable(NetBoxTable):
|
||||||
name = tables.Column(
|
name = tables.Column(
|
||||||
@@ -14,6 +14,48 @@ class CoordinateGroupListTable(NetBoxTable):
|
|||||||
fields = ('pk', 'id', 'name', 'description', 'devices')
|
fields = ('pk', 'id', 'name', 'description', 'devices')
|
||||||
default_columns = ('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):
|
class CoordinateListTable(NetBoxTable):
|
||||||
group = tables.Column(
|
group = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
@@ -27,4 +69,3 @@ class CoordinateListTable(NetBoxTable):
|
|||||||
model = Coordinate
|
model = Coordinate
|
||||||
fields = ('pk', 'id', 'group', 'device', 'x', 'y')
|
fields = ('pk', 'id', 'group', 'device', 'x', 'y')
|
||||||
default_columns = ('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 %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row mb-3">
|
||||||
<div class="col col-md-12">
|
<div class="col col-md-12">
|
||||||
<div class="card">
|
<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">
|
<div class="card-body table-responsive">
|
||||||
{% render_table coordinates_table %}
|
{% render_table coordinates_table %}
|
||||||
</div>
|
</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>/delete/", views.CoordinateGroupDeleteView.as_view(), name="coordinategroup_delete"),
|
||||||
path("coordinate-groups/<int:pk>/changelog/", ObjectChangeLogView.as_view(), name="coordinategroup_changelog", kwargs={'model': models.CoordinateGroup}),
|
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
|
# Coordinate
|
||||||
path("coordinate/", views.CoordinateListView.as_view(), name="coordinate_list"),
|
path("coordinate/", views.CoordinateListView.as_view(), name="coordinate_list"),
|
||||||
path("coordinate/add/", views.CoordinateAddView.as_view(), name="coordinate_add"),
|
path("coordinate/add/", views.CoordinateAddView.as_view(), name="coordinate_add"),
|
||||||
|
|||||||
@@ -37,20 +37,36 @@ from netbox.views.generic import (
|
|||||||
ObjectChangeLogView,
|
ObjectChangeLogView,
|
||||||
BulkImportView
|
BulkImportView
|
||||||
)
|
)
|
||||||
|
from netbox_topology_views.filters import DeviceFilterSet, CoordinatesFilterSet, CircuitCoordinatesFilterSet, PowerPanelCoordinatesFilterSet, PowerFeedCoordinatesFilterSet
|
||||||
|
|
||||||
from netbox_topology_views.filters import DeviceFilterSet, CoordinatesFilterSet
|
|
||||||
from netbox_topology_views.forms import (
|
from netbox_topology_views.forms import (
|
||||||
DeviceFilterForm,
|
DeviceFilterForm,
|
||||||
IndividualOptionsForm,
|
IndividualOptionsForm,
|
||||||
CoordinateGroupsForm,
|
CoordinateGroupsForm,
|
||||||
|
CircuitCoordinatesForm,
|
||||||
|
CircuitCoordinatesFilterForm,
|
||||||
|
CircuitCoordinatesImportForm,
|
||||||
|
PowerPanelCoordinatesForm,
|
||||||
|
PowerPanelCoordinatesFilterForm,
|
||||||
|
PowerPanelCoordinatesImportForm,
|
||||||
|
PowerFeedCoordinatesForm,
|
||||||
|
PowerFeedCoordinatesFilterForm,
|
||||||
|
PowerFeedCoordinatesImportForm,
|
||||||
CoordinatesForm,
|
CoordinatesForm,
|
||||||
CoordinatesFilterForm,
|
CoordinatesFilterForm,
|
||||||
CoordinateGroupsImportForm,
|
CoordinateGroupsImportForm,
|
||||||
CoordinatesImportForm
|
CoordinatesImportForm
|
||||||
)
|
)
|
||||||
from netbox_topology_views.models import RoleImage, CoordinateGroup, Coordinate, IndividualOptions
|
import netbox_topology_views.models
|
||||||
from netbox_topology_views.tables import CoordinateGroupListTable, CoordinateListTable
|
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 (
|
from netbox_topology_views.utils import (
|
||||||
CONF_IMAGE_DIR,
|
CONF_IMAGE_DIR,
|
||||||
find_image_url,
|
find_image_url,
|
||||||
@@ -88,6 +104,7 @@ def create_node(
|
|||||||
if isinstance(device, Circuit):
|
if isinstance(device, Circuit):
|
||||||
dev_name = device.cid
|
dev_name = device.cid
|
||||||
node["id"] = f"c{device.pk}"
|
node["id"] = f"c{device.pk}"
|
||||||
|
model_name = 'CircuitCoordinate'
|
||||||
|
|
||||||
if device.provider is not None:
|
if device.provider is not None:
|
||||||
node_content += (
|
node_content += (
|
||||||
@@ -98,6 +115,7 @@ def create_node(
|
|||||||
elif isinstance(device, PowerPanel):
|
elif isinstance(device, PowerPanel):
|
||||||
dev_name = device.name
|
dev_name = device.name
|
||||||
node["id"] = f"p{device.pk}"
|
node["id"] = f"p{device.pk}"
|
||||||
|
model_name = 'PowerPanelCoordinate'
|
||||||
|
|
||||||
if device.site is not None:
|
if device.site is not None:
|
||||||
node_content += f"<tr><th>Site: </th><td>{device.site.name}</td></tr>"
|
node_content += f"<tr><th>Site: </th><td>{device.site.name}</td></tr>"
|
||||||
@@ -108,6 +126,7 @@ def create_node(
|
|||||||
elif isinstance(device, PowerFeed):
|
elif isinstance(device, PowerFeed):
|
||||||
dev_name = device.name
|
dev_name = device.name
|
||||||
node["id"] = f"f{device.pk}"
|
node["id"] = f"f{device.pk}"
|
||||||
|
model_name = 'PowerFeedCoordinate'
|
||||||
|
|
||||||
if device.power_panel is not None:
|
if device.power_panel is not None:
|
||||||
node_content += (
|
node_content += (
|
||||||
@@ -124,6 +143,7 @@ def create_node(
|
|||||||
if device.voltage is not None:
|
if device.voltage is not None:
|
||||||
node_content += f"<tr><th>Voltage: </th><td>{device.voltage}</td></tr>"
|
node_content += f"<tr><th>Voltage: </th><td>{device.voltage}</td></tr>"
|
||||||
else:
|
else:
|
||||||
|
model_name = 'Coordinate'
|
||||||
dev_name = device.name
|
dev_name = device.name
|
||||||
if dev_name is None:
|
if dev_name is None:
|
||||||
dev_name = device.device_type.get_full_name
|
dev_name = device.device_type.get_full_name
|
||||||
@@ -163,8 +183,10 @@ def create_node(
|
|||||||
if device.device_role.color != "":
|
if device.device_role.color != "":
|
||||||
node["color.border"] = "#" + 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":
|
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:
|
if not group_id:
|
||||||
print('Exception occured while handling default group.')
|
print('Exception occured while handling default group.')
|
||||||
return node
|
return node
|
||||||
@@ -176,10 +198,10 @@ def create_node(
|
|||||||
# will not be placed correctly by vis-network.
|
# will not be placed correctly by vis-network.
|
||||||
node["x"] = 0
|
node["x"] = 0
|
||||||
node["y"] = 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
|
# 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["x"] = model_class.objects.get(group=group, device=device.pk).x
|
||||||
node["y"] = Coordinate.objects.get(group=group, device=device.pk).y
|
node["y"] = model_class.objects.get(group=group, device=device.pk).y
|
||||||
node["physics"] = False
|
node["physics"] = False
|
||||||
elif "coordinates" in device.custom_field_data:
|
elif "coordinates" in device.custom_field_data:
|
||||||
# We prefer the new Coordinate model but leave the deprecated method
|
# 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):
|
class CoordinateView(PermissionRequiredMixin, ObjectView):
|
||||||
permission_required = 'netbox_topology_views.view_coordinate'
|
permission_required = 'netbox_topology_views.view_coordinate'
|
||||||
|
|
||||||
@@ -847,10 +995,19 @@ class CoordinateGroupView(PermissionRequiredMixin, ObjectView):
|
|||||||
queryset = CoordinateGroup.objects.all()
|
queryset = CoordinateGroup.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
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 = CoordinateListTable(instance.coordinate_set.all())
|
||||||
table.configure(request)
|
table.configure(request)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
'circuitcoordinates_table': circuittable,
|
||||||
|
'powerpanelcoordinates_table': powerpaneltable,
|
||||||
|
'powerfeedcoordinates_table': powerfeedtable,
|
||||||
'coordinates_table': table,
|
'coordinates_table': table,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user