mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-30 12:26:25 -06:00
WIP
This commit is contained in:
parent
03d413565f
commit
9aa4ae599e
@ -2,9 +2,13 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from circuits.choices import CircuitPriorityChoices, CircuitStatusChoices
|
from circuits.choices import CircuitPriorityChoices, CircuitStatusChoices, VirtualCircuitTerminationRoleChoices
|
||||||
from circuits.constants import CIRCUIT_TERMINATION_TERMINATION_TYPES
|
from circuits.constants import CIRCUIT_TERMINATION_TERMINATION_TYPES
|
||||||
from circuits.models import Circuit, CircuitGroup, CircuitGroupAssignment, CircuitTermination, CircuitType
|
from circuits.models import (
|
||||||
|
Circuit, CircuitGroup, CircuitGroupAssignment, CircuitTermination, CircuitType, VirtualCircuit,
|
||||||
|
VirtualCircuitTermination,
|
||||||
|
)
|
||||||
|
from dcim.api.serializers_.device_components import InterfaceSerializer
|
||||||
from dcim.api.serializers_.cables import CabledObjectSerializer
|
from dcim.api.serializers_.cables import CabledObjectSerializer
|
||||||
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField
|
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField
|
||||||
from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer
|
from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer
|
||||||
@ -20,6 +24,8 @@ __all__ = (
|
|||||||
'CircuitGroupSerializer',
|
'CircuitGroupSerializer',
|
||||||
'CircuitTerminationSerializer',
|
'CircuitTerminationSerializer',
|
||||||
'CircuitTypeSerializer',
|
'CircuitTypeSerializer',
|
||||||
|
'VirtualCircuitSerializer',
|
||||||
|
'VirtualCircuitTerminationSerializer',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -156,3 +162,32 @@ class CircuitGroupAssignmentSerializer(CircuitGroupAssignmentSerializer_):
|
|||||||
'id', 'url', 'display_url', 'display', 'group', 'circuit', 'priority', 'tags', 'created', 'last_updated',
|
'id', 'url', 'display_url', 'display', 'group', 'circuit', 'priority', 'tags', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'group', 'circuit', 'priority')
|
brief_fields = ('id', 'url', 'display', 'group', 'circuit', 'priority')
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitSerializer(NetBoxModelSerializer):
|
||||||
|
provider_network = ProviderNetworkSerializer(nested=True)
|
||||||
|
provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True, default=None)
|
||||||
|
status = ChoiceField(choices=CircuitStatusChoices, required=False)
|
||||||
|
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = VirtualCircuit
|
||||||
|
fields = [
|
||||||
|
'id', 'url', 'display_url', 'display', 'cid', 'provider_network', 'provider_account', 'status', 'tenant',
|
||||||
|
'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||||
|
]
|
||||||
|
brief_fields = ('id', 'url', 'display', 'provider_network', 'cid', 'description')
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitTerminationSerializer(NetBoxModelSerializer, CabledObjectSerializer):
|
||||||
|
virtual_circuit = VirtualCircuitSerializer(nested=True)
|
||||||
|
role = ChoiceField(choices=VirtualCircuitTerminationRoleChoices, required=False)
|
||||||
|
interface = InterfaceSerializer(nested=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = VirtualCircuitTermination
|
||||||
|
fields = [
|
||||||
|
'id', 'url', 'display_url', 'display', 'virtual_circuit', 'role', 'interface', 'description', 'tags',
|
||||||
|
'custom_fields', 'created', 'last_updated',
|
||||||
|
]
|
||||||
|
brief_fields = ('id', 'url', 'display', 'virtual_circuit', 'role', 'interface')
|
||||||
|
@ -17,5 +17,9 @@ router.register('circuit-terminations', views.CircuitTerminationViewSet)
|
|||||||
router.register('circuit-groups', views.CircuitGroupViewSet)
|
router.register('circuit-groups', views.CircuitGroupViewSet)
|
||||||
router.register('circuit-group-assignments', views.CircuitGroupAssignmentViewSet)
|
router.register('circuit-group-assignments', views.CircuitGroupAssignmentViewSet)
|
||||||
|
|
||||||
|
# Virtual circuits
|
||||||
|
router.register('virtual-circuits', views.VirtualCircuitViewSet)
|
||||||
|
router.register('virtual-circuit-terminations', views.VirtualCircuitTerminationViewSet)
|
||||||
|
|
||||||
app_name = 'circuits-api'
|
app_name = 'circuits-api'
|
||||||
urlpatterns = router.urls
|
urlpatterns = router.urls
|
||||||
|
@ -93,3 +93,23 @@ class ProviderNetworkViewSet(NetBoxModelViewSet):
|
|||||||
queryset = ProviderNetwork.objects.all()
|
queryset = ProviderNetwork.objects.all()
|
||||||
serializer_class = serializers.ProviderNetworkSerializer
|
serializer_class = serializers.ProviderNetworkSerializer
|
||||||
filterset_class = filtersets.ProviderNetworkFilterSet
|
filterset_class = filtersets.ProviderNetworkFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Virtual circuits
|
||||||
|
#
|
||||||
|
|
||||||
|
class VirtualCircuitViewSet(NetBoxModelViewSet):
|
||||||
|
queryset = VirtualCircuit.objects.all()
|
||||||
|
serializer_class = serializers.VirtualCircuitSerializer
|
||||||
|
filterset_class = filtersets.VirtualCircuitFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Virtual circuit terminations
|
||||||
|
#
|
||||||
|
|
||||||
|
class VirtualCircuitTerminationViewSet(PassThroughPortMixin, NetBoxModelViewSet):
|
||||||
|
queryset = VirtualCircuitTermination.objects.all()
|
||||||
|
serializer_class = serializers.VirtualCircuitTerminationSerializer
|
||||||
|
filterset_class = filtersets.VirtualCircuitTerminationFilterSet
|
||||||
|
@ -92,3 +92,19 @@ class CircuitPriorityChoices(ChoiceSet):
|
|||||||
(PRIORITY_TERTIARY, _('Tertiary')),
|
(PRIORITY_TERTIARY, _('Tertiary')),
|
||||||
(PRIORITY_INACTIVE, _('Inactive')),
|
(PRIORITY_INACTIVE, _('Inactive')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Virtual circuits
|
||||||
|
#
|
||||||
|
|
||||||
|
class VirtualCircuitTerminationRoleChoices(ChoiceSet):
|
||||||
|
ROLE_PEER = 'peer'
|
||||||
|
ROLE_HUB = 'hub'
|
||||||
|
ROLE_SPOKE = 'spoke'
|
||||||
|
|
||||||
|
CHOICES = [
|
||||||
|
(ROLE_PEER, _('Peer'), 'green'),
|
||||||
|
(ROLE_HUB, _('Hub'), 'blue'),
|
||||||
|
(ROLE_SPOKE, _('Spoke'), 'orange'),
|
||||||
|
]
|
||||||
|
@ -404,3 +404,103 @@ class CircuitGroupAssignmentFilterSet(NetBoxModelFilterSet):
|
|||||||
Q(circuit__cid__icontains=value) |
|
Q(circuit__cid__icontains=value) |
|
||||||
Q(group__name__icontains=value)
|
Q(group__name__icontains=value)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
||||||
|
provider_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='provider_network__provider',
|
||||||
|
queryset=Provider.objects.all(),
|
||||||
|
label=_('Provider (ID)'),
|
||||||
|
)
|
||||||
|
provider = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='provider_network__provider__slug',
|
||||||
|
queryset=Provider.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label=_('Provider (slug)'),
|
||||||
|
)
|
||||||
|
provider_account_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='provider_account',
|
||||||
|
queryset=ProviderAccount.objects.all(),
|
||||||
|
label=_('Provider account (ID)'),
|
||||||
|
)
|
||||||
|
provider_account = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='provider_account__account',
|
||||||
|
queryset=Provider.objects.all(),
|
||||||
|
to_field_name='account',
|
||||||
|
label=_('Provider account (account)'),
|
||||||
|
)
|
||||||
|
provider_network_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=ProviderNetwork.objects.all(),
|
||||||
|
label=_('Provider network (ID)'),
|
||||||
|
)
|
||||||
|
status = django_filters.MultipleChoiceFilter(
|
||||||
|
choices=CircuitStatusChoices,
|
||||||
|
null_value=None
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = VirtualCircuit
|
||||||
|
fields = ('id', 'cid', 'description')
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(
|
||||||
|
Q(cid__icontains=value) |
|
||||||
|
Q(description__icontains=value) |
|
||||||
|
Q(comments__icontains=value)
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitTerminationFilterSet(NetBoxModelFilterSet):
|
||||||
|
q = django_filters.CharFilter(
|
||||||
|
method='search',
|
||||||
|
label=_('Search'),
|
||||||
|
)
|
||||||
|
virtual_circuit_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=VirtualCircuit.objects.all(),
|
||||||
|
label=_('Virtual circuit'),
|
||||||
|
)
|
||||||
|
role = django_filters.MultipleChoiceFilter(
|
||||||
|
choices=VirtualCircuitTerminationRoleChoices,
|
||||||
|
null_value=None
|
||||||
|
)
|
||||||
|
provider_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='virtual_circuit__provider_network__provider',
|
||||||
|
queryset=Provider.objects.all(),
|
||||||
|
label=_('Provider (ID)'),
|
||||||
|
)
|
||||||
|
provider = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='virtual_circuit__provider_network__provider__slug',
|
||||||
|
queryset=Provider.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label=_('Provider (slug)'),
|
||||||
|
)
|
||||||
|
provider_account_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='virtual_circuit__provider_account',
|
||||||
|
queryset=ProviderAccount.objects.all(),
|
||||||
|
label=_('Provider account (ID)'),
|
||||||
|
)
|
||||||
|
provider_account = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='virtual_circuit__provider_account__account',
|
||||||
|
queryset=Provider.objects.all(),
|
||||||
|
to_field_name='account',
|
||||||
|
label=_('Provider account (account)'),
|
||||||
|
)
|
||||||
|
provider_network_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=ProviderNetwork.objects.all(),
|
||||||
|
field_name='virtual_circuit__provider_network',
|
||||||
|
label=_('Provider network (ID)'),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = VirtualCircuitTermination
|
||||||
|
fields = ('id', 'interface_id')
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(
|
||||||
|
Q(circuit__cid__icontains=value) |
|
||||||
|
Q(description__icontains=value)
|
||||||
|
).distinct()
|
||||||
|
@ -3,7 +3,9 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from circuits.choices import CircuitCommitRateChoices, CircuitPriorityChoices, CircuitStatusChoices
|
from circuits.choices import (
|
||||||
|
CircuitCommitRateChoices, CircuitPriorityChoices, CircuitStatusChoices, VirtualCircuitTerminationRoleChoices,
|
||||||
|
)
|
||||||
from circuits.constants import CIRCUIT_TERMINATION_TERMINATION_TYPES
|
from circuits.constants import CIRCUIT_TERMINATION_TERMINATION_TYPES
|
||||||
from circuits.models import *
|
from circuits.models import *
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
@ -28,6 +30,8 @@ __all__ = (
|
|||||||
'ProviderBulkEditForm',
|
'ProviderBulkEditForm',
|
||||||
'ProviderAccountBulkEditForm',
|
'ProviderAccountBulkEditForm',
|
||||||
'ProviderNetworkBulkEditForm',
|
'ProviderNetworkBulkEditForm',
|
||||||
|
'VirtualCircuitBulkEditForm',
|
||||||
|
'VirtualCircuitTerminationBulkEditForm',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -291,3 +295,62 @@ class CircuitGroupAssignmentBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
FieldSet('circuit', 'priority'),
|
FieldSet('circuit', 'priority'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('priority',)
|
nullable_fields = ('priority',)
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
|
provider_network = DynamicModelChoiceField(
|
||||||
|
label=_('Provider network'),
|
||||||
|
queryset=ProviderNetwork.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
provider_account = DynamicModelChoiceField(
|
||||||
|
label=_('Provider account'),
|
||||||
|
queryset=ProviderAccount.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
status = forms.ChoiceField(
|
||||||
|
label=_('Status'),
|
||||||
|
choices=add_blank_choice(CircuitStatusChoices),
|
||||||
|
required=False,
|
||||||
|
initial=''
|
||||||
|
)
|
||||||
|
tenant = DynamicModelChoiceField(
|
||||||
|
label=_('Tenant'),
|
||||||
|
queryset=Tenant.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
description = forms.CharField(
|
||||||
|
label=_('Description'),
|
||||||
|
max_length=100,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
comments = CommentField()
|
||||||
|
|
||||||
|
model = VirtualCircuit
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet('provider_network', 'provider_account', 'status', 'description', name=_('Virtual circuit')),
|
||||||
|
FieldSet('tenant', name=_('Tenancy')),
|
||||||
|
)
|
||||||
|
nullable_fields = (
|
||||||
|
'provider_account', 'tenant', 'description', 'comments',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitTerminationBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
|
role = forms.ChoiceField(
|
||||||
|
label=_('Role'),
|
||||||
|
choices=add_blank_choice(VirtualCircuitTerminationRoleChoices),
|
||||||
|
required=False,
|
||||||
|
initial=''
|
||||||
|
)
|
||||||
|
description = forms.CharField(
|
||||||
|
label=_('Description'),
|
||||||
|
max_length=200,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
model = VirtualCircuitTermination
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet('role', 'description'),
|
||||||
|
)
|
||||||
|
nullable_fields = ('description',)
|
||||||
|
@ -5,6 +5,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from circuits.choices import *
|
from circuits.choices import *
|
||||||
from circuits.constants import *
|
from circuits.constants import *
|
||||||
from circuits.models import *
|
from circuits.models import *
|
||||||
|
from dcim.models import Interface
|
||||||
from netbox.choices import DistanceUnitChoices
|
from netbox.choices import DistanceUnitChoices
|
||||||
from netbox.forms import NetBoxModelImportForm
|
from netbox.forms import NetBoxModelImportForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
@ -20,6 +21,8 @@ __all__ = (
|
|||||||
'ProviderImportForm',
|
'ProviderImportForm',
|
||||||
'ProviderAccountImportForm',
|
'ProviderAccountImportForm',
|
||||||
'ProviderNetworkImportForm',
|
'ProviderNetworkImportForm',
|
||||||
|
'VirtualCircuitImportForm',
|
||||||
|
'VirtualCircuitTerminationImportForm',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -179,3 +182,61 @@ class CircuitGroupAssignmentImportForm(NetBoxModelImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = CircuitGroupAssignment
|
model = CircuitGroupAssignment
|
||||||
fields = ('circuit', 'group', 'priority')
|
fields = ('circuit', 'group', 'priority')
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitImportForm(NetBoxModelImportForm):
|
||||||
|
provider_network = CSVModelChoiceField(
|
||||||
|
label=_('Provider network'),
|
||||||
|
queryset=ProviderNetwork.objects.all(),
|
||||||
|
to_field_name='cid',
|
||||||
|
help_text=_('The network to which this virtual circuit belongs')
|
||||||
|
)
|
||||||
|
provider_account = CSVModelChoiceField(
|
||||||
|
label=_('Provider account'),
|
||||||
|
queryset=ProviderAccount.objects.all(),
|
||||||
|
to_field_name='name',
|
||||||
|
help_text=_('Assigned provider account (if any)'),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
status = CSVChoiceField(
|
||||||
|
label=_('Status'),
|
||||||
|
choices=CircuitStatusChoices,
|
||||||
|
help_text=_('Operational status')
|
||||||
|
)
|
||||||
|
tenant = CSVModelChoiceField(
|
||||||
|
label=_('Tenant'),
|
||||||
|
queryset=Tenant.objects.all(),
|
||||||
|
required=False,
|
||||||
|
to_field_name='name',
|
||||||
|
help_text=_('Assigned tenant')
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = VirtualCircuit
|
||||||
|
fields = [
|
||||||
|
'cid', 'provider_network', 'provider_account', 'status', 'tenant','description', 'comments', 'tags',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitTerminationImportForm(NetBoxModelImportForm):
|
||||||
|
virtual_circuit = CSVModelChoiceField(
|
||||||
|
label=_('Virtual circuit'),
|
||||||
|
queryset=VirtualCircuit.objects.all(),
|
||||||
|
to_field_name='cid',
|
||||||
|
)
|
||||||
|
role = CSVChoiceField(
|
||||||
|
label=_('Role'),
|
||||||
|
choices=VirtualCircuitTerminationRoleChoices,
|
||||||
|
help_text=_('Operational role')
|
||||||
|
)
|
||||||
|
interface = CSVModelChoiceField(
|
||||||
|
label=_('Interface'),
|
||||||
|
queryset=Interface.objects.all(),
|
||||||
|
to_field_name='pk',
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = VirtualCircuitTermination
|
||||||
|
fields = [
|
||||||
|
'virtual_circuit', 'description', 'tags'
|
||||||
|
]
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from circuits.choices import CircuitCommitRateChoices, CircuitPriorityChoices, CircuitStatusChoices, CircuitTerminationSideChoices
|
from circuits.choices import (
|
||||||
|
CircuitCommitRateChoices, CircuitPriorityChoices, CircuitStatusChoices, CircuitTerminationSideChoices,
|
||||||
|
VirtualCircuitTerminationRoleChoices,
|
||||||
|
)
|
||||||
from circuits.models import *
|
from circuits.models import *
|
||||||
from dcim.models import Location, Region, Site, SiteGroup
|
from dcim.models import Location, Region, Site, SiteGroup
|
||||||
from ipam.models import ASN
|
from ipam.models import ASN
|
||||||
@ -22,6 +25,8 @@ __all__ = (
|
|||||||
'ProviderFilterForm',
|
'ProviderFilterForm',
|
||||||
'ProviderAccountFilterForm',
|
'ProviderAccountFilterForm',
|
||||||
'ProviderNetworkFilterForm',
|
'ProviderNetworkFilterForm',
|
||||||
|
'VirtualCircuitFilterForm',
|
||||||
|
'VirtualCircuitTerminationFilterForm',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -292,3 +297,74 @@ class CircuitGroupAssignmentFilterForm(NetBoxModelFilterSetForm):
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||||
|
model = VirtualCircuit
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
|
FieldSet('provider_id', 'provider_account_id', 'provider_network_id', name=_('Provider')),
|
||||||
|
FieldSet('status', name=_('Attributes')),
|
||||||
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
|
)
|
||||||
|
selector_fields = ('filter_id', 'q', 'provider_id', 'provider_network_id')
|
||||||
|
provider_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Provider.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Provider')
|
||||||
|
)
|
||||||
|
provider_account_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=ProviderAccount.objects.all(),
|
||||||
|
required=False,
|
||||||
|
query_params={
|
||||||
|
'provider_id': '$provider_id'
|
||||||
|
},
|
||||||
|
label=_('Provider account')
|
||||||
|
)
|
||||||
|
provider_network_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=ProviderNetwork.objects.all(),
|
||||||
|
required=False,
|
||||||
|
query_params={
|
||||||
|
'provider_id': '$provider_id'
|
||||||
|
},
|
||||||
|
label=_('Provider network')
|
||||||
|
)
|
||||||
|
status = forms.MultipleChoiceField(
|
||||||
|
label=_('Status'),
|
||||||
|
choices=CircuitStatusChoices,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitTerminationFilterForm(NetBoxModelFilterSetForm):
|
||||||
|
model = VirtualCircuitTermination
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
|
FieldSet('virtual_circuit_id', 'role', name=_('Virtual circuit')),
|
||||||
|
FieldSet('provider_id', 'provider_account_id', 'provider_network_id', name=_('Provider')),
|
||||||
|
)
|
||||||
|
virtual_circuit_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=VirtualCircuit.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Virtual circuit')
|
||||||
|
)
|
||||||
|
role = forms.MultipleChoiceField(
|
||||||
|
label=_('ROle'),
|
||||||
|
choices=VirtualCircuitTerminationRoleChoices,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
provider_network_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=ProviderNetwork.objects.all(),
|
||||||
|
required=False,
|
||||||
|
query_params={
|
||||||
|
'provider_id': '$provider_id'
|
||||||
|
},
|
||||||
|
label=_('Provider network')
|
||||||
|
)
|
||||||
|
provider_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Provider.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Provider')
|
||||||
|
)
|
||||||
|
tag = TagFilterField(model)
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
|
from django import forms
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from circuits.choices import CircuitCommitRateChoices, CircuitTerminationPortSpeedChoices
|
from circuits.choices import (
|
||||||
|
CircuitCommitRateChoices, CircuitTerminationPortSpeedChoices, VirtualCircuitTerminationRoleChoices,
|
||||||
|
)
|
||||||
from circuits.constants import *
|
from circuits.constants import *
|
||||||
from circuits.models import *
|
from circuits.models import *
|
||||||
from dcim.models import Site
|
from dcim.models import Interface, Site
|
||||||
from ipam.models import ASN
|
from ipam.models import ASN
|
||||||
from netbox.forms import NetBoxModelForm
|
from netbox.forms import NetBoxModelForm
|
||||||
from tenancy.forms import TenancyForm
|
from tenancy.forms import TenancyForm
|
||||||
from utilities.forms import get_field_value
|
from utilities.forms import get_field_value
|
||||||
from utilities.forms.fields import CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField
|
from utilities.forms.fields import (
|
||||||
|
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField,
|
||||||
|
)
|
||||||
from utilities.forms.rendering import FieldSet, InlineFields
|
from utilities.forms.rendering import FieldSet, InlineFields
|
||||||
from utilities.forms.widgets import DatePicker, HTMXSelect, NumberWithOptions
|
from utilities.forms.widgets import DatePicker, HTMXSelect, NumberWithOptions
|
||||||
from utilities.templatetags.builtins.filters import bettertitle
|
from utilities.templatetags.builtins.filters import bettertitle
|
||||||
@ -24,6 +29,8 @@ __all__ = (
|
|||||||
'ProviderForm',
|
'ProviderForm',
|
||||||
'ProviderAccountForm',
|
'ProviderAccountForm',
|
||||||
'ProviderNetworkForm',
|
'ProviderNetworkForm',
|
||||||
|
'VirtualCircuitForm',
|
||||||
|
'VirtualCircuitTerminationForm',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -249,3 +256,62 @@ class CircuitGroupAssignmentForm(NetBoxModelForm):
|
|||||||
fields = [
|
fields = [
|
||||||
'group', 'circuit', 'priority', 'tags',
|
'group', 'circuit', 'priority', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitForm(TenancyForm, NetBoxModelForm):
|
||||||
|
provider_network = DynamicModelChoiceField(
|
||||||
|
label=_('Provider network'),
|
||||||
|
queryset=ProviderNetwork.objects.all(),
|
||||||
|
selector=True
|
||||||
|
)
|
||||||
|
provider_account = DynamicModelChoiceField(
|
||||||
|
label=_('Provider account'),
|
||||||
|
queryset=ProviderAccount.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
comments = CommentField()
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet(
|
||||||
|
'provider_network', 'provider_account', 'cid', 'status', 'description', 'tags', name=_('Virtual circuit'),
|
||||||
|
),
|
||||||
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = VirtualCircuit
|
||||||
|
fields = [
|
||||||
|
'cid', 'provider_network', 'provider_account', 'status', 'description', 'tenant_group', 'tenant',
|
||||||
|
'comments', 'tags',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitTerminationForm(NetBoxModelForm):
|
||||||
|
virtual_circuit = DynamicModelChoiceField(
|
||||||
|
label=_('Virtual circuit'),
|
||||||
|
queryset=VirtualCircuit.objects.all(),
|
||||||
|
selector=True
|
||||||
|
)
|
||||||
|
role = forms.ChoiceField(
|
||||||
|
choices=VirtualCircuitTerminationRoleChoices,
|
||||||
|
widget=HTMXSelect(),
|
||||||
|
label=_('Role')
|
||||||
|
)
|
||||||
|
interface = DynamicModelChoiceField(
|
||||||
|
label=_('Interface'),
|
||||||
|
queryset=Interface.objects.all(),
|
||||||
|
selector=True,
|
||||||
|
query_params={
|
||||||
|
'kind': 'virtual',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet('virtual_circuit', 'role', 'description', 'tags', 'interface'),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = VirtualCircuitTermination
|
||||||
|
fields = [
|
||||||
|
'virtual_circuit', 'role', 'interface', 'description', 'tags',
|
||||||
|
]
|
||||||
|
@ -4,14 +4,16 @@ from circuits import filtersets, models
|
|||||||
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'CircuitTerminationFilter',
|
|
||||||
'CircuitFilter',
|
'CircuitFilter',
|
||||||
'CircuitGroupAssignmentFilter',
|
'CircuitGroupAssignmentFilter',
|
||||||
'CircuitGroupFilter',
|
'CircuitGroupFilter',
|
||||||
|
'CircuitTerminationFilter',
|
||||||
'CircuitTypeFilter',
|
'CircuitTypeFilter',
|
||||||
'ProviderFilter',
|
'ProviderFilter',
|
||||||
'ProviderAccountFilter',
|
'ProviderAccountFilter',
|
||||||
'ProviderNetworkFilter',
|
'ProviderNetworkFilter',
|
||||||
|
'VirtualCircuitFilter',
|
||||||
|
'VirtualCircuitTerminationFilter',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -61,3 +63,15 @@ class ProviderAccountFilter(BaseFilterMixin):
|
|||||||
@autotype_decorator(filtersets.ProviderNetworkFilterSet)
|
@autotype_decorator(filtersets.ProviderNetworkFilterSet)
|
||||||
class ProviderNetworkFilter(BaseFilterMixin):
|
class ProviderNetworkFilter(BaseFilterMixin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry_django.filter(models.VirtualCircuit, lookups=True)
|
||||||
|
@autotype_decorator(filtersets.VirtualCircuitFilterSet)
|
||||||
|
class VirtualCircuitFilter(BaseFilterMixin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry_django.filter(models.VirtualCircuitTermination, lookups=True)
|
||||||
|
@autotype_decorator(filtersets.VirtualCircuitTerminationFilterSet)
|
||||||
|
class VirtualCircuitTerminationFilter(BaseFilterMixin):
|
||||||
|
pass
|
||||||
|
@ -31,3 +31,9 @@ class CircuitsQuery:
|
|||||||
|
|
||||||
provider_network: ProviderNetworkType = strawberry_django.field()
|
provider_network: ProviderNetworkType = strawberry_django.field()
|
||||||
provider_network_list: List[ProviderNetworkType] = strawberry_django.field()
|
provider_network_list: List[ProviderNetworkType] = strawberry_django.field()
|
||||||
|
|
||||||
|
virtual_circuit: VirtualCircuitType = strawberry_django.field()
|
||||||
|
virtual_circuit_list: List[VirtualCircuitType] = strawberry_django.field()
|
||||||
|
|
||||||
|
virtual_circuit_termination: VirtualCircuitTerminationType = strawberry_django.field()
|
||||||
|
virtual_circuit_termination_list: List[VirtualCircuitTerminationType] = strawberry_django.field()
|
||||||
|
@ -19,6 +19,8 @@ __all__ = (
|
|||||||
'ProviderType',
|
'ProviderType',
|
||||||
'ProviderAccountType',
|
'ProviderAccountType',
|
||||||
'ProviderNetworkType',
|
'ProviderNetworkType',
|
||||||
|
'VirtualCircuitTerminationType',
|
||||||
|
'VirtualCircuitType',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -120,3 +122,26 @@ class CircuitGroupType(OrganizationalObjectType):
|
|||||||
class CircuitGroupAssignmentType(TagsMixin, BaseObjectType):
|
class CircuitGroupAssignmentType(TagsMixin, BaseObjectType):
|
||||||
group: Annotated["CircuitGroupType", strawberry.lazy('circuits.graphql.types')]
|
group: Annotated["CircuitGroupType", strawberry.lazy('circuits.graphql.types')]
|
||||||
circuit: Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]
|
circuit: Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry_django.type(
|
||||||
|
models.VirtualCircuitTermination,
|
||||||
|
fields='__all__',
|
||||||
|
filters=VirtualCircuitTerminationFilter
|
||||||
|
)
|
||||||
|
class VirtualCircuitTerminationType(CustomFieldsMixin, TagsMixin, CabledObjectMixin, ObjectType):
|
||||||
|
virtual_circuit: Annotated["VirtualCircuitType", strawberry.lazy('circuits.graphql.types')]
|
||||||
|
interface: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry_django.type(
|
||||||
|
models.VirtualCircuit,
|
||||||
|
fields='__all__',
|
||||||
|
filters=VirtualCircuitFilter
|
||||||
|
)
|
||||||
|
class VirtualCircuitType(NetBoxObjectType):
|
||||||
|
provider_network: ProviderNetworkType
|
||||||
|
provider_account: ProviderAccountType | None
|
||||||
|
tenant: TenantType | None
|
||||||
|
|
||||||
|
virtual_terminations: List[VirtualCircuitTerminationType]
|
||||||
|
68
netbox/circuits/migrations/0049_virtual_circuits.py
Normal file
68
netbox/circuits/migrations/0049_virtual_circuits.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2024-11-04 18:46
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import taggit.managers
|
||||||
|
import utilities.json
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('circuits', '0048_circuitterminations_cached_relations'),
|
||||||
|
('dcim', '0196_qinq_svlan'),
|
||||||
|
('extras', '0122_charfield_null_choices'),
|
||||||
|
('tenancy', '0016_charfield_null_choices'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='VirtualCircuit',
|
||||||
|
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)),
|
||||||
|
('description', models.CharField(blank=True, max_length=200)),
|
||||||
|
('comments', models.TextField(blank=True)),
|
||||||
|
('cid', models.CharField(max_length=100)),
|
||||||
|
('status', models.CharField(default='active', max_length=50)),
|
||||||
|
('provider_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='virtual_circuits', to='circuits.provideraccount')),
|
||||||
|
('provider_network', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='virtual_circuits', to='circuits.providernetwork')),
|
||||||
|
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||||
|
('tenant', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='virtual_circuits', to='tenancy.tenant')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'circuit',
|
||||||
|
'verbose_name_plural': 'circuits',
|
||||||
|
'ordering': ['provider_network', 'provider_account', 'cid'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='VirtualCircuitTermination',
|
||||||
|
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)),
|
||||||
|
('role', models.CharField(default='peer', max_length=50)),
|
||||||
|
('description', models.CharField(blank=True, max_length=200)),
|
||||||
|
('interface', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='virtual_circuit_termination', to='dcim.interface')),
|
||||||
|
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||||
|
('virtual_circuit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='terminations', to='circuits.virtualcircuit')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'virtual circuit termination',
|
||||||
|
'verbose_name_plural': 'virtual circuit terminations',
|
||||||
|
'ordering': ['virtual_circuit', 'role', 'pk'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='virtualcircuit',
|
||||||
|
constraint=models.UniqueConstraint(fields=('provider_network', 'cid'), name='circuits_virtualcircuit_unique_provider_network_cid'),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='virtualcircuit',
|
||||||
|
constraint=models.UniqueConstraint(fields=('provider_account', 'cid'), name='circuits_virtualcircuit_unique_provideraccount_cid'),
|
||||||
|
),
|
||||||
|
]
|
@ -1,2 +1,3 @@
|
|||||||
from .circuits import *
|
from .circuits import *
|
||||||
from .providers import *
|
from .providers import *
|
||||||
|
from .virtual_circuits import *
|
||||||
|
141
netbox/circuits/models/virtual_circuits.py
Normal file
141
netbox/circuits/models/virtual_circuits.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from circuits.choices import *
|
||||||
|
from netbox.models import ChangeLoggedModel, PrimaryModel
|
||||||
|
from netbox.models.features import CustomFieldsMixin, CustomLinksMixin, TagsMixin
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'VirtualCircuit',
|
||||||
|
'VirtualCircuitTermination',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuit(PrimaryModel):
|
||||||
|
"""
|
||||||
|
A virtual connection between two or more endpoints, delivered across one or more physical circuits.
|
||||||
|
"""
|
||||||
|
cid = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
verbose_name=_('circuit ID'),
|
||||||
|
help_text=_('Unique circuit ID')
|
||||||
|
)
|
||||||
|
provider_network = models.ForeignKey(
|
||||||
|
to='circuits.ProviderNetwork',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='virtual_circuits'
|
||||||
|
)
|
||||||
|
provider_account = models.ForeignKey(
|
||||||
|
to='circuits.ProviderAccount',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='virtual_circuits',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
status = models.CharField(
|
||||||
|
verbose_name=_('status'),
|
||||||
|
max_length=50,
|
||||||
|
choices=CircuitStatusChoices,
|
||||||
|
default=CircuitStatusChoices.STATUS_ACTIVE
|
||||||
|
)
|
||||||
|
tenant = models.ForeignKey(
|
||||||
|
to='tenancy.Tenant',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='virtual_circuits',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
|
||||||
|
clone_fields = (
|
||||||
|
'provider_network', 'provider_account', 'status', 'tenant', 'description',
|
||||||
|
)
|
||||||
|
prerequisite_models = (
|
||||||
|
'circuits.ProviderNetwork',
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['provider_network', 'provider_account', 'cid']
|
||||||
|
constraints = (
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=('provider_network', 'cid'),
|
||||||
|
name='%(app_label)s_%(class)s_unique_provider_network_cid'
|
||||||
|
),
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=('provider_account', 'cid'),
|
||||||
|
name='%(app_label)s_%(class)s_unique_provideraccount_cid'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
verbose_name = _('virtual circuit')
|
||||||
|
verbose_name_plural = _('virtual circuits')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.cid
|
||||||
|
|
||||||
|
def get_status_color(self):
|
||||||
|
return CircuitStatusChoices.colors.get(self.status)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
super().clean()
|
||||||
|
|
||||||
|
if self.provider_account and self.provider_network.provider != self.provider_account.provider:
|
||||||
|
raise ValidationError({
|
||||||
|
'provider_account': "The assigned account must belong to the provider of the assigned network."
|
||||||
|
})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def provider(self):
|
||||||
|
return self.provider_network.provider
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitTermination(
|
||||||
|
CustomFieldsMixin,
|
||||||
|
CustomLinksMixin,
|
||||||
|
TagsMixin,
|
||||||
|
ChangeLoggedModel
|
||||||
|
):
|
||||||
|
virtual_circuit = models.ForeignKey(
|
||||||
|
to='circuits.VirtualCircuit',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='terminations'
|
||||||
|
)
|
||||||
|
role = models.CharField(
|
||||||
|
verbose_name=_('role'),
|
||||||
|
max_length=50,
|
||||||
|
choices=VirtualCircuitTerminationRoleChoices,
|
||||||
|
default=VirtualCircuitTerminationRoleChoices.ROLE_PEER
|
||||||
|
)
|
||||||
|
interface = models.OneToOneField(
|
||||||
|
to='dcim.Interface',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='virtual_circuit_termination'
|
||||||
|
)
|
||||||
|
description = models.CharField(
|
||||||
|
verbose_name=_('description'),
|
||||||
|
max_length=200,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['virtual_circuit', 'role', 'pk']
|
||||||
|
verbose_name = _('virtual circuit termination')
|
||||||
|
verbose_name_plural = _('virtual circuit terminations')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.virtual_circuit}: {self.get_role_display()} termination'
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('circuits:virtualcircuittermination', args=[self.pk])
|
||||||
|
|
||||||
|
def get_role_color(self):
|
||||||
|
return VirtualCircuitTerminationRoleChoices.colors.get(self.role)
|
||||||
|
|
||||||
|
def to_objectchange(self, action):
|
||||||
|
objectchange = super().to_objectchange(action)
|
||||||
|
objectchange.related_object = self.virtual_circuit
|
||||||
|
return objectchange
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parent_object(self):
|
||||||
|
return self.virtual_circuit
|
@ -80,3 +80,23 @@ class ProviderNetworkIndex(SearchIndex):
|
|||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
display_attrs = ('provider', 'service_id', 'description')
|
display_attrs = ('provider', 'service_id', 'description')
|
||||||
|
|
||||||
|
|
||||||
|
@register_search
|
||||||
|
class VirtualCircuitIndex(SearchIndex):
|
||||||
|
model = models.VirtualCircuit
|
||||||
|
fields = (
|
||||||
|
('cid', 100),
|
||||||
|
('description', 500),
|
||||||
|
('comments', 5000),
|
||||||
|
)
|
||||||
|
display_attrs = ('provider', 'provider_network', 'provider_account', 'status', 'tenant', 'description')
|
||||||
|
|
||||||
|
|
||||||
|
@register_search
|
||||||
|
class VirtualCircuitTerminationIndex(SearchIndex):
|
||||||
|
model = models.VirtualCircuitTermination
|
||||||
|
fields = (
|
||||||
|
('description', 500),
|
||||||
|
)
|
||||||
|
display_attrs = ('virtual_circuit', 'role', 'description')
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
from .circuits import *
|
from .circuits import *
|
||||||
from .columns import *
|
from .columns import *
|
||||||
from .providers import *
|
from .providers import *
|
||||||
|
from .virtual_circuits import *
|
||||||
|
95
netbox/circuits/tables/virtual_circuits.py
Normal file
95
netbox/circuits/tables/virtual_circuits.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import django_tables2 as tables
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from circuits.models import *
|
||||||
|
from netbox.tables import NetBoxTable, columns
|
||||||
|
from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'VirtualCircuitTable',
|
||||||
|
'VirtualCircuitTerminationTable',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
||||||
|
cid = tables.Column(
|
||||||
|
linkify=True,
|
||||||
|
verbose_name=_('Circuit ID')
|
||||||
|
)
|
||||||
|
provider = tables.Column(
|
||||||
|
accessor=tables.A('provider_network__provider'),
|
||||||
|
verbose_name=_('Provider'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
provider_network = tables.Column(
|
||||||
|
linkify=True,
|
||||||
|
verbose_name=_('Provider network')
|
||||||
|
)
|
||||||
|
provider_account = tables.Column(
|
||||||
|
linkify=True,
|
||||||
|
verbose_name=_('Account')
|
||||||
|
)
|
||||||
|
status = columns.ChoiceFieldColumn()
|
||||||
|
termination_count = columns.LinkedCountColumn(
|
||||||
|
viewname='circuits:virtualcircuittermination_list',
|
||||||
|
url_params={'virtual_circuit_id': 'pk'},
|
||||||
|
verbose_name=_('Terminations')
|
||||||
|
)
|
||||||
|
comments = columns.MarkdownColumn(
|
||||||
|
verbose_name=_('Comments')
|
||||||
|
)
|
||||||
|
tags = columns.TagColumn(
|
||||||
|
url_name='circuits:virtualcircuit_list'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(NetBoxTable.Meta):
|
||||||
|
model = Circuit
|
||||||
|
fields = (
|
||||||
|
'pk', 'id', 'cid', 'provider', 'provider_account', 'provider_network', 'status', 'tenant', 'tenant_group',
|
||||||
|
'description', 'comments', 'tags', 'created', 'last_updated',
|
||||||
|
)
|
||||||
|
default_columns = (
|
||||||
|
'pk', 'cid', 'provider', 'provider_account', 'provider_network', 'status', 'tenant', 'termination_count',
|
||||||
|
'description',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitTerminationTable(NetBoxTable):
|
||||||
|
virtual_circuit = tables.Column(
|
||||||
|
verbose_name=_('Virtual circuit'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
provider = tables.Column(
|
||||||
|
accessor=tables.A('virtual_circuit__provider_network__provider'),
|
||||||
|
verbose_name=_('Provider'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
provider_network = tables.Column(
|
||||||
|
accessor=tables.A('virtual_circuit__provider_network'),
|
||||||
|
linkify=True,
|
||||||
|
verbose_name=_('Provider network')
|
||||||
|
)
|
||||||
|
provider_account = tables.Column(
|
||||||
|
linkify=True,
|
||||||
|
verbose_name=_('Account')
|
||||||
|
)
|
||||||
|
role = columns.ChoiceFieldColumn()
|
||||||
|
device = tables.Column(
|
||||||
|
accessor=tables.A('interface__device'),
|
||||||
|
linkify=True,
|
||||||
|
verbose_name=_('Device')
|
||||||
|
)
|
||||||
|
interface = tables.Column(
|
||||||
|
verbose_name=_('Interface'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(NetBoxTable.Meta):
|
||||||
|
model = VirtualCircuitTermination
|
||||||
|
fields = (
|
||||||
|
'pk', 'id', 'virtual_circuit', 'provider', 'provider_network', 'provider_account', 'role', 'interfaces',
|
||||||
|
'description', 'created', 'last_updated', 'actions',
|
||||||
|
)
|
||||||
|
default_columns = (
|
||||||
|
'pk', 'id', 'virtual_circuit', 'role', 'device', 'interface', 'description',
|
||||||
|
)
|
@ -70,4 +70,20 @@ urlpatterns = [
|
|||||||
path('circuit-group-assignments/edit/', views.CircuitGroupAssignmentBulkEditView.as_view(), name='circuitgroupassignment_bulk_edit'),
|
path('circuit-group-assignments/edit/', views.CircuitGroupAssignmentBulkEditView.as_view(), name='circuitgroupassignment_bulk_edit'),
|
||||||
path('circuit-group-assignments/delete/', views.CircuitGroupAssignmentBulkDeleteView.as_view(), name='circuitgroupassignment_bulk_delete'),
|
path('circuit-group-assignments/delete/', views.CircuitGroupAssignmentBulkDeleteView.as_view(), name='circuitgroupassignment_bulk_delete'),
|
||||||
path('circuit-group-assignments/<int:pk>/', include(get_model_urls('circuits', 'circuitgroupassignment'))),
|
path('circuit-group-assignments/<int:pk>/', include(get_model_urls('circuits', 'circuitgroupassignment'))),
|
||||||
|
|
||||||
|
# Virtual circuits
|
||||||
|
path('virtual-circuits/', views.VirtualCircuitListView.as_view(), name='virtualcircuit_list'),
|
||||||
|
path('virtual-circuits/add/', views.VirtualCircuitEditView.as_view(), name='virtualcircuit_add'),
|
||||||
|
path('virtual-circuits/import/', views.VirtualCircuitBulkImportView.as_view(), name='virtualcircuit_import'),
|
||||||
|
path('virtual-circuits/edit/', views.VirtualCircuitBulkEditView.as_view(), name='virtualcircuit_bulk_edit'),
|
||||||
|
path('virtual-circuits/delete/', views.VirtualCircuitBulkDeleteView.as_view(), name='virtualcircuit_bulk_delete'),
|
||||||
|
path('virtual-circuits/<int:pk>/', include(get_model_urls('circuits', 'virtualcircuit'))),
|
||||||
|
|
||||||
|
# Virtual circuit terminations
|
||||||
|
path('virtual-circuit-terminations/', views.VirtualCircuitTerminationListView.as_view(), name='virtualcircuittermination_list'),
|
||||||
|
path('virtual-circuit-terminations/add/', views.VirtualCircuitTerminationEditView.as_view(), name='virtualcircuittermination_add'),
|
||||||
|
path('virtual-circuit-terminations/import/', views.VirtualCircuitTerminationBulkImportView.as_view(), name='virtualcircuittermination_import'),
|
||||||
|
path('virtual-circuit-terminations/edit/', views.VirtualCircuitTerminationBulkEditView.as_view(), name='virtualcircuittermination_bulk_edit'),
|
||||||
|
path('virtual-circuit-terminations/delete/', views.VirtualCircuitTerminationBulkDeleteView.as_view(), name='virtualcircuittermination_bulk_delete'),
|
||||||
|
path('virtual-circuit-terminations/<int:pk>/', include(get_model_urls('circuits', 'virtualcircuittermination'))),
|
||||||
]
|
]
|
||||||
|
@ -537,3 +537,109 @@ class CircuitGroupAssignmentBulkDeleteView(generic.BulkDeleteView):
|
|||||||
queryset = CircuitGroupAssignment.objects.all()
|
queryset = CircuitGroupAssignment.objects.all()
|
||||||
filterset = filtersets.CircuitGroupAssignmentFilterSet
|
filterset = filtersets.CircuitGroupAssignmentFilterSet
|
||||||
table = tables.CircuitGroupAssignmentTable
|
table = tables.CircuitGroupAssignmentTable
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Virtual circuits
|
||||||
|
#
|
||||||
|
|
||||||
|
class VirtualCircuitListView(generic.ObjectListView):
|
||||||
|
queryset = VirtualCircuit.objects.annotate(
|
||||||
|
termination_count=count_related(VirtualCircuitTermination, 'virtual_circuit')
|
||||||
|
)
|
||||||
|
filterset = filtersets.VirtualCircuitFilterSet
|
||||||
|
filterset_form = forms.VirtualCircuitFilterForm
|
||||||
|
table = tables.VirtualCircuitTable
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(VirtualCircuit)
|
||||||
|
class VirtualCircuitView(generic.ObjectView):
|
||||||
|
queryset = VirtualCircuit.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(VirtualCircuit, 'edit')
|
||||||
|
class VirtualCircuitEditView(generic.ObjectEditView):
|
||||||
|
queryset = VirtualCircuit.objects.all()
|
||||||
|
form = forms.VirtualCircuitForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(VirtualCircuit, 'delete')
|
||||||
|
class VirtualCircuitDeleteView(generic.ObjectDeleteView):
|
||||||
|
queryset = VirtualCircuit.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitBulkImportView(generic.BulkImportView):
|
||||||
|
queryset = VirtualCircuit.objects.all()
|
||||||
|
model_form = forms.VirtualCircuitImportForm
|
||||||
|
# additional_permissions = [
|
||||||
|
# 'circuits.add_circuittermination',
|
||||||
|
# ]
|
||||||
|
# related_object_forms = {
|
||||||
|
# 'terminations': forms.VirtualCircuitTerminationImportRelatedForm,
|
||||||
|
# }
|
||||||
|
|
||||||
|
def prep_related_object_data(self, parent, data):
|
||||||
|
data.update({'circuit': parent})
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitBulkEditView(generic.BulkEditView):
|
||||||
|
queryset = VirtualCircuit.objects.annotate(
|
||||||
|
termination_count=count_related(VirtualCircuitTermination, 'virtual_circuit')
|
||||||
|
)
|
||||||
|
filterset = filtersets.VirtualCircuitFilterSet
|
||||||
|
table = tables.VirtualCircuitTable
|
||||||
|
form = forms.VirtualCircuitBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitBulkDeleteView(generic.BulkDeleteView):
|
||||||
|
queryset = VirtualCircuit.objects.annotate(
|
||||||
|
termination_count=count_related(VirtualCircuitTermination, 'virtual_circuit')
|
||||||
|
)
|
||||||
|
filterset = filtersets.VirtualCircuitFilterSet
|
||||||
|
table = tables.VirtualCircuitTable
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Virtual circuit terminations
|
||||||
|
#
|
||||||
|
|
||||||
|
class VirtualCircuitTerminationListView(generic.ObjectListView):
|
||||||
|
queryset = VirtualCircuitTermination.objects.all()
|
||||||
|
filterset = filtersets.VirtualCircuitTerminationFilterSet
|
||||||
|
filterset_form = forms.VirtualCircuitTerminationFilterForm
|
||||||
|
table = tables.VirtualCircuitTerminationTable
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(VirtualCircuitTermination)
|
||||||
|
class VirtualCircuitTerminationView(generic.ObjectView):
|
||||||
|
queryset = VirtualCircuitTermination.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(VirtualCircuitTermination, 'edit')
|
||||||
|
class VirtualCircuitTerminationEditView(generic.ObjectEditView):
|
||||||
|
queryset = VirtualCircuitTermination.objects.all()
|
||||||
|
form = forms.VirtualCircuitTerminationForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(VirtualCircuitTermination, 'delete')
|
||||||
|
class VirtualCircuitTerminationDeleteView(generic.ObjectDeleteView):
|
||||||
|
queryset = VirtualCircuitTermination.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitTerminationBulkImportView(generic.BulkImportView):
|
||||||
|
queryset = VirtualCircuitTermination.objects.all()
|
||||||
|
model_form = forms.VirtualCircuitTerminationImportForm
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitTerminationBulkEditView(generic.BulkEditView):
|
||||||
|
queryset = VirtualCircuitTermination.objects.all()
|
||||||
|
filterset = filtersets.VirtualCircuitTerminationFilterSet
|
||||||
|
table = tables.VirtualCircuitTerminationTable
|
||||||
|
form = forms.VirtualCircuitTerminationBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualCircuitTerminationBulkDeleteView(generic.BulkDeleteView):
|
||||||
|
queryset = VirtualCircuitTermination.objects.all()
|
||||||
|
filterset = filtersets.VirtualCircuitTerminationFilterSet
|
||||||
|
table = tables.VirtualCircuitTerminationTable
|
||||||
|
@ -4,7 +4,7 @@ from django.utils.translation import gettext as _
|
|||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
|
|
||||||
from circuits.models import CircuitTermination
|
from circuits.models import CircuitTermination, VirtualCircuit, VirtualCircuitTermination
|
||||||
from extras.filtersets import LocalConfigContextFilterSet
|
from extras.filtersets import LocalConfigContextFilterSet
|
||||||
from extras.models import ConfigTemplate
|
from extras.models import ConfigTemplate
|
||||||
from ipam.filtersets import PrimaryIPFilterSet
|
from ipam.filtersets import PrimaryIPFilterSet
|
||||||
@ -1747,6 +1747,16 @@ class InterfaceFilterSet(
|
|||||||
queryset=WirelessLink.objects.all(),
|
queryset=WirelessLink.objects.all(),
|
||||||
label=_('Wireless link')
|
label=_('Wireless link')
|
||||||
)
|
)
|
||||||
|
virtual_circuit_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='virtual_circuit_termination__virtual_circuit',
|
||||||
|
queryset=VirtualCircuit.objects.all(),
|
||||||
|
label=_('Virtual circuit (ID)'),
|
||||||
|
)
|
||||||
|
virtual_circuit_termination_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='virtual_circuit_termination',
|
||||||
|
queryset=VirtualCircuitTermination.objects.all(),
|
||||||
|
label=_('Virtual circuit termination (ID)'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Interface
|
model = Interface
|
||||||
|
@ -1167,6 +1167,8 @@ class TagTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
'tunnelgroup',
|
'tunnelgroup',
|
||||||
'tunneltermination',
|
'tunneltermination',
|
||||||
'virtualchassis',
|
'virtualchassis',
|
||||||
|
'virtualcircuit',
|
||||||
|
'virtualcircuittermination',
|
||||||
'virtualdevicecontext',
|
'virtualdevicecontext',
|
||||||
'virtualdisk',
|
'virtualdisk',
|
||||||
'virtualmachine',
|
'virtualmachine',
|
||||||
|
@ -278,6 +278,13 @@ CIRCUITS_MENU = Menu(
|
|||||||
get_model_item('circuits', 'circuittermination', _('Circuit Terminations')),
|
get_model_item('circuits', 'circuittermination', _('Circuit Terminations')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
MenuGroup(
|
||||||
|
label=_('Virtual Circuits'),
|
||||||
|
items=(
|
||||||
|
get_model_item('circuits', 'virtualcircuit', _('Virtual Circuits')),
|
||||||
|
get_model_item('circuits', 'virtualcircuittermination', _('Virtual Circuit Terminations')),
|
||||||
|
),
|
||||||
|
),
|
||||||
MenuGroup(
|
MenuGroup(
|
||||||
label=_('Providers'),
|
label=_('Providers'),
|
||||||
items=(
|
items=(
|
||||||
|
@ -50,6 +50,19 @@
|
|||||||
<h2 class="card-header">{% trans "Circuits" %}</h2>
|
<h2 class="card-header">{% trans "Circuits" %}</h2>
|
||||||
{% htmx_table 'circuits:circuit_list' provider_network_id=object.pk %}
|
{% htmx_table 'circuits:circuit_list' provider_network_id=object.pk %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h2 class="card-header">
|
||||||
|
{% trans "Virtual Circuits" %}
|
||||||
|
{% if perms.circuits.add_virtualcircuit %}
|
||||||
|
<div class="card-actions">
|
||||||
|
<a href="{% url 'circuits:virtualcircuit_add' %}?provider_network={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-ghost-primary btn-sm">
|
||||||
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add a Virtual Circuit" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</h2>
|
||||||
|
{% htmx_table 'circuits:virtualcircuit_list' provider_network_id=object.pk %}
|
||||||
|
</div>
|
||||||
{% plugin_full_width_page object %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
84
netbox/templates/circuits/virtualcircuit.html
Normal file
84
netbox/templates/circuits/virtualcircuit.html
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
{% extends 'generic/object.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load plugins %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
{{ block.super }}
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{% url 'circuits:virtualcircuit_list' %}?provider_id={{ object.provider.pk }}">{{ object.provider }}</a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{% url 'circuits:virtualcircuit_list' %}?provider_network_id={{ object.provider_network.pk }}">{{ object.provider_network }}</a>
|
||||||
|
</li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<h2 class="card-header">{% trans "Virtual circuit" %}</h2>
|
||||||
|
<table class="table table-hover attr-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Provider" %}</th>
|
||||||
|
<td>{{ object.provider|linkify }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Provider Network" %}</th>
|
||||||
|
<td>{{ object.provider_network|linkify }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Provider account" %}</th>
|
||||||
|
<td>{{ object.provider_account|linkify|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Circuit ID" %}</th>
|
||||||
|
<td>{{ object.cid }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Status" %}</th>
|
||||||
|
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Tenant" %}</th>
|
||||||
|
<td>
|
||||||
|
{% if object.tenant.group %}
|
||||||
|
{{ object.tenant.group|linkify }} /
|
||||||
|
{% endif %}
|
||||||
|
{{ object.tenant|linkify|placeholder }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Description" %}</th>
|
||||||
|
<td>{{ object.description|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% include 'inc/panels/tags.html' %}
|
||||||
|
{% plugin_left_page object %}
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-6">
|
||||||
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
|
{% include 'inc/panels/comments.html' %}
|
||||||
|
{% plugin_right_page object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-12">
|
||||||
|
<div class="card">
|
||||||
|
<h2 class="card-header">
|
||||||
|
{% trans "Terminations" %}
|
||||||
|
{% if perms.circuits.add_virtualcircuittermination %}
|
||||||
|
<div class="card-actions">
|
||||||
|
<a href="{% url 'circuits:virtualcircuittermination_add' %}?virtual_circuit={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-ghost-primary btn-sm">
|
||||||
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add Termination" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</h2>
|
||||||
|
{% htmx_table 'circuits:virtualcircuittermination_list' virtual_circuit_id=object.pk %}
|
||||||
|
</div>
|
||||||
|
{% plugin_full_width_page object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
59
netbox/templates/circuits/virtualcircuittermination.html
Normal file
59
netbox/templates/circuits/virtualcircuittermination.html
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
{% extends 'generic/object.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load plugins %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
{{ block.super }}
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{% url 'circuits:virtualcircuit_list' %}?provider_id={{ object.virtual_circuit.provider.pk }}">{{ object.virtual_circuit.provider }}</a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{% url 'circuits:virtualcircuit_list' %}?provider_network_id={{ object.virtual_circuit.provider_network.pk }}">{{ object.virtual_circuit.provider_network }}</a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{% url 'circuits:virtualcircuittermination_list' %}?virtual_circuit_id={{ object.virtual_circuit.pk }}">{{ object.virtual_circuit }}</a>
|
||||||
|
</li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<table class="table table-hover attr-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Provider" %}</th>
|
||||||
|
<td>{{ object.virtual_circuit.provider|linkify }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Provider Network" %}</th>
|
||||||
|
<td>{{ object.virtual_circuit.provider_network|linkify }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Provider account" %}</th>
|
||||||
|
<td>{{ object.virtual_circuit.provider_account|linkify|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Virtual circuit" %}</th>
|
||||||
|
<td>{{ object.virtual_circuit|linkify }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Role" %}</th>
|
||||||
|
<td>{% badge object.get_role_display bg_color=object.get_role_color %}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% include 'inc/panels/tags.html' %}
|
||||||
|
{% plugin_left_page object %}
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-6">
|
||||||
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
|
{% plugin_right_page object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-12">
|
||||||
|
{% plugin_full_width_page object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue
Block a user