Explorar el Código

#20923: Migrate remaining DCIM views to new UI layouts (#21706)

Jeremy Stretch hace 2 días
padre
commit
aa01c16db0
Se han modificado 36 ficheros con 1186 adiciones y 1885 borrados
  1. 341 1
      netbox/dcim/ui/panels.py
  2. 321 18
      netbox/dcim/views.py
  3. 0 86
      netbox/templates/dcim/cable.html
  4. 0 87
      netbox/templates/dcim/consoleport.html
  5. 0 87
      netbox/templates/dcim/consoleserverport.html
  6. 0 62
      netbox/templates/dcim/devicebay.html
  7. 0 148
      netbox/templates/dcim/frontport.html
  8. 0 436
      netbox/templates/dcim/interface.html
  9. 3 0
      netbox/templates/dcim/interface/attrs/mac_address.html
  10. 2 0
      netbox/templates/dcim/interface/attrs/speed.html
  11. 0 73
      netbox/templates/dcim/inventoryitem.html
  12. 0 52
      netbox/templates/dcim/inventoryitemrole.html
  13. 0 54
      netbox/templates/dcim/macaddress.html
  14. 0 82
      netbox/templates/dcim/modulebay.html
  15. 6 0
      netbox/templates/dcim/panels/cable_termination_a.html
  16. 6 0
      netbox/templates/dcim/panels/cable_termination_b.html
  17. 40 0
      netbox/templates/dcim/panels/component_inventory_items.html
  18. 96 0
      netbox/templates/dcim/panels/connection.html
  19. 29 0
      netbox/templates/dcim/panels/front_port_mappings.html
  20. 21 0
      netbox/templates/dcim/panels/installed_device.html
  21. 33 0
      netbox/templates/dcim/panels/installed_module.html
  22. 105 0
      netbox/templates/dcim/panels/interface_connection.html
  23. 35 0
      netbox/templates/dcim/panels/interface_virtual_circuit.html
  24. 72 0
      netbox/templates/dcim/panels/interface_wireless.html
  25. 25 0
      netbox/templates/dcim/panels/interface_wireless_lans.html
  26. 29 0
      netbox/templates/dcim/panels/rear_port_mappings.html
  27. 1 1
      netbox/templates/dcim/panels/virtual_chassis_members.html
  28. 0 128
      netbox/templates/dcim/powerfeed.html
  29. 6 0
      netbox/templates/dcim/powerfeed/attrs/connected_device.html
  30. 15 0
      netbox/templates/dcim/powerfeed/attrs/utilization.html
  31. 0 92
      netbox/templates/dcim/poweroutlet.html
  32. 0 73
      netbox/templates/dcim/powerpanel.html
  33. 0 88
      netbox/templates/dcim/powerport.html
  34. 0 142
      netbox/templates/dcim/rearport.html
  35. 0 89
      netbox/templates/dcim/virtualchassis.html
  36. 0 86
      netbox/templates/dcim/virtualdevicecontext.html

+ 341 - 1
netbox/dcim/ui/panels.py

@@ -1,6 +1,8 @@
+from django.contrib.contenttypes.models import ContentType
+from django.template.loader import render_to_string
 from django.utils.translation import gettext_lazy as _
 
-from netbox.ui import attrs, panels
+from netbox.ui import actions, attrs, panels
 
 
 class SitePanel(panels.ObjectAttributesPanel):
@@ -189,16 +191,251 @@ class PlatformPanel(panels.NestedGroupObjectPanel):
     config_template = attrs.RelatedObjectAttr('config_template', linkify=True)
 
 
+class ConsolePortPanel(panels.ObjectAttributesPanel):
+    device = attrs.RelatedObjectAttr('device', linkify=True)
+    module = attrs.RelatedObjectAttr('module', linkify=True)
+    name = attrs.TextAttr('name')
+    label = attrs.TextAttr('label')
+    type = attrs.ChoiceAttr('type')
+    speed = attrs.ChoiceAttr('speed')
+    description = attrs.TextAttr('description')
+
+
+class ConsoleServerPortPanel(panels.ObjectAttributesPanel):
+    device = attrs.RelatedObjectAttr('device', linkify=True)
+    module = attrs.RelatedObjectAttr('module', linkify=True)
+    name = attrs.TextAttr('name')
+    label = attrs.TextAttr('label')
+    type = attrs.ChoiceAttr('type')
+    speed = attrs.ChoiceAttr('speed')
+    description = attrs.TextAttr('description')
+
+
+class PowerPortPanel(panels.ObjectAttributesPanel):
+    device = attrs.RelatedObjectAttr('device', linkify=True)
+    module = attrs.RelatedObjectAttr('module', linkify=True)
+    name = attrs.TextAttr('name')
+    label = attrs.TextAttr('label')
+    type = attrs.ChoiceAttr('type')
+    description = attrs.TextAttr('description')
+    maximum_draw = attrs.TextAttr('maximum_draw')
+    allocated_draw = attrs.TextAttr('allocated_draw')
+
+
+class PowerOutletPanel(panels.ObjectAttributesPanel):
+    device = attrs.RelatedObjectAttr('device', linkify=True)
+    module = attrs.RelatedObjectAttr('module', linkify=True)
+    name = attrs.TextAttr('name')
+    label = attrs.TextAttr('label')
+    type = attrs.ChoiceAttr('type')
+    status = attrs.ChoiceAttr('status')
+    description = attrs.TextAttr('description')
+    color = attrs.ColorAttr('color')
+    power_port = attrs.RelatedObjectAttr('power_port', linkify=True)
+    feed_leg = attrs.ChoiceAttr('feed_leg')
+
+
+class FrontPortPanel(panels.ObjectAttributesPanel):
+    device = attrs.RelatedObjectAttr('device', linkify=True)
+    module = attrs.RelatedObjectAttr('module', linkify=True)
+    name = attrs.TextAttr('name')
+    label = attrs.TextAttr('label')
+    type = attrs.ChoiceAttr('type')
+    color = attrs.ColorAttr('color')
+    positions = attrs.TextAttr('positions')
+    description = attrs.TextAttr('description')
+
+
+class RearPortPanel(panels.ObjectAttributesPanel):
+    device = attrs.RelatedObjectAttr('device', linkify=True)
+    module = attrs.RelatedObjectAttr('module', linkify=True)
+    name = attrs.TextAttr('name')
+    label = attrs.TextAttr('label')
+    type = attrs.ChoiceAttr('type')
+    color = attrs.ColorAttr('color')
+    positions = attrs.TextAttr('positions')
+    description = attrs.TextAttr('description')
+
+
+class ModuleBayPanel(panels.ObjectAttributesPanel):
+    device = attrs.RelatedObjectAttr('device', linkify=True)
+    module = attrs.RelatedObjectAttr('module', linkify=True)
+    name = attrs.TextAttr('name')
+    label = attrs.TextAttr('label')
+    position = attrs.TextAttr('position')
+    description = attrs.TextAttr('description')
+
+
+class DeviceBayPanel(panels.ObjectAttributesPanel):
+    device = attrs.RelatedObjectAttr('device', linkify=True)
+    name = attrs.TextAttr('name')
+    label = attrs.TextAttr('label')
+    description = attrs.TextAttr('description')
+
+
+class InventoryItemPanel(panels.ObjectAttributesPanel):
+    device = attrs.RelatedObjectAttr('device', linkify=True)
+    parent = attrs.RelatedObjectAttr('parent', linkify=True, label=_('Parent item'))
+    name = attrs.TextAttr('name')
+    label = attrs.TextAttr('label')
+    status = attrs.ChoiceAttr('status')
+    role = attrs.RelatedObjectAttr('role', linkify=True)
+    component = attrs.GenericForeignKeyAttr('component', linkify=True)
+    manufacturer = attrs.RelatedObjectAttr('manufacturer', linkify=True)
+    part_id = attrs.TextAttr('part_id', label=_('Part ID'))
+    serial = attrs.TextAttr('serial')
+    asset_tag = attrs.TextAttr('asset_tag')
+    description = attrs.TextAttr('description')
+
+
+class InventoryItemRolePanel(panels.OrganizationalObjectPanel):
+    color = attrs.ColorAttr('color')
+
+
+class CablePanel(panels.ObjectAttributesPanel):
+    type = attrs.ChoiceAttr('type')
+    status = attrs.ChoiceAttr('status')
+    profile = attrs.ChoiceAttr('profile')
+    tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
+    label = attrs.TextAttr('label')
+    description = attrs.TextAttr('description')
+    color = attrs.ColorAttr('color')
+    length = attrs.NumericAttr('length', unit_accessor='get_length_unit_display')
+
+
+class VirtualChassisPanel(panels.ObjectAttributesPanel):
+    domain = attrs.TextAttr('domain')
+    master = attrs.RelatedObjectAttr('master', linkify=True)
+    description = attrs.TextAttr('description')
+
+
+class PowerPanelPanel(panels.ObjectAttributesPanel):
+    site = attrs.RelatedObjectAttr('site', linkify=True)
+    location = attrs.NestedObjectAttr('location', linkify=True)
+    description = attrs.TextAttr('description')
+
+
+class PowerFeedPanel(panels.ObjectAttributesPanel):
+    power_panel = attrs.RelatedObjectAttr('power_panel', linkify=True)
+    rack = attrs.RelatedObjectAttr('rack', linkify=True)
+    type = attrs.ChoiceAttr('type')
+    status = attrs.ChoiceAttr('status')
+    description = attrs.TextAttr('description')
+    tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
+    connected_device = attrs.TemplatedAttr(
+        'connected_endpoints',
+        label=_('Connected device'),
+        template_name='dcim/powerfeed/attrs/connected_device.html',
+    )
+    utilization = attrs.TemplatedAttr(
+        'connected_endpoints',
+        label=_('Utilization (allocated)'),
+        template_name='dcim/powerfeed/attrs/utilization.html',
+    )
+
+
+class PowerFeedElectricalPanel(panels.ObjectAttributesPanel):
+    title = _('Electrical Characteristics')
+
+    supply = attrs.ChoiceAttr('supply')
+    voltage = attrs.TextAttr('voltage', format_string=_('{}V'))
+    amperage = attrs.TextAttr('amperage', format_string=_('{}A'))
+    phase = attrs.ChoiceAttr('phase')
+    max_utilization = attrs.TextAttr('max_utilization', format_string='{}%')
+
+
+class VirtualDeviceContextPanel(panels.ObjectAttributesPanel):
+    name = attrs.TextAttr('name')
+    device = attrs.RelatedObjectAttr('device', linkify=True)
+    identifier = attrs.TextAttr('identifier')
+    status = attrs.ChoiceAttr('status')
+    primary_ip4 = attrs.TemplatedAttr(
+        'primary_ip4',
+        label=_('Primary IPv4'),
+        template_name='dcim/device/attrs/ipaddress.html',
+    )
+    primary_ip6 = attrs.TemplatedAttr(
+        'primary_ip6',
+        label=_('Primary IPv6'),
+        template_name='dcim/device/attrs/ipaddress.html',
+    )
+    tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
+
+
+class MACAddressPanel(panels.ObjectAttributesPanel):
+    mac_address = attrs.TextAttr('mac_address', label=_('MAC address'), style='font-monospace', copy_button=True)
+    description = attrs.TextAttr('description')
+    assignment = attrs.RelatedObjectAttr('assigned_object', linkify=True, grouped_by='parent_object')
+    is_primary = attrs.BooleanAttr('is_primary', label=_('Primary for interface'))
+
+
+class ConnectionPanel(panels.ObjectPanel):
+    """
+    A panel which displays connection information for a cabled object.
+    """
+    template_name = 'dcim/panels/connection.html'
+    title = _('Connection')
+
+    def __init__(self, trace_url_name, connect_options=None, show_endpoints=True, **kwargs):
+        super().__init__(**kwargs)
+        self.trace_url_name = trace_url_name
+        self.connect_options = connect_options or []
+        self.show_endpoints = show_endpoints
+
+    def get_context(self, context):
+        return {
+            **super().get_context(context),
+            'trace_url_name': self.trace_url_name,
+            'connect_options': self.connect_options,
+            'show_endpoints': self.show_endpoints,
+        }
+
+    def render(self, context):
+        ctx = self.get_context(context)
+        return render_to_string(self.template_name, ctx, request=ctx.get('request'))
+
+
+class InventoryItemsPanel(panels.ObjectPanel):
+    """
+    A panel which displays inventory items associated with a component.
+    """
+    template_name = 'dcim/panels/component_inventory_items.html'
+    title = _('Inventory Items')
+    actions = [
+        actions.AddObject(
+            'dcim.inventoryitem',
+            url_params={
+                'component_type': lambda ctx: ContentType.objects.get_for_model(ctx['object']).pk,
+                'component_id': lambda ctx: ctx['object'].pk,
+            },
+        ),
+    ]
+
+    def render(self, context):
+        ctx = self.get_context(context)
+        return render_to_string(self.template_name, ctx, request=ctx.get('request'))
+
+
 class VirtualChassisMembersPanel(panels.ObjectPanel):
     """
     A panel which lists all members of a virtual chassis.
     """
     template_name = 'dcim/panels/virtual_chassis_members.html'
     title = _('Virtual Chassis Members')
+    actions = [
+        actions.AddObject(
+            'dcim.device',
+            url_params={
+                'site': lambda ctx: ctx['object'].master.site_id if ctx['object'].master else '',
+                'rack': lambda ctx: ctx['object'].master.rack_id if ctx['object'].master else '',
+            },
+        ),
+    ]
 
     def get_context(self, context):
         return {
             **super().get_context(context),
+            'virtual_chassis': context.get('virtual_chassis'),
             'vc_members': context.get('vc_members'),
         }
 
@@ -226,3 +463,106 @@ class PowerUtilizationPanel(panels.ObjectPanel):
         if not obj.powerports.exists() or not obj.poweroutlets.exists():
             return ''
         return super().render(context)
