Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
fd3a9a0
Initial work on #20204
jeremystretch Oct 29, 2025
3890043
Change approach for declaring object panels
jeremystretch Oct 30, 2025
d4783b7
Refactor
jeremystretch Oct 30, 2025
7d993cc
WIP
jeremystretch Oct 30, 2025
1acd567
Add site panel
jeremystretch Oct 30, 2025
83de784
Add region & site group panels
jeremystretch Oct 30, 2025
2a629d6
Enable panel inheritance; add location panel
jeremystretch Oct 30, 2025
90874ad
Add rack panel
jeremystretch Oct 30, 2025
eef9db5
Cleanup
jeremystretch Oct 31, 2025
3fd4664
Implement layout declaration under view
jeremystretch Oct 31, 2025
77613b3
Add panels for common inclusion templates
jeremystretch Oct 31, 2025
4d5f8e9
Add PluginContentPanel
jeremystretch Oct 31, 2025
e9b1543
Add EmbeddedTablePanel
jeremystretch Oct 31, 2025
da68503
Remove panels from get_extra_context()
jeremystretch Oct 31, 2025
37bea1e
Introduce panel actions
jeremystretch Nov 3, 2025
c392988
Replace EmbeddedTablePanel with ObjectsTablePanel
jeremystretch Nov 3, 2025
21bb734
Define layouts for regions, site groups, locations
jeremystretch Nov 3, 2025
17cffd7
Add rack role & type layouts
jeremystretch Nov 3, 2025
ed3dd01
Move some panels to extras
jeremystretch Nov 3, 2025
1cffbb2
Restore original object templates
jeremystretch Nov 3, 2025
40b114c
Add rack layout
jeremystretch Nov 3, 2025
17429c4
Clean up obsolete code
jeremystretch Nov 3, 2025
c05106f
Limit object assignment to object panels
jeremystretch Nov 3, 2025
59899d0
Lots of cleanup
jeremystretch Nov 4, 2025
d5cec37
Introduce SimpleLayout
jeremystretch Nov 4, 2025
1de41b4
Add layouts for DeviceType & ModuleTypeProfile
jeremystretch Nov 5, 2025
838794a
Derive attribute labels from name if not passed for instance
jeremystretch Nov 5, 2025
281cb4f
Split ObjectPanel into a base class and ObjectAttrsPanel; use base cl…
jeremystretch Nov 5, 2025
9d6522c
RackType has no airflow attribute
jeremystretch Nov 5, 2025
dfb08ff
Split PanelAction into a base class and LinkAction; CopyContent shoul…
jeremystretch Nov 5, 2025
4edaa48
Refactor render() on Attr to split out context and reduce boilerplate
jeremystretch Nov 5, 2025
1d2aef7
Hide custom fields panels if no custom fields exist on the model
jeremystretch Nov 5, 2025
e9777d3
Flesh out device layout
jeremystretch Nov 5, 2025
60cc009
Move templates for extras panels
jeremystretch Nov 6, 2025
e55a4ae
Finish layout for device view
jeremystretch Nov 6, 2025
6fc04bd
Fix accessor
jeremystretch Nov 6, 2025
a024012
Misc cleanup
jeremystretch Nov 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added netbox/dcim/ui/__init__.py
Empty file.
179 changes: 179 additions & 0 deletions netbox/dcim/ui/panels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
from django.utils.translation import gettext_lazy as _

from netbox.ui import attrs, panels


class SitePanel(panels.ObjectAttributesPanel):
region = attrs.NestedObjectAttr('region', linkify=True)
group = attrs.NestedObjectAttr('group', linkify=True)
status = attrs.ChoiceAttr('status')
tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
facility = attrs.TextAttr('facility')
description = attrs.TextAttr('description')
timezone = attrs.TimezoneAttr('time_zone')
physical_address = attrs.AddressAttr('physical_address', map_url=True)
shipping_address = attrs.AddressAttr('shipping_address', map_url=True)
gps_coordinates = attrs.GPSCoordinatesAttr()


class LocationPanel(panels.NestedGroupObjectPanel):
site = attrs.RelatedObjectAttr('site', linkify=True, grouped_by='group')
status = attrs.ChoiceAttr('status')
tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
facility = attrs.TextAttr('facility')


class RackDimensionsPanel(panels.ObjectAttributesPanel):
form_factor = attrs.ChoiceAttr('form_factor')
width = attrs.ChoiceAttr('width')
height = attrs.TextAttr('u_height', format_string='{}U', label=_('Height'))
outer_width = attrs.NumericAttr('outer_width', unit_accessor='get_outer_unit_display')
outer_height = attrs.NumericAttr('outer_height', unit_accessor='get_outer_unit_display')
outer_depth = attrs.NumericAttr('outer_depth', unit_accessor='get_outer_unit_display')
mounting_depth = attrs.TextAttr('mounting_depth', format_string='{}mm')


class RackNumberingPanel(panels.ObjectAttributesPanel):
starting_unit = attrs.TextAttr('starting_unit')
desc_units = attrs.BooleanAttr('desc_units', label=_('Descending units'))


