Jeremy Stretch 3 месяцев назад
Родитель
Сommit
e9777d3193

+ 29 - 3
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'),
+        }

+ 38 - 0
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

+ 3 - 0
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" %})

+ 31 - 0
netbox/templates/dcim/panels/virtual_chassis_members.html

@@ -0,0 +1,31 @@
+{% extends "ui/panels/_base.html" %}
+{% load i18n %}
+
+{% block panel_content %}
+  <table class="table table-hover attr-table">
+    <thead>
+      <tr class="border-bottom">
+        <th>{% trans "Device" %}</th>
+        <th>{% trans "Position" %}</th>
+        <th>{% trans "Master" %}</th>
+        <th>{% trans "Priority" %}</th>
+      </tr>
+    </thead>
+    <tbody>
+      {% for vc_member in vc_members %}
+        <tr{% if vc_member == object %} class="table-primary"{% endif %}>
+          <td>{{ vc_member|linkify }}</td>
+          <td>{% badge vc_member.vc_position show_empty=True %}</td>
+          <td>
+            {% if object.virtual_chassis.master == vc_member %}
+              {% checkmark True %}
+            {% else %}
+              {{ ''|placeholder }}
+            {% endif %}
+          </td>
+          <td>{{ vc_member.vc_priority|placeholder }}</td>
+        </tr>
+      {% endfor %}
+    </tbody>
+  </table>
+{% endblock panel_content %}