+
+
+class InterfacePanel(panels.ObjectAttributesPanel):
+    device = attrs.RelatedObjectAttr('device', linkify=True)
+    module = attrs.RelatedObjectAttr('module', linkify=True)
+    name = attrs.TextAttr('name')
+    label = attrs.TextAttr('label')
+    type = attrs.ChoiceAttr('type')
+    speed = attrs.TemplatedAttr('speed', template_name='dcim/interface/attrs/speed.html', label=_('Speed'))
+    duplex = attrs.ChoiceAttr('duplex')
+    mtu = attrs.TextAttr('mtu', label=_('MTU'))
+    enabled = attrs.BooleanAttr('enabled')
+    mgmt_only = attrs.BooleanAttr('mgmt_only', label=_('Management only'))
+    description = attrs.TextAttr('description')
+    poe_mode = attrs.ChoiceAttr('poe_mode', label=_('PoE mode'))
+    poe_type = attrs.ChoiceAttr('poe_type', label=_('PoE type'))
+    mode = attrs.ChoiceAttr('mode', label=_('802.1Q mode'))
+    qinq_svlan = attrs.RelatedObjectAttr('qinq_svlan', linkify=True, label=_('Q-in-Q SVLAN'))
+    untagged_vlan = attrs.RelatedObjectAttr('untagged_vlan', linkify=True, label=_('Untagged VLAN'))
+    tx_power = attrs.TextAttr('tx_power', label=_('Transmit power (dBm)'))
+    tunnel = attrs.RelatedObjectAttr('tunnel_termination.tunnel', linkify=True, label=_('Tunnel'))
+    l2vpn = attrs.RelatedObjectAttr('l2vpn_termination.l2vpn', linkify=True, label=_('L2VPN'))
+
+
+class RelatedInterfacesPanel(panels.ObjectAttributesPanel):
+    title = _('Related Interfaces')
+
+    parent = attrs.RelatedObjectAttr('parent', linkify=True)
+    bridge = attrs.RelatedObjectAttr('bridge', linkify=True)
+    lag = attrs.RelatedObjectAttr('lag', linkify=True, label=_('LAG'))
+
+
+class InterfaceAddressingPanel(panels.ObjectAttributesPanel):
+    title = _('Addressing')
+
+    mac_address = attrs.TemplatedAttr(
+        'primary_mac_address',
+        template_name='dcim/interface/attrs/mac_address.html',
+        label=_('MAC address'),
+    )
+    wwn = attrs.TextAttr('wwn', style='font-monospace', label=_('WWN'))
+    vrf = attrs.RelatedObjectAttr('vrf', linkify=True, label=_('VRF'))
+    vlan_translation = attrs.RelatedObjectAttr('vlan_translation_policy', linkify=True, label=_('VLAN translation'))
+
+
+class InterfaceConnectionPanel(panels.ObjectPanel):
+    """
+    A connection panel for interfaces, which handles cable, wireless link, and virtual circuit cases.
+    """
+    template_name = 'dcim/panels/interface_connection.html'
+    title = _('Connection')
+
+    def render(self, context):
+        obj = context.get('object')
+        if obj and obj.is_virtual:
+            return ''
+        ctx = self.get_context(context)
+        return render_to_string(self.template_name, ctx, request=ctx.get('request'))
+
+
+class VirtualCircuitPanel(panels.ObjectPanel):
+    """
+    A panel which displays virtual circuit information for a virtual interface.
+    """
+    template_name = 'dcim/panels/interface_virtual_circuit.html'
+    title = _('Virtual Circuit')
+
+    def render(self, context):
+        obj = context.get('object')
+        if not obj or not obj.is_virtual or not obj.virtual_circuit_termination:
+            return ''
+        ctx = self.get_context(context)
+        return render_to_string(self.template_name, ctx, request=ctx.get('request'))
+
+
+class InterfaceWirelessPanel(panels.ObjectPanel):
+    """
+    A panel which displays wireless RF attributes for an interface, comparing local and peer values.
+    """
+    template_name = 'dcim/panels/interface_wireless.html'
+    title = _('Wireless')
+
+    def render(self, context):
+        obj = context.get('object')
+        if not obj or not obj.is_wireless:
+            return ''
+        ctx = self.get_context(context)
+        return render_to_string(self.template_name, ctx, request=ctx.get('request'))
+
+
+class WirelessLANsPanel(panels.ObjectPanel):
+    """
+    A panel which lists the wireless LANs associated with an interface.
+    """
+    template_name = 'dcim/panels/interface_wireless_lans.html'
+    title = _('Wireless LANs')
+
+    def render(self, context):
+        obj = context.get('object')
+        if not obj or not obj.is_wireless:
+            return ''
+        ctx = self.get_context(context)
+        return render_to_string(self.template_name, ctx, request=ctx.get('request'))

+ 321 - 18
netbox/dcim/views.py

@@ -17,10 +17,12 @@ from extras.ui.panels import CustomFieldsPanel, ImageAttachmentsPanel, TagsPanel
 from extras.views import ObjectConfigContextView, ObjectRenderConfigView
 from ipam.models import ASN, VLAN, IPAddress, Prefix, VLANGroup
 from ipam.tables import VLANTranslationRuleTable
+from ipam.ui.panels import FHRPGroupAssignmentsPanel
 from netbox.object_actions import *
 from netbox.ui import actions, layout
 from netbox.ui.panels import (
     CommentsPanel,
+    ContextTablePanel,
     JSONPanel,
     NestedGroupObjectPanel,
     ObjectsTablePanel,
@@ -1577,7 +1579,7 @@ class ModuleTypeProfileListView(generic.ObjectListView):
 
 
 @register_model_view(ModuleTypeProfile)
-class ModuleTypeProfileView(GetRelatedModelsMixin, generic.ObjectView):
+class ModuleTypeProfileView(generic.ObjectView):
     template_name = 'generic/object.html'
     queryset = ModuleTypeProfile.objects.all()
     layout = layout.SimpleLayout(
@@ -2555,6 +2557,7 @@ class DeviceView(generic.ObjectView):
             vc_members = []
 
         return {
+            'virtual_chassis': instance.virtual_chassis,
             'vc_members': vc_members,
             'svg_extra': f'highlight=id:{instance.pk}',
         }
@@ -2907,6 +2910,28 @@ class ConsolePortListView(generic.ObjectListView):
 @register_model_view(ConsolePort)
 class ConsolePortView(generic.ObjectView):
     queryset = ConsolePort.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.ConsolePortPanel(),
+            CustomFieldsPanel(),
+            TagsPanel(),
+        ],
+        right_panels=[
+            panels.ConnectionPanel(
+                trace_url_name='dcim:consoleport_trace',
+                connect_options=[
+                    {
+                        'a_type': 'dcim.consoleport',
+                        'b_type': 'dcim.consoleserverport',
+                        'label': _('Console Server Port'),
+                    },
+                    {'a_type': 'dcim.consoleport', 'b_type': 'dcim.frontport', 'label': _('Front Port')},
+                    {'a_type': 'dcim.consoleport', 'b_type': 'dcim.rearport', 'label': _('Rear Port')},
+                ],
+            ),
+            panels.InventoryItemsPanel(),
+        ],
+    )
 
 
 @register_model_view(ConsolePort, 'add', detail=False)
@@ -2978,6 +3003,24 @@ class ConsoleServerPortListView(generic.ObjectListView):
 @register_model_view(ConsoleServerPort)
 class ConsoleServerPortView(generic.ObjectView):
     queryset = ConsoleServerPort.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.ConsoleServerPortPanel(),
+            CustomFieldsPanel(),
+            TagsPanel(),
+        ],
+        right_panels=[
+            panels.ConnectionPanel(
+                trace_url_name='dcim:consoleserverport_trace',
+                connect_options=[
+                    {'a_type': 'dcim.consoleserverport', 'b_type': 'dcim.consoleport', 'label': _('Console Port')},
+                    {'a_type': 'dcim.consoleserverport', 'b_type': 'dcim.frontport', 'label': _('Front Port')},
+                    {'a_type': 'dcim.consoleserverport', 'b_type': 'dcim.rearport', 'label': _('Rear Port')},
+                ],
+            ),
+            panels.InventoryItemsPanel(),
+        ],
+    )
 
 
 @register_model_view(ConsoleServerPort, 'add', detail=False)
@@ -3049,6 +3092,23 @@ class PowerPortListView(generic.ObjectListView):
 @register_model_view(PowerPort)
 class PowerPortView(generic.ObjectView):
     queryset = PowerPort.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.PowerPortPanel(),
+            CustomFieldsPanel(),
+            TagsPanel(),
+        ],
+        right_panels=[
+            panels.ConnectionPanel(
+                trace_url_name='dcim:powerport_trace',
+                connect_options=[
+                    {'a_type': 'dcim.powerport', 'b_type': 'dcim.poweroutlet', 'label': _('Power Outlet')},
+                    {'a_type': 'dcim.powerport', 'b_type': 'dcim.powerfeed', 'label': _('Power Feed')},
+                ],
+            ),
+            panels.InventoryItemsPanel(),
+        ],
+    )
 
 
 @register_model_view(PowerPort, 'add', detail=False)
@@ -3120,6 +3180,22 @@ class PowerOutletListView(generic.ObjectListView):
 @register_model_view(PowerOutlet)
 class PowerOutletView(generic.ObjectView):
     queryset = PowerOutlet.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.PowerOutletPanel(),
+            CustomFieldsPanel(),
+            TagsPanel(),
+        ],
+        right_panels=[
+            panels.ConnectionPanel(
+                trace_url_name='dcim:poweroutlet_trace',
+                connect_options=[
+                    {'a_type': 'dcim.poweroutlet', 'b_type': 'dcim.powerport', 'label': _('Power Port')},
+                ],
+            ),
+            panels.InventoryItemsPanel(),
+        ],
+    )
 
 
 @register_model_view(PowerOutlet, 'add', detail=False)
@@ -3191,6 +3267,45 @@ class InterfaceListView(generic.ObjectListView):
 @register_model_view(Interface)
 class InterfaceView(generic.ObjectView):
     queryset = Interface.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.InterfacePanel(),
+            panels.RelatedInterfacesPanel(),
+            CustomFieldsPanel(),
+            TagsPanel(),
+        ],
+        right_panels=[
+            ContextTablePanel('vdc_table', title=_('Virtual Device Contexts')),
+            panels.InterfaceAddressingPanel(),
+            panels.VirtualCircuitPanel(),
+            panels.InterfaceConnectionPanel(),
+            panels.InterfaceWirelessPanel(),
+            panels.WirelessLANsPanel(),
+            FHRPGroupAssignmentsPanel(),
+            panels.InventoryItemsPanel(),
+        ],
+        bottom_panels=[
+            ObjectsTablePanel(
+                model='ipam.IPAddress',
+                filters={'interface_id': lambda ctx: ctx['object'].pk},
+                title=_('IP Addresses'),
+            ),
+            ObjectsTablePanel(
+                model='dcim.MACAddress',
+                filters={'interface_id': lambda ctx: ctx['object'].pk},
+                title=_('MAC Addresses'),
+            ),
+            ObjectsTablePanel(
+                model='ipam.VLAN',
+                filters={'interface_id': lambda ctx: ctx['object'].pk},
+                title=_('VLANs'),
+            ),
+            ContextTablePanel('lag_interfaces_table', title=_('LAG Members')),
+            ContextTablePanel('vlan_translation_table', title=_('VLAN Translation')),
+            ContextTablePanel('bridge_interfaces_table', title=_('Bridged Interfaces')),
+            ContextTablePanel('child_interfaces_table', title=_('Child Interfaces')),
+        ],
+    )
 
     def get_extra_context(self, request, instance):
         # Get assigned VDCs
@@ -3205,30 +3320,29 @@ class InterfaceView(generic.ObjectView):
         vdc_table.configure(request)
 
         # Get bridge interfaces
-        bridge_interfaces = Interface.objects.restrict(request.user, 'view').filter(bridge=instance)
         bridge_interfaces_table = tables.InterfaceTable(
-            bridge_interfaces,
+            Interface.objects.restrict(request.user, 'view').filter(bridge=instance),
             exclude=('device', 'parent'),
             orderable=False
         )
         bridge_interfaces_table.configure(request)
 
         # Get child interfaces
-        child_interfaces = Interface.objects.restrict(request.user, 'view').filter(parent=instance)
         child_interfaces_table = tables.InterfaceTable(
-            child_interfaces,
+            Interface.objects.restrict(request.user, 'view').filter(parent=instance),
             exclude=('device', 'parent'),
             orderable=False
         )
         child_interfaces_table.configure(request)
 
-        # Get LAG interfaces
-        lag_interfaces = Interface.objects.restrict(request.user, 'view').filter(lag=instance)
-        lag_interfaces_table = tables.InterfaceLAGMemberTable(
-            lag_interfaces,
-            orderable=False
-        )
-        lag_interfaces_table.configure(request)
+        # Get LAG members (only for LAG interfaces)
+        lag_interfaces_table = None
+        if instance.is_lag:
+            lag_interfaces_table = tables.InterfaceLAGMemberTable(
+                Interface.objects.restrict(request.user, 'view').filter(lag=instance),
+                orderable=False
+            )
+            lag_interfaces_table.configure(request)
 
         # Get VLAN translation rules
         vlan_translation_table = None
@@ -3241,7 +3355,6 @@ class InterfaceView(generic.ObjectView):
 
         return {
             'vdc_table': vdc_table,
-            'bridge_interfaces': bridge_interfaces,
             'bridge_interfaces_table': bridge_interfaces_table,
             'child_interfaces_table': child_interfaces_table,
             'lag_interfaces_table': lag_interfaces_table,
@@ -3329,6 +3442,33 @@ class FrontPortListView(generic.ObjectListView):
 @register_model_view(FrontPort)
 class FrontPortView(generic.ObjectView):
     queryset = FrontPort.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.FrontPortPanel(),
+            CustomFieldsPanel(),
+            TagsPanel(),
+            panels.InventoryItemsPanel(),
+        ],
+        right_panels=[
+            panels.ConnectionPanel(
+                trace_url_name='dcim:frontport_trace',
+                show_endpoints=False,
+                connect_options=[
+                    {'a_type': 'dcim.frontport', 'b_type': 'dcim.interface', 'label': _('Interface')},
+                    {'a_type': 'dcim.frontport', 'b_type': 'dcim.consoleserverport', 'label': _('Console Server Port')},
+                    {'a_type': 'dcim.frontport', 'b_type': 'dcim.consoleport', 'label': _('Console Port')},
+                    {'a_type': 'dcim.frontport', 'b_type': 'dcim.frontport', 'label': _('Front Port')},
+                    {'a_type': 'dcim.frontport', 'b_type': 'dcim.rearport', 'label': _('Rear Port')},
+                    {
+                        'a_type': 'dcim.frontport',
+                        'b_type': 'circuits.circuittermination',
+                        'label': _('Circuit Termination'),
+                    },
+                ],
+            ),
+            TemplatePanel('dcim/panels/front_port_mappings.html'),
+        ],
+    )
 
     def get_extra_context(self, request, instance):
         return {
@@ -3405,6 +3545,31 @@ class RearPortListView(generic.ObjectListView):
 @register_model_view(RearPort)
 class RearPortView(generic.ObjectView):
     queryset = RearPort.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.RearPortPanel(),
+            CustomFieldsPanel(),
+            TagsPanel(),
+            panels.InventoryItemsPanel(),
+        ],
+        right_panels=[
+            panels.ConnectionPanel(
+                trace_url_name='dcim:rearport_trace',
+                show_endpoints=False,
+                connect_options=[
+                    {'a_type': 'dcim.rearport', 'b_type': 'dcim.interface', 'label': _('Interface')},
+                    {'a_type': 'dcim.rearport', 'b_type': 'dcim.frontport', 'label': _('Front Port')},
+                    {'a_type': 'dcim.rearport', 'b_type': 'dcim.rearport', 'label': _('Rear Port')},
+                    {
+                        'a_type': 'dcim.rearport',
+                        'b_type': 'circuits.circuittermination',
+                        'label': _('Circuit Termination'),
+                    },
+                ],
+            ),
+            TemplatePanel('dcim/panels/rear_port_mappings.html'),
+        ],
+    )
 
     def get_extra_context(self, request, instance):
         return {
@@ -3481,6 +3646,19 @@ class ModuleBayListView(generic.ObjectListView):
 @register_model_view(ModuleBay)
 class ModuleBayView(generic.ObjectView):
     queryset = ModuleBay.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.ModuleBayPanel(),
+            TagsPanel(),
+        ],
+        right_panels=[
+            CustomFieldsPanel(),
+            Panel(
+                title=_('Installed Module'),
+                template_name='dcim/panels/installed_module.html',
+            ),
+        ],
+    )
 
 
 @register_model_view(ModuleBay, 'add', detail=False)
@@ -3543,6 +3721,19 @@ class DeviceBayListView(generic.ObjectListView):
 @register_model_view(DeviceBay)
 class DeviceBayView(generic.ObjectView):
     queryset = DeviceBay.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.DeviceBayPanel(),
+            CustomFieldsPanel(),
+            TagsPanel(),
+        ],
+        right_panels=[
+            Panel(
+                title=_('Installed Device'),
+                template_name='dcim/panels/installed_device.html',
+            ),
+        ],
+    )
 
 
 @register_model_view(DeviceBay, 'add', detail=False)
@@ -3686,6 +3877,13 @@ class InventoryItemListView(generic.ObjectListView):
 @register_model_view(InventoryItem)
 class InventoryItemView(generic.ObjectView):
     queryset = InventoryItem.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.InventoryItemPanel(),