class RackPanel(panels.ObjectAttributesPanel):
region = attrs.NestedObjectAttr('site.region', linkify=True)
site = attrs.RelatedObjectAttr('site', linkify=True, grouped_by='group')
location = attrs.NestedObjectAttr('location', linkify=True)
facility = attrs.TextAttr('facility')
tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
status = attrs.ChoiceAttr('status')
rack_type = attrs.RelatedObjectAttr('rack_type', linkify=True, grouped_by='manufacturer')
role = attrs.RelatedObjectAttr('role', linkify=True)
description = attrs.TextAttr('description')
serial = attrs.TextAttr('serial', label=_('Serial number'), style='font-monospace', copy_button=True)
asset_tag = attrs.TextAttr('asset_tag', style='font-monospace', copy_button=True)
airflow = attrs.ChoiceAttr('airflow')
space_utilization = attrs.UtilizationAttr('get_utilization')
power_utilization = attrs.UtilizationAttr('get_power_utilization')


class RackWeightPanel(panels.ObjectAttributesPanel):
weight = attrs.NumericAttr('weight', unit_accessor='get_weight_unit_display')
max_weight = attrs.NumericAttr('max_weight', unit_accessor='get_weight_unit_display', label=_('Maximum weight'))
total_weight = attrs.NumericAttr('total_weight', unit_accessor='get_weight_unit_display')


class RackRolePanel(panels.OrganizationalObjectPanel):
color = attrs.ColorAttr('color')


class RackTypePanel(panels.ObjectAttributesPanel):
manufacturer = attrs.RelatedObjectAttr('manufacturer', linkify=True)
model = attrs.TextAttr('model')
description = attrs.TextAttr('description')


class DevicePanel(panels.ObjectAttributesPanel):
region = attrs.NestedObjectAttr('site.region', linkify=True)
site = attrs.RelatedObjectAttr('site', linkify=True, grouped_by='group')
location = attrs.NestedObjectAttr('location', linkify=True)
rack = attrs.TemplatedAttr('rack', template_name='dcim/device/attrs/rack.html')
virtual_chassis = attrs.RelatedObjectAttr('virtual_chassis', linkify=True)
parent_device = attrs.TemplatedAttr('parent_bay', template_name='dcim/device/attrs/parent_device.html')
gps_coordinates = attrs.GPSCoordinatesAttr()
tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
device_type = attrs.RelatedObjectAttr('device_type', linkify=True, grouped_by='manufacturer')
description = attrs.TextAttr('description')
airflow = attrs.ChoiceAttr('airflow')
serial = attrs.TextAttr('serial', label=_('Serial number'), style='font-monospace', copy_button=True)
asset_tag = attrs.TextAttr('asset_tag', style='font-monospace', copy_button=True)
config_template = attrs.RelatedObjectAttr('config_template', linkify=True)


class DeviceManagementPanel(panels.ObjectAttributesPanel):
title = _('Management')

status = attrs.ChoiceAttr('status')
role = attrs.NestedObjectAttr('role', linkify=True, max_depth=3)
platform = attrs.NestedObjectAttr('platform', linkify=True, max_depth=3)
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',
)
oob_ip = attrs.TemplatedAttr(
'oob_ip',
label=_('Out-of-band IP'),
template_name='dcim/device/attrs/ipaddress.html',
)
cluster = attrs.RelatedObjectAttr('cluster', linkify=True)


class DeviceDimensionsPanel(panels.ObjectAttributesPanel):
title = _('Dimensions')

height = attrs.TextAttr('device_type.u_height', format_string='{}U')
total_weight = attrs.TemplatedAttr('total_weight', template_name='dcim/device/attrs/total_weight.html')


class DeviceTypePanel(panels.ObjectAttributesPanel):
manufacturer = attrs.RelatedObjectAttr('manufacturer', linkify=True)
model = attrs.TextAttr('model')
part_number = attrs.TextAttr('part_number')
default_platform = attrs.RelatedObjectAttr('default_platform', linkify=True)
description = attrs.TextAttr('description')
height = attrs.TextAttr('u_height', format_string='{}U', label=_('Height'))
exclude_from_utilization = attrs.BooleanAttr('exclude_from_utilization')
full_depth = attrs.BooleanAttr('is_full_depth')
weight = attrs.NumericAttr('weight', unit_accessor='get_weight_unit_display')
subdevice_role = attrs.ChoiceAttr('subdevice_role', label=_('Parent/child'))
airflow = attrs.ChoiceAttr('airflow')
front_image = attrs.ImageAttr('front_image')
rear_image = attrs.ImageAttr('rear_image')


class ModuleTypeProfilePanel(panels.ObjectAttributesPanel):
name = attrs.TextAttr('name')
description = attrs.TextAttr('description')


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')

def get_context(self, context):
return {
**super().get_context(context),
'vc_members': context.get('vc_members'),
}

def render(self, context):
if not context.get('vc_members'):
return ''
return super().render(context)


class PowerUtilizationPanel(panels.ObjectPanel):
"""
A panel which displays the power utilization statistics for a device.
"""
template_name = 'dcim/panels/power_utilization.html'
title = _('Power Utilization')

def get_context(self, context):
return {
**super().get_context(context),
'vc_members': context.get('vc_members'),
}

def render(self, context):
obj = context['object']
if not obj.powerports.exists() or not obj.poweroutlets.exists():
return ''
return super().render(context)
Loading