diff --git a/netbox/dcim/ui/panels.py b/netbox/dcim/ui/panels.py index ffa85e90d..e4845bfc0 100644 --- a/netbox/dcim/ui/panels.py +++ b/netbox/dcim/ui/panels.py @@ -26,7 +26,7 @@ class LocationPanel(panels.NestedGroupObjectPanel): class RackDimensionsPanel(panels.ObjectAttributesPanel): form_factor = attrs.ChoiceAttr('form_factor') width = attrs.ChoiceAttr('width') - u_height = attrs.TextAttr('u_height', format_string='{}U', label=_('Height')) + 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') @@ -76,7 +76,7 @@ class DevicePanel(panels.ObjectAttributesPanel): site = attrs.ObjectAttr('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.NestedObjectAttr('virtual_chassis', linkify=True) + virtual_chassis = attrs.ObjectAttr('virtual_chassis', linkify=True) parent_device = attrs.TemplatedAttr('parent_bay', template_name='dcim/device/attrs/parent_device.html') gps_coordinates = attrs.GPSCoordinatesAttr() tenant = attrs.ObjectAttr('tenant', linkify=True, grouped_by='group') @@ -107,6 +107,12 @@ class DeviceManagementPanel(panels.ObjectAttributesPanel): label=_('Out-of-band IP'), template_name='dcim/device/attrs/ipaddress.html', ) + cluster = attrs.ObjectAttr('cluster', linkify=True) + + +class DeviceDimensionsPanel(panels.ObjectAttributesPanel): + 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): @@ -115,7 +121,7 @@ class DeviceTypePanel(panels.ObjectAttributesPanel): part_number = attrs.TextAttr('part_number') default_platform = attrs.ObjectAttr('default_platform', linkify=True) description = attrs.TextAttr('description') - u_height = attrs.TextAttr('u_height', format_string='{}U', label=_('Height')) + 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') @@ -128,3 +134,23 @@ class DeviceTypePanel(panels.ObjectAttributesPanel): 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 the context data to be used when rendering the panel. + + Parameters: + context: The template context + """ + return { + **super().get_context(context), + 'vc_members': context.get('vc_members'), + } diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 051d2867d..9f7b3f06a 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -2439,6 +2439,44 @@ class DeviceListView(generic.ObjectListView): @register_model_view(Device) class DeviceView(generic.ObjectView): queryset = Device.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.DevicePanel(), + panels.VirtualChassisMembersPanel(), + CustomFieldsPanel(), + TagsPanel(), + CommentsPanel(), + ObjectsTablePanel( + model='dcim.VirtualDeviceContext', + filters={'device_id': lambda ctx: ctx['object'].pk}, + actions=[ + actions.AddObject('dcim.VirtualDeviceContext', url_params={'device': lambda ctx: ctx['object'].pk}), + ], + ), + ], + right_panels=[ + panels.DeviceManagementPanel(), + # TODO: Power utilization + ObjectsTablePanel( + model='ipam.Service', + title=_('Application Services'), + filters={'device_id': lambda ctx: ctx['object'].pk}, + actions=[ + actions.AddObject( + 'ipam.Service', + url_params={ + 'parent_object_type': lambda ctx: ContentType.objects.get_for_model(ctx['object']).pk, + 'parent': lambda ctx: ctx['object'].pk + } + ), + ], + ), + ImageAttachmentsPanel(), + panels.DeviceDimensionsPanel(title=_('Dimensions')), + # TODO: Rack elevations + # TemplatePanel('dcim/panels/rack_elevations.html'), + ], + ) def get_extra_context(self, request, instance): # VirtualChassis members diff --git a/netbox/templates/dcim/device/attrs/total_weight.html b/netbox/templates/dcim/device/attrs/total_weight.html new file mode 100644 index 000000000..73ac54ef5 --- /dev/null +++ b/netbox/templates/dcim/device/attrs/total_weight.html @@ -0,0 +1,3 @@ +{% load helpers i18n %} +{{ value|floatformat }} {% trans "Kilograms" %} +({{ value|kg_to_pounds|floatformat }} {% trans "Pounds" %}) diff --git a/netbox/templates/dcim/panels/virtual_chassis_members.html b/netbox/templates/dcim/panels/virtual_chassis_members.html new file mode 100644 index 000000000..29e422ea6 --- /dev/null +++ b/netbox/templates/dcim/panels/virtual_chassis_members.html @@ -0,0 +1,31 @@ +{% extends "ui/panels/_base.html" %} +{% load i18n %} + +{% block panel_content %} + + + + + + + + + + + {% for vc_member in vc_members %} + + + + + + + {% endfor %} + +
{% trans "Device" %}{% trans "Position" %}{% trans "Master" %}{% trans "Priority" %}
{{ vc_member|linkify }}{% badge vc_member.vc_position show_empty=True %} + {% if object.virtual_chassis.master == vc_member %} + {% checkmark True %} + {% else %} + {{ ''|placeholder }} + {% endif %} + {{ vc_member.vc_priority|placeholder }}
+{% endblock panel_content %}