From d64c4d75f8f0cd344677537c5df0c9c17d56f323 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 9 Mar 2026 15:25:25 -0400 Subject: [PATCH 1/2] #20923: Convert vpn views to new UI layout --- netbox/templates/vpn/attrs/preshared_key.html | 3 + netbox/templates/vpn/attrs/proposals.html | 5 + netbox/templates/vpn/ikepolicy.html | 62 ----- netbox/templates/vpn/ikeproposal.html | 61 ----- netbox/templates/vpn/ipsecpolicy.html | 50 ----- netbox/templates/vpn/ipsecprofile.html | 101 --------- netbox/templates/vpn/ipsecproposal.html | 57 ----- netbox/templates/vpn/l2vpn.html | 77 ------- netbox/templates/vpn/l2vpntermination.html | 27 --- .../vpn/panels/ipsecprofile_ike_policy.html | 34 +++ .../vpn/panels/ipsecprofile_ipsec_policy.html | 30 +++ netbox/templates/vpn/tunnel.html | 76 ------- netbox/templates/vpn/tunnelgroup.html | 40 ---- netbox/templates/vpn/tunneltermination.html | 56 ----- netbox/vpn/ui/__init__.py | 0 netbox/vpn/ui/panels.py | 85 +++++++ netbox/vpn/views.py | 211 +++++++++++++++++- 17 files changed, 366 insertions(+), 609 deletions(-) create mode 100644 netbox/templates/vpn/attrs/preshared_key.html create mode 100644 netbox/templates/vpn/attrs/proposals.html create mode 100644 netbox/templates/vpn/panels/ipsecprofile_ike_policy.html create mode 100644 netbox/templates/vpn/panels/ipsecprofile_ipsec_policy.html create mode 100644 netbox/vpn/ui/__init__.py create mode 100644 netbox/vpn/ui/panels.py diff --git a/netbox/templates/vpn/attrs/preshared_key.html b/netbox/templates/vpn/attrs/preshared_key.html new file mode 100644 index 000000000..3401a3eb4 --- /dev/null +++ b/netbox/templates/vpn/attrs/preshared_key.html @@ -0,0 +1,3 @@ +{% load i18n %} +{{ value }} + diff --git a/netbox/templates/vpn/attrs/proposals.html b/netbox/templates/vpn/attrs/proposals.html new file mode 100644 index 000000000..1be57acaa --- /dev/null +++ b/netbox/templates/vpn/attrs/proposals.html @@ -0,0 +1,5 @@ + diff --git a/netbox/templates/vpn/ikepolicy.html b/netbox/templates/vpn/ikepolicy.html index a098fb91b..f15e1d050 100644 --- a/netbox/templates/vpn/ikepolicy.html +++ b/netbox/templates/vpn/ikepolicy.html @@ -1,63 +1 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} -{% load i18n %} - -{% block content %} -
-
-
-

{% trans "IKE Policy" %}

- - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans "Name" %}{{ object.name }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "IKE Version" %}{{ object.get_version_display }}
{% trans "Mode" %}{{ object.get_mode_display }}
{% trans "Pre-Shared Key" %} - {{ object.preshared_key|placeholder }} - {% if object.preshared_key %} - - {% endif %} -
{% trans "IPSec Profiles" %} - {{ object.ipsec_profiles.count }} -
-
- {% plugin_left_page object %} -
-
- {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/comments.html' %} - {% include 'inc/panels/tags.html' %} - {% plugin_right_page object %} -
-
-
-
-
-

{% trans "Proposals" %}

- {% htmx_table 'vpn:ikeproposal_list' ike_policy_id=object.pk %} -
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/vpn/ikeproposal.html b/netbox/templates/vpn/ikeproposal.html index fdc87455b..f15e1d050 100644 --- a/netbox/templates/vpn/ikeproposal.html +++ b/netbox/templates/vpn/ikeproposal.html @@ -1,62 +1 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} -{% load i18n %} - -{% block content %} -
-
-
-