+            CustomFieldsPanel(),
+            TagsPanel(),
+        ],
+    )
 
 
 @register_model_view(InventoryItem, 'edit')
@@ -3767,12 +3965,23 @@ class InventoryItemRoleListView(generic.ObjectListView):
 
 
 @register_model_view(InventoryItemRole)
-class InventoryItemRoleView(generic.ObjectView):
+class InventoryItemRoleView(GetRelatedModelsMixin, generic.ObjectView):
     queryset = InventoryItemRole.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.InventoryItemRolePanel(),
+            TagsPanel(),
+        ],
+        right_panels=[
+            RelatedObjectsPanel(),
+            CustomFieldsPanel(),
+            CommentsPanel(),
+        ],
+    )
 
     def get_extra_context(self, request, instance):
         return {
-            'inventoryitem_count': InventoryItem.objects.filter(role=instance).count(),
+            'related_models': self.get_related_models(request, instance),
         }
 
 
@@ -3940,6 +4149,24 @@ class CableListView(generic.ObjectListView):
 @register_model_view(Cable)
 class CableView(generic.ObjectView):
     queryset = Cable.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.CablePanel(),
+            CustomFieldsPanel(),
+            TagsPanel(),
+            CommentsPanel(),
+        ],
+        right_panels=[
+            Panel(
+                title=_('Termination A'),
+                template_name='dcim/panels/cable_termination_a.html',
+            ),
+            Panel(
+                title=_('Termination B'),
+                template_name='dcim/panels/cable_termination_b.html',
+            ),
+        ],
+    )
 
 
 @register_model_view(Cable, 'add', detail=False)
@@ -4072,12 +4299,23 @@ class VirtualChassisListView(generic.ObjectListView):
 @register_model_view(VirtualChassis)
 class VirtualChassisView(generic.ObjectView):
     queryset = VirtualChassis.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.VirtualChassisPanel(),
+            TagsPanel(),
+            CustomFieldsPanel(),
+        ],
+        right_panels=[
+            panels.VirtualChassisMembersPanel(),
+            CommentsPanel(),
+        ],
+    )
 
     def get_extra_context(self, request, instance):
-        members = Device.objects.restrict(request.user).filter(virtual_chassis=instance)
-
+        vc_members = Device.objects.restrict(request.user).filter(virtual_chassis=instance).order_by('vc_position')
         return {
-            'members': members,
+            'virtual_chassis': instance,
+            'vc_members': vc_members,
         }
 
 
@@ -4317,6 +4555,27 @@ class PowerPanelListView(generic.ObjectListView):
 @register_model_view(PowerPanel)
 class PowerPanelView(GetRelatedModelsMixin, generic.ObjectView):
     queryset = PowerPanel.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.PowerPanelPanel(),
+            TagsPanel(),
+            CommentsPanel(),
+        ],
+        right_panels=[
+            RelatedObjectsPanel(),
+            CustomFieldsPanel(),
+            ImageAttachmentsPanel(),
+        ],
+        bottom_panels=[
+            ObjectsTablePanel(
+                model='dcim.PowerFeed',
+                filters={'power_panel_id': lambda ctx: ctx['object'].pk},
+                actions=[
+                    actions.AddObject('dcim.PowerFeed', url_params={'power_panel': lambda ctx: ctx['object'].pk}),
+                ],
+            ),
+        ],
+    )
 
     def get_extra_context(self, request, instance):
         return {
@@ -4380,6 +4639,23 @@ class PowerFeedListView(generic.ObjectListView):
 @register_model_view(PowerFeed)
 class PowerFeedView(generic.ObjectView):
     queryset = PowerFeed.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.PowerFeedPanel(),
+            panels.PowerFeedElectricalPanel(),
+            CustomFieldsPanel(),
+            TagsPanel(),
+        ],
+        right_panels=[
+            panels.ConnectionPanel(
+                trace_url_name='dcim:powerfeed_trace',
+                connect_options=[
+                    {'a_type': 'dcim.powerfeed', 'b_type': 'dcim.powerport', 'label': _('Power Port')},
+                ],
+            ),
+            CommentsPanel(),
+        ],
+    )
 
 
 @register_model_view(PowerFeed, 'add', detail=False)
@@ -4448,6 +4724,23 @@ class VirtualDeviceContextListView(generic.ObjectListView):
 @register_model_view(VirtualDeviceContext)
 class VirtualDeviceContextView(GetRelatedModelsMixin, generic.ObjectView):
     queryset = VirtualDeviceContext.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.VirtualDeviceContextPanel(),
+            TagsPanel(),
+        ],
+        right_panels=[
+            RelatedObjectsPanel(),
+            CommentsPanel(),
+            CustomFieldsPanel(),
+        ],
+        bottom_panels=[
+            ObjectsTablePanel(
+                model='dcim.Interface',
+                filters={'vdc_id': lambda ctx: ctx['object'].pk},
+            ),
+        ],
+    )
 
     def get_extra_context(self, request, instance):
         return {
@@ -4516,6 +4809,16 @@ class MACAddressListView(generic.ObjectListView):
 @register_model_view(MACAddress)
 class MACAddressView(generic.ObjectView):
     queryset = MACAddress.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.MACAddressPanel(),
+            TagsPanel(),
+            CustomFieldsPanel(),
+        ],
+        right_panels=[
+            CommentsPanel(),
+        ],
+    )
 
 
 @register_model_view(MACAddress, 'add', detail=False)

+ 0 - 86
netbox/templates/dcim/cable.html

@@ -1,87 +1 @@
 {% extends 'generic/object.html' %}
