diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index 439348152..6d629d89c 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -367,6 +367,16 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary def get_status_color(self): return PrefixStatusChoices.colors.get(self.status) + @cached_property + def aggregate(self): + """ + Return the containing Aggregate for this Prefix, if any. + """ + try: + return Aggregate.objects.get(prefix__net_contains_or_equals=str(self.prefix)) + except Aggregate.DoesNotExist: + return None + def get_parents(self, include_self=False): """ Return all containing Prefixes in the hierarchy. diff --git a/netbox/ipam/ui/attrs.py b/netbox/ipam/ui/attrs.py new file mode 100644 index 000000000..cd5cba0f2 --- /dev/null +++ b/netbox/ipam/ui/attrs.py @@ -0,0 +1,24 @@ +from django.template.loader import render_to_string + +from netbox.ui import attrs + + +class VRFDisplayAttr(attrs.ObjectAttribute): + """ + Renders a VRF reference, displaying 'Global' when no VRF is assigned. Optionally includes + the route distinguisher (RD). + """ + template_name = 'ipam/attrs/vrf.html' + + def __init__(self, *args, show_rd=False, **kwargs): + super().__init__(*args, **kwargs) + self.show_rd = show_rd + + def render(self, obj, context): + value = self.get_value(obj) + return render_to_string(self.template_name, { + **self.get_context(obj, context), + 'name': context['name'], + 'value': value, + 'show_rd': self.show_rd, + }) diff --git a/netbox/ipam/ui/panels.py b/netbox/ipam/ui/panels.py index a2148057a..c47791cc5 100644 --- a/netbox/ipam/ui/panels.py +++ b/netbox/ipam/ui/panels.py @@ -2,14 +2,15 @@ from django.contrib.contenttypes.models import ContentType from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from netbox.ui import actions, panels +from netbox.ui import actions, attrs, panels + +from .attrs import VRFDisplayAttr class FHRPGroupAssignmentsPanel(panels.ObjectPanel): """ A panel which lists all FHRP group assignments for a given object. """ - template_name = 'ipam/panels/fhrp_groups.html' title = _('FHRP Groups') actions = [ @@ -35,3 +36,220 @@ class FHRPGroupAssignmentsPanel(panels.ObjectPanel): label=_('Assign Group'), ), ] + + +class VRFPanel(panels.ObjectAttributesPanel): + rd = attrs.TextAttr('rd', label=_('Route Distinguisher'), style='font-monospace') + tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group') + enforce_unique = attrs.BooleanAttr('enforce_unique', label=_('Unique IP Space')) + description = attrs.TextAttr('description') + + +class RouteTargetPanel(panels.ObjectAttributesPanel): + name = attrs.TextAttr('name', style='font-monospace') + tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group') + description = attrs.TextAttr('description') + + +class RIRPanel(panels.OrganizationalObjectPanel): + is_private = attrs.BooleanAttr('is_private', label=_('Private')) + + +class ASNRangePanel(panels.ObjectAttributesPanel): + name = attrs.TextAttr('name') + rir = attrs.RelatedObjectAttr('rir', linkify=True, label=_('RIR')) + range = attrs.TextAttr('range_as_string_with_asdot', label=_('Range')) + tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group') + description = attrs.TextAttr('description') + + +class ASNPanel(panels.ObjectAttributesPanel): + asn = attrs.TextAttr('asn_with_asdot', label=_('AS Number')) + rir = attrs.RelatedObjectAttr('rir', linkify=True, label=_('RIR')) + tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group') + description = attrs.TextAttr('description') + + +class AggregatePanel(panels.ObjectAttributesPanel): + family = attrs.TextAttr('family', format_string='IPv{}', label=_('Family')) + rir = attrs.RelatedObjectAttr('rir', linkify=True, label=_('RIR')) + utilization = attrs.TemplatedAttr( + 'prefix', + template_name='ipam/aggregate/attrs/utilization.html', + label=_('Utilization'), + ) + tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group') + date_added = attrs.DateTimeAttr('date_added', spec='date', label=_('Date Added')) + description = attrs.TextAttr('description') + + +class RolePanel(panels.OrganizationalObjectPanel): + weight = attrs.NumericAttr('weight') + + +class IPRangePanel(panels.ObjectAttributesPanel): + family = attrs.TextAttr('family', format_string='IPv{}', label=_('Family')) + start_address = attrs.TextAttr('start_address', label=_('Starting Address')) + end_address = attrs.TextAttr('end_address', label=_('Ending Address')) + size = attrs.NumericAttr('size') + mark_populated = attrs.BooleanAttr('mark_populated', label=_('Marked Populated')) + mark_utilized = attrs.BooleanAttr('mark_utilized', label=_('Marked Utilized')) + utilization = attrs.TemplatedAttr( + 'utilization', + template_name='ipam/iprange/attrs/utilization.html', + label=_('Utilization'), + ) + vrf = VRFDisplayAttr('vrf', label=_('VRF'), show_rd=True) + role = attrs.RelatedObjectAttr('role', linkify=True) + status = attrs.ChoiceAttr('status') + tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group') + description = attrs.TextAttr('description') + + +class IPAddressPanel(panels.ObjectAttributesPanel): + family = attrs.TextAttr('family', format_string='IPv{}', label=_('Family')) + vrf = VRFDisplayAttr('vrf', label=_('VRF')) + tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group') + status = attrs.ChoiceAttr('status') + role = attrs.ChoiceAttr('role') + dns_name = attrs.TextAttr('dns_name', label=_('DNS Name')) + description = attrs.TextAttr('description') + assigned_object = attrs.RelatedObjectAttr( + 'assigned_object', + linkify=True, + grouped_by='parent_object', + label=_('Assignment'), + ) + nat_inside = attrs.TemplatedAttr( + 'nat_inside', + template_name='ipam/ipaddress/attrs/nat_inside.html', + label=_('NAT (inside)'), + ) + nat_outside = attrs.TemplatedAttr( + 'nat_outside', + template_name='ipam/ipaddress/attrs/nat_outside.html', + label=_('NAT (outside)'), + ) + is_primary_ip = attrs.BooleanAttr('is_primary_ip', label=_('Primary IP')) + is_oob_ip = attrs.BooleanAttr('is_oob_ip', label=_('OOB IP')) + + +class PrefixPanel(panels.ObjectAttributesPanel): + family = attrs.TextAttr('family', format_string='IPv{}', label=_('Family')) + vrf = VRFDisplayAttr('vrf', label=_('VRF')) + tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group') + aggregate = attrs.TemplatedAttr( + 'aggregate', + template_name='ipam/prefix/attrs/aggregate.html', + label=_('Aggregate'), + ) + scope = attrs.GenericForeignKeyAttr('scope', linkify=True) + vlan = attrs.RelatedObjectAttr('vlan', linkify=True, label=_('VLAN'), grouped_by='group') + status = attrs.ChoiceAttr('status') + role = attrs.RelatedObjectAttr('role', linkify=True) + description = attrs.TextAttr('description') + is_pool = attrs.BooleanAttr('is_pool', label=_('Is a pool')) + + +class VLANGroupPanel(panels.ObjectAttributesPanel): + name = attrs.TextAttr('name') + description = attrs.TextAttr('description') + scope = attrs.GenericForeignKeyAttr('scope', linkify=True) + vid_ranges = attrs.TemplatedAttr( + 'vid_ranges_items', + template_name='ipam/vlangroup/attrs/vid_ranges.html', + label=_('VLAN IDs'), + ) + utilization = attrs.UtilizationAttr('utilization') + tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group') + + +class VLANTranslationPolicyPanel(panels.ObjectAttributesPanel): + name = attrs.TextAttr('name') + description = attrs.TextAttr('description') + + +class VLANTranslationRulePanel(panels.ObjectAttributesPanel): + policy = attrs.RelatedObjectAttr('policy', linkify=True) + local_vid = attrs.NumericAttr('local_vid', label=_('Local VID')) + remote_vid = attrs.NumericAttr('remote_vid', label=_('Remote VID')) + description = attrs.TextAttr('description') + + +class FHRPGroupPanel(panels.ObjectAttributesPanel): + protocol = attrs.ChoiceAttr('protocol') + group_id = attrs.NumericAttr('group_id', label=_('Group ID')) + name = attrs.TextAttr('name') + description = attrs.TextAttr('description') + member_count = attrs.NumericAttr('member_count', label=_('Members')) + + +class FHRPGroupAuthPanel(panels.ObjectAttributesPanel): + title = _('Authentication') + + auth_type = attrs.ChoiceAttr('auth_type', label=_('Authentication Type')) + auth_key = attrs.TextAttr('auth_key', label=_('Authentication Key')) + + +class VLANPanel(panels.ObjectAttributesPanel): + region = attrs.NestedObjectAttr('site.region', linkify=True, label=_('Region')) + site = attrs.RelatedObjectAttr('site', linkify=True) + group = attrs.RelatedObjectAttr('group', linkify=True) + vid = attrs.NumericAttr('vid', label=_('VLAN ID')) + name = attrs.TextAttr('name') + tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group') + status = attrs.ChoiceAttr('status') + role = attrs.RelatedObjectAttr('role', linkify=True) + description = attrs.TextAttr('description') + qinq_role = attrs.ChoiceAttr('qinq_role', label=_('Q-in-Q Role')) + qinq_svlan = attrs.RelatedObjectAttr('qinq_svlan', linkify=True, label=_('Q-in-Q SVLAN')) + l2vpn = attrs.RelatedObjectAttr('l2vpn_termination.l2vpn', linkify=True, label=_('L2VPN')) + + +class VLANCustomerVLANsPanel(panels.ObjectsTablePanel): + """ + A panel listing customer VLANs (C-VLANs) for an S-VLAN. Only renders when the VLAN has Q-in-Q + role 'svlan'. + """ + def __init__(self): + super().__init__( + 'ipam.vlan', + filters={'qinq_svlan_id': lambda ctx: ctx['object'].pk}, + title=_('Customer VLANs'), + actions=[ + actions.AddObject( + 'ipam.vlan', + url_params={ + 'qinq_role': 'cvlan', + 'qinq_svlan': lambda ctx: ctx['object'].pk, + }, + label=_('Add a VLAN'), + ), + ], + ) + + def render(self, context): + obj = context.get('object') + if not obj or obj.qinq_role != 'svlan': + return '' + return super().render(context) + + +class ServiceTemplatePanel(panels.ObjectAttributesPanel): + name = attrs.TextAttr('name') + protocol = attrs.ChoiceAttr('protocol') + ports = attrs.TextAttr('port_list', label=_('Ports')) + description = attrs.TextAttr('description') + + +class ServicePanel(panels.ObjectAttributesPanel): + name = attrs.TextAttr('name') + parent = attrs.RelatedObjectAttr('parent', linkify=True) + protocol = attrs.ChoiceAttr('protocol') + ports = attrs.TextAttr('port_list', label=_('Ports')) + ip_addresses = attrs.TemplatedAttr( + 'ipaddresses', + template_name='ipam/service/attrs/ip_addresses.html', + label=_('IP Addresses'), + ) + description = attrs.TextAttr('description') diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index cbea4f318..44cba22b2 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -9,8 +9,16 @@ from circuits.models import Provider from dcim.filtersets import InterfaceFilterSet from dcim.forms import InterfaceFilterForm from dcim.models import Device, Interface, Site -from ipam.tables import VLANTranslationRuleTable +from extras.ui.panels import CustomFieldsPanel, TagsPanel from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport +from netbox.ui import actions, layout +from netbox.ui.panels import ( + CommentsPanel, + ContextTablePanel, + ObjectsTablePanel, + RelatedObjectsPanel, + TemplatePanel, +) from netbox.views import generic from utilities.query import count_related from utilities.tables import get_table_ordering @@ -23,6 +31,7 @@ from . import filtersets, forms, tables from .choices import PrefixStatusChoices from .constants import * from .models import * +from .ui import panels from .utils import add_available_vlans, add_requested_prefixes, annotate_ip_space # @@ -41,6 +50,27 @@ class VRFListView(generic.ObjectListView): @register_model_view(VRF) class VRFView(GetRelatedModelsMixin, generic.ObjectView): queryset = VRF.objects.all() + layout = layout.Layout( + layout.Row( + layout.Column( + panels.VRFPanel(), + TagsPanel(), + ), + layout.Column( + RelatedObjectsPanel(), + CustomFieldsPanel(), + CommentsPanel(), + ), + ), + layout.Row( + layout.Column( + ContextTablePanel('import_targets_table', title=_('Import route targets')), + ), + layout.Column( + ContextTablePanel('export_targets_table', title=_('Export route targets')), + ), + ), + ) def get_extra_context(self, request, instance): import_targets_table = tables.RouteTargetTable( @@ -134,6 +164,50 @@ class RouteTargetListView(generic.ObjectListView): @register_model_view(RouteTarget) class RouteTargetView(generic.ObjectView): queryset = RouteTarget.objects.all() + layout = layout.Layout( + layout.Row( + layout.Column( + panels.RouteTargetPanel(), + TagsPanel(), + ), + layout.Column( + CustomFieldsPanel(), + CommentsPanel(), + ), + ), + layout.Row( + layout.Column( + ObjectsTablePanel( + 'ipam.vrf', + filters={'import_target_id': lambda ctx: ctx['object'].pk}, + title=_('Importing VRFs'), + ), + ), + layout.Column( + ObjectsTablePanel( + 'ipam.vrf', + filters={'export_target_id': lambda ctx: ctx['object'].pk}, + title=_('Exporting VRFs'), + ), + ), + ), + layout.Row( + layout.Column( + ObjectsTablePanel( + 'vpn.l2vpn', + filters={'import_target_id': lambda ctx: ctx['object'].pk}, + title=_('Importing L2VPNs'), + ), + ), + layout.Column( + ObjectsTablePanel( + 'vpn.l2vpn', + filters={'export_target_id': lambda ctx: ctx['object'].pk}, + title=_('Exporting L2VPNs'), + ), + ), + ), + ) @register_model_view(RouteTarget, 'add', detail=False) @@ -192,6 +266,17 @@ class RIRListView(generic.ObjectListView): @register_model_view(RIR) class RIRView(GetRelatedModelsMixin, generic.ObjectView): queryset = RIR.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.RIRPanel(), + TagsPanel(), + ], + right_panels=[ + RelatedObjectsPanel(), + CommentsPanel(), + CustomFieldsPanel(), + ], + ) def get_extra_context(self, request, instance): return { @@ -257,6 +342,16 @@ class ASNRangeListView(generic.ObjectListView): @register_model_view(ASNRange) class ASNRangeView(generic.ObjectView): queryset = ASNRange.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.ASNRangePanel(), + TagsPanel(), + ], + right_panels=[ + CommentsPanel(), + CustomFieldsPanel(), + ], + ) @register_model_view(ASNRange, 'asns') @@ -337,6 +432,17 @@ class ASNListView(generic.ObjectListView): @register_model_view(ASN) class ASNView(GetRelatedModelsMixin, generic.ObjectView): queryset = ASN.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.ASNPanel(), + TagsPanel(), + ], + right_panels=[ + RelatedObjectsPanel(), + CustomFieldsPanel(), + CommentsPanel(), + ], + ) def get_extra_context(self, request, instance): return { @@ -412,6 +518,16 @@ class AggregateListView(generic.ObjectListView): @register_model_view(Aggregate) class AggregateView(generic.ObjectView): queryset = Aggregate.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.AggregatePanel(), + ], + right_panels=[ + CustomFieldsPanel(), + TagsPanel(), + CommentsPanel(), + ], + ) @register_model_view(Aggregate, 'prefixes') @@ -506,6 +622,17 @@ class RoleListView(generic.ObjectListView): @register_model_view(Role) class RoleView(GetRelatedModelsMixin, generic.ObjectView): queryset = Role.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.RolePanel(), + TagsPanel(), + ], + right_panels=[ + RelatedObjectsPanel(), + CommentsPanel(), + CustomFieldsPanel(), + ], + ) def get_extra_context(self, request, instance): return { @@ -569,15 +696,23 @@ class PrefixListView(generic.ObjectListView): @register_model_view(Prefix) class PrefixView(generic.ObjectView): queryset = Prefix.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.PrefixPanel(), + ], + right_panels=[ + TemplatePanel('ipam/panels/prefix_addressing.html'), + CustomFieldsPanel(), + TagsPanel(), + CommentsPanel(), + ], + bottom_panels=[ + ContextTablePanel('duplicate_prefix_table', title=_('Duplicate prefixes')), + ContextTablePanel('parent_prefix_table', title=_('Parent prefixes')), + ], + ) def get_extra_context(self, request, instance): - try: - aggregate = Aggregate.objects.restrict(request.user, 'view').get( - prefix__net_contains_or_equals=str(instance.prefix) - ) - except Aggregate.DoesNotExist: - aggregate = None - # Parent prefixes table parent_prefixes = Prefix.objects.restrict(request.user, 'view').filter( Q(vrf=instance.vrf) | Q(vrf__isnull=True, status=PrefixStatusChoices.STATUS_CONTAINER) @@ -608,11 +743,12 @@ class PrefixView(generic.ObjectView): ) duplicate_prefix_table.configure(request) - return { - 'aggregate': aggregate, + context = { 'parent_prefix_table': parent_prefix_table, - 'duplicate_prefix_table': duplicate_prefix_table, } + if duplicate_prefixes.exists(): + context['duplicate_prefix_table'] = duplicate_prefix_table + return context @register_model_view(Prefix, 'prefixes') @@ -756,6 +892,19 @@ class IPRangeListView(generic.ObjectListView): @register_model_view(IPRange) class IPRangeView(generic.ObjectView): queryset = IPRange.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.IPRangePanel(), + ], + right_panels=[ + TagsPanel(), + CustomFieldsPanel(), + CommentsPanel(), + ], + bottom_panels=[ + ContextTablePanel('parent_prefixes_table', title=_('Parent prefixes')), + ], + ) def get_extra_context(self, request, instance): @@ -853,6 +1002,23 @@ class IPAddressListView(generic.ObjectListView): @register_model_view(IPAddress) class IPAddressView(generic.ObjectView): queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant') + layout = layout.SimpleLayout( + left_panels=[ + panels.IPAddressPanel(), + TagsPanel(), + CustomFieldsPanel(), + CommentsPanel(), + ], + right_panels=[ + ContextTablePanel('parent_prefixes_table', title=_('Parent prefixes')), + ContextTablePanel('duplicate_ips_table', title=_('Duplicate IPs')), + ObjectsTablePanel( + 'ipam.service', + filters={'ip_address_id': lambda ctx: ctx['object'].pk}, + title=_('Application services'), + ), + ], + ) def get_extra_context(self, request, instance): # Parent prefixes table @@ -885,10 +1051,12 @@ class IPAddressView(generic.ObjectView): duplicate_ips_table = tables.IPAddressTable(duplicate_ips[:10], orderable=False) duplicate_ips_table.configure(request) - return { + context = { 'parent_prefixes_table': parent_prefixes_table, - 'duplicate_ips_table': duplicate_ips_table, } + if duplicate_ips.exists(): + context['duplicate_ips_table'] = duplicate_ips_table + return context @register_model_view(IPAddress, 'add', detail=False) @@ -1038,6 +1206,17 @@ class VLANGroupListView(generic.ObjectListView): @register_model_view(VLANGroup) class VLANGroupView(GetRelatedModelsMixin, generic.ObjectView): queryset = VLANGroup.objects.annotate_utilization() + layout = layout.SimpleLayout( + left_panels=[ + panels.VLANGroupPanel(), + TagsPanel(), + ], + right_panels=[ + RelatedObjectsPanel(), + CommentsPanel(), + CustomFieldsPanel(), + ], + ) def get_extra_context(self, request, instance): return { @@ -1125,19 +1304,32 @@ class VLANTranslationPolicyListView(generic.ObjectListView): @register_model_view(VLANTranslationPolicy) -class VLANTranslationPolicyView(GetRelatedModelsMixin, generic.ObjectView): +class VLANTranslationPolicyView(generic.ObjectView): queryset = VLANTranslationPolicy.objects.all() - - def get_extra_context(self, request, instance): - vlan_translation_table = VLANTranslationRuleTable( - data=instance.rules.all(), - orderable=False - ) - vlan_translation_table.configure(request) - - return { - 'vlan_translation_table': vlan_translation_table, - } + layout = layout.SimpleLayout( + left_panels=[ + panels.VLANTranslationPolicyPanel(), + ], + right_panels=[ + TagsPanel(), + CustomFieldsPanel(), + CommentsPanel(), + ], + bottom_panels=[ + ObjectsTablePanel( + 'ipam.vlantranslationrule', + filters={'policy_id': lambda ctx: ctx['object'].pk}, + title=_('VLAN translation rules'), + actions=[ + actions.AddObject( + 'ipam.vlantranslationrule', + url_params={'policy': lambda ctx: ctx['object'].pk}, + label=_('Add Rule'), + ), + ], + ), + ], + ) @register_model_view(VLANTranslationPolicy, 'add', detail=False) @@ -1193,13 +1385,17 @@ class VLANTranslationRuleListView(generic.ObjectListView): @register_model_view(VLANTranslationRule) -class VLANTranslationRuleView(GetRelatedModelsMixin, generic.ObjectView): +class VLANTranslationRuleView(generic.ObjectView): queryset = VLANTranslationRule.objects.all() - - def get_extra_context(self, request, instance): - return { - 'related_models': self.get_related_models(request, instance), - } + layout = layout.SimpleLayout( + left_panels=[ + panels.VLANTranslationRulePanel(), + ], + right_panels=[ + TagsPanel(), + CustomFieldsPanel(), + ], + ) @register_model_view(VLANTranslationRule, 'add', detail=False) @@ -1251,7 +1447,36 @@ class FHRPGroupListView(generic.ObjectListView): @register_model_view(FHRPGroup) class FHRPGroupView(GetRelatedModelsMixin, generic.ObjectView): - queryset = FHRPGroup.objects.all() + queryset = FHRPGroup.objects.annotate( + member_count=count_related(FHRPGroupAssignment, 'group') + ) + layout = layout.SimpleLayout( + left_panels=[ + panels.FHRPGroupPanel(), + TagsPanel(), + CommentsPanel(), + ], + right_panels=[ + panels.FHRPGroupAuthPanel(), + RelatedObjectsPanel(), + CustomFieldsPanel(), + ], + bottom_panels=[ + ObjectsTablePanel( + 'ipam.ipaddress', + filters={'fhrpgroup_id': lambda ctx: ctx['object'].pk}, + title=_('Virtual IP addresses'), + actions=[ + actions.AddObject( + 'ipam.ipaddress', + url_params={'fhrpgroup': lambda ctx: ctx['object'].pk}, + label=_('Add IP Address'), + ), + ], + ), + ContextTablePanel('members_table', title=_('Members')), + ], + ) def get_extra_context(self, request, instance): # Get assigned interfaces @@ -1276,7 +1501,6 @@ class FHRPGroupView(GetRelatedModelsMixin, generic.ObjectView): ), ), 'members_table': members_table, - 'member_count': FHRPGroupAssignment.objects.filter(group=instance).count(), } @@ -1379,17 +1603,35 @@ class VLANListView(generic.ObjectListView): @register_model_view(VLAN) class VLANView(generic.ObjectView): queryset = VLAN.objects.all() - - def get_extra_context(self, request, instance): - prefixes = Prefix.objects.restrict(request.user, 'view').filter(vlan=instance).prefetch_related( - 'vrf', 'scope', 'role', 'tenant' - ) - prefix_table = tables.PrefixTable(list(prefixes), exclude=('vlan', 'utilization'), orderable=False) - prefix_table.configure(request) - - return { - 'prefix_table': prefix_table, - } + layout = layout.SimpleLayout( + left_panels=[ + panels.VLANPanel(), + ], + right_panels=[ + CustomFieldsPanel(), + TagsPanel(), + CommentsPanel(), + ], + bottom_panels=[ + ObjectsTablePanel( + 'ipam.prefix', + filters={'vlan_id': lambda ctx: ctx['object'].pk}, + title=_('Prefixes'), + actions=[ + actions.AddObject( + 'ipam.prefix', + url_params={ + 'tenant': lambda ctx: ctx['object'].tenant.pk if ctx['object'].tenant else None, + 'site': lambda ctx: ctx['object'].site.pk if ctx['object'].site else None, + 'vlan': lambda ctx: ctx['object'].pk, + }, + label=_('Add a Prefix'), + ), + ], + ), + panels.VLANCustomerVLANsPanel(), + ], + ) @register_model_view(VLAN, 'interfaces') @@ -1483,6 +1725,16 @@ class ServiceTemplateListView(generic.ObjectListView): @register_model_view(ServiceTemplate) class ServiceTemplateView(generic.ObjectView): queryset = ServiceTemplate.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.ServiceTemplatePanel(), + ], + right_panels=[ + CustomFieldsPanel(), + TagsPanel(), + CommentsPanel(), + ], + ) @register_model_view(ServiceTemplate, 'add', detail=False) @@ -1539,6 +1791,16 @@ class ServiceListView(generic.ObjectListView): @register_model_view(Service) class ServiceView(generic.ObjectView): queryset = Service.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.ServicePanel(), + ], + right_panels=[ + CustomFieldsPanel(), + TagsPanel(), + CommentsPanel(), + ], + ) def get_extra_context(self, request, instance): context = {} diff --git a/netbox/templates/ipam/aggregate.html b/netbox/templates/ipam/aggregate.html index 54452f1f3..c02c5f1a3 100644 --- a/netbox/templates/ipam/aggregate.html +++ b/netbox/templates/ipam/aggregate.html @@ -1,62 +1 @@ {% extends 'ipam/aggregate/base.html' %} -{% load buttons %} -{% load helpers %} -{% load plugins %} -{% load i18n %} - -{% block content %} -
-
-
-

{% trans "Aggregate" %}

- - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans "Family" %}IPv{{ object.family }}
{% trans "RIR" %} - {{ object.rir }} -
{% trans "Utilization" %} - {% utilization_graph object.get_utilization %} -
{% trans "Tenant" %} - {% if object.tenant.group %} - {{ object.tenant.group|linkify }} / - {% endif %} - {{ object.tenant|linkify|placeholder }} -
{% trans "Date Added" %}{{ object.date_added|isodate|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 %} -
-
-
-
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/ipam/aggregate/attrs/utilization.html b/netbox/templates/ipam/aggregate/attrs/utilization.html new file mode 100644 index 000000000..f105ba59c --- /dev/null +++ b/netbox/templates/ipam/aggregate/attrs/utilization.html @@ -0,0 +1,2 @@ +{% load helpers %} +{% utilization_graph object.get_utilization %} diff --git a/netbox/templates/ipam/asn.html b/netbox/templates/ipam/asn.html index 3a54e453b..f10cf5243 100644 --- a/netbox/templates/ipam/asn.html +++ b/netbox/templates/ipam/asn.html @@ -1,8 +1,4 @@ {% extends 'generic/object.html' %} -{% load buttons %} -{% load helpers %} -{% load plugins %} -{% load render_table from django_tables2 %} {% load i18n %} {% block breadcrumbs %} @@ -12,51 +8,3 @@ {% endif %} {% endblock breadcrumbs %} - -{% block content %} -
-
-
-

{% trans "ASN" %}

- - - - - - - - - - - - - - - - - -
{% trans "AS Number" %}{{ object.asn_with_asdot }}
{% trans "RIR" %} - {{ object.rir }} -
{% trans "Tenant" %} - {% if object.tenant.group %} - {{ object.tenant.group|linkify }} / - {% endif %} - {{ object.tenant|linkify|placeholder }} -
{% trans "Description" %}{{ object.description|placeholder }}
-
- {% plugin_left_page object %} - {% include 'inc/panels/tags.html' %} -
-
- {% include 'inc/panels/related_objects.html' %} - {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/comments.html' %} - {% plugin_right_page object %} -
-
-
-
- {% plugin_full_width_page object %} -
-
-{% endblock content %} diff --git a/netbox/templates/ipam/asnrange.html b/netbox/templates/ipam/asnrange.html index eed3630ef..5ad59ca0c 100644 --- a/netbox/templates/ipam/asnrange.html +++ b/netbox/templates/ipam/asnrange.html @@ -1,57 +1 @@ {% extends 'ipam/asnrange/base.html' %} -{% load buttons %} -{% load helpers %} -{% load plugins %} -{% load render_table from django_tables2 %} -{% load i18n %} - -{% block content %} -
-
-
-

{% trans "ASN Range" %}

- - - - - - - - - - - - - - - - - - - - - -
{% trans "Name" %}{{ object.name }}
{% trans "RIR" %} - {{ object.rir }} -
{% trans "Range" %}{{ object.range_as_string_with_asdot }}
{% trans "Tenant" %} - {% if object.tenant.group %} - {{ object.tenant.group|linkify }} / - {% endif %} - {{ object.tenant|linkify|placeholder }} -
{% trans "Description" %}{{ object.description|placeholder }}
-
- {% plugin_left_page object %} - {% include 'inc/panels/tags.html' %} -
-
- {% include 'inc/panels/comments.html' %} - {% include 'inc/panels/custom_fields.html' %} - {% plugin_right_page object %} -
-
-
-
- {% plugin_full_width_page object %} -
-
-{% endblock content %} diff --git a/netbox/templates/ipam/attrs/vrf.html b/netbox/templates/ipam/attrs/vrf.html new file mode 100644 index 000000000..10280ed8a --- /dev/null +++ b/netbox/templates/ipam/attrs/vrf.html @@ -0,0 +1,2 @@ +{% load helpers i18n %} +{% if value %}{{ value|linkify }}{% if show_rd %} ({{ value.rd }}){% endif %}{% else %}{% trans "Global" %}{% endif %} diff --git a/netbox/templates/ipam/fhrpgroup.html b/netbox/templates/ipam/fhrpgroup.html index 9c414398e..835f26881 100644 --- a/netbox/templates/ipam/fhrpgroup.html +++ b/netbox/templates/ipam/fhrpgroup.html @@ -1,7 +1,4 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} -{% load render_table from django_tables2 %} {% load i18n %} {# Omit assigned IP addresses from object representation #} @@ -11,75 +8,3 @@ {{ block.super }} {% endblock breadcrumbs %} - -{% block content %} -
-
-
-

{% trans "FHRP Group" %}

- - - - - - - - - - - - - - - - - - - - - -
{% trans "Protocol" %}{{ object.get_protocol_display }}
{% trans "Group ID" %}{{ object.group_id }}
{% trans "Name" %}{{ object.name|placeholder }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Members" %}{{ member_count }}
-
- {% include 'inc/panels/tags.html' %} - {% include 'inc/panels/comments.html' %} - {% plugin_left_page object %} -
-
-
-

{% trans "Authentication" %}

- - - - - - - - - -
{% trans "Authentication Type" %}{{ object.get_auth_type_display|placeholder }}
{% trans "Authentication Key" %}{{ object.auth_key|placeholder }}
-
- {% include 'inc/panels/related_objects.html' %} - {% include 'inc/panels/custom_fields.html' %} - {% plugin_right_page object %} -
-
-
-
-
-

- {% trans "Virtual IP Addresses" %} - {% if perms.ipam.add_ipaddress %} - - {% endif %} -

- {% htmx_table 'ipam:ipaddress_list' fhrpgroup_id=object.pk %} -
- {% include 'inc/panel_table.html' with table=members_table heading='Members' %} - {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/ipam/ipaddress.html b/netbox/templates/ipam/ipaddress.html index 8b4473192..18045590d 100644 --- a/netbox/templates/ipam/ipaddress.html +++ b/netbox/templates/ipam/ipaddress.html @@ -1,129 +1 @@ -{% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} -{% load render_table from django_tables2 %} -{% load i18n %} - -{% block content %} -
-
-
-

{% trans "IP Address" %}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans "Family" %}IPv{{ object.family }}
{% trans "VRF" %} - {% if object.vrf %} - {{ object.vrf }} - {% else %} - {% trans "Global" %} - {% endif %} -
{% trans "Tenant" %} - {% if object.tenant.group %} - {{ object.tenant.group|linkify }} / - {% endif %} - {{ object.tenant|linkify|placeholder }} -
{% trans "Status" %}{% badge object.get_status_display bg_color=object.get_status_color %}
{% trans "Role" %} - {% if object.role %} - {{ object.get_role_display }} - {% else %} - {{ ''|placeholder }} - {% endif %} -
{% trans "DNS Name" %}{{ object.dns_name|placeholder }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Assignment" %} - {% if object.assigned_object %} - {% if object.assigned_object.parent_object %} - {{ object.assigned_object.parent_object|linkify }} / - {% endif %} - {{ object.assigned_object|linkify }} - {% else %} - {{ ''|placeholder }} - {% endif %} -
{% trans "NAT (inside)" %} - {% if object.nat_inside %} - {{ object.nat_inside|linkify }} - {% if object.nat_inside.assigned_object %} - ({{ object.nat_inside.assigned_object.parent_object|linkify }}) - {% endif %} - {% else %} - {{ ''|placeholder }} - {% endif %} -
{% trans "NAT (outside)" %} - {% for ip in object.nat_outside.all %} - {{ ip|linkify }} - {% if ip.assigned_object %} - ({{ ip.assigned_object.parent_object|linkify }}) - {% endif %}
- {% empty %} - {{ ''|placeholder }} - {% endfor %} -
Primary IP{% checkmark object.is_primary_ip %}
OOB IP{% checkmark object.is_oob_ip %}
-
- {% include 'inc/panels/tags.html' %} - {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/comments.html' %} - {% plugin_left_page object %} -
-
- {% include 'inc/panel_table.html' with table=parent_prefixes_table heading='Parent Prefixes' %} - {% if duplicate_ips_table.rows %} - {% include 'inc/panel_table.html' with table=duplicate_ips_table heading='Duplicate IPs' panel_class='danger' %} - {% endif %} -
-

{% trans "Application Services" %}

- {% htmx_table 'ipam:service_list' ip_address_id=object.pk %} -
- {% plugin_right_page object %} -
-
-
-
- {% plugin_full_width_page object %} -
-
-{% endblock %} +{% extends 'ipam/ipaddress/base.html' %} diff --git a/netbox/templates/ipam/ipaddress/attrs/assigned_object.html b/netbox/templates/ipam/ipaddress/attrs/assigned_object.html new file mode 100644 index 000000000..7e585a627 --- /dev/null +++ b/netbox/templates/ipam/ipaddress/attrs/assigned_object.html @@ -0,0 +1,2 @@ +{% load helpers %} +{% if value.parent_object %}{{ value.parent_object|linkify }} / {% endif %}{{ value|linkify }} diff --git a/netbox/templates/ipam/ipaddress/attrs/nat_inside.html b/netbox/templates/ipam/ipaddress/attrs/nat_inside.html new file mode 100644 index 000000000..17dd6f8ea --- /dev/null +++ b/netbox/templates/ipam/ipaddress/attrs/nat_inside.html @@ -0,0 +1,2 @@ +{% load helpers %} +{{ value|linkify }}{% if value.assigned_object %} ({{ value.assigned_object.parent_object|linkify }}){% endif %} diff --git a/netbox/templates/ipam/ipaddress/attrs/nat_outside.html b/netbox/templates/ipam/ipaddress/attrs/nat_outside.html new file mode 100644 index 000000000..73dd72035 --- /dev/null +++ b/netbox/templates/ipam/ipaddress/attrs/nat_outside.html @@ -0,0 +1,2 @@ +{% load helpers %} +{% for ip in value.all %}{{ ip|linkify }}{% if ip.assigned_object %} ({{ ip.assigned_object.parent_object|linkify }}){% endif %}
{% empty %}{% endfor %} diff --git a/netbox/templates/ipam/iprange.html b/netbox/templates/ipam/iprange.html index c4026848b..7fcb860c9 100644 --- a/netbox/templates/ipam/iprange.html +++ b/netbox/templates/ipam/iprange.html @@ -1,98 +1 @@ {% extends 'ipam/iprange/base.html' %} -{% load helpers %} -{% load plugins %} -{% load i18n %} - -{% block content %} -
-
-
-

{% trans "IP Range" %}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans "Family" %}IPv{{ object.family }}
{% trans "Starting Address" %}{{ object.start_address }}
{% trans "Ending Address" %}{{ object.end_address }}
{% trans "Size" %}{{ object.size }}
{% trans "Marked Populated" %}{% checkmark object.mark_populated %}
{% trans "Marked Utilized" %}{% checkmark object.mark_utilized %}
{% trans "Utilization" %} - {% if object.mark_utilized %} - {% utilization_graph 100 warning_threshold=0 danger_threshold=0 %} - {% else %} - {% utilization_graph object.utilization %} - {% endif %} -
{% trans "VRF" %} - {% if object.vrf %} - {{ object.vrf|linkify }} ({{ object.vrf.rd }}) - {% else %} - {% trans "Global" %} - {% endif %} -
{% trans "Role" %}{{ object.role|linkify|placeholder }}
{% trans "Status" %}{% badge object.get_status_display bg_color=object.get_status_color %}
{% trans "Tenant" %} - {% if object.tenant.group %} - {{ object.tenant.group|linkify }} / - {% endif %} - {{ object.tenant|linkify|placeholder }} -
{% trans "Description" %}{{ object.description|placeholder }}
-
- {% plugin_left_page object %} -
-
- {% include 'inc/panels/tags.html' %} - {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/comments.html' %} - {% plugin_right_page object %} -
-
-
-
- {% include 'inc/panel_table.html' with table=parent_prefixes_table heading='Parent Prefixes' %} -
-
-
-
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/ipam/iprange/attrs/utilization.html b/netbox/templates/ipam/iprange/attrs/utilization.html new file mode 100644 index 000000000..ed9579ed6 --- /dev/null +++ b/netbox/templates/ipam/iprange/attrs/utilization.html @@ -0,0 +1,6 @@ +{% load helpers %} +{% if object.mark_utilized %} + {% utilization_graph 100 warning_threshold=0 danger_threshold=0 %} +{% else %} + {% utilization_graph value %} +{% endif %} diff --git a/netbox/templates/ipam/panels/prefix_addressing.html b/netbox/templates/ipam/panels/prefix_addressing.html new file mode 100644 index 000000000..ff74d6c0a --- /dev/null +++ b/netbox/templates/ipam/panels/prefix_addressing.html @@ -0,0 +1,62 @@ +{% load humanize helpers i18n %} +
+

+ {% trans "Addressing" %} + {% if object.prefix.version == 4 %} + + {% endif %} +

+ + + + + + {% with child_ip_count=object.get_child_ips.count %} + + + + + {% endwith %} + {% with available_count=object.get_available_ips.size %} + + + + + {% endwith %} + + + + +
{% trans "Utilization" %} + {% if object.mark_utilized %} + {% utilization_graph 100 warning_threshold=0 danger_threshold=0 %} + ({% trans "Marked fully utilized" %}) + {% else %} + {% utilization_graph object.get_utilization %} + {% endif %} +
{% trans "Child IPs" %} + {{ child_ip_count }} +
{% trans "Available IPs" %} + {% if available_count > 1000000 %} + {{ available_count|intword }} + {% else %} + {{ available_count|intcomma }} + {% endif %} +
{% trans "First available IP" %} + {% with first_available_ip=object.get_first_available_ip %} + {% if first_available_ip %} + {% if perms.ipam.add_ipaddress %} + {{ first_available_ip }} + {% else %} + {{ first_available_ip }} + {% endif %} + {% else %} + {{ ''|placeholder }} + {% endif %} + {% endwith %} +
+
diff --git a/netbox/templates/ipam/prefix.html b/netbox/templates/ipam/prefix.html index a582561bd..dbee0253d 100644 --- a/netbox/templates/ipam/prefix.html +++ b/netbox/templates/ipam/prefix.html @@ -1,202 +1 @@ {% extends 'ipam/prefix/base.html' %} -{% load humanize %} -{% load helpers %} -{% load plugins %} -{% load i18n %} -{% load mptt %} - -{% block content %} -
-
-
-

{% trans "Prefix" %}

- - - - - - - - - - - - - - - - - - - - {% if object.scope %} - - {% else %} - - {% endif %} - - - - - - - - - - - - - - - - - - - - - -
{% trans "Family" %}IPv{{ object.family }}
{% trans "VRF" %} - {% if object.vrf %} - {{ object.vrf }} - {% else %} - {% trans "Global" %} - {% endif %} -
{% trans "Tenant" %} - {% if object.tenant.group %} - {{ object.tenant.group|linkify }} / - {% endif %} - {{ object.tenant|linkify|placeholder }} -
{% trans "Aggregate" %} - {% if aggregate %} - {{ aggregate.prefix }} ({{ aggregate.rir }}) - {% else %} - {{ ''|placeholder }} - {% endif %} -
{% trans "Scope" %}{{ object.scope|linkify }} ({% trans object.scope_type.name %}){{ ''|placeholder }}
{% trans "VLAN" %} - {% if object.vlan %} - {% if object.vlan.group %} - {{ object.vlan.group|linkify }} / - {% endif %} - {{ object.vlan|linkify }} - {% else %} - {{ ''|placeholder }} - {% endif %} -
{% trans "Status" %}{% badge object.get_status_display bg_color=object.get_status_color %}
{% trans "Role" %}{{ object.role|linkify|placeholder }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Is a pool" %}{% checkmark object.is_pool %}
-
- {% plugin_left_page object %} -
-
-
-

- {% trans "Addressing" %} - {% if object.prefix.version == 4 %} - - {% endif %} -

- - - - - - {% with child_ip_count=object.get_child_ips.count %} - - - - - {% endwith %} - {% with available_count=object.get_available_ips.size %} - - - - - {% endwith %} - - - - -
{% trans "Utilization" %} - {% if object.mark_utilized %} - {% utilization_graph 100 warning_threshold=0 danger_threshold=0 %} - ({% trans "Marked fully utilized" %}) - {% else %} - {% utilization_graph object.get_utilization %} - {% endif %} -
{% trans "Child IPs" %} - {{ child_ip_count }} -
{% trans "Available IPs" %} - {# Use human-friendly words for counts greater than one million #} - {% if available_count > 1000000 %} - {{ available_count|intword }} - {% else %} - {{ available_count|intcomma }} - {% endif %} -
{% trans "First available IP" %} - {% with first_available_ip=object.get_first_available_ip %} - {% if first_available_ip %} - {% if perms.ipam.add_ipaddress %} - {{ first_available_ip }} - {% else %} - {{ first_available_ip }} - {% endif %} - {% else %} - {{ ''|placeholder }} - {% endif %} - {% endwith %} -
-
- {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/tags.html' %} - {% include 'inc/panels/comments.html' %} - {% plugin_right_page object %} -
-
-
-
- {% if duplicate_prefix_table.rows %} - {% include 'inc/panel_table.html' with table=duplicate_prefix_table heading='Duplicate Prefixes' %} - {% endif %} - {% include 'inc/panel_table.html' with table=parent_prefix_table heading='Parent Prefixes' %} - {% plugin_full_width_page object %} -
-
-{% endblock %} - -{% block modals %} - {{ block.super }} - {% if object.prefix.version == 4 %} - - {% endif %} -{% endblock %} diff --git a/netbox/templates/ipam/prefix/attrs/aggregate.html b/netbox/templates/ipam/prefix/attrs/aggregate.html new file mode 100644 index 000000000..924993f4e --- /dev/null +++ b/netbox/templates/ipam/prefix/attrs/aggregate.html @@ -0,0 +1,2 @@ +{% load helpers %} +{{ value|linkify }} ({{ value.rir }}) diff --git a/netbox/templates/ipam/prefix/base.html b/netbox/templates/ipam/prefix/base.html index 7ac307014..9d7f60cb7 100644 --- a/netbox/templates/ipam/prefix/base.html +++ b/netbox/templates/ipam/prefix/base.html @@ -1,6 +1,7 @@ {% extends 'generic/object.html' %} {% load buttons %} {% load helpers %} +{% load i18n %} {% block breadcrumbs %} {{ block.super }} @@ -8,3 +9,39 @@ {% endif %} {% endblock %} + +{% block modals %} + {{ block.super }} + {% if object.prefix.version == 4 %} + + {% endif %} +{% endblock modals %} diff --git a/netbox/templates/ipam/rir.html b/netbox/templates/ipam/rir.html index a9a1b7849..41f31de93 100644 --- a/netbox/templates/ipam/rir.html +++ b/netbox/templates/ipam/rir.html @@ -1,7 +1,4 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} -{% load render_table from django_tables2 %} {% load i18n %} {% block extra_controls %} @@ -11,40 +8,3 @@ {% endif %} {% endblock extra_controls %} - -{% block content %} -
-
-
-

{% trans "RIR" %}

- - - - - - - - - - - - - -
{% trans "Name" %}{{ object.name }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Private" %}{% checkmark object.is_private %}
-
- {% 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/ipam/role.html b/netbox/templates/ipam/role.html index 8b780d992..b7763170a 100644 --- a/netbox/templates/ipam/role.html +++ b/netbox/templates/ipam/role.html @@ -1,7 +1,4 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} -{% load render_table from django_tables2 %} {% load i18n %} {% block extra_controls %} @@ -11,40 +8,3 @@ {% endif %} {% endblock extra_controls %} - -{% block content %} -
-
-
-

{% trans "Role" %}

- - - - - - - - - - - - - -
{% trans "Name" %}{{ object.name }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Weight" %}{{ object.weight }}
-
- {% 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/ipam/routetarget.html b/netbox/templates/ipam/routetarget.html index 2f7d8e806..f15e1d050 100644 --- a/netbox/templates/ipam/routetarget.html +++ b/netbox/templates/ipam/routetarget.html @@ -1,68 +1 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} -{% load i18n %} - -{% block content %} -
-
-
-

{% trans "Route Target" %}

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

{% trans "Importing VRFs" %}

- {% htmx_table 'ipam:vrf_list' import_target_id=object.pk %} -
-
-
-
-

{% trans "Exporting VRFs" %}

- {% htmx_table 'ipam:vrf_list' export_target_id=object.pk %} -
-
-
-
-
-
-

{% trans "Importing L2VPNs" %}

- {% htmx_table 'vpn:l2vpn_list' import_target_id=object.pk %} -
-
-
-
-

{% trans "Exporting L2VPNs" %}

- {% htmx_table 'vpn:l2vpn_list' export_target_id=object.pk %} -
-
-
-
-
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/ipam/service.html b/netbox/templates/ipam/service.html index 2a504485b..18aac8b08 100644 --- a/netbox/templates/ipam/service.html +++ b/netbox/templates/ipam/service.html @@ -1,8 +1,4 @@ {% extends 'generic/object.html' %} -{% load buttons %} -{% load helpers %} -{% load perms %} -{% load plugins %} {% load i18n %} {% block breadcrumbs %} @@ -14,58 +10,4 @@ {% endif %} -{% endblock %} - -{% block content %} -
-
-
-

{% trans "Service" %}

- - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans "Name" %}{{ object.name }}
{% trans "Parent" %}{{ object.parent|linkify }}
{% trans "Protocol" %}{{ object.get_protocol_display }}
{% trans "Ports" %}{{ object.port_list }}
{% trans "IP Addresses" %} - {% for ipaddress in object.ipaddresses.all %} - {{ ipaddress|linkify }}
- {% empty %} - {{ ''|placeholder }} - {% endfor %} -
{% 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 %} -
-
-
-
- {% plugin_full_width_page object %} -
-
-{% endblock %} +{% endblock breadcrumbs %} diff --git a/netbox/templates/ipam/service/attrs/ip_addresses.html b/netbox/templates/ipam/service/attrs/ip_addresses.html new file mode 100644 index 000000000..ce0516c69 --- /dev/null +++ b/netbox/templates/ipam/service/attrs/ip_addresses.html @@ -0,0 +1,2 @@ +{% load helpers %} +{% for ipaddress in value.all %}{{ ipaddress|linkify }}
{% empty %}{% endfor %} diff --git a/netbox/templates/ipam/servicetemplate.html b/netbox/templates/ipam/servicetemplate.html index 5c3e53621..f15e1d050 100644 --- a/netbox/templates/ipam/servicetemplate.html +++ b/netbox/templates/ipam/servicetemplate.html @@ -1,46 +1 @@ {% extends 'generic/object.html' %} -{% load buttons %} -{% load helpers %} -{% load perms %} -{% load plugins %} -{% load i18n %} - -{% block content %} -
-
-
-

{% trans "Application Service Template" %}

- - - - - - - - - - - - - - - - - -
{% trans "Name" %}{{ object.name }}
{% trans "Protocol" %}{{ object.get_protocol_display }}
{% trans "Ports" %}{{ object.port_list }}
{% 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 %} -
-
-
-
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/ipam/vlan.html b/netbox/templates/ipam/vlan.html index 72f5058aa..f158586cd 100644 --- a/netbox/templates/ipam/vlan.html +++ b/netbox/templates/ipam/vlan.html @@ -1,129 +1 @@ {% extends 'ipam/vlan/base.html' %} -{% load helpers %} -{% load render_table from django_tables2 %} -{% load plugins %} -{% load i18n %} -{% load mptt %} - -{% block content %} -
-
-
-

{% trans "VLAN" %}

- - {% if object.site.region %} - - - - - {% endif %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {% if object.qinq_role == 'cvlan' %} - - - - - {% endif %} - - - - -
{% trans "Region" %} - {% nested_tree object.site.region %} -
{% trans "Site" %}{{ object.site|linkify|placeholder }}
{% trans "Group" %}{{ object.group|linkify|placeholder }}
{% trans "VLAN ID" %}{{ object.vid }}
{% trans "Name" %}{{ object.name }}
{% trans "Tenant" %} - {% if object.tenant.group %} - {{ object.tenant.group|linkify }} / - {% endif %} - {{ object.tenant|linkify|placeholder }} -
{% trans "Status" %}{% badge object.get_status_display bg_color=object.get_status_color %}
{% trans "Role" %} - {% if object.role %} - {{ object.role }} - {% else %} - {{ ''|placeholder }} - {% endif %} -
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Q-in-Q Role" %} - {% if object.qinq_role %} - {% badge object.get_qinq_role_display bg_color=object.get_qinq_role_color %} - {% else %} - {{ ''|placeholder }} - {% endif %} -
{% trans "Q-in-Q SVLAN" %}{{ object.qinq_svlan|linkify|placeholder }}
{% trans "L2VPN" %}{{ object.l2vpn_termination.l2vpn|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 "Prefixes" %} - {% if perms.ipam.add_prefix %} - - {% endif %} -

- {% htmx_table 'ipam:prefix_list' vlan_id=object.pk %} -
- {% if object.qinq_role == 'svlan' %} -
-

- {% trans "Customer VLANs" %} - {% if perms.ipam.add_vlan %} - - {% endif %} -

- {% htmx_table 'ipam:vlan_list' qinq_svlan_id=object.pk %} -
- {% endif %} - {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/ipam/vlangroup.html b/netbox/templates/ipam/vlangroup.html index 86a12dfe0..43908db2c 100644 --- a/netbox/templates/ipam/vlangroup.html +++ b/netbox/templates/ipam/vlangroup.html @@ -1,7 +1,5 @@ {% extends 'generic/object.html' %} {% load helpers %} -{% load plugins %} -{% load render_table from django_tables2 %} {% load i18n %} {% block breadcrumbs %} @@ -18,53 +16,4 @@ {% trans "Add VLAN" %} {% endif %} -{% endblock %} - -{% block content %} -
-
-
-

{% trans "VLAN Group" %}

- - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans "Name" %}{{ object.name }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Scope" %}{{ object.scope|linkify|placeholder }}
{% trans "VLAN IDs" %}{{ object.vid_ranges_items|join:", " }}
Utilization{% utilization_graph object.utilization %}
{% trans "Tenant" %} - {% if object.tenant.group %} - {{ object.tenant.group|linkify }} / - {% endif %} - {{ object.tenant|linkify|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 %} -
-
-{% endblock %} +{% endblock extra_controls %} diff --git a/netbox/templates/ipam/vlangroup/attrs/vid_ranges.html b/netbox/templates/ipam/vlangroup/attrs/vid_ranges.html new file mode 100644 index 000000000..9ceaf63b1 --- /dev/null +++ b/netbox/templates/ipam/vlangroup/attrs/vid_ranges.html @@ -0,0 +1 @@ +{{ value|join:", " }} diff --git a/netbox/templates/ipam/vlantranslationpolicy.html b/netbox/templates/ipam/vlantranslationpolicy.html index 399054881..f15e1d050 100644 --- a/netbox/templates/ipam/vlantranslationpolicy.html +++ b/netbox/templates/ipam/vlantranslationpolicy.html @@ -1,65 +1 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} -{% load render_table from django_tables2 %} -{% load i18n %} - -{% block content %} -
-
-
-

{% trans "VLAN Translation Policy" %}

- - - - - - - - - - - - - -
{% trans "Name" %}{{ object.name|placeholder }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Rules" %} - {% if object.rules.count %} - {{ object.rules.count }} - {% else %} - 0 - {% endif %} -
-
- {% plugin_left_page object %} -
-
- {% include 'inc/panels/tags.html' %} - {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/comments.html' %} - {% plugin_right_page object %} -
-
-
-
-
-

- {% trans "VLAN Translation Rules" %} - {% if perms.ipam.add_vlantranslationrule %} - - {% endif %} -

- {% htmx_table 'ipam:vlantranslationrule_list' policy_id=object.pk %} -
-
-
-
-
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/ipam/vlantranslationrule.html b/netbox/templates/ipam/vlantranslationrule.html index 042ade7fc..f15e1d050 100644 --- a/netbox/templates/ipam/vlantranslationrule.html +++ b/netbox/templates/ipam/vlantranslationrule.html @@ -1,45 +1 @@ {% extends 'generic/object.html' %} -{% load helpers %} -{% load plugins %} -{% load render_table from django_tables2 %} -{% load i18n %} - -{% block content %} -
-
-
-

{% trans "VLAN Translation Rule" %}

- - - - - - - - - - - - - - - - - -
{% trans "Policy" %}{{ object.policy|linkify }}
{% trans "Local VID" %}{{ object.local_vid }}
{% trans "Remote VID" %}{{ object.remote_vid }}
{% trans "Description" %}{{ object.description }}
-
- {% plugin_left_page object %} -
-
- {% include 'inc/panels/tags.html' %} - {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/comments.html' %} - {% plugin_right_page object %} -
-
-
-
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/ipam/vrf.html b/netbox/templates/ipam/vrf.html index e632e827d..f15e1d050 100644 --- a/netbox/templates/ipam/vrf.html +++ b/netbox/templates/ipam/vrf.html @@ -1,61 +1 @@ {% extends 'generic/object.html' %} -{% load buttons %} -{% load helpers %} -{% load plugins %} -{% load i18n %} - -{% block title %}{% trans "VRF" %} {{ object }}{% endblock %} - -{% block content %} -
-
-
-

{% trans "VRF" %}

- - - - - - - - - - - - - - - - - -
{% trans "Route Distinguisher" %}{{ object.rd|placeholder }}
{% trans "Tenant" %} - {% if object.tenant.group %} - {{ object.tenant.group|linkify }} / - {% endif %} - {{ object.tenant|linkify|placeholder }} -
{% trans "Unique IP Space" %}{% checkmark object.enforce_unique %}
{% trans "Description" %}{{ object.description|placeholder }}
-
- {% include 'inc/panels/tags.html' %} - {% plugin_left_page object %} -
-
- {% include 'inc/panels/related_objects.html' %} - {% 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" %} -
-
-
-
- {% plugin_full_width_page object %} -
-
-{% endblock %}