{% trans "IKE Proposal" %}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans "Name" %}{{ object.name }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Authentication method" %}{{ object.get_authentication_method_display }}
{% trans "Encryption algorithm" %}{{ object.get_encryption_algorithm_display }}
{% trans "Authentication algorithm" %}{{ object.get_authentication_algorithm_display }}
{% trans "DH group" %}{{ object.get_group_display }}
{% trans "SA lifetime (seconds)" %}{{ object.sa_lifetime|placeholder }}
{% trans "IKE Policies" %} - {{ object.ike_policies.count }} -
-
- {% plugin_left_page object %} -
-
- {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/comments.html' %} - {% include 'inc/panels/tags.html' %} - {% plugin_right_page object %} -
-
-
-
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/vpn/ipsecpolicy.html b/netbox/templates/vpn/ipsecpolicy.html index 0daa5aba2..f15e1d050 100644 --- a/netbox/templates/vpn/ipsecpolicy.html +++ b/netbox/templates/vpn/ipsecpolicy.html @@ -1,51 +1 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} -{% load i18n %} - -{% block content %} -
-
-
-

{% trans "IPSec Policy" %}

- - - - - - - - - - - - - - - - - -
{% trans "Name" %}{{ object.name }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "PFS group" %}{{ object.get_pfs_group_display|placeholder }}
{% trans "IPSec Profiles" %} - {{ object.ipsec_profiles.count }} -
-
- {% plugin_left_page object %} -
-
- {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/comments.html' %} - {% include 'inc/panels/tags.html' %} - {% plugin_right_page object %} -
-
-
-
-
-
-

{% trans "Proposals" %}

- {% htmx_table 'vpn:ipsecproposal_list' ipsec_policy_id=object.pk %} -
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/vpn/ipsecprofile.html b/netbox/templates/vpn/ipsecprofile.html index d7d5a1e5b..f15e1d050 100644 --- a/netbox/templates/vpn/ipsecprofile.html +++ b/netbox/templates/vpn/ipsecprofile.html @@ -1,102 +1 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} -{% load i18n %} - -{% block content %} -
-
-
-

{% trans "IPSec Profile" %}

- - - - - - - - - - - - - -
{% trans "Name" %}{{ object.name }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Mode" %}{{ object.get_mode_display }}
-
- {% include 'inc/panels/tags.html' %} - {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/comments.html' %} - {% plugin_left_page object %} -
-
-
-

{% trans "IKE Policy" %}

- - - - - - - - - - - - - - - - - - - - - -
{% trans "Name" %}{{ object.ike_policy|linkify }}
{% trans "Description" %}{{ object.ike_policy.description|placeholder }}
{% trans "Version" %}{{ object.ike_policy.get_version_display }}
{% trans "Mode" %}{{ object.ike_policy.get_mode_display }}
{% trans "Proposals" %} -
    - {% for proposal in object.ike_policy.proposals.all %} -
  • - {{ proposal }} -
  • - {% endfor %} -
-
-
-
-

{% trans "IPSec Policy" %}

- - - - - - - - - - - - - - - - - -
{% trans "Name" %}{{ object.ipsec_policy|linkify }}
{% trans "Description" %}{{ object.ipsec_policy.description|placeholder }}
{% trans "Proposals" %} -
    - {% for proposal in object.ipsec_policy.proposals.all %} -
  • - {{ proposal }} -
  • - {% endfor %} -
-
{% trans "PFS Group" %}{{ object.ipsec_policy.get_pfs_group_display }}
-
- {% plugin_right_page object %} -
-
-
-
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/vpn/ipsecproposal.html b/netbox/templates/vpn/ipsecproposal.html index 5b199905e..f15e1d050 100644 --- a/netbox/templates/vpn/ipsecproposal.html +++ b/netbox/templates/vpn/ipsecproposal.html @@ -1,58 +1 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} -{% load i18n %} - -{% block content %} -
-
-
-

{% trans "IPSec Proposal" %}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans "Name" %}{{ object.name }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Encryption algorithm" %}{{ object.get_encryption_algorithm_display }}
{% trans "Authentication algorithm" %}{{ object.get_authentication_algorithm_display }}
{% trans "SA lifetime (seconds)" %}{{ object.sa_lifetime_seconds|placeholder }}
{% trans "SA lifetime (KB)" %}{{ object.sa_lifetime_data|placeholder }}
{% trans "IPSec Policies" %} - {{ object.ipsec_policies.count }} -
-
- {% plugin_left_page object %} -
-
- {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/comments.html' %} - {% include 'inc/panels/tags.html' %} - {% plugin_right_page object %} -
-
-
-
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/vpn/l2vpn.html b/netbox/templates/vpn/l2vpn.html index 593123849..f15e1d050 100644 --- a/netbox/templates/vpn/l2vpn.html +++ b/netbox/templates/vpn/l2vpn.html @@ -1,78 +1 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} -{% load render_table from django_tables2 %} -{% load i18n %} - -{% block content %} -
-
-
-

{% trans "L2VPN Attributes" %}

- - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans "Name" %}{{ object.name|placeholder }}
{% trans "Identifier" %}{{ object.identifier|placeholder }}
{% trans "Type" %}{{ object.get_type_display }}
{% trans "Status" %}{% badge object.get_status_display bg_color=object.get_status_color %}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Tenant" %}{{ object.tenant|linkify|placeholder }}
-
- {% include 'inc/panels/tags.html' with tags=object.tags.all url='vpn:l2vpn_list' %} - {% plugin_left_page object %} -
-
- {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/comments.html' %} - {% plugin_right_page object %} -
-
-
-
- {% include 'inc/panel_table.html' with table=import_targets_table heading="Import Route Targets" %} -
-
- {% include 'inc/panel_table.html' with table=export_targets_table heading="Export Route Targets" %} -
-
-
-
-
-

- {% trans "Terminations" %} - {% if perms.vpn.add_l2vpntermination %} - - {% endif %} -

- {% htmx_table 'vpn:l2vpntermination_list' l2vpn_id=object.pk %} -
-
-
-
-
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/vpn/l2vpntermination.html b/netbox/templates/vpn/l2vpntermination.html index 8b6b31aa7..f15e1d050 100644 --- a/netbox/templates/vpn/l2vpntermination.html +++ b/netbox/templates/vpn/l2vpntermination.html @@ -1,28 +1 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load i18n %} - -{% block content %} -
-
-
-

{% trans "L2VPN Attributes" %}

- - - - - - - - - -
{% trans "L2VPN" %}{{ object.l2vpn|linkify }}
{% trans "Assigned Object" %}{{ object.assigned_object|linkify }}
-
-
-
- {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/tags.html' with tags=object.tags.all url='vpn:l2vpntermination_list' %} -
-
- -{% endblock %} diff --git a/netbox/templates/vpn/panels/ipsecprofile_ike_policy.html b/netbox/templates/vpn/panels/ipsecprofile_ike_policy.html new file mode 100644 index 000000000..8b3065cfd --- /dev/null +++ b/netbox/templates/vpn/panels/ipsecprofile_ike_policy.html @@ -0,0 +1,34 @@ +{% load helpers %} +{% load i18n %} + +
+

{% trans "IKE Policy" %}

+ + + + + + + + + + + + + + + + + + + + + +
{% trans "Name" %}{{ object.ike_policy|linkify }}
{% trans "Description" %}{{ object.ike_policy.description|placeholder }}
{% trans "Version" %}{{ object.ike_policy.get_version_display }}
{% trans "Mode" %}{{ object.ike_policy.get_mode_display }}
{% trans "Proposals" %} +
    + {% for proposal in object.ike_policy.proposals.all %} +
  • {{ proposal }}
  • + {% endfor %} +
+
+
diff --git a/netbox/templates/vpn/panels/ipsecprofile_ipsec_policy.html b/netbox/templates/vpn/panels/ipsecprofile_ipsec_policy.html new file mode 100644 index 000000000..cb28c1620 --- /dev/null +++ b/netbox/templates/vpn/panels/ipsecprofile_ipsec_policy.html @@ -0,0 +1,30 @@ +{% load helpers %} +{% load i18n %} + +
+

{% trans "IPSec Policy" %}

+ + + + + + + + + + + + + + + + + +
{% trans "Name" %}{{ object.ipsec_policy|linkify }}
{% trans "Description" %}{{ object.ipsec_policy.description|placeholder }}
{% trans "Proposals" %} +
    + {% for proposal in object.ipsec_policy.proposals.all %} +
  • {{ proposal }}
  • + {% endfor %} +
+
{% trans "PFS Group" %}{{ object.ipsec_policy.get_pfs_group_display }}
+
diff --git a/netbox/templates/vpn/tunnel.html b/netbox/templates/vpn/tunnel.html index 4e4b5f56e..671f7447d 100644 --- a/netbox/templates/vpn/tunnel.html +++ b/netbox/templates/vpn/tunnel.html @@ -1,6 +1,4 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} {% load i18n %} {% block extra_controls %} @@ -10,77 +8,3 @@ {% endif %} {% endblock %} - -{% block content %} -
-
-
-

{% trans "Tunnel" %}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans "Name" %}{{ object.name }}
{% trans "Status" %}{% badge object.get_status_display bg_color=object.get_status_color %}
{% trans "Group" %}{{ object.group|linkify|placeholder }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Encapsulation" %}{{ object.get_encapsulation_display }}
{% trans "IPSec profile" %}{{ object.ipsec_profile|linkify|placeholder }}
{% trans "Tunnel ID" %}{{ object.tunnel_id|placeholder }}
{% trans "Tenant" %} - {% if object.tenant.group %} - {{ object.tenant.group|linkify }} / - {% endif %} - {{ object.tenant|linkify|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 %} -
-
-
-
-
-

- {% trans "Terminations" %} - {% if perms.vpn.add_tunneltermination %} - - {% endif %} -

- {% htmx_table 'vpn:tunneltermination_list' tunnel_id=object.pk %} -
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/vpn/tunnelgroup.html b/netbox/templates/vpn/tunnelgroup.html index 3c76e33ff..cc34b7e36 100644 --- a/netbox/templates/vpn/tunnelgroup.html +++ b/netbox/templates/vpn/tunnelgroup.html @@ -1,13 +1,6 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} -{% load render_table from django_tables2 %} {% load i18n %} -{% block breadcrumbs %} - -{% endblock %} - {% block extra_controls %} {% if perms.vpn.add_tunnel %} @@ -15,36 +8,3 @@ {% endif %} {% endblock extra_controls %} - -{% block content %} -
-
-
-

{% trans "Tunnel Group" %}

- - - - - - - - - -
{% trans "Name" %}{{ object.name }}
{% trans "Description" %}{{ object.description|placeholder }}
-
- {% include 'inc/panels/tags.html' %} - {% plugin_left_page object %} -
-
- {% include 'inc/panels/related_objects.html' %} - {% include 'inc/panels/comments.html' %} - {% include 'inc/panels/custom_fields.html' %} - {% plugin_right_page object %} -
-
-
-
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/vpn/tunneltermination.html b/netbox/templates/vpn/tunneltermination.html index bc5c591ec..f15e1d050 100644 --- a/netbox/templates/vpn/tunneltermination.html +++ b/netbox/templates/vpn/tunneltermination.html @@ -1,57 +1 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} -{% load i18n %} - -{% block content %} -
-
-
-