-{% load buttons %}
-{% load helpers %}
-{% load perms %}
-{% load plugins %}
-{% load i18n %}
-
-{% block content %}
-  <div class="row">
-    <div class="col col-12 col-md-6">
-      <div class="card">
-        <h2 class="card-header">{% trans "Cable" %}</h2>
-        <table class="table table-hover attr-table">
-          <tr>
-            <th scope="row">{% trans "Type" %}</th>
-            <td>{{ object.get_type_display|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Status" %}</th>
-            <td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Profile" %}</th>
-            <td>{% badge object.get_profile_display %}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Tenant" %}</th>
-            <td>
-              {% if object.tenant.group %}
-                {{ object.tenant.group|linkify }} /
-              {% endif %}
-              {{ object.tenant|linkify|placeholder }}
-            </td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Label" %}</th>
-            <td>{{ object.label|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Description" %}</th>
-            <td>{{ object.description|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Color" %}</th>
-            <td>
-              {% if object.color %}
-                <span class="color-label" style="background-color: #{{ object.color }}">&nbsp;</span>
-              {% else %}
-                {{ ''|placeholder }}
-              {% endif %}
-            </td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Length" %}</th>
-            <td>
-              {% if object.length is not None %}
-                {{ object.length|floatformat }} {{ object.get_length_unit_display }}
-              {% else %}
-                {{ ''|placeholder }}
-              {% endif %}
-            </td>
-          </tr>
-        </table>
-      </div>
-      {% include 'inc/panels/custom_fields.html' %}
-      {% include 'inc/panels/tags.html' %}
-      {% include 'inc/panels/comments.html' %}
-      {% plugin_left_page object %}
-    </div>
-    <div class="col col-12 col-md-6">
-      <div class="card">
-        <h2 class="card-header">{% trans "Termination" %} A</h2>
-        {% include 'dcim/inc/cable_termination.html' with terminations=object.a_terminations %}
-      </div>
-      <div class="card">
-        <h2 class="card-header">{% trans "Termination" %} B</h2>
-        {% include 'dcim/inc/cable_termination.html' with terminations=object.b_terminations %}
-      </div>
-      {% plugin_right_page object %}
-    </div>
-  </div>
-  <div class="row">
-    <div class="col col-md-12">
-      {% plugin_full_width_page object %}
-    </div>
-  </div>
-{% endblock %}

+ 0 - 87
netbox/templates/dcim/consoleport.html

@@ -1,6 +1,4 @@
 {% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
 {% load i18n %}
 
 {% block breadcrumbs %}
@@ -9,88 +7,3 @@
     <a href="{% url 'dcim:device_consoleports' pk=object.device.pk %}">{{ object.device }}</a>
   </li>
 {% endblock %}
-
-{% block content %}
-    <div class="row">
-        <div class="col col-12 col-md-6">
-            <div class="card">
-                <h2 class="card-header">{% trans "Console Port" %}</h2>
-                <table class="table table-hover attr-table">
-                    <tr>
-                        <th scope="row">{% trans "Device" %}</th>
-                        <td>{{ object.device|linkify }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Module" %}</th>
-                        <td>{{ object.module|linkify|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Name" %}</th>
-                        <td>{{ object.name }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Label" %}</th>
-                        <td>{{ object.label|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Type" %}</th>
-                        <td>{{ object.get_type_display }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Speed" %}</th>
-                        <td>{{ object.get_speed_display }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Description" %}</th>
-                        <td>{{ object.description|placeholder }}</td>
-                    </tr>
-                </table>
-            </div>
-            {% include 'inc/panels/custom_fields.html' %}
-            {% include 'inc/panels/tags.html' %}
-            {% plugin_left_page object %}
-        </div>
-        <div class="col col-12 col-md-6">
-          <div class="card">
-            <h2 class="card-header">{% trans "Connection" %}</h2>
-            {% if object.mark_connected %}
-              <div class="card-body">
-                <span class="text-success"><i class="mdi mdi-check-bold"></i></span>
-                {% trans "Marked as connected" %}
-              </div>
-            {% elif object.cable %}
-              {% include 'dcim/inc/connection_endpoints.html' with trace_url='dcim:consoleport_trace' %}
-            {% else %}
-              <div class="card-body text-muted">
-                {% trans "Not Connected" %}
-                {% if perms.dcim.add_cable %}
-                  <div class="dropdown float-end">
-                    <button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
-                      <span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> {% trans "Connect" %}
-                    </button>
-                    <ul class="dropdown-menu dropdown-menu-end">
-                      <li>
-                        <a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleport&a_terminations={{ object.pk }}&b_terminations_type=dcim.consoleserverport&return_url={{ object.get_absolute_url }}" class="dropdown-item">{% trans "Console Server Port" %}</a>
-                      </li>
-                      <li>
-                        <a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleport&a_terminations={{ object.pk }}&b_terminations_type=dcim.frontport&return_url={{ object.get_absolute_url }}" class="dropdown-item">{% trans "Front Port" %}</a>
-                      </li>
-                      <li>
-                        <a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleport&a_terminations={{ object.pk }}&b_terminations_type=dcim.rearport&return_url={{ object.get_absolute_url }}" class="dropdown-item">{% trans "Rear Port" %}</a>
-                      </li>
-                    </ul>
-                  </div>
-                {% endif %}
-              </div>
-            {% endif %}
-          </div>
-          {% include 'dcim/inc/panels/inventory_items.html' %}
-          {% plugin_right_page object %}
-        </div>
-    </div>
-    <div class="row">
-        <div class="col col-md-12">
-            {% plugin_full_width_page object %}
-        </div>
-    </div>
-{% endblock %}

+ 0 - 87
netbox/templates/dcim/consoleserverport.html

@@ -1,6 +1,4 @@
 {% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
 {% load i18n %}
 
 {% block breadcrumbs %}
@@ -9,88 +7,3 @@
     <a href="{% url 'dcim:device_consoleserverports' pk=object.device.pk %}">{{ object.device }}</a>
   </li>
 {% endblock %}
-
-{% block content %}
-    <div class="row">
-        <div class="col col-12 col-md-6">
-            <div class="card">
-                <h2 class="card-header">{% trans "Console Server Port" %}</h2>
-                <table class="table table-hover attr-table">
-                    <tr>
-                        <th scope="row">{% trans "Device" %}</th>
-                        <td>{{ object.device|linkify }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Module" %}</th>
-                        <td>{{ object.module|linkify|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Name" %}</th>
-                        <td>{{ object.name }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Label" %}</th>
-                        <td>{{ object.label|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Type" %}</th>
-                        <td>{{ object.get_type_display|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Speed" %}</th>
-                        <td>{{ object.get_speed_display|placeholder }}</td>
-                </tr>
-                    <tr>
-                        <th scope="row">{% trans "Description" %}</th>
-                        <td>{{ object.description|placeholder }}</td>
-                    </tr>
-                </table>
-            </div>
-            {% include 'inc/panels/custom_fields.html' %}
-            {% include 'inc/panels/tags.html' %}
-            {% plugin_left_page object %}
-        </div>
-        <div class="col col-12 col-md-6">
-          <div class="card">
-            <h2 class="card-header">{% trans "Connection" %}</h2>
-            {% if object.mark_connected %}
-              <div class="card-body">
-                <span class="text-success"><i class="mdi mdi-check-bold"></i></span>
-                {% trans "Marked as connected" %}
-              </div>
-            {% elif object.cable %}
-              {% include 'dcim/inc/connection_endpoints.html' with trace_url='dcim:consoleserverport_trace' %}
-            {% else %}
-              <div class="card-body text-muted">
-                {% trans "Not Connected" %}
-                {% if perms.dcim.add_cable %}
-                  <div class="dropdown float-end">
-                    <button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
-                      <span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> {% trans "Connect" %}
-                    </button>
-                    <ul class="dropdown-menu dropdown-menu-end">
-                      <li>
-                        <a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleserverport&a_terminations={{ object.pk }}&b_terminations_type=dcim.consoleport&return_url={{ object.get_absolute_url }}" class="dropdown-item">{% trans "Console Port" %}</a>
-                      </li>
-                      <li>
-                        <a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleserverport&a_terminations={{ object.pk }}&b_terminations_type=dcim.frontport&return_url={{ object.get_absolute_url }}" class="dropdown-item">{% trans "Front Port" %}</a>
-                      </li>
-                      <li>
-                        <a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.consoleserverport&a_terminations={{ object.pk }}&b_terminations_type=dcim.rearport&return_url={{ object.get_absolute_url }}" class="dropdown-item">{% trans "Rear Port" %}</a>
-                      </li>
-                    </ul>
-                  </div>
-                {% endif %}
-              </div>
-            {% endif %}
-          </div>
-          {% include 'dcim/inc/panels/inventory_items.html' %}
-          {% plugin_right_page object %}
-        </div>
-    </div>
-    <div class="row">
-        <div class="col col-md-12">
-            {% plugin_full_width_page object %}
-        </div>
-    </div>
-{% endblock %}

+ 0 - 62
netbox/templates/dcim/devicebay.html

@@ -1,6 +1,4 @@
 {% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
 {% load i18n %}
 
 {% block breadcrumbs %}
@@ -9,63 +7,3 @@
     <a href="{% url 'dcim:device_devicebays' pk=object.device.pk %}">{{ object.device }}</a>
   </li>
 {% endblock %}
-
-{% block content %}
-    <div class="row">
-        <div class="col col-12 col-md-6">
-            <div class="card">
-                <h2 class="card-header">{% trans "Device Bay" %}</h2>
-                <table class="table table-hover attr-table">
-                    <tr>
-                        <th scope="row">{% trans "Device" %}</th>
-                        <td>{{ object.device|linkify }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Name" %}</th>
-                        <td>{{ object.name }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Label" %}</th>
-                        <td>{{ object.label|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Description" %}</th>
-                        <td>{{ object.description|placeholder }}</td>
-                    </tr>
-                </table>
-            </div>
-        {% include 'inc/panels/custom_fields.html' %}
-        {% include 'inc/panels/tags.html' %}
-        {% plugin_left_page object %}
-        </div>
-        <div class="col col-12 col-md-6">
-            <div class="card">
-                <h2 class="card-header">{% trans "Installed Device" %}</h2>
-                {% if object.installed_device %}
-                    {% with device=object.installed_device %}
-                        <table class="table table-hover attr-table">
-                            <tr>
-                                <th scope="row">{% trans "Device" %}</th>
-                                <td>{{ device|linkify }}</td>
-                            </tr>
-                            <tr>
-                                <th scope="row">{% trans "Device Type" %}</th>
-                                <td>{{ device.device_type }}</td>
-                            </tr>
-                        </table>
-                    {% endwith %}
-                {% else %}
-                    <div class="card-body text-muted">
-                        {% trans "None" %}
-                    </div>
-                {% endif %}
-            </div>
-            {% plugin_right_page object %}
-        </div>
-    </div>
-    <div class="row">
-        <div class="col col-md-12">
-            {% plugin_full_width_page object %}
-        </div>
-    </div>
-{% endblock %}

+ 0 - 148
netbox/templates/dcim/frontport.html

@@ -1,6 +1,4 @@
 {% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
 {% load i18n %}
 
 {% block breadcrumbs %}
@@ -9,149 +7,3 @@
     <a href="{% url 'dcim:device_frontports' pk=object.device.pk %}">{{ object.device }}</a>
   </li>
 {% endblock %}
-
-{% block content %}
-    <div class="row">
-        <div class="col col-12 col-md-6">
-            <div class="card">
-                <h2 class="card-header">{% trans "Front Port" %}</h2>
-                <table class="table table-hover attr-table">
-                    <tr>
-                        <th scope="row">{% trans "Device" %}</th>
-                        <td>{{ object.device|linkify }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Module" %}</th>
-                        <td>{{ object.module|linkify|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Name" %}</th>
-                        <td>{{ object.name }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Label" %}</th>
-                        <td>{{ object.label|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Type" %}</th>
-                        <td>{{ object.get_type_display }}</td>
-                    </tr>
-                    <tr>
-                      <th scope="row">{% trans "Color" %}</th>
-                      <td>
-                        {% if object.color %}
-                          <span class="badge color-label" style="background-color: #{{ object.color }}">&nbsp;</span>
-                        {% else %}
-                          {{ ''|placeholder }}
-                        {% endif %}
-                      </td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Positions" %}</th>
-                        <td>{{ object.positions }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Description" %}</th>
-                        <td>{{ object.description|placeholder }}</td>
-                    </tr>
-                </table>
-            </div>
-            {% include 'inc/panels/custom_fields.html' %}
-            {% include 'inc/panels/tags.html' %}
-            {% include 'dcim/inc/panels/inventory_items.html' %}
-            {% plugin_left_page object %}
-        </div>
-        <div class="col col-12 col-md-6">
-            <div class="card">
-                <h2 class="card-header">{% trans "Connection" %}</h2>
-                {% if object.mark_connected %}
-                    <div class="card-body text-muted">
-                      <span class="text-success"><i class="mdi mdi-check-bold"></i></span> {% trans "Marked as Connected" %}
-                    </div>
-                {% elif object.cable %}
-                    <table class="table table-hover attr-table">
-                        <tr>
-                            <th scope="row">{% trans "Cable" %}</th>
-                            <td>
-                                {{ object.cable|linkify }}
-                                <a href="{% url 'dcim:frontport_trace' pk=object.pk %}" class="btn btn-sm btn-primary" title="{% trans "Trace" %}">
-                                    <i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
-                                </a>
-                            </td>
-                        </tr>
-                        <tr>
-                            <th scope="row">{% trans "Connection Status" %}</th>
-                            <td>
-                                {% if object.cable.status %}
-                                    <span class="badge text-bg-success">{{ object.cable.get_status_display }}</span>
-                                {% else %}
-                                    <span class="badge text-bg-info">{{ object.cable.get_status_display }}</span>
-                                {% endif %}
-                            </td>
-                        </tr>
-                    </table>
-                {% else %}
-                    <div class="card-body text-muted">
-                        {% trans "Not Connected" %}
-                        {% if perms.dcim.add_cable %}
-                            <div class="dropdown float-end">
-                                <button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                                    <span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> {% trans "Connect" %}
-                                </button>
-                                <ul class="dropdown-menu dropdown-menu-end">
-                                    <li>
-                                        <a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ object.pk }}&b_terminations_type=dcim.interface&return_url={{ object.get_absolute_url }}">{% trans "Interface" %}</a>
-                                    </li>
-                                    <li>
-                                        <a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ object.pk }}&b_terminations_type=dcim.consoleserverport&return_url={{ object.get_absolute_url }}">{% trans "Console Server Port" %}</a>
-                                    </li>
-                                    <li>
-                                        <a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ object.pk }}&b_terminations_type=dcim.consoleport&return_url={{ object.get_absolute_url }}">{% trans "Console Port" %}</a>
-                                    </li>
-                                    <li>
-                                        <a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ object.pk }}&b_terminations_type=dcim.frontport&return_url={{ object.get_absolute_url }}">{% trans "Front Port" %}</a>
-                                    </li>
-                                    <li>
-                                        <a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ object.pk }}&b_terminations_type=dcim.rearport&return_url={{ object.get_absolute_url }}">{% trans "Rear Port" %}</a>
-                                    </li>
-                                    <li>
-                                        <a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.frontport&a_terminations={{ object.pk }}&b_terminations_type=circuits.circuittermination&return_url={{ object.get_absolute_url }}">{% trans "Circuit Termination" %}</a>
-                                    </li>
-                                </ul>
-                            </div>
-                        {% endif %}
-                    </div>
-                {% endif %}
-            </div>
-            <div class="card">
-              <h2 class="card-header">{% trans "Port Mappings" %}</h2>
-              <table class="table table-hover">
-                {% if rear_port_mappings %}
-                  <thead>
-                    <tr>
-                      <th>{% trans "Position" %}</th>
-                      <th>{% trans "Rear Port" %}</th>
-                    </tr>
-                  </thead>
-                {% endif %}
-                {% for mapping in rear_port_mappings %}
-                  <tr>
-                    <td>{{ mapping.front_port_position }}</td>
-                    <td>
-                      <a href="{{ mapping.rear_port.get_absolute_url }}">{{ mapping.rear_port }}:{{ mapping.rear_port_position }}</a>
-                    </td>
-                  </tr>
-                {% empty %}
-                  {% trans "No mappings defined" %}
-                {% endfor %}
-              </table>
-            </div>
-            {% plugin_right_page object %}
-        </div>
-    </div>
-    <div class="row">
-        <div class="col col-md-12">
-            {% plugin_full_width_page object %}
-        </div>
-    </div>
-{% endblock %}

+ 0 - 436
netbox/templates/dcim/interface.html

@@ -1,7 +1,4 @@
 {% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
-{% load render_table from django_tables2 %}
 {% load i18n %}
 
 {% block breadcrumbs %}
@@ -19,436 +16,3 @@
   {% endif %}
   {{ block.super }}
 {% endblock %}
-
-{% block content %}
-  <div class="row mb-3">
-    <div class="col col-12 col-md-6">
-      <div class="card">
-        <h2 class="card-header">{% trans "Interface" %}</h2>
-        <table class="table table-hover attr-table">
-          <tr>
-            <th scope="row">{% trans "Device" %}</th>
-            <td>{{ object.device|linkify }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Module" %}</th>
-            <td>{{ object.module|linkify|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Name" %}</th>
-            <td>{{ object.name }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Label" %}</th>
-            <td>{{ object.label|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Type" %}</th>
-            <td>{{ object.get_type_display }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Speed/Duplex" %}</th>
-            <td>
-              {{ object.speed|humanize_speed|placeholder }} /
-              {{ object.get_duplex_display|placeholder }}
-            </td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "MTU" %}</th>
-            <td>{{ object.mtu|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Enabled" %}</th>
-            <td>{% checkmark object.enabled %}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Management Only" %}</th>
-            <td>{% checkmark object.mgmt_only %}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Description" %}</th>
-            <td>{{ object.description|placeholder }} </td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "PoE Mode" %}</th>
-            <td>{{ object.get_poe_mode_display|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "PoE Type" %}</th>
-            <td>{{ object.get_poe_type_display|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "802.1Q Mode" %}</th>
-            <td>{{ object.get_mode_display|placeholder }}</td>
-          </tr>
-          {% if object.mode == 'q-in-q' %}
-            <tr>
-              <th scope="row">{% trans "Q-in-Q SVLAN" %}</th>
-              <td>{{ object.qinq_svlan|linkify|placeholder }}</td>
-            </tr>
-          {% elif object.mode %}
-            <tr>
-              <th scope="row">{% trans "Untagged VLAN" %}</th>
-              <td>{{ object.untagged_vlan|linkify|placeholder }}</td>
-            </tr>
-          {% endif %}
-          <tr>
-            <th scope="row">{% trans "Transmit power (dBm)" %}</th>
-            <td>{{ object.tx_power|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Tunnel" %}</th>
-            <td>{{ object.tunnel_termination.tunnel|linkify|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "L2VPN" %}</th>
-            <td>{{ object.l2vpn_termination.l2vpn|linkify|placeholder }}</td>
-          </tr>
-        </table>
-      </div>
-      <div class="card">
-        <h2 class="card-header">{% trans "Related Interfaces" %}</h2>
-        <table class="table table-hover attr-table">
-          <tr>
-            <th scope="row">{% trans "Parent" %}</th>
-            <td>{{ object.parent|linkify|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Bridge" %}</th>
-            <td>{{ object.bridge|linkify|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Bridged Interfaces" %}</th>
-            <td>
-              {% if bridge_interfaces %}
-                {% for interface in bridge_interfaces %}
-                  {{ interface|linkify }}
-                  {% if not forloop.last %}<br />{% endif %}
-                {% endfor %}
-              {% else %}
-                {{ ''|placeholder }}
-              {% endif %}
-            </td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "LAG" %}</th>
-            <td>{{ object.lag|linkify|placeholder }}</td>
-          </tr>
-        </table>
-      </div>
-      {% include 'inc/panels/custom_fields.html' %}
-      {% include 'inc/panels/tags.html' %}
-      {% plugin_left_page object %}
-    </div>
-    <div class="col col-12 col-md-6">
-      {% include 'inc/panel_table.html' with table=vdc_table heading="Virtual Device Contexts" %}
-      <div class="card">
-        <h2 class="card-header">{% trans "Addressing" %}</h2>
-        <table class="table table-hover attr-table">
-          <tr>
-            <th scope="row">{% trans "MAC Address" %}</th>
-            <td>
-              {% if object.primary_mac_address %}
-                <span class="font-monospace">{{ object.primary_mac_address|linkify }}</span>
-                <span class="badge text-bg-primary">{% trans "Primary" %}</span>
-              {% else %}
-                {{ ''|placeholder }}
-              {% endif %}
-            </td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "WWN" %}</th>
-            <td>
-              {% if object.wwn %}
-                <span class="font-monospace">{{ object.wwn }}</span>
-              {% else %}
-                {{ ''|placeholder }}
-              {% endif %}
-            </td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "VRF" %}</th>
-            <td>{{ object.vrf|linkify|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "VLAN Translation" %}</th>
-            <td>{{ object.vlan_translation_policy|linkify|placeholder }}</td>
-          </tr>
-        </table>
-      </div>
-      {% if object.is_virtual and object.virtual_circuit_termination %}
-        <div class="card">
-          <h2 class="card-header">{% trans "Virtual Circuit" %}</h2>
-          <table class="table table-hover attr-table">
-            <tr>
-              <th scope="row">{% trans "Provider" %}</th>
-              <td>{{ object.virtual_circuit_termination.virtual_circuit.provider|linkify }}</td>
-            </tr>
-            <tr>
-              <th scope="row">{% trans "Provider Network" %}</th>
-              <td>{{ object.virtual_circuit_termination.virtual_circuit.provider_network|linkify }}</td>
-            </tr>
-            <tr>
-              <th scope="row">{% trans "Circuit ID" %}</th>
-              <td>{{ object.virtual_circuit_termination.virtual_circuit|linkify }}</td>
-            </tr>
-            <tr>
-              <th scope="row">{% trans "Role" %}</th>
-              <td>{{ object.virtual_circuit_termination.get_role_display }}</td>
-            </tr>
-            <tr>
-              <th scope="row">{% trans "Connections" %}</th>
-              <td>
-                {% for termination in object.virtual_circuit_termination.peer_terminations %}
-                  <a href="{{ termination.interface.parent_object.get_absolute_url }}">{{ termination.interface.parent_object }}</a>
-                  <i class="mdi mdi-chevron-right"></i>
-                  <a href="{{ termination.interface.get_absolute_url }}">{{ termination.interface }}</a>
-                  ({{ termination.get_role_display }})
-                  {% if not forloop.last %}<br />{% endif %}
-                {% endfor %}
-              </td>
-            </tr>
-          </table>
-        </div>
-      {% elif not object.is_virtual %}
-        <div class="card">
-          <h2 class="card-header">{% trans "Connection" %}</h2>
-          {% if object.mark_connected %}
-            <div class="card-body">
-              <span class="text-success"><i class="mdi mdi-check-bold"></i></span>
-              {% trans "Marked as Connected" %}
-            </div>
-          {% elif object.cable %}
-            {% include 'dcim/inc/connection_endpoints.html' with trace_url='dcim:interface_trace' %}
-          {% elif object.wireless_link %}
-            <table class="table table-hover attr-table">
-              <tr>
-                <th scope="row">{% trans "Wireless Link" %}</th>
-                <td>
-                  {{ object.wireless_link|linkify }}
-                  <a href="{% url 'dcim:interface_trace' pk=object.pk %}" class="btn btn-sm btn-primary" title="{% trans "Trace" %}">
-                    <i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
-                  </a>
-                </td>
-              </tr>
-              {% with peer_interface=object.link_peers.0 %}
-                <tr>
-                  <th scope="row">{% trans "Device" %}</th>
-                  <td>{{ peer_interface.device|linkify }}</td>
-                </tr>
-                <tr>
-                  <th scope="row">{% trans "Name" %}</th>
-                  <td>{{ peer_interface|linkify }}</td>
-                </tr>
-                <tr>
-                  <th scope="row">{% trans "Type" %}</th>
-                  <td>{{ peer_interface.get_type_display }}</td>
-                </tr>
-              {% endwith %}
-            </table>
-          {% else %}
-            <div class="card-body text-muted">
-              {% trans "Not Connected" %}
-              {% if object.is_wired and perms.dcim.add_cable %}
-                <div class="dropdown float-end">
-                  <button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                    <span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> {% trans "Connect" %}
-                  </button>
-                  <ul class="dropdown-menu dropdown-menu-end">
-                    <li>
-                      <a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ object.pk }}&b_terminations_type=dcim.interface&return_url={{ object.get_absolute_url }}">{% trans "Interface" %}</a>
-                    </li>
-                    <li>
-                      <a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ object.pk }}&b_terminations_type=dcim.frontport&return_url={{ object.get_absolute_url }}">{% trans "Front Port" %}</a>
-                    </li>
-                    <li>
-                      <a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ object.pk }}&b_terminations_type=dcim.rearport&return_url={{ object.get_absolute_url }}">{% trans "Rear Port" %}</a>
-                    </li>
-                    <li>
-                      <a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ object.pk }}&b_terminations_type=circuits.circuittermination&return_url={{ object.get_absolute_url }}">{% trans "Circuit Termination" %}</a>
-                    </li>
-                  </ul>
-                </div>
-              {% elif object.is_wireless and perms.wireless.add_wirelesslink %}
-                <div class="dropdown float-end">
-                  <a href="{% url 'wireless:wirelesslink_add' %}?interface_a={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary">
-                    <span class="mdi mdi-wifi-plus" aria-hidden="true"></span> {% trans "Connect" %}
-                  </a>
-                </div>
-              {% endif %}
-            </div>
-          {% endif %}
-        </div>
-      {% endif %}
-      {% if object.is_wireless %}
-        <div class="card">
-          <h2 class="card-header">{% trans "Wireless" %}</h2>
-          {% with peer=object.connected_endpoints.0 %}
-            <table class="table table-hover">
-              <thead>
-                <tr>
-                  <th></th>
-                  <th>{% trans "Local" %}</th>
-                  {% if peer %}
-                    <th>{% trans "Peer" %}</th>
-                  {% endif %}
-                </tr>
-              </thead>
-              <tr>
-                <th scope="row">{% trans "Role" %}</th>
-                <td>{{ object.get_rf_role_display|placeholder }}</td>
-                {% if peer %}
-                  <td>{{ peer.get_rf_role_display|placeholder }}</td>
-                {% endif %}
-              </tr>
-              <tr>
-                <th scope="row">{% trans "Channel" %}</th>
-                <td>{{ object.get_rf_channel_display|placeholder }}</td>
-                {% if peer %}
-                  <td{% if peer.rf_channel != object.rf_channel %} class="text-danger"{% endif %}>
-                    {{ peer.get_rf_channel_display|placeholder }}
-                  </td>
-                {% endif %}
-              </tr>
-              <tr>
-                <th scope="row">{% trans "Channel Frequency" %}</th>
-                <td>
-                  {% if object.rf_channel_frequency %}
-                    {{ object.rf_channel_frequency|floatformat:"-2" }} {% trans "MHz" %}
-                  {% else %}
-                    {{ ''|placeholder }}
-                  {% endif %}
-                </td>
-                {% if peer %}
-                  <td{% if peer.rf_channel_frequency != object.rf_channel_frequency %} class="text-danger"{% endif %}>
-                    {% if peer.rf_channel_frequency %}
-                      {{ peer.rf_channel_frequency|floatformat:"-2" }} {% trans "MHz" %}
-                    {% else %}
-                      {{ ''|placeholder }}
-                    {% endif %}
-                  </td>
-                {% endif %}
-              </tr>
-              <tr>
-                <th scope="row">{% trans "Channel Width" %}</th>
-                <td>
-                  {% if object.rf_channel_width %}
-                    {{ object.rf_channel_width|floatformat:"-3" }} {% trans "MHz" %}
-                  {% else %}
-                    {{ ''|placeholder }}
-                  {% endif %}
-                </td>
-                {% if peer %}
-                  <td{% if peer.rf_channel_width != object.rf_channel_width %} class="text-danger"{% endif %}>
-                    {% if peer.rf_channel_width %}
-                      {{ peer.rf_channel_width|floatformat:"-3" }} {% trans "MHz" %}
-                    {% else %}
-                      {{ ''|placeholder }}
-                    {% endif %}
-                  </td>
-                {% endif %}
-              </tr>
-            </table>
-          {% endwith %}
-        </div>
-        <div class="card">
-          <h2 class="card-header">{% trans "Wireless LANs" %}</h2>
-          <table class="table table-hover">
-            <thead>
-              <tr>
-                <th>{% trans "Group" %}</th>
-                <th>{% trans "SSID" %}</th>
-              </tr>
-            </thead>
-            <tbody>
-              {% for wlan in object.wireless_lans.all %}
-                <tr>
-                  <td>{{ wlan.group|linkify|placeholder }}</td>
-                  <td>{{ wlan|linkify:"ssid" }}</td>
-                </tr>
-              {% empty %}
-                <tr>
-                  <td colspan="3" class="text-muted">{% trans "None" %}</td>
-                </tr>
-              {% endfor %}
-            </tbody>
-          </table>
-        </div>
-      {% endif %}
-      {% include 'ipam/inc/panels/fhrp_groups.html' %}
-      {% include 'dcim/inc/panels/inventory_items.html' %}
-      {% plugin_right_page object %}
-    </div>
-  </div>
-  <div class="row mb-3">
-    <div class="col col-md-12">
-      <div class="card">
-        <h2 class="card-header">
-          {% trans "IP Addresses" %}
-          {% if perms.ipam.add_ipaddress %}
-            <div class="card-actions">
-              <a href="{% url 'ipam:ipaddress_add' %}?device={{ object.device.pk }}&interface={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-ghost-primary btn-sm">
-                <span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add IP Address" %}
-              </a>
-            </div>
-          {% endif %}
-        </h2>
-        {% htmx_table 'ipam:ipaddress_list' interface_id=object.pk %}
-      </div>
-    </div>
-  </div>
-  <div class="row mb-3">
-    <div class="col col-md-12">
-      <div class="card">
-        <h2 class="card-header">
-          {% trans "MAC Addresses" %}
-          {% if perms.dcim.add_macaddress %}
-            <div class="card-actions">
-              <a href="{% url 'dcim:macaddress_add' %}?device={{ object.device.pk }}&interface={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-ghost-primary btn-sm">
-                <span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add MAC Address" %}
-              </a>
-            </div>
-          {% endif %}
-        </h2>
-        {% htmx_table 'dcim:macaddress_list' interface_id=object.pk %}
-      </div>
-    </div>
-  </div>
-  <div class="row mb-3">
-    <div class="col col-md-12">
-      <div class="card">
-        <h2 class="card-header">{% trans "VLANs" %}</h2>
-        {% htmx_table 'ipam:vlan_list' interface_id=object.pk %}
-      </div>
-    </div>
-  </div>
-  {% if object.is_lag %}
-    <div class="row mb-3">
-      <div class="col col-md-12">
-        {% include 'inc/panel_table.html' with table=lag_interfaces_table heading="LAG Members" %}
-      </div>
-    </div>
-  {% endif %}
-  {% if object.vlan_translation_policy %}
-    <div class="row mb-3">
-      <div class="col col-md-12">
-        {% include 'inc/panel_table.html' with table=vlan_translation_table heading="VLAN Translation" %}
-      </div>
-    </div>
-  {% endif %}
-  <div class="row mb-3">
-    <div class="col col-md-12">
-      {% include 'inc/panel_table.html' with table=bridge_interfaces_table heading="Bridged Interfaces" %}
-    </div>
-  </div>
-  <div class="row mb-3">
-    <div class="col col-md-12">
-      {% include 'inc/panel_table.html' with table=child_interfaces_table heading="Child Interfaces" %}
-    </div>
-  </div>
-  <div class="row mb-3">
-    <div class="col col-md-12">
-      {% plugin_full_width_page object %}
-    </div>
-  </div>
-{% endblock %}

+ 3 - 0
netbox/templates/dcim/interface/attrs/mac_address.html

@@ -0,0 +1,3 @@
+{% load helpers i18n %}
+<span class="font-monospace">{{ value|linkify }}</span>
+<span class="badge text-bg-primary">{% trans "Primary" %}</span>

+ 2 - 0
netbox/templates/dcim/interface/attrs/speed.html

@@ -0,0 +1,2 @@
+{% load helpers %}
+{{ value|humanize_speed }}

+ 0 - 73
netbox/templates/dcim/inventoryitem.html

@@ -1,6 +1,4 @@
 {% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
 {% load i18n %}
 
 {% block breadcrumbs %}
@@ -9,74 +7,3 @@
     <a href="{% url 'dcim:device_inventory' pk=object.device.pk %}">{{ object.device }}</a>
   </li>
 {% endblock %}
-
-{% block content %}
-    <div class="row mb-3">
-        <div class="col col-12 col-md-6">
-            <div class="card">
-                <h2 class="card-header">{% trans "Inventory Item" %}</h2>
-                <table class="table table-hover attr-table">
-                    <tr>
-                        <th scope="row">{% trans "Device" %}</th>
-                        <td>{{ object.device|linkify }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Parent Item" %}</th>
-                        <td>{{ object.parent|linkify|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Name" %}</th>
-                        <td>{{ object.name }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Label" %}</th>
-                        <td>{{ object.label|placeholder }}</td>
-                    </tr>
-                    <tr>
-                      <th scope="row">{% trans "Status" %}</th>
-                      <td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Role" %}</th>
-                        <td>{{ object.role|linkify|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Component" %}</th>
-                        <td>{{ object.component|linkify|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Manufacturer" %}</th>
-                        <td>{{ object.manufacturer|linkify|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Part ID" %}</th>
-                        <td>{{ object.part_id|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Serial" %}</th>
-                        <td>{{ object.serial|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Asset Tag" %}</th>
-                        <td>{{ object.asset_tag|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Description" %}</th>
-                        <td>{{ object.description|placeholder }}</td>
-                    </tr>
-                </table>
-            </div>
-            {% include 'inc/panels/custom_fields.html' %}
-            {% include 'inc/panels/tags.html' %}
-            {% plugin_left_page object %}
-        </div>
-        <div class="col col-12 col-md-6">
-            {% plugin_right_page object %}
-        </div>
-    </div>
-    <div class="row mb-3">
-        <div class="col col-md-12">
-            {% plugin_full_width_page object %}
-        </div>
-    </div>
-{% endblock %}

+ 0 - 52
netbox/templates/dcim/inventoryitemrole.html

@@ -1,53 +1 @@
 {% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
-{% load render_table from django_tables2 %}
-{% load i18n %}
-
-{% block breadcrumbs %}
-  <li class="breadcrumb-item"><a href="{% url 'dcim:inventoryitemrole_list' %}">{% trans "Inventory Item Roles" %}</a></li>
-{% endblock %}
-
-{% block content %}
-<div class="row mb-3">
-	<div class="col col-12 col-md-6">
-    <div class="card">
-      <h2 class="card-header">{% trans "Inventory Item Role" %}</h2>
-      <table class="table table-hover attr-table">
-        <tr>
-          <th scope="row">{% trans "Name" %}</th>
-          <td>{{ object.name }}</td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Description" %}</th>
-          <td>{{ object.description|placeholder }}</td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Color" %}</th>
-          <td>
-            <span class="badge color-label" style="background-color: #{{ object.color }}">&nbsp;</span>
-          </td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Inventory Items" %}</th>
-          <td>
-            <a href="{% url 'dcim:inventoryitem_list' %}?role_id={{ object.pk }}">{{ inventoryitem_count }}</a>
-          </td>
-        </tr>
-      </table>
-    </div>
-    {% include 'inc/panels/tags.html' %}
-    {% plugin_left_page object %}
-	</div>
-	<div class="col col-12 col-md-6">
-    {% include 'inc/panels/comments.html' %}
-    {% include 'inc/panels/custom_fields.html' %}
-    {% plugin_right_page object %}
-  </div>
-</div>
-<div class="row mb-3">
-	<div class="col col-md-12">
-    {% plugin_full_width_page object %}
-  </div>
-</div>
-{% endblock %}

+ 0 - 54
netbox/templates/dcim/macaddress.html

@@ -1,55 +1 @@
 {% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
-{% load render_table from django_tables2 %}
-{% load i18n %}
-
-{% block content %}
-  <div class="row">
-    <div class="col col-12 col-md-6">
-      <div class="card">
-        <h2 class="card-header">{% trans "MAC Address" %}</h2>
-        <table class="table table-hover attr-table">
-          <tr>
-            <th scope="row">{% trans "MAC Address" %}</th>
-            <td>
-              <span id="macaddress_{{ object.pk }}">{{ object.mac_address|placeholder }}</span>
-              {% copy_content object.pk prefix="macaddress_" %}
-            </td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Description" %}</th>
-            <td>{{ object.description|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Assignment" %}</th>
-            <td>
-              {% if object.assigned_object %}
-                {{ object.assigned_object.parent_object|linkify }} /
-                {{ object.assigned_object|linkify }}
-              {% else %}
-                {{ ''|placeholder }}
-              {% endif %}
-            </td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Primary for interface" %}</th>
-            <td>{% checkmark object.is_primary %}</td>
-          </tr>
-        </table>
-      </div>
-      {% include 'inc/panels/tags.html' %}
-      {% include 'inc/panels/custom_fields.html' %}
-      {% plugin_left_page object %}
-    </div>
-    <div class="col col-12 col-md-6">
-      {% include 'inc/panels/comments.html' %}
-      {% plugin_right_page object %}
-    </div>
-  </div>
-  <div class="row">
-    <div class="col col-md-12">
-      {% plugin_full_width_page object %}
-    </div>
-  </div>
-{% endblock %}

+ 0 - 82
netbox/templates/dcim/modulebay.html

@@ -1,6 +1,4 @@
 {% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
 {% load i18n %}
 
 {% block breadcrumbs %}
@@ -9,83 +7,3 @@
     <a href="{% url 'dcim:device_modulebays' pk=object.device.pk %}">{{ object.device }}</a>
   </li>
 {% endblock %}
-
-{% block content %}
-  <div class="row">
-    <div class="col col-12 col-md-6">
-      <div class="card">
-        <h2 class="card-header">{% trans "Module Bay" %}</h2>
-        <table class="table table-hover attr-table">
-          <tr>
-            <th scope="row">{% trans "Device" %}</th>
-            <td>
-              <a href="{% url 'dcim:device_modulebays' pk=object.device.pk %}">{{ object.device }}</a>
-            </td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Module" %}</th>
-            <td>{{ object.module|linkify|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Name" %}</th>
-            <td>{{ object.name }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Label" %}</th>
-            <td>{{ object.label|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Position" %}</th>
-            <td>{{ object.position|placeholder }}</td>
-          </tr>
-          <tr>
-            <th scope="row">{% trans "Description" %}</th>
-            <td>{{ object.description|placeholder }}</td>
-          </tr>
-        </table>
-      </div>
-      {% include 'inc/panels/tags.html' %}
-      {% plugin_left_page object %}
-    </div>
-    <div class="col col-12 col-md-6">
-      {% include 'inc/panels/custom_fields.html' %}
-      <div class="card">
-        <h2 class="card-header">{% trans "Installed Module" %}</h2>
-        {% if object.installed_module %}
-          {% with module=object.installed_module %}
-            <table class="table table-hover attr-table">
-              <tr>
-                <th scope="row">{% trans "Module" %}</th>
-                <td>{{ module|linkify }}</td>
-              </tr>
-              <tr>
-                <th scope="row">{% trans "Manufacturer" %}</th>
-                <td>{{ module.module_type.manufacturer|linkify }}</td>
-              </tr>
-              <tr>
-                <th scope="row">{% trans "Module Type" %}</th>
-                <td>{{ module.module_type|linkify }}</td>
-              </tr>
-              <tr>
-                <th scope="row">{% trans "Serial Number" %}</th>
-                <td class="font-monospace">{{ module.serial|placeholder }}</td>
-              </tr>
-              <tr>
-                <th scope="row">{% trans "Asset Tag" %}</th>
-                <td class="font-monospace">{{ module.asset_tag|placeholder }}</td>
-              </tr>
-            </table>
-          {% endwith %}
-        {% else %}
-          <div class="card-body text-muted">{% trans "None" %}</div>
-        {% endif %}
-      </div>
-      {% plugin_right_page object %}
-    </div>
-  </div>
-  <div class="row">
-    <div class="col col-md-12">
-      {% plugin_full_width_page object %}
-    </div>
-  </div>
-{% endblock %}

+ 6 - 0
netbox/templates/dcim/panels/cable_termination_a.html

@@ -0,0 +1,6 @@
+{% extends "ui/panels/_base.html" %}
+{% load helpers i18n %}
+
+{% block panel_content %}
+  {% include 'dcim/inc/cable_termination.html' with terminations=object.a_terminations %}
+{% endblock panel_content %}

+ 6 - 0
netbox/templates/dcim/panels/cable_termination_b.html

@@ -0,0 +1,6 @@
+{% extends "ui/panels/_base.html" %}
+{% load helpers i18n %}
+
+{% block panel_content %}
+  {% include 'dcim/inc/cable_termination.html' with terminations=object.b_terminations %}
+{% endblock panel_content %}

+ 40 - 0
netbox/templates/dcim/panels/component_inventory_items.html

@@ -0,0 +1,40 @@
+{% extends "ui/panels/_base.html" %}
+{% load helpers i18n %}
+
+{% block panel_content %}
+  <table class="table table-hover">
+    <thead>
+      <tr>
+        <th>{% trans "Name" %}</th>
+        <th>{% trans "Label" %}</th>
+        <th>{% trans "Role" %}</th>
+        <th></th>
+      </tr>
+    </thead>
+    <tbody>
+      {% for item in object.inventory_items.all %}
+        <tr>
+          <td>{{ item|linkify:"name" }}</td>
+          <td>{{ item.label|placeholder }}</td>
+          <td>{{ item.role|linkify|placeholder }}</td>
+          <td class="text-end d-print-none">
+            {% if perms.dcim.change_inventoryitem %}
+              <a href="{% url 'dcim:inventoryitem_edit' pk=item.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-warning" title="{% trans "Edit" %}">
+                <i class="mdi mdi-pencil" aria-hidden="true"></i>
+              </a>
+            {% endif %}
+            {% if perms.dcim.delete_inventoryitem %}
+              <a href="{% url 'dcim:inventoryitem_delete' pk=item.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-danger" title="{% trans "Delete" %}">
+                <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i>
+              </a>
+            {% endif %}
+          </td>
+        </tr>
+      {% empty %}
+        <tr>
+          <td colspan="4" class="text-muted">{% trans "None" %}</td>
+        </tr>
+      {% endfor %}
+    </tbody>
+  </table>
+{% endblock panel_content %}

+ 96 - 0
netbox/templates/dcim/panels/connection.html

@@ -0,0 +1,96 @@
+{% extends "ui/panels/_base.html" %}
+{% load helpers i18n %}
+
+{% block panel_content %}
+  {% if object.mark_connected %}
+    <div class="card-body">
+      <span class="text-success"><i class="mdi mdi-check-bold"></i></span>
+      {% trans "Marked as connected" %}
+    </div>
+  {% elif object.cable %}
+    {% if show_endpoints %}
+      <table class="table table-hover attr-table">
+        <tr>
+          <th scope="row">{% trans "Cable" %}</th>
+          <td>
+            {{ object.cable|linkify }}
+            <a href="{% url trace_url_name pk=object.pk %}" class="btn btn-sm btn-primary" title="{% trans "Trace" %}">
+              <i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
+            </a>
+          </td>
+        </tr>
+        <tr>
+          <th scope="row">{% trans "Path status" %}</th>
+          <td>
+            {% if object.path.is_complete and object.path.is_active %}
+              <span class="badge text-bg-success">{% trans "Reachable" %}</span>
+            {% else %}
+              <span class="badge text-bg-danger">{% trans "Not Reachable" %}</span>
+            {% endif %}
+          </td>
+        </tr>
+        <tr>
+          <th scope="row">{% trans "Path endpoints" %}</th>
+          <td>
+            {% for endpoint in object.connected_endpoints %}
+              {% if endpoint.parent_object %}
+                {{ endpoint.parent_object|linkify }}
+                <i class="mdi mdi-chevron-right"></i>
+              {% endif %}
+              {{ endpoint|linkify }}
+              {% if not forloop.last %}<br />{% endif %}
+            {% empty %}
+              {{ ''|placeholder }}
+            {% endfor %}
+          </td>
+        </tr>
+      </table>
+    {% else %}
+      <table class="table table-hover attr-table">
+        <tr>
+          <th scope="row">{% trans "Cable" %}</th>
+          <td>
+            {{ object.cable|linkify }}
+            <a href="{% url trace_url_name pk=object.pk %}" class="btn btn-sm btn-primary" title="{% trans "Trace" %}">
+              <i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
+            </a>
+          </td>
+        </tr>
+        <tr>
+          <th scope="row">{% trans "Connection status" %}</th>
+          <td>
+            {% if object.cable.status %}
+              <span class="badge text-bg-success">{{ object.cable.get_status_display }}</span>
+            {% else %}
+              <span class="badge text-bg-info">{{ object.cable.get_status_display }}</span>
+            {% endif %}
+          </td>
+        </tr>
+      </table>
+    {% endif %}
+  {% else %}
+    <div class="card-body text-muted">
+      {% trans "Not Connected" %}
+      {% if perms.dcim.add_cable %}
+        {% if connect_options|length > 1 %}
+          <div class="dropdown float-end">
+            <button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
+              <span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> {% trans "Connect" %}
+            </button>
+            <ul class="dropdown-menu dropdown-menu-end">
+              {% for option in connect_options %}
+                <li>
+                  <a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type={{ option.a_type }}&a_terminations={{ object.pk }}&b_terminations_type={{ option.b_type }}&return_url={{ object.get_absolute_url }}">{{ option.label }}</a>
+                </li>
+              {% endfor %}
+            </ul>
+          </div>
+        {% elif connect_options|length == 1 %}
+          <a href="{% url 'dcim:cable_add' %}?a_terminations_type={{ connect_options.0.a_type }}&a_terminations={{ object.pk }}&b_terminations_type={{ connect_options.0.b_type }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary float-end">
+            <i class="mdi mdi-ethernet-cable" aria-hidden="true"></i> {% trans "Connect" %}
+          </a>
+        {% endif %}
+      {% endif %}
+    </div>
+  {% endif %}
+{% endblock panel_content %}

+ 29 - 0
netbox/templates/dcim/panels/front_port_mappings.html

@@ -0,0 +1,29 @@
+{% load i18n %}
+
+<div class="card">
+  <h2 class="card-header">{% trans "Port Mappings" %}</h2>
+  <table class="table table-hover">
+    {% if rear_port_mappings %}
+      <thead>
+        <tr>
+          <th>{% trans "Position" %}</th>
+          <th>{% trans "Rear Port" %}</th>
+        </tr>
+      </thead>
+    {% endif %}
+    <tbody>
+      {% for mapping in rear_port_mappings %}
+        <tr>
+          <td>{{ mapping.front_port_position }}</td>
+          <td>
+            <a href="{{ mapping.rear_port.get_absolute_url }}">{{ mapping.rear_port }}:{{ mapping.rear_port_position }}</a>
+          </td>
+        </tr>
+      {% empty %}
+        <tr>
+          <td class="text-muted">{% trans "No mappings defined" %}</td>
+        </tr>
+      {% endfor %}
+    </tbody>
+  </table>
+</div>

+ 21 - 0
netbox/templates/dcim/panels/installed_device.html

@@ -0,0 +1,21 @@
+{% extends "ui/panels/_base.html" %}
+{% load helpers i18n %}
+
+{% block panel_content %}
+  {% if object.installed_device %}
+    {% with device=object.installed_device %}
+      <table class="table table-hover attr-table">
+        <tr>
+          <th scope="row">{% trans "Device" %}</th>
+          <td>{{ device|linkify }}</td>
+        </tr>
+        <tr>
+          <th scope="row">{% trans "Device type" %}</th>
+          <td>{{ device.device_type }}</td>
+        </tr>
+      </table>
+    {% endwith %}
+  {% else %}
+    <div class="card-body text-muted">{% trans "None" %}</div>
+  {% endif %}
+{% endblock panel_content %}

+ 33 - 0
netbox/templates/dcim/panels/installed_module.html

@@ -0,0 +1,33 @@
+{% extends "ui/panels/_base.html" %}
+{% load helpers i18n %}
+
+{% block panel_content %}
+  {% if object.installed_module %}
+    {% with module=object.installed_module %}
+      <table class="table table-hover attr-table">
+        <tr>
+          <th scope="row">{% trans "Module" %}</th>
+          <td>{{ module|linkify }}</td>
+        </tr>
+        <tr>
+          <th scope="row">{% trans "Manufacturer" %}</th>
+          <td>{{ module.module_type.manufacturer|linkify }}</td>
+        </tr>
+        <tr>
+          <th scope="row">{% trans "Module type" %}</th>
+          <td>{{ module.module_type|linkify }}</td>
+        </tr>
+        <tr>
+          <th scope="row">{% trans "Serial number" %}</th>
+          <td class="font-monospace">{{ module.serial|placeholder }}</td>
+        </tr>
+        <tr>
+          <th scope="row">{% trans "Asset tag" %}</th>
+          <td class="font-monospace">{{ module.asset_tag|placeholder }}</td>
+        </tr>
+      </table>
+    {% endwith %}
+  {% else %}
+    <div class="card-body text-muted">{% trans "None" %}</div>
+  {% endif %}
+{% endblock panel_content %}

+ 105 - 0
netbox/templates/dcim/panels/interface_connection.html

@@ -0,0 +1,105 @@
+{% extends "ui/panels/_base.html" %}
+{% load helpers i18n %}
+
+{% block panel_content %}
+  {% if object.mark_connected %}
+    <div class="card-body">
+      <span class="text-success"><i class="mdi mdi-check-bold"></i></span>
+      {% trans "Marked as connected" %}
+    </div>
+  {% elif object.cable %}
+    <table class="table table-hover attr-table">
+      <tr>
+        <th scope="row">{% trans "Cable" %}</th>
+        <td>
+          {{ object.cable|linkify }}
+          <a href="{% url 'dcim:interface_trace' pk=object.pk %}" class="btn btn-sm btn-primary" title="{% trans "Trace" %}">
+            <i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
+          </a>
+        </td>
+      </tr>
+      <tr>
+        <th scope="row">{% trans "Path status" %}</th>
+        <td>
+          {% if object.path.is_complete and object.path.is_active %}
+            <span class="badge text-bg-success">{% trans "Reachable" %}</span>
+          {% else %}
+            <span class="badge text-bg-danger">{% trans "Not Reachable" %}</span>
+          {% endif %}
+        </td>
+      </tr>
+      <tr>
+        <th scope="row">{% trans "Path endpoints" %}</th>
+        <td>
+          {% for endpoint in object.connected_endpoints %}
+            {% if endpoint.parent_object %}
+              {{ endpoint.parent_object|linkify }}
+              <i class="mdi mdi-chevron-right"></i>
+            {% endif %}
+            {{ endpoint|linkify }}
+            {% if not forloop.last %}<br />{% endif %}
+          {% empty %}
+            {{ ''|placeholder }}
+          {% endfor %}
+        </td>
+      </tr>
+    </table>
+  {% elif object.wireless_link %}
+    <table class="table table-hover attr-table">
+      <tr>
+        <th scope="row">{% trans "Wireless Link" %}</th>
+        <td>
+          {{ object.wireless_link|linkify }}
+          <a href="{% url 'dcim:interface_trace' pk=object.pk %}" class="btn btn-sm btn-primary" title="{% trans "Trace" %}">
+            <i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
+          </a>
+        </td>
+      </tr>
+      {% with peer_interface=object.link_peers.0 %}
+        <tr>
+          <th scope="row">{% trans "Device" %}</th>
+          <td>{{ peer_interface.device|linkify }}</td>
+        </tr>
+        <tr>
+          <th scope="row">{% trans "Name" %}</th>
+          <td>{{ peer_interface|linkify }}</td>
+        </tr>
+        <tr>
+          <th scope="row">{% trans "Type" %}</th>
+          <td>{{ peer_interface.get_type_display }}</td>
+        </tr>
+      {% endwith %}
+    </table>
+  {% else %}
+    <div class="card-body text-muted">
+      {% trans "Not Connected" %}
+      {% if object.is_wired and perms.dcim.add_cable %}
+        <div class="dropdown float-end">
+          <button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
+            <span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> {% trans "Connect" %}
+          </button>
+          <ul class="dropdown-menu dropdown-menu-end">
+            <li>
+              <a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ object.pk }}&b_terminations_type=dcim.interface&return_url={{ object.get_absolute_url }}">{% trans "Interface" %}</a>
+            </li>
+            <li>
+              <a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ object.pk }}&b_terminations_type=dcim.frontport&return_url={{ object.get_absolute_url }}">{% trans "Front Port" %}</a>
+            </li>
+            <li>
+              <a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ object.pk }}&b_terminations_type=dcim.rearport&return_url={{ object.get_absolute_url }}">{% trans "Rear Port" %}</a>
+            </li>
+            <li>
+              <a class="dropdown-item" href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.interface&a_terminations={{ object.pk }}&b_terminations_type=circuits.circuittermination&return_url={{ object.get_absolute_url }}">{% trans "Circuit Termination" %}</a>
+            </li>
+          </ul>
+        </div>
+      {% elif object.is_wireless and perms.wireless.add_wirelesslink %}
+        <div class="dropdown float-end">
+          <a href="{% url 'wireless:wirelesslink_add' %}?interface_a={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary">
+            <span class="mdi mdi-wifi-plus" aria-hidden="true"></span> {% trans "Connect" %}
+          </a>
+        </div>
+      {% endif %}
+    </div>
+  {% endif %}
+{% endblock panel_content %}

+ 35 - 0
netbox/templates/dcim/panels/interface_virtual_circuit.html

@@ -0,0 +1,35 @@
+{% extends "ui/panels/_base.html" %}
+{% load helpers i18n %}
+
+{% block panel_content %}
+  <table class="table table-hover attr-table">
+    <tr>
+      <th scope="row">{% trans "Provider" %}</th>
+      <td>{{ object.virtual_circuit_termination.virtual_circuit.provider|linkify }}</td>
+    </tr>
+    <tr>
+      <th scope="row">{% trans "Provider Network" %}</th>
+      <td>{{ object.virtual_circuit_termination.virtual_circuit.provider_network|linkify }}</td>
+    </tr>
+    <tr>
+      <th scope="row">{% trans "Circuit ID" %}</th>
+      <td>{{ object.virtual_circuit_termination.virtual_circuit|linkify }}</td>
+    </tr>
+    <tr>
+      <th scope="row">{% trans "Role" %}</th>
+      <td>{{ object.virtual_circuit_termination.get_role_display }}</td>
+    </tr>
+    <tr>
+      <th scope="row">{% trans "Connections" %}</th>
+      <td>
+        {% for termination in object.virtual_circuit_termination.peer_terminations %}
+          <a href="{{ termination.interface.parent_object.get_absolute_url }}">{{ termination.interface.parent_object }}</a>
+          <i class="mdi mdi-chevron-right"></i>
+          <a href="{{ termination.interface.get_absolute_url }}">{{ termination.interface }}</a>
+          ({{ termination.get_role_display }})
+          {% if not forloop.last %}<br />{% endif %}
+        {% endfor %}
+      </td>
+    </tr>
+  </table>
+{% endblock panel_content %}

+ 72 - 0
netbox/templates/dcim/panels/interface_wireless.html

@@ -0,0 +1,72 @@
+{% extends "ui/panels/_base.html" %}
+{% load helpers i18n %}
+
+{% block panel_content %}
+  {% with peer=object.connected_endpoints.0 %}
+    <table class="table table-hover attr-table">
+      <thead>
+        <tr class="border-bottom">
+          <th></th>
+          <th>{% trans "Local" %}</th>
+          {% if peer %}
+            <th>{% trans "Peer" %}</th>
+          {% endif %}
+        </tr>
+      </thead>
+      <tr>
+        <th scope="row">{% trans "Role" %}</th>
+        <td>{{ object.get_rf_role_display|placeholder }}</td>
+        {% if peer %}
+          <td>{{ peer.get_rf_role_display|placeholder }}</td>
+        {% endif %}
+      </tr>
+      <tr>
+        <th scope="row">{% trans "Channel" %}</th>
+        <td>{{ object.get_rf_channel_display|placeholder }}</td>
+        {% if peer %}
+          <td{% if peer.rf_channel != object.rf_channel %} class="text-danger"{% endif %}>
+            {{ peer.get_rf_channel_display|placeholder }}
+          </td>
+        {% endif %}
+      </tr>
+      <tr>
+        <th scope="row">{% trans "Channel frequency" %}</th>
+        <td>
+          {% if object.rf_channel_frequency %}
+            {{ object.rf_channel_frequency|floatformat:"-2" }} {% trans "MHz" %}
+          {% else %}
+            {{ ''|placeholder }}
+          {% endif %}
+        </td>
+        {% if peer %}
+          <td{% if peer.rf_channel_frequency != object.rf_channel_frequency %} class="text-danger"{% endif %}>
+            {% if peer.rf_channel_frequency %}
+              {{ peer.rf_channel_frequency|floatformat:"-2" }} {% trans "MHz" %}
+            {% else %}
+              {{ ''|placeholder }}
+            {% endif %}
+          </td>
+        {% endif %}
+      </tr>
+      <tr>
+        <th scope="row">{% trans "Channel width" %}</th>
+        <td>
+          {% if object.rf_channel_width %}
+            {{ object.rf_channel_width|floatformat:"-3" }} {% trans "MHz" %}
+          {% else %}
+            {{ ''|placeholder }}
+          {% endif %}
+        </td>
+        {% if peer %}
+          <td{% if peer.rf_channel_width != object.rf_channel_width %} class="text-danger"{% endif %}>
+            {% if peer.rf_channel_width %}
+              {{ peer.rf_channel_width|floatformat:"-3" }} {% trans "MHz" %}
+            {% else %}
+              {{ ''|placeholder }}
+            {% endif %}
+          </td>
+        {% endif %}
+      </tr>
+    </table>
+  {% endwith %}
+{% endblock panel_content %}

+ 25 - 0
netbox/templates/dcim/panels/interface_wireless_lans.html

@@ -0,0 +1,25 @@
+{% extends "ui/panels/_base.html" %}
+{% load helpers i18n %}
+
+{% block panel_content %}
+  <table class="table table-hover">
+    <thead>
+      <tr>
+        <th>{% trans "Group" %}</th>
+        <th>{% trans "SSID" %}</th>
+      </tr>
+    </thead>
+    <tbody>
+      {% for wlan in object.wireless_lans.all %}
+        <tr>
+          <td>{{ wlan.group|linkify|placeholder }}</td>
+          <td>{{ wlan|linkify:"ssid" }}</td>
+        </tr>
+      {% empty %}
+        <tr>
+          <td colspan="2" class="text-muted">{% trans "None" %}</td>
+        </tr>
+      {% endfor %}
+    </tbody>
+  </table>
+{% endblock panel_content %}

+ 29 - 0
netbox/templates/dcim/panels/rear_port_mappings.html

@@ -0,0 +1,29 @@
+{% load i18n %}
+
+<div class="card">
+  <h2 class="card-header">{% trans "Port Mappings" %}</h2>
+  <table class="table table-hover">
+    {% if front_port_mappings %}
+      <thead>
+        <tr>
+          <th>{% trans "Position" %}</th>
+          <th>{% trans "Front Port" %}</th>
+        </tr>
+      </thead>
+    {% endif %}
+    <tbody>
+      {% for mapping in front_port_mappings %}
+        <tr>
+          <td>{{ mapping.rear_port_position }}</td>
+          <td>
+            <a href="{{ mapping.front_port.get_absolute_url }}">{{ mapping.front_port }}:{{ mapping.front_port_position }}</a>
+          </td>
+        </tr>
+      {% empty %}
+        <tr>
+          <td class="text-muted">{% trans "No mappings defined" %}</td>
+        </tr>
+      {% endfor %}
+    </tbody>
+  </table>
+</div>

+ 1 - 1
netbox/templates/dcim/panels/virtual_chassis_members.html

@@ -17,7 +17,7 @@
           <td>{{ vc_member|linkify }}</td>
           <td>{% badge vc_member.vc_position show_empty=True %}</td>
           <td>
-            {% if object.virtual_chassis.master == vc_member %}
+            {% if virtual_chassis.master == vc_member %}
               {% checkmark True %}
             {% else %}
               {{ ''|placeholder }}

+ 0 - 128
netbox/templates/dcim/powerfeed.html

@@ -1,9 +1,4 @@
 {% extends 'generic/object.html' %}
-{% load buttons %}
-{% load static %}
-{% load helpers %}
-{% load plugins %}
-{% load i18n %}
 
 {% block breadcrumbs %}
   {{ block.super }}
@@ -13,126 +8,3 @@
     <li class="breadcrumb-item"><a href="{% url 'dcim:powerfeed_list' %}?rack_id={{ object.rack.pk }}">{{ object.rack }}</a></li>
   {% endif %}
 {% endblock %}
-
-{% block content %}
-<div class="row">
-	<div class="col col-12 col-md-6">
-        <div class="card">
-            <h2 class="card-header">{% trans "Power Feed" %}</h2>
-            <table class="table table-hover attr-table">
-                <tr>
-                    <th scope="row">{% trans "Power Panel" %}</th>
-                    <td>{{ object.power_panel|linkify }}</td>
-                </tr>
-                <tr>
-                    <th scope="row">{% trans "Rack" %}</th>
-                    <td>{{ object.rack|linkify|placeholder }}</td>
-                </tr>
-                <tr>
-                    <th scope="row">{% trans "Type" %}</th>
-                    <td>{% badge object.get_type_display bg_color=object.get_type_color %}</td>
-                </tr>
-                <tr>
-                    <th scope="row">{% trans "Status" %}</th>
-                    <td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
-                </tr>
-                <tr>
-                    <th scope="row">{% trans "Description" %}</th>
-                    <td>{{ object.description|placeholder }}</td>
-                </tr>
-                <tr>
-                    <th scope="row">{% trans "Tenant" %}</th>
-                    <td>
-                        {% if object.tenant.group %}
-                          {{ object.tenant.group|linkify }} /
-                        {% endif %}
-                        {{ object.tenant|linkify|placeholder }}
-                    </td>
-                </tr>
-                <tr>
-                    <th scope="row">{% trans "Connected Device" %}</th>
-                    <td>
-                        {% if object.connected_endpoints %}
-                            {{ object.connected_endpoints.0.device|linkify }} ({{ object.connected_endpoints.0|linkify:"name" }})
-                        {% else %}
-                            {{ ''|placeholder }}
-                        {% endif %}
-                    </td>
-                </tr>
-                <tr>
-                    <th scope="row">{% trans "Utilization (Allocated" %})</th>
-                    {% with utilization=object.connected_endpoints.0.get_power_draw %}
-                        {% if utilization %}
-                            <td>
-                                {{ utilization.allocated }}{% trans "VA" %} / {{ object.available_power }}{% trans "VA" %}
-                                {% if object.available_power > 0 %}
-                                    {% utilization_graph utilization.allocated|percentage:object.available_power %}
-                                {% endif %}
-                            </td>
-                        {% else %}
-                            <td>{{ ''|placeholder }}</td>
-                        {% endif %}
-                    {% endwith %}
-                </tr>
-            </table>
-        </div>
-        <div class="card">
-            <h2 class="card-header">{% trans "Electrical Characteristics" %}</h2>
-            <table class="table table-hover attr-table">
-                <tr>
-                    <th scope="row">{% trans "Supply" %}</th>
-                    <td>{{ object.get_supply_display }}</td>
-                </tr>
-                <tr>
-                    <th scope="row">{% trans "Voltage" %}</th>
-                    <td>{{ object.voltage }}{% trans "V" context "Abbreviation for volts" %}</td>
-                </tr>
-                <tr>
-                    <th scope="row">{% trans "Amperage" %}</th>
-                    <td>{{ object.amperage }}{% trans "A" context "Abbreviation for amperes" %}</td>
-                </tr>
-                <tr>
-                    <th scope="row">{% trans "Phase" %}</th>
-                    <td>{{ object.get_phase_display }}</td>
-                </tr>
-                <tr>
-                    <th scope="row">{% trans "Max Utilization" %}</th>
-                    <td>{{ object.max_utilization }}%</td>
-                </tr>
-            </table>
-        </div>
-        {% include 'inc/panels/custom_fields.html' %}
-        {% include 'inc/panels/tags.html' %}
-        {% plugin_left_page object %}
-    </div>
-    <div class="col col-12 col-md-6">
-      <div class="card">
-        <h2 class="card-header">{% trans "Connection" %}</h2>
-        {% if object.mark_connected %}
-          <div class="card-body">
-            <span class="text-success"><i class="mdi mdi-check-bold"></i></span>
-            {% trans "Marked as connected" %}
-          </div>
-        {% elif object.cable %}
-          {% include 'dcim/inc/connection_endpoints.html' with trace_url='dcim:powerfeed_trace' %}
-        {% else %}
-          <div class="card-body text-muted">
-            {% trans "Not connected" %}
-            {% if perms.dcim.add_cable %}
-              <a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.powerfeed&a_terminations={{ object.pk }}&b_terminations_type=dcim.powerport&return_url={{ object.get_absolute_url }}" class="btn btn-primary float-end">
-                <i class="mdi mdi-ethernet-cable" aria-hidden="true"></i> {% trans "Connect" %}
-              </a>
-            {% endif %}
-          </div>
-        {% endif %}
-      </div>
-      {% include 'inc/panels/comments.html' %}
-      {% plugin_right_page object %}
-    </div>
-</div>
-<div class="row">
-    <div class="col col-md-12">
-        {% plugin_full_width_page object %}
-    </div>
-</div>
-{% endblock %}

+ 6 - 0
netbox/templates/dcim/powerfeed/attrs/connected_device.html

@@ -0,0 +1,6 @@
+{% load helpers %}
+{% if value %}
+  {{ value.0.device|linkify }} ({{ value.0|linkify:"name" }})
+{% else %}
+  {{ ''|placeholder }}
+{% endif %}

+ 15 - 0
netbox/templates/dcim/powerfeed/attrs/utilization.html

@@ -0,0 +1,15 @@
+{% load helpers i18n %}
+{% if value %}
+  {% with utilization=value.0.get_power_draw %}
+    {% if utilization %}
+      {{ utilization.allocated }}{% trans "VA" %} / {{ object.available_power }}{% trans "VA" %}
+      {% if object.available_power > 0 %}
+        {% utilization_graph utilization.allocated|percentage:object.available_power %}
+      {% endif %}
+    {% else %}
+      {{ ''|placeholder }}
+    {% endif %}
+  {% endwith %}
+{% else %}
+  {{ ''|placeholder }}
+{% endif %}

+ 0 - 92
netbox/templates/dcim/poweroutlet.html

@@ -1,6 +1,4 @@
 {% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
 {% load i18n %}
 
 {% block breadcrumbs %}
@@ -9,93 +7,3 @@
     <a href="{% url 'dcim:device_poweroutlets' pk=object.device.pk %}">{{ object.device }}</a>
   </li>
 {% endblock %}
-
-{% block content %}
-    <div class="row mb-3">
-        <div class="col col-12 col-md-6">
-            <div class="card">
-                <h2 class="card-header">{% trans "Power Outlet" %}</h2>
-                <table class="table table-hover attr-table">
-                    <tr>
-                        <th scope="row">{% trans "Device" %}</th>
-                        <td>{{ object.device|linkify }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Module" %}</th>
-                        <td>{{ object.module|linkify|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Name" %}</th>
-                        <td>{{ object.name }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Label" %}</th>
-                        <td>{{ object.label|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Type" %}</th>
-                        <td>{{ object.get_type_display }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Status" %}</th>
-                        <td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Description" %}</th>
-                        <td>{{ object.description|placeholder }}</td>
-                    </tr>
-                    <tr>
-                      <th scope="row">{% trans "Color" %}</th>
-                      <td>
-                        {% if object.color %}
-                          <span class="badge color-label" style="background-color: #{{ object.color }}">&nbsp;</span>
-                        {% else %}
-                          {{ ''|placeholder }}
-                        {% endif %}
-                      </td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Power Port" %}</th>
-                        <td>{{ object.power_port|linkify|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Feed Leg" %}</th>
-                        <td>{{ object.get_feed_leg_display|placeholder }}</td>
-                    </tr>
-                </table>
-            </div>
-            {% include 'inc/panels/custom_fields.html' %}
-            {% include 'inc/panels/tags.html' %}
-            {% plugin_left_page object %}
-        </div>
-        <div class="col col-12 col-md-6">
-          <div class="card">
-            <h2 class="card-header">{% trans "Connection" %}</h2>
-            {% if object.mark_connected %}
-              <div class="card-body">
-                <span class="text-success"><i class="mdi mdi-check-bold"></i></span>
-                {% trans "Marked as Connected" %}
-              </div>
-            {% elif object.cable %}
-              {% include 'dcim/inc/connection_endpoints.html' with trace_url='dcim:poweroutlet_trace' %}
-            {% else %}
-              <div class="card-body text-muted">
-                {% trans "Not Connected" %}
-                {% if perms.dcim.add_cable %}
-                  <a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.poweroutlet&a_terminations={{ object.pk }}&b_terminations_type=dcim.powerport&return_url={{ object.get_absolute_url }}" title="{% trans "Connect" %}" class="btn btn-primary float-end">
-                    <i class="mdi mdi-ethernet-cable" aria-hidden="true"></i> {% trans "Connect" %}
-                  </a>
-                {% endif %}
-              </div>
-            {% endif %}
-          </div>
-          {% include 'dcim/inc/panels/inventory_items.html' %}
-          {% plugin_right_page object %}
-        </div>
-    </div>
-    <div class="row mb-3">
-        <div class="col col-md-12">
-            {% plugin_full_width_page object %}
-        </div>
-    </div>
-{% endblock %}

+ 0 - 73
netbox/templates/dcim/powerpanel.html

@@ -1,8 +1,4 @@
 {% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
-{% load render_table from django_tables2 %}
-{% load i18n %}
 
 {% block breadcrumbs %}
   {{ block.super }}
@@ -11,72 +7,3 @@
     <li class="breadcrumb-item">{{ object.location|linkify }}</li>
   {% endif %}
 {% endblock %}
-
-{% block content %}
-<div class="row">
-	<div class="col col-12 col-md-6">
-    <div class="card">
-      <h2 class="card-header">{% trans "Power Panel" %}</h2>
-      <table class="table table-hover attr-table">
-        <tr>
-          <th scope="row">{% trans "Site" %}</th>
-          <td>{{ object.site|linkify }}</td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Location" %}</th>
-          <td>{{ object.location|linkify|placeholder }}</td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Description" %}</th>
-          <td>{{ object.description|placeholder }}</td>
-        </tr>
-      </table>
-    </div>
-    {% include 'inc/panels/tags.html' %}
-    {% include 'inc/panels/comments.html' %}
-    {% plugin_left_page object %}
-  </div>
-	<div class="col col-12 col-md-6">
-    {% include 'inc/panels/related_objects.html' %}
-    {% include 'inc/panels/custom_fields.html' %}
-    {% include 'inc/panels/image_attachments.html' %}
-    {% plugin_right_page object %}
-  </div>
-</div>
-<div class="row my-3">
-  <div class="col col-md-12">
-    <form method="post">
-      {% csrf_token %}
-      <div class="card">
-        <h2 class="card-header">{% trans "Power Feeds" %}</h2>
-        {% htmx_table 'dcim:powerfeed_list' power_panel_id=object.pk %}
-        <div class="card-footer d-print-none">
-          {% if perms.dcim.change_powerfeed %}
-            <button type="submit" name="_edit" {% formaction %}="{% url 'dcim:powerfeed_bulk_edit' %}?return_url={% url 'dcim:powerpanel' pk=object.pk %}" class="btn btn-warning">
-              <span class="mdi mdi-pencil" aria-hidden="true"></span> {% trans "Edit" %}
-            </button>
-          {% endif %}
-          {% if perms.dcim.delete_cable %}
-            <button type="submit" name="_disconnect" {% formaction %}="{% url 'dcim:powerfeed_bulk_disconnect' %}?return_url={% url 'dcim:powerpanel' pk=object.pk %}" class="btn btn-outline-danger">
-              <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
-            </button>
-          {% endif %}
-          {% if perms.dcim.delete_powerfeed %}
-            <button type="submit" name="_delete" {% formaction %}="{% url 'dcim:powerfeed_bulk_delete' %}?return_url={% url 'dcim:powerpanel' pk=object.pk %}" class="btn btn-danger">
-              <span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> {% trans "Delete" %}
-            </button>
-          {% endif %}
-          {% if perms.dcim.add_powerfeed %}
-            <div class="float-end">
-              <a href="{% url 'dcim:powerfeed_add' %}?power_panel={{ object.pk }}&return_url={% url 'dcim:powerpanel' pk=object.pk %}" class="btn btn-primary">
-                <span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add Power Feeds" %}
-              </a>
-            </div>
-          {% endif %}
-        </div>
-      </div>
-    </form>
-    {% plugin_full_width_page object %}
-  </div>
-</div>
-{% endblock %}

+ 0 - 88
netbox/templates/dcim/powerport.html

@@ -1,6 +1,4 @@
 {% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
 {% load i18n %}
 
 {% block breadcrumbs %}
@@ -9,89 +7,3 @@
     <a href="{% url 'dcim:device_powerports' pk=object.device.pk %}">{{ object.device }}</a>
   </li>
 {% endblock %}
-
-{% block content %}
-    <div class="row mb-3">
-        <div class="col col-12 col-md-6">
-            <div class="card">
-                <h2 class="card-header">{% trans "Power Port" %}</h2>
-                <table class="table table-hover attr-table">
-                    <tr>
-                        <th scope="row">{% trans "Device" %}</th>
-                        <td>{{ object.device|linkify }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Module" %}</th>
-                        <td>{{ object.module|linkify|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Name" %}</th>
-                        <td>{{ object.name }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Label" %}</th>
-                        <td>{{ object.label|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Type" %}</th>
-                        <td>{{ object.get_type_display|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Description" %}</th>
-                        <td>{{ object.description|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Maximum Draw" %}</th>
-                        <td>{{ object.maximum_draw|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Allocated Draw" %}</th>
-                        <td>{{ object.allocated_draw|placeholder }}</td>
-                    </tr>
-                </table>
-            </div>
-            {% include 'inc/panels/custom_fields.html' %}
-            {% include 'inc/panels/tags.html' %}
-            {% plugin_left_page object %}
-        </div>
-        <div class="col col-12 col-md-6">
-          <div class="card">
-            <h2 class="card-header">{% trans "Connection" %}</h2>
-            {% if object.mark_connected %}
-              <div class="card-body">
-                <span class="text-success"><i class="mdi mdi-check-bold"></i></span>
-                {% trans "Marked as Connected" %}
-              </div>
-            {% elif object.cable %}
-              {% include 'dcim/inc/connection_endpoints.html' with trace_url='dcim:powerport_trace' %}
-            {% else %}
-              <div class="card-body text-muted">
-                {% trans "Not Connected" %}
-                {% if perms.dcim.add_cable %}
-                  <span class="dropdown float-end">
-                    <button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                      <span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> {% trans "Connect" %}
-                    </button>
-                    <ul class="dropdown-menu dropdown-menu-end">
-                      <li>
-                        <a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.powerport&a_terminations={{ object.pk }}&b_terminations_type=dcim.poweroutlet&return_url={{ object.get_absolute_url }}" class="dropdown-item">{% trans "Power Outlet" %}</a>
-                      </li>
-                      <li>
-                        <a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.powerport&a_terminations={{ object.pk }}&b_terminations_type=dcim.powerfeed&return_url={{ object.get_absolute_url }}" class="dropdown-item">{% trans "Power Feed" %}</a>
-                      </li>
-                    </ul>
-                  </span>
-                {% endif %}
-              </div>
-            {% endif %}
-          </div>
-          {% include 'dcim/inc/panels/inventory_items.html' %}
-          {% plugin_right_page object %}
-        </div>
-    </div>
-    <div class="row">
-        <div class="col col-md-12">
-            {% plugin_full_width_page object %}
-        </div>
-    </div>
-{% endblock %}

+ 0 - 142
netbox/templates/dcim/rearport.html

@@ -1,6 +1,4 @@
 {% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
 {% load i18n %}
 
 {% block breadcrumbs %}
@@ -9,143 +7,3 @@
     <a href="{% url 'dcim:device_rearports' pk=object.device.pk %}">{{ object.device }}</a>
   </li>
 {% endblock %}
-
-{% block content %}
-    <div class="row">
-        <div class="col col-12 col-md-6">
-            <div class="card">
-                <h2 class="card-header">{% trans "Rear Port" %}</h2>
-                <table class="table table-hover attr-table">
-                    <tr>
-                        <th scope="row">{% trans "Device" %}</th>
-                        <td>{{ object.device|linkify }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Module" %}</th>
-                        <td>{{ object.module|linkify|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Name" %}</th>
-                        <td>{{ object.name }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Label" %}</th>
-                        <td>{{ object.label|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Type" %}</th>
-                        <td>{{ object.get_type_display }}</td>
-                    </tr>
-                    <tr>
-                      <th scope="row">{% trans "Color" %}</th>
-                      <td>
-                        {% if object.color %}
-                          <span class="badge color-label" style="background-color: #{{ object.color }}">&nbsp;</span>
-                        {% else %}
-                          {{ ''|placeholder }}
-                        {% endif %}
-                      </td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Positions" %}</th>
-                        <td>{{ object.positions }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{% trans "Description" %}</th>
-                        <td>{{ object.description|placeholder }}</td>
-                    </tr>
-                </table>
-            </div>
-            {% include 'inc/panels/custom_fields.html' %}
-            {% include 'inc/panels/tags.html' %}
-            {% include 'dcim/inc/panels/inventory_items.html' %}
-            {% plugin_left_page object %}
-        </div>
-        <div class="col col-12 col-md-6">
-            <div class="card">
-                <h2 class="card-header">{% trans "Connection" %}</h2>
-                {% if object.mark_connected %}
-                    <div class="card-body text-muted">
-                      <span class="text-success"><i class="mdi mdi-check-bold"></i></span> {% trans "Marked as Connected" %}
-                    </div>
-                {% elif object.cable %}
-                    <table class="table table-hover attr-table">
-                        <tr>
-                            <th scope="row">{% trans "Cable" %}</th>
-                            <td>
-                                {{ object.cable|linkify }}
-                                <a href="{% url 'dcim:rearport_trace' pk=object.pk %}" class="btn btn-sm btn-primary" title="{% trans "Trace" %}">
-                                    <i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
-                                </a>
-                            </td>
-                        </tr>
-                        <tr>
-                            <th scope="row">{% trans "Connection Status" %}</th>
-                            <td>
-                                {% if object.cable.status %}
-                                    <span class="badge text-bg-success">{{ object.cable.get_status_display }}</span>
-                                {% else %}
-                                    <span class="badge text-bg-info">{{ object.cable.get_status_display }}</span>
-                                {% endif %}
-                            </td>
-                        </tr>
-                    </table>
-                {% else %}
-                    <div class="card-body text-muted">
-                        {% trans "Not connected" %}
-                        {% if perms.dcim.add_cable %}
-                            <span class="dropdown float-end">
-                                <button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                                    <span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> {% trans "Connect" %}
-                                </button>
-                                <ul class="dropdown-menu dropdown-menu-end">
-                                    <li>
-                                        <a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ object.pk }}&b_terminations_type=dcim.interface&return_url={{ object.get_absolute_url }}" class="dropdown-item">{% trans "Interface" %}</a>
-                                    </li>
-                                    <li>
-                                        <a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ object.pk }}&b_terminations_type=dcim.frontport&return_url={{ object.get_absolute_url }}" class="dropdown-item">{% trans "Front Port" %}</a>
-                                    </li>
-                                    <li>
-                                        <a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ object.pk }}&b_terminations_type=dcim.rearport&return_url={{ object.get_absolute_url }}" class="dropdown-item">{% trans "Rear Port" %}</a>
-                                    </li>
-                                    <li>
-                                        <a href="{% url 'dcim:cable_add' %}?a_terminations_type=dcim.rearport&a_terminations={{ object.pk }}&b_terminations_type=circuits.circuittermination&return_url={{ object.get_absolute_url }}" class="dropdown-item">{% trans "Circuit Termination" %}</a>
-                                    </li>
-                                </ul>
-                            </span>
-                        {% endif %}
-                    </div>
-                {% endif %}
-            </div>
-            <div class="card">
-              <h2 class="card-header">{% trans "Port Mappings" %}</h2>
-              <table class="table table-hover">
-                {% if front_port_mappings %}
-                  <thead>
-                    <tr>
-                      <th>{% trans "Position" %}</th>
-                      <th>{% trans "Front Port" %}</th>
-                    </tr>
-                  </thead>
-                {% endif %}
-                {% for mapping in front_port_mappings %}
-                  <tr>
-                    <td>{{ mapping.rear_port_position }}</td>
-                    <td>
-                      <a href="{{ mapping.front_port.get_absolute_url }}">{{ mapping.front_port }}:{{ mapping.front_port_position }}</a>
-                    </td>
-                  </tr>
-                {% empty %}
-                  {% trans "No mappings defined" %}
-                {% endfor %}
-              </table>
-            </div>
-            {% plugin_right_page object %}
-        </div>
-    </div>
-    <div class="row">
-        <div class="col col-md-12">
-            {% plugin_full_width_page object %}
-        </div>
-    </div>
-{% endblock %}

+ 0 - 89
netbox/templates/dcim/virtualchassis.html

@@ -1,90 +1 @@
 {% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
-{% load i18n %}
-
-{% block content %}
-<div class="row">
-	<div class="col col-12 col-md-4">
-    <div class="card">
-      <h2 class="card-header">{% trans "Virtual Chassis" %}</h2>
-      <table class="table table-hover attr-table">
-        <tr>
-          <th scope="row">{% trans "Domain" %}</th>
-          <td>{{ object.domain|placeholder }}</td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Master" %}</th>
-          <td>{{ object.master|linkify }}</td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Description" %}</th>
-          <td>{{ object.description|placeholder }}</td>
-        </tr>
-        <tr>
-          <th scope="row">Members</th>
-          <td>
-            {% if object.member_count %}
-              <a href="{% url 'dcim:device_list' %}?virtual_chassis_id={{ object.pk }}">{{ object.member_count }}</a>
-            {% else %}
-              {{ object.member_count }}
-            {% endif %}
-          </td>
-        </tr>
-      </table>
-    </div>
-    {% include 'inc/panels/tags.html' %}
-    {% include 'inc/panels/custom_fields.html' %}
-    {% plugin_left_page object %}
-    </div>
-    <div class="col col-12 col-md-8">
-      <div class="card">
-        <h2 class="card-header">
-          {% trans "Members" %}
-          {% if perms.dcim.change_virtualchassis %}
-            <div class="card-actions">
-              <a href="{% url 'dcim:virtualchassis_add_member' pk=object.pk %}?site={{ object.master.site.pk }}&rack={{ object.master.rack.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-ghost-primary btn-sm">
-                <i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add Member" %}
-              </a>
-            </div>
-          {% endif %}
-        </h2>
-        <table class="table table-hover object-list">
-          <thead>
-            <tr>
-              <th>{% trans "Device" %}</th>
-              <th>{% trans "Position" %}</th>
-              <th>{% trans "Master" %}</th>
-              <th>{% trans "Priority" %}</th>
-            </tr>
-          </thead>
-          {% for vc_member in members %}
-            <tr{% if vc_member == device %} class="info"{% endif %}>
-              <td>
-                {{ vc_member|linkify }}
-              </td>
-              <td>
-                {% badge vc_member.vc_position show_empty=True %}
-              </td>
-              <td>
-                {% if object.master == vc_member %}
-                  {% checkmark True %}
-                {% endif %}
-              </td>
-              <td>
-                {{ vc_member.vc_priority|placeholder }}
-              </td>
-            </tr>
-          {% endfor %}
-        </table>
-      </div>
-      {% include 'inc/panels/comments.html' %}
-      {% plugin_right_page object %}
-	</div>
-</div>
-<div class="row">
-  <div class="col col-md-12">
-    {% plugin_full_width_page object %}
-  </div>
-</div>
-{% endblock %}

+ 0 - 86
netbox/templates/dcim/virtualdevicecontext.html

@@ -1,87 +1 @@
 {% extends 'generic/object.html' %}
-{% load helpers %}
-{% load plugins %}
-{% load render_table from django_tables2 %}
-{% load i18n %}
-
-{% block breadcrumbs %}
-  <li class="breadcrumb-item"><a href="{% url 'dcim:virtualdevicecontext_list' %}">{% trans "Virtual Device Contexts" %}</a></li>
-{% endblock %}
-
-{% block content %}
-<div class="row mb-3">
-	<div class="col col-12 col-md-6">
-    <div class="card">
-      <h2 class="card-header">{% trans "Virtual Device Context" %}</h2>
-      <table class="table table-hover attr-table">
-        <tr>
-          <th scope="row">{% trans "Name" %}</th>
-          <td>{{ object.name }}</td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Device" %}</th>
-          <td>{{ object.device|linkify }}</td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Identifier" %}</th>
-          <td>{{ object.identifier|placeholder }}</td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Primary IPv4" %}</th>
-          <td>
-            {% if object.primary_ip4 %}
-              <a href="{{ object.primary_ip4.get_absolute_url }}" id="primary_ip4">{{ object.primary_ip4 }}</a>
-              {% copy_content "primary_ip4" %}
-           {% else %}
-              <span class="text-muted">—</span>
-           {% endif %}
-          </td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Primary IPv6" %}</th>
-          <td>
-            {% if object.primary_ip6 %}
-              <a href="{{ object.primary_ip6.get_absolute_url }}" id="primary_ip6">{{ object.primary_ip6 }}</a>
-              {% copy_content "primary_ip6" %}
-           {% else %}
-              <span class="text-muted">—</span>
-           {% endif %}
-          </td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Tenant" %}</th>
-          <td>
-            {% if object.tenant.group %}
-              {{ object.tenant.group|linkify }} /
-            {% endif %}
-            {{ object.tenant|linkify|placeholder }}
-          </td>
-        </tr>
-        <tr>
-          <th scope="row">{% trans "Interfaces" %}</th>
-          <td>
-            <a href="{% url 'dcim:interface_list' %}?vdc_id={{ object.pk }}">{{ object.interfaces.count }}</a>
-          </td>
-        </tr>
-      </table>
-    </div>
-    {% plugin_left_page object %}
-    {% include 'inc/panels/tags.html' %}
-  </div>
-  <div class="col col-12 col-md-6">
-    {% include 'inc/panels/related_objects.html' %}
-    {% include 'inc/panels/comments.html' %}
-    {% include 'inc/panels/custom_fields.html' %}
-    {% plugin_right_page object %}
-  </div>
-</div>
-<div class="row mb-3">
-  <div class="col col-md-12">
-    <div class="card">
-      <h2 class="card-header">{% trans "Interfaces" %}</h2>
-      {% htmx_table 'dcim:interface_list' vdc_id=object.pk %}
-    </div>
-    {% plugin_full_width_page object %}
-  </div>
-</div>
-{% endblock %}