diff --git a/netbox/core/management/commands/nbshell.py b/netbox/core/management/commands/nbshell.py
index 674a878c7..fd86627d2 100644
--- a/netbox/core/management/commands/nbshell.py
+++ b/netbox/core/management/commands/nbshell.py
@@ -9,7 +9,7 @@ from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand
-APPS = ('circuits', 'core', 'dcim', 'extras', 'ipam', 'tenancy', 'users', 'virtualization', 'wireless')
+APPS = ('circuits', 'core', 'dcim', 'extras', 'ipam', 'tenancy', 'users', 'virtualization', 'vpn', 'wireless')
BANNER_TEXT = """### NetBox interactive shell ({node})
### Python {python} | Django {django} | NetBox {netbox}
diff --git a/netbox/netbox/api/views.py b/netbox/netbox/api/views.py
index 4e71ca193..cfbe82f14 100644
--- a/netbox/netbox/api/views.py
+++ b/netbox/netbox/api/views.py
@@ -39,6 +39,7 @@ class APIRootView(APIView):
'tenancy': reverse('tenancy-api:api-root', request=request, format=format),
'users': reverse('users-api:api-root', request=request, format=format),
'virtualization': reverse('virtualization-api:api-root', request=request, format=format),
+ 'vpn': reverse('vpn-api:api-root', request=request, format=format),
'wireless': reverse('wireless-api:api-root', request=request, format=format),
})
diff --git a/netbox/templates/vpn/ipsecprofile.html b/netbox/templates/vpn/ipsecprofile.html
new file mode 100644
index 000000000..3f50c39ab
--- /dev/null
+++ b/netbox/templates/vpn/ipsecprofile.html
@@ -0,0 +1,92 @@
+{% extends 'generic/object.html' %}
+{% load helpers %}
+{% load plugins %}
+{% load i18n %}
+
+{% block content %}
+
+
+
+
+
+
+
+ {% trans "Name" %} |
+ {{ object.name }} |
+
+
+ {% trans "Protocol" %} |
+ {{ object.get_protocol_display }} |
+
+
+ {% trans "IKE Version" %} |
+ {{ object.get_ike_version_display }} |
+
+
+ {% trans "Description" %} |
+ {{ object.description|placeholder }} |
+
+
+
+
+ {% include 'inc/panels/custom_fields.html' %}
+ {% include 'inc/panels/tags.html' %}
+ {% include 'inc/panels/comments.html' %}
+ {% plugin_left_page object %}
+
+
+
+
+
+
+
+ {% trans "Encryption" %} |
+ {{ object.get_phase1_encryption_display }} |
+
+
+ {% trans "Authentication" %} |
+ {{ object.get_phase1_authentication_display }} |
+
+
+ {% trans "DH Group" %} |
+ {{ object.get_phase1_group_display }} |
+
+
+ {% trans "SA Lifetime" %} |
+ {{ object.phase1_sa_lifetime|placeholder }} |
+
+
+
+
+
+
+
+
+
+ {% trans "Encryption" %} |
+ {{ object.get_phase2_encryption_display }} |
+
+
+ {% trans "Authentication" %} |
+ {{ object.get_phase2_authentication_display }} |
+
+
+ {% trans "DH Group" %} |
+ {{ object.get_phase2_group_display }} |
+
+
+ {% trans "SA Lifetime" %} |
+ {{ object.phase2_sa_lifetime|placeholder }} |
+
+
+
+
+ {% plugin_right_page object %}
+
+
+
+
+ {% plugin_full_width_page object %}
+
+
+{% endblock %}
diff --git a/netbox/templates/vpn/tunnel.html b/netbox/templates/vpn/tunnel.html
new file mode 100644
index 000000000..2420a1230
--- /dev/null
+++ b/netbox/templates/vpn/tunnel.html
@@ -0,0 +1,81 @@
+{% extends 'generic/object.html' %}
+{% load helpers %}
+{% load plugins %}
+{% load i18n %}
+
+{% block content %}
+
+
+
+
+
+
+
+ {% trans "Name" %} |
+ {{ object.name }} |
+
+
+ {% trans "Status" %} |
+ {% badge object.get_status_display bg_color=object.get_status_color %} |
+
+
+ {% trans "Encapsulation" %} |
+ {{ object.get_encapsulation_display }} |
+
+
+ {% trans "IPSec profile" %} |
+ {{ object.ipsec_profile|linkify|placeholder }} |
+
+
+ {% trans "Tenant" %} |
+
+ {% if object.tenant.group %}
+ {{ object.tenant.group|linkify }} /
+ {% endif %}
+ {{ object.tenant|linkify|placeholder }}
+ |
+
+
+ {% trans "Pre-shared key" %} |
+ {{ object.preshared_key|placeholder }} |
+
+
+ {% trans "Tunnel ID" %} |
+ {{ object.tunnel_id|placeholder }} |
+
+
+ {% trans "Description" %} |
+ {{ object.description|placeholder }} |
+
+
+
+
+ {% plugin_left_page object %}
+
+
+ {% include 'inc/panels/custom_fields.html' %}
+ {% include 'inc/panels/tags.html' %}
+ {% include 'inc/panels/comments.html' %}
+ {% plugin_right_page object %}
+
+
+
+
+
+
+
+ {% if perms.vpn.add_tunneltermination %}
+
+ {% endif %}
+
+ {% plugin_full_width_page object %}
+
+
+{% endblock %}
diff --git a/netbox/vpn/api/serializers.py b/netbox/vpn/api/serializers.py
index 803db2acd..c342110a3 100644
--- a/netbox/vpn/api/serializers.py
+++ b/netbox/vpn/api/serializers.py
@@ -69,14 +69,14 @@ class TunnelTerminationSerializer(NetBoxModelSerializer):
model = TunnelTermination
fields = (
'id', 'url', 'display', 'tunnel', 'role', 'interface_type', 'interface_id', 'interface', 'outside_ip',
- 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
+ 'tags', 'custom_fields', 'created', 'last_updated',
)
@extend_schema_field(serializers.JSONField(allow_null=True))
def get_interface(self, obj):
serializer = get_serializer_for_model(obj.interface, prefix=NESTED_SERIALIZER_PREFIX)
context = {'request': self.context['request']}
- return serializer(obj.assigned_object, context=context).data
+ return serializer(obj.interface, context=context).data
class IPSecProfileSerializer(NetBoxModelSerializer):
diff --git a/netbox/vpn/api/views.py b/netbox/vpn/api/views.py
index 6cb99f2a7..7d01c48cf 100644
--- a/netbox/vpn/api/views.py
+++ b/netbox/vpn/api/views.py
@@ -35,7 +35,7 @@ class TunnelViewSet(NetBoxModelViewSet):
class TunnelTerminationViewSet(NetBoxModelViewSet):
- queryset = Tunnel.objects.prefetch_related('tunnel')
+ queryset = TunnelTermination.objects.prefetch_related('tunnel')
serializer_class = serializers.TunnelTerminationSerializer
filterset_class = filtersets.TunnelTerminationFilterSet
diff --git a/netbox/vpn/choices.py b/netbox/vpn/choices.py
index 24a7b8c8d..96d73633f 100644
--- a/netbox/vpn/choices.py
+++ b/netbox/vpn/choices.py
@@ -24,12 +24,14 @@ class TunnelStatusChoices(ChoiceSet):
class TunnelEncapsulationChoices(ChoiceSet):
ENCAP_GRE = 'gre'
ENCAP_IP_IP = 'ip-ip'
- ENCAP_IPSEC = 'ipsec'
+ ENCAP_IPSEC_TRANSPORT = 'ipsec-transport'
+ ENCAP_IPSEC_TUNNEL = 'ipsec-tunnel'
CHOICES = [
- (ENCAP_IPSEC, _('IPsec')),
- (ENCAP_IP_IP, _('Active')),
- (ENCAP_GRE, _('Disabled')),
+ (ENCAP_IPSEC_TRANSPORT, _('IPsec - Transport')),
+ (ENCAP_IPSEC_TUNNEL, _('IPsec - Tunnel')),
+ (ENCAP_IP_IP, _('IP-in-IP')),
+ (ENCAP_GRE, _('GRE')),
]
@@ -39,9 +41,9 @@ class TunnelTerminationRoleChoices(ChoiceSet):
ROLE_SPOKE = 'spoke'
CHOICES = [
- (ROLE_PEER, _('Peer')),
- (ROLE_HUB, _('Hub')),
- (ROLE_SPOKE, _('Spoke')),
+ (ROLE_PEER, _('Peer'), 'green'),
+ (ROLE_HUB, _('Hub'), 'blue'),
+ (ROLE_SPOKE, _('Spoke'), 'orange'),
]
diff --git a/netbox/vpn/forms/filtersets.py b/netbox/vpn/forms/filtersets.py
index 9f11de5a3..53a3bd634 100644
--- a/netbox/vpn/forms/filtersets.py
+++ b/netbox/vpn/forms/filtersets.py
@@ -2,6 +2,7 @@ from django import forms
from django.utils.translation import gettext as _
from netbox.forms import NetBoxModelFilterSetForm
+from tenancy.forms import TenancyFilterForm
from utilities.forms.fields import DynamicModelMultipleChoiceField, TagFilterField
from vpn.choices import *
from vpn.models import *
@@ -13,13 +14,13 @@ __all__ = (
)
-class TunnelFilterForm(NetBoxModelFilterSetForm):
+class TunnelFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
model = Tunnel
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
(_('Tunnel'), ('status', 'encapsulation', 'tunnel_id')),
(_('Security'), ('ipsec_profile_id', 'preshared_key')),
- (_('Tenancy'), ('tenant',)),
+ (_('Tenancy'), ('tenant_group_id', 'tenant_id')),
)
status = forms.MultipleChoiceField(
label=_('Status'),
@@ -36,6 +37,14 @@ class TunnelFilterForm(NetBoxModelFilterSetForm):
required=False,
label=_('IPSec profile')
)
+ preshared_key = forms.CharField(
+ required=False,
+ label=_('Pre-shared key')
+ )
+ tunnel_id = forms.IntegerField(
+ required=False,
+ label=_('Tunnel ID')
+ )
tag = TagFilterField(model)
diff --git a/netbox/vpn/forms/model_forms.py b/netbox/vpn/forms/model_forms.py
index 2621cdd46..9755bd538 100644
--- a/netbox/vpn/forms/model_forms.py
+++ b/netbox/vpn/forms/model_forms.py
@@ -2,6 +2,7 @@ from django import forms
from django.utils.translation import gettext_lazy as _
from dcim.models import Interface
+from ipam.models import IPAddress
from netbox.forms import NetBoxModelForm
from tenancy.forms import TenancyForm
from utilities.forms.fields import CommentField, DynamicModelChoiceField
@@ -17,7 +18,8 @@ __all__ = (
class TunnelForm(TenancyForm, NetBoxModelForm):
ipsec_profile = DynamicModelChoiceField(
- queryset=IPSecProfile.objects.all()
+ queryset=IPSecProfile.objects.all(),
+ label=_('IPSec Profile')
)
comments = CommentField()
@@ -51,6 +53,11 @@ class TunnelTerminationForm(NetBoxModelForm):
selector=True,
label=_('Interface'),
)
+ outside_ip = DynamicModelChoiceField(
+ queryset=IPAddress.objects.all(),
+ selector=True,
+ label=_('Outside IP'),
+ )
class Meta:
model = TunnelTermination
diff --git a/netbox/vpn/graphql/__init__.py b/netbox/vpn/graphql/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/netbox/vpn/graphql/schema.py b/netbox/vpn/graphql/schema.py
new file mode 100644
index 000000000..0ec4cd207
--- /dev/null
+++ b/netbox/vpn/graphql/schema.py
@@ -0,0 +1,26 @@
+import graphene
+
+from netbox.graphql.fields import ObjectField, ObjectListField
+from utilities.graphql_optimizer import gql_query_optimizer
+from vpn import models
+from .types import *
+
+
+class VPNQuery(graphene.ObjectType):
+ ipsec_profile = ObjectField(IPSecProfileType)
+ ipsec_profile_list = ObjectListField(IPSecProfileType)
+
+ def resolve_ipsec_profile_list(root, info, **kwargs):
+ return gql_query_optimizer(models.IPSecProfile.objects.all(), info)
+
+ tunnel = ObjectField(TunnelType)
+ tunnel_list = ObjectListField(TunnelType)
+
+ def resolve_tunnel_list(root, info, **kwargs):
+ return gql_query_optimizer(models.Tunnel.objects.all(), info)
+
+ tunnel_termination = ObjectField(TunnelTerminationType)
+ tunnel_termination_list = ObjectListField(TunnelTerminationType)
+
+ def resolve_tunnel_termination_list(root, info, **kwargs):
+ return gql_query_optimizer(models.TunnelTermination.objects.all(), info)
diff --git a/netbox/vpn/graphql/types.py b/netbox/vpn/graphql/types.py
new file mode 100644
index 000000000..d6b04ad2f
--- /dev/null
+++ b/netbox/vpn/graphql/types.py
@@ -0,0 +1,33 @@
+from extras.graphql.mixins import CustomFieldsMixin, TagsMixin
+from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType
+from vpn import filtersets, models
+
+__all__ = (
+ 'IPSecProfileType',
+ 'TunnelTerminationType',
+ 'TunnelType',
+)
+
+
+class TunnelTerminationType(CustomFieldsMixin, TagsMixin, ObjectType):
+
+ class Meta:
+ model = models.TunnelTermination
+ fields = '__all__'
+ filterset_class = filtersets.TunnelTerminationFilterSet
+
+
+class TunnelType(NetBoxObjectType):
+
+ class Meta:
+ model = models.Tunnel
+ fields = '__all__'
+ filterset_class = filtersets.TunnelFilterSet
+
+
+class IPSecProfileType(OrganizationalObjectType):
+
+ class Meta:
+ model = models.IPSecProfile
+ fields = '__all__'
+ filterset_class = filtersets.IPSecProfileFilterSet
diff --git a/netbox/vpn/migrations/0001_initial.py b/netbox/vpn/migrations/0001_initial.py
new file mode 100644
index 000000000..0bb111859
--- /dev/null
+++ b/netbox/vpn/migrations/0001_initial.py
@@ -0,0 +1,93 @@
+# Generated by Django 4.2.6 on 2023-11-07 21:49
+
+from django.db import migrations, models
+import django.db.models.deletion
+import taggit.managers
+import utilities.json
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('tenancy', '0011_contactassignment_tags'),
+ ('extras', '0099_cachedvalue_ordering'),
+ ('contenttypes', '0002_remove_content_type_name'),
+ ('ipam', '0067_ipaddress_index_host'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='IPSecProfile',
+ 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)),
+ ('name', models.CharField(max_length=100, unique=True)),
+ ('protocol', models.CharField()),
+ ('ike_version', models.PositiveSmallIntegerField(default=2)),
+ ('phase1_encryption', models.CharField()),
+ ('phase1_authentication', models.CharField()),
+ ('phase1_group', models.PositiveSmallIntegerField()),
+ ('phase1_sa_lifetime', models.PositiveSmallIntegerField(blank=True, null=True)),
+ ('phase2_encryption', models.CharField()),
+ ('phase2_authentication', models.CharField()),
+ ('phase2_group', models.PositiveSmallIntegerField()),
+ ('phase2_sa_lifetime', models.PositiveSmallIntegerField(blank=True, null=True)),
+ ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
+ ],
+ options={
+ 'verbose_name': 'tunnel',
+ 'verbose_name_plural': 'tunnels',
+ 'ordering': ('name',),
+ },
+ ),
+ migrations.CreateModel(
+ name='Tunnel',
+ 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)),
+ ('name', models.CharField(max_length=100, unique=True)),
+ ('status', models.CharField(default='active', max_length=50)),
+ ('encapsulation', models.CharField(max_length=50)),
+ ('preshared_key', models.TextField(blank=True)),
+ ('tunnel_id', models.PositiveBigIntegerField(blank=True)),
+ ('ipsec_profile', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='tunnels', to='vpn.ipsecprofile')),
+ ('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='tunnels', to='tenancy.tenant')),
+ ],
+ options={
+ 'verbose_name': 'tunnel',
+ 'verbose_name_plural': 'tunnels',
+ 'ordering': ('name',),
+ },
+ ),
+ migrations.CreateModel(
+ name='TunnelTermination',
+ 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)),
+ ('interface_id', models.PositiveBigIntegerField(blank=True, null=True)),
+ ('interface_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype')),
+ ('outside_ip', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='tunnel_termination', to='ipam.ipaddress')),
+ ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
+ ('tunnel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='terminations', to='vpn.tunnel')),
+ ],
+ options={
+ 'verbose_name': 'tunnel termination',
+ 'verbose_name_plural': 'tunnel terminations',
+ 'ordering': ('tunnel', 'pk'),
+ },
+ ),
+ ]
diff --git a/netbox/vpn/migrations/0002_alter_ipsecprofile_phase1_sa_lifetime_and_more.py b/netbox/vpn/migrations/0002_alter_ipsecprofile_phase1_sa_lifetime_and_more.py
new file mode 100644
index 000000000..f07076c50
--- /dev/null
+++ b/netbox/vpn/migrations/0002_alter_ipsecprofile_phase1_sa_lifetime_and_more.py
@@ -0,0 +1,23 @@
+# Generated by Django 4.2.6 on 2023-11-08 16:04
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('vpn', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='ipsecprofile',
+ name='phase1_sa_lifetime',
+ field=models.PositiveIntegerField(blank=True, null=True),
+ ),
+ migrations.AlterField(
+ model_name='ipsecprofile',
+ name='phase2_sa_lifetime',
+ field=models.PositiveIntegerField(blank=True, null=True),
+ ),
+ ]
diff --git a/netbox/vpn/migrations/0003_alter_tunnel_tunnel_id.py b/netbox/vpn/migrations/0003_alter_tunnel_tunnel_id.py
new file mode 100644
index 000000000..b02b4aa86
--- /dev/null
+++ b/netbox/vpn/migrations/0003_alter_tunnel_tunnel_id.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.6 on 2023-11-08 16:06
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('vpn', '0002_alter_ipsecprofile_phase1_sa_lifetime_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='tunnel',
+ name='tunnel_id',
+ field=models.PositiveBigIntegerField(blank=True, null=True),
+ ),
+ ]
diff --git a/netbox/vpn/models/crypto.py b/netbox/vpn/models/crypto.py
index 8e1ee2870..8c817b296 100644
--- a/netbox/vpn/models/crypto.py
+++ b/netbox/vpn/models/crypto.py
@@ -40,7 +40,7 @@ class IPSecProfile(PrimaryModel):
choices=DHGroupChoices,
help_text=_('Diffie-Hellman group')
)
- phase1_sa_lifetime = models.PositiveSmallIntegerField(
+ phase1_sa_lifetime = models.PositiveIntegerField(
verbose_name=_('phase 1 SA lifetime'),
blank=True,
null=True,
@@ -61,7 +61,7 @@ class IPSecProfile(PrimaryModel):
choices=DHGroupChoices,
help_text=_('Diffie-Hellman group')
)
- phase2_sa_lifetime = models.PositiveSmallIntegerField(
+ phase2_sa_lifetime = models.PositiveIntegerField(
verbose_name=_('phase 2 SA lifetime'),
blank=True,
null=True,
@@ -70,8 +70,8 @@ class IPSecProfile(PrimaryModel):
# TODO: Add PFS group?
clone_fields = (
- 'protocol', 'ike_version', 'phase1_encryption', 'phase1_authentication', 'phase1_group', 'phase1_as_lifetime',
- 'phase2_encryption', 'phase2_authentication', 'phase2_group', 'phase2_as_lifetime',
+ 'protocol', 'ike_version', 'phase1_encryption', 'phase1_authentication', 'phase1_group', 'phase1_sa_lifetime',
+ 'phase2_encryption', 'phase2_authentication', 'phase2_group', 'phase2_sa_lifetime',
)
class Meta:
diff --git a/netbox/vpn/models/tunnels.py b/netbox/vpn/models/tunnels.py
index 4912ac3cd..b48a30a4f 100644
--- a/netbox/vpn/models/tunnels.py
+++ b/netbox/vpn/models/tunnels.py
@@ -50,7 +50,8 @@ class Tunnel(PrimaryModel):
)
tunnel_id = models.PositiveBigIntegerField(
verbose_name=_('tunnel ID'),
- blank=True
+ blank=True,
+ null=True
)
clone_fields = (
@@ -113,3 +114,6 @@ class TunnelTermination(CustomFieldsMixin, CustomLinksMixin, TagsMixin, ChangeLo
def get_absolute_url(self):
return self.tunnel.get_absolute_url()
+
+ def get_role_color(self):
+ return TunnelTerminationRoleChoices.colors.get(self.role)
diff --git a/netbox/vpn/tables.py b/netbox/vpn/tables.py
index 4f8b08066..3d589abca 100644
--- a/netbox/vpn/tables.py
+++ b/netbox/vpn/tables.py
@@ -21,9 +21,6 @@ class TunnelTable(TenancyColumnsMixin, NetBoxTable):
status = columns.ChoiceFieldColumn(
verbose_name=_('Status')
)
- encapsulation = columns.ChoiceFieldColumn(
- verbose_name=_('Encapsulation')
- )
ipsec_profile = tables.Column(
verbose_name=_('IPSec profile'),
linkify=True
@@ -47,7 +44,7 @@ class TunnelTable(TenancyColumnsMixin, NetBoxTable):
'pk', 'id', 'name', 'status', 'encapsulation', 'ipsec_profile', 'tenant', 'tenant_group', 'preshared_key',
'tunnel_id', 'termination_count', 'description', 'comments', 'tags', 'created', 'last_updated',
)
- default_columns = ('pk', 'name', 'status', 'encapsulation', 'tenant', 'termination_count')
+ default_columns = ('pk', 'name', 'status', 'encapsulation', 'tenant', 'terminations_count')
class TunnelTerminationTable(TenancyColumnsMixin, NetBoxTable):
diff --git a/netbox/vpn/views.py b/netbox/vpn/views.py
index 58034391a..9ea4fd215 100644
--- a/netbox/vpn/views.py
+++ b/netbox/vpn/views.py
@@ -67,11 +67,6 @@ class TunnelTerminationListView(generic.ObjectListView):
table = tables.TunnelTerminationTable
-@register_model_view(TunnelTermination)
-class TunnelTerminationView(generic.ObjectView):
- queryset = TunnelTermination.objects.all()
-
-
@register_model_view(TunnelTermination, 'edit')
class TunnelTerminationEditView(generic.ObjectEditView):
queryset = TunnelTermination.objects.all()