{% trans "Tunnel Termination" %}

- - - - - - - - - - - - - - - - - - - - - -
{% trans "Tunnel" %}{{ object.tunnel|linkify }}
{% trans "Role" %}{% badge object.get_role_display bg_color=object.get_role_color %}
- {% if object.termination.device %} - {% trans "Device" %} - {% elif object.termination.virtual_machine %} - {% trans "Virtual Machine" %} - {% endif %} - {{ object.termination.parent_object|linkify }}
{% trans "Interface" %}{{ object.termination|linkify }}
{% trans "Outside IP" %}{{ object.outside_ip|linkify|placeholder }}
-
- {% plugin_left_page object %} -
-
- {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/tags.html' %} - {% plugin_right_page object %} -
-
-
-
-
-

{% trans "Peer Terminations" %}

- {% htmx_table 'vpn:tunneltermination_list' tunnel_id=object.tunnel.pk id__n=object.pk %} -
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/vpn/ui/__init__.py b/netbox/vpn/ui/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/netbox/vpn/ui/panels.py b/netbox/vpn/ui/panels.py new file mode 100644 index 000000000..41afec132 --- /dev/null +++ b/netbox/vpn/ui/panels.py @@ -0,0 +1,85 @@ +from django.utils.translation import gettext_lazy as _ + +from netbox.ui import attrs, panels + + +class TunnelGroupPanel(panels.OrganizationalObjectPanel): + pass + + +class TunnelPanel(panels.ObjectAttributesPanel): + name = attrs.TextAttr('name') + status = attrs.ChoiceAttr('status') + group = attrs.RelatedObjectAttr('group', linkify=True) + description = attrs.TextAttr('description') + encapsulation = attrs.ChoiceAttr('encapsulation') + ipsec_profile = attrs.RelatedObjectAttr('ipsec_profile', linkify=True, label=_('IPSec profile')) + tunnel_id = attrs.TextAttr('tunnel_id', label=_('Tunnel ID')) + tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group') + + +class TunnelTerminationPanel(panels.ObjectAttributesPanel): + tunnel = attrs.RelatedObjectAttr('tunnel', linkify=True) + role = attrs.ChoiceAttr('role') + parent_object = attrs.RelatedObjectAttr( + 'termination.parent_object', linkify=True, label=_('Parent') + ) + termination = attrs.RelatedObjectAttr('termination', linkify=True, label=_('Interface')) + outside_ip = attrs.RelatedObjectAttr('outside_ip', linkify=True, label=_('Outside IP')) + + +class IKEProposalPanel(panels.ObjectAttributesPanel): + name = attrs.TextAttr('name') + description = attrs.TextAttr('description') + authentication_method = attrs.ChoiceAttr('authentication_method', label=_('Authentication method')) + encryption_algorithm = attrs.ChoiceAttr('encryption_algorithm', label=_('Encryption algorithm')) + authentication_algorithm = attrs.ChoiceAttr('authentication_algorithm', label=_('Authentication algorithm')) + group = attrs.ChoiceAttr('group', label=_('DH group')) + sa_lifetime = attrs.TextAttr('sa_lifetime', label=_('SA lifetime (seconds)')) + + +class IKEPolicyPanel(panels.ObjectAttributesPanel): + name = attrs.TextAttr('name') + description = attrs.TextAttr('description') + version = attrs.ChoiceAttr('version', label=_('IKE version')) + mode = attrs.ChoiceAttr('mode') + preshared_key = attrs.TemplatedAttr( + 'preshared_key', + label=_('Pre-shared key'), + template_name='vpn/attrs/preshared_key.html', + ) + + +class IPSecProposalPanel(panels.ObjectAttributesPanel): + name = attrs.TextAttr('name') + description = attrs.TextAttr('description') + encryption_algorithm = attrs.ChoiceAttr('encryption_algorithm', label=_('Encryption algorithm')) + authentication_algorithm = attrs.ChoiceAttr('authentication_algorithm', label=_('Authentication algorithm')) + sa_lifetime_seconds = attrs.TextAttr('sa_lifetime_seconds', label=_('SA lifetime (seconds)')) + sa_lifetime_data = attrs.TextAttr('sa_lifetime_data', label=_('SA lifetime (KB)')) + + +class IPSecPolicyPanel(panels.ObjectAttributesPanel): + name = attrs.TextAttr('name') + description = attrs.TextAttr('description') + pfs_group = attrs.ChoiceAttr('pfs_group', label=_('PFS group')) + + +class IPSecProfilePanel(panels.ObjectAttributesPanel): + name = attrs.TextAttr('name') + description = attrs.TextAttr('description') + mode = attrs.ChoiceAttr('mode') + + +class L2VPNPanel(panels.ObjectAttributesPanel): + name = attrs.TextAttr('name') + identifier = attrs.TextAttr('identifier') + type = attrs.ChoiceAttr('type') + status = attrs.ChoiceAttr('status') + description = attrs.TextAttr('description') + tenant = attrs.RelatedObjectAttr('tenant', linkify=True) + + +class L2VPNTerminationPanel(panels.ObjectAttributesPanel): + l2vpn = attrs.RelatedObjectAttr('l2vpn', linkify=True, label=_('L2VPN')) + assigned_object = attrs.RelatedObjectAttr('assigned_object', linkify=True, label=_('Assigned object')) diff --git a/netbox/vpn/views.py b/netbox/vpn/views.py index 219eab681..7da05e007 100644 --- a/netbox/vpn/views.py +++ b/netbox/vpn/views.py @@ -1,11 +1,24 @@ +from django.utils.translation import gettext_lazy as _ + +from extras.ui.panels import CustomFieldsPanel, TagsPanel from ipam.tables import RouteTargetTable from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport +from netbox.ui import actions, layout +from netbox.ui.panels import ( + CommentsPanel, + ContextTablePanel, + ObjectsTablePanel, + PluginContentPanel, + RelatedObjectsPanel, + TemplatePanel, +) from netbox.views import generic from utilities.query import count_related from utilities.views import GetRelatedModelsMixin, register_model_view from . import filtersets, forms, tables from .models import * +from .ui import panels # # Tunnel groups @@ -25,6 +38,17 @@ class TunnelGroupListView(generic.ObjectListView): @register_model_view(TunnelGroup) class TunnelGroupView(GetRelatedModelsMixin, generic.ObjectView): queryset = TunnelGroup.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.TunnelGroupPanel(), + TagsPanel(), + ], + right_panels=[ + RelatedObjectsPanel(), + CommentsPanel(), + CustomFieldsPanel(), + ], + ) def get_extra_context(self, request, instance): return { @@ -92,6 +116,30 @@ class TunnelListView(generic.ObjectListView): @register_model_view(Tunnel) class TunnelView(generic.ObjectView): queryset = Tunnel.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.TunnelPanel(), + ], + right_panels=[ + CustomFieldsPanel(), + TagsPanel(), + CommentsPanel(), + ], + bottom_panels=[ + ObjectsTablePanel( + 'vpn.tunneltermination', + filters={'tunnel_id': lambda ctx: ctx['object'].pk}, + actions=[ + actions.AddObject( + 'vpn.tunneltermination', + url_params={'tunnel': lambda ctx: ctx['object'].pk}, + label=_('Add a Termination'), + ), + ], + title=_('Terminations'), + ), + ], + ) @register_model_view(Tunnel, 'add', detail=False) @@ -160,6 +208,25 @@ class TunnelTerminationListView(generic.ObjectListView): @register_model_view(TunnelTermination) class TunnelTerminationView(generic.ObjectView): queryset = TunnelTermination.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.TunnelTerminationPanel(), + ], + right_panels=[ + CustomFieldsPanel(), + TagsPanel(), + ], + bottom_panels=[ + ObjectsTablePanel( + 'vpn.tunneltermination', + filters={ + 'tunnel_id': lambda ctx: ctx['object'].tunnel.pk, + 'id__n': lambda ctx: ctx['object'].pk, + }, + title=_('Peer Terminations'), + ), + ], + ) @register_model_view(TunnelTermination, 'add', detail=False) @@ -210,6 +277,23 @@ class IKEProposalListView(generic.ObjectListView): @register_model_view(IKEProposal) class IKEProposalView(generic.ObjectView): queryset = IKEProposal.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.IKEProposalPanel(), + ], + right_panels=[ + CustomFieldsPanel(), + CommentsPanel(), + TagsPanel(), + ], + bottom_panels=[ + ObjectsTablePanel( + 'vpn.ikepolicy', + filters={'ike_proposal_id': lambda ctx: ctx['object'].pk}, + title=_('IKE Policies'), + ), + ], + ) @register_model_view(IKEProposal, 'add', detail=False) @@ -264,8 +348,31 @@ class IKEPolicyListView(generic.ObjectListView): @register_model_view(IKEPolicy) -class IKEPolicyView(generic.ObjectView): +class IKEPolicyView(GetRelatedModelsMixin, generic.ObjectView): queryset = IKEPolicy.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.IKEPolicyPanel(), + ], + right_panels=[ + CustomFieldsPanel(), + CommentsPanel(), + TagsPanel(), + RelatedObjectsPanel(), + ], + bottom_panels=[ + ObjectsTablePanel( + 'vpn.ikeproposal', + filters={'ike_policy_id': lambda ctx: ctx['object'].pk}, + title=_('Proposals'), + ), + ], + ) + + def get_extra_context(self, request, instance): + return { + 'related_models': self.get_related_models(request, instance), + } @register_model_view(IKEPolicy, 'add', detail=False) @@ -322,6 +429,23 @@ class IPSecProposalListView(generic.ObjectListView): @register_model_view(IPSecProposal) class IPSecProposalView(generic.ObjectView): queryset = IPSecProposal.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.IPSecProposalPanel(), + ], + right_panels=[ + CustomFieldsPanel(), + CommentsPanel(), + TagsPanel(), + ], + bottom_panels=[ + ObjectsTablePanel( + 'vpn.ipsecpolicy', + filters={'ipsec_proposal_id': lambda ctx: ctx['object'].pk}, + title=_('IPSec Policies'), + ), + ], + ) @register_model_view(IPSecProposal, 'add', detail=False) @@ -376,8 +500,31 @@ class IPSecPolicyListView(generic.ObjectListView): @register_model_view(IPSecPolicy) -class IPSecPolicyView(generic.ObjectView): +class IPSecPolicyView(GetRelatedModelsMixin, generic.ObjectView): queryset = IPSecPolicy.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.IPSecPolicyPanel(), + ], + right_panels=[ + CustomFieldsPanel(), + CommentsPanel(), + TagsPanel(), + RelatedObjectsPanel(), + ], + bottom_panels=[ + ObjectsTablePanel( + 'vpn.ipsecproposal', + filters={'ipsec_policy_id': lambda ctx: ctx['object'].pk}, + title=_('Proposals'), + ), + ], + ) + + def get_extra_context(self, request, instance): + return { + 'related_models': self.get_related_models(request, instance), + } @register_model_view(IPSecPolicy, 'add', detail=False) @@ -434,6 +581,18 @@ class IPSecProfileListView(generic.ObjectListView): @register_model_view(IPSecProfile) class IPSecProfileView(generic.ObjectView): queryset = IPSecProfile.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.IPSecProfilePanel(), + TagsPanel(), + CustomFieldsPanel(), + CommentsPanel(), + ], + right_panels=[ + TemplatePanel('vpn/panels/ipsecprofile_ike_policy.html'), + TemplatePanel('vpn/panels/ipsecprofile_ipsec_policy.html'), + ], + ) @register_model_view(IPSecProfile, 'add', detail=False) @@ -490,6 +649,45 @@ class L2VPNListView(generic.ObjectListView): @register_model_view(L2VPN) class L2VPNView(generic.ObjectView): queryset = L2VPN.objects.all() + layout = layout.Layout( + layout.Row( + layout.Column( + panels.L2VPNPanel(), + TagsPanel(), + PluginContentPanel('left_page'), + ), + layout.Column( + CustomFieldsPanel(), + CommentsPanel(), + PluginContentPanel('right_page'), + ), + ), + layout.Row( + layout.Column( + ContextTablePanel('import_targets_table', title=_('Import Route Targets')), + ), + layout.Column( + ContextTablePanel('export_targets_table', title=_('Export Route Targets')), + ), + ), + layout.Row( + layout.Column( + ObjectsTablePanel( + 'vpn.l2vpntermination', + filters={'l2vpn_id': lambda ctx: ctx['object'].pk}, + actions=[ + actions.AddObject( + 'vpn.l2vpntermination', + url_params={'l2vpn': lambda ctx: ctx['object'].pk}, + label=_('Add a Termination'), + ), + ], + title=_('Terminations'), + ), + PluginContentPanel('full_width_page'), + ), + ), + ) def get_extra_context(self, request, instance): import_targets_table = RouteTargetTable( @@ -564,6 +762,15 @@ class L2VPNTerminationListView(generic.ObjectListView): @register_model_view(L2VPNTermination) class L2VPNTerminationView(generic.ObjectView): queryset = L2VPNTermination.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.L2VPNTerminationPanel(), + ], + right_panels=[ + CustomFieldsPanel(), + TagsPanel(), + ], + ) @register_model_view(L2VPNTermination, 'add', detail=False) From b19d0d61f447b55fbb4ec039e2e9eba627223103 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 9 Mar 2026 15:48:04 -0400 Subject: [PATCH 2/2] Delete unused template --- netbox/templates/vpn/attrs/proposals.html | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 netbox/templates/vpn/attrs/proposals.html diff --git a/netbox/templates/vpn/attrs/proposals.html b/netbox/templates/vpn/attrs/proposals.html deleted file mode 100644 index 1be57acaa..000000000 --- a/netbox/templates/vpn/attrs/proposals.html +++ /dev/null @@ -1,5 +0,0 @@ -