Răsfoiți Sursa

Add rack role & type layouts

Jeremy Stretch 3 luni în urmă
părinte
comite
17cffd7860

+ 31 - 0
netbox/dcim/ui/panels.py

@@ -23,6 +23,26 @@ class LocationPanel(panels.NestedGroupObjectPanel):
     facility = attrs.TextAttr('facility', label=_('Facility'))
 
 
+class RackDimensionsPanel(panels.ObjectPanel):
+    form_factor = attrs.ChoiceAttr('form_factor', label=_('Form factor'))
+    width = attrs.ChoiceAttr('width', label=_('Width'))
+    u_height = attrs.TextAttr('u_height', format_string='{}U', label=_('Height'))
+    outer_width = attrs.NumericAttr('outer_width', unit_accessor='get_outer_unit_display', label=_('Outer width'))
+    outer_height = attrs.NumericAttr('outer_height', unit_accessor='get_outer_unit_display', label=_('Outer height'))
+    outer_depth = attrs.NumericAttr('outer_depth', unit_accessor='get_outer_unit_display', label=_('Outer depth'))
+    mounting_depth = attrs.TextAttr('mounting_depth', format_string='{}mm', label=_('Mounting depth'))
+
+
+class RackNumberingPanel(panels.ObjectPanel):
+    starting_unit = attrs.TextAttr('starting_unit', label=_('Starting unit'))
+    desc_units = attrs.BooleanAttr('desc_units', label=_('Descending units'))
+
+
+class RackWeightPanel(panels.ObjectPanel):
+    weight = attrs.NumericAttr('weight', unit_accessor='get_weight_unit_display', label=_('Weight'))
+    max_weight = attrs.NumericAttr('max_weight', unit_accessor='get_weight_unit_display', label=_('Maximum weight'))
+
+
 class RackPanel(panels.ObjectPanel):
     region = attrs.NestedObjectAttr('site.region', label=_('Region'), linkify=True)
     site = attrs.ObjectAttr('site', label=_('Site'), linkify=True, grouped_by='group')
@@ -40,6 +60,17 @@ class RackPanel(panels.ObjectPanel):
     power_utilization = attrs.UtilizationAttr('get_power_utilization', label=_('Power utilization'))
 
 
+class RackRolePanel(panels.OrganizationalObjectPanel):
+    color = attrs.ColorAttr('color')
+
+
+class RackTypePanel(panels.ObjectPanel):
+    manufacturer = attrs.ObjectAttr('manufacturer', label=_('Manufacturer'), linkify=True)
+    model = attrs.TextAttr('model', label=_('Model'))
+    description = attrs.TextAttr('description', label=_('Description'))
+    airflow = attrs.ChoiceAttr('airflow', label=_('Airflow'))
+
+
 class DevicePanel(panels.ObjectPanel):
     region = attrs.NestedObjectAttr('site.region', label=_('Region'), linkify=True)
     site = attrs.ObjectAttr('site', label=_('Site'), linkify=True, grouped_by='group')

+ 43 - 1
netbox/dcim/views.py

@@ -527,7 +527,7 @@ class SiteView(GetRelatedModelsMixin, generic.ObjectView):
     layout = layout.Layout(
         layout.Row(
             layout.Column(
-                panels.SitePanel(_('Site')),
+                panels.SitePanel(),
                 CustomFieldsPanel(),
                 TagsPanel(),
                 CommentsPanel(),
@@ -817,6 +817,25 @@ class RackRoleListView(generic.ObjectListView):
 @register_model_view(RackRole)
 class RackRoleView(GetRelatedModelsMixin, generic.ObjectView):
     queryset = RackRole.objects.all()
+    layout = layout.Layout(
+        layout.Row(
+            layout.Column(
+                panels.RackRolePanel(),
+                TagsPanel(),
+                PluginContentPanel('left_page'),
+            ),
+            layout.Column(
+                RelatedObjectsPanel(),
+                CustomFieldsPanel(),
+                PluginContentPanel('right_page'),
+            ),
+        ),
+        layout.Row(
+            layout.Column(
+                PluginContentPanel('full_width_page'),
+            ),
+        ),
+    )
 
     def get_extra_context(self, request, instance):
         return {
@@ -884,6 +903,29 @@ class RackTypeListView(generic.ObjectListView):
 @register_model_view(RackType)
 class RackTypeView(GetRelatedModelsMixin, generic.ObjectView):
     queryset = RackType.objects.all()
+    layout = layout.Layout(
+        layout.Row(
+            layout.Column(
+                panels.RackTypePanel(),
+                panels.RackDimensionsPanel(_('Dimensions')),
+                TagsPanel(),
+                CommentsPanel(),
+                PluginContentPanel('left_page'),
+            ),
+            layout.Column(
+                panels.RackNumberingPanel(_('Numbering')),
+                panels.RackWeightPanel(_('Weight')),
+                CustomFieldsPanel(),
+                RelatedObjectsPanel(),
+                PluginContentPanel('right_page'),
+            ),
+        ),
+        layout.Row(
+            layout.Column(
+                PluginContentPanel('full_width_page'),
+            ),
+        ),
+    )
 
     def get_extra_context(self, request, instance):
         return {

+ 61 - 3
netbox/netbox/ui/attrs.py

@@ -13,12 +13,14 @@ from netbox.config import get_config
 
 class Attr(ABC):
     template_name = None
+    label = None
     placeholder = mark_safe('<span class="text-muted">&mdash;</span>')
 
     def __init__(self, accessor, label=None, template_name=None):
         self.accessor = accessor
-        self.label = label
         self.template_name = template_name or self.template_name
+        if label is not None:
+            self.label = label
 
     @abstractmethod
     def render(self, obj, context=None):
@@ -37,9 +39,10 @@ class Attr(ABC):
 class TextAttr(Attr):
     template_name = 'ui/attrs/text.html'
 
-    def __init__(self, *args, style=None, copy_button=False, **kwargs):
+    def __init__(self, *args, style=None, format_string=None, copy_button=False, **kwargs):
         super().__init__(*args, **kwargs)
         self.style = style
+        self.format_string = format_string
         self.copy_button = copy_button
 
     def render(self, obj, context=None):
@@ -47,6 +50,8 @@ class TextAttr(Attr):
         value = self._resolve_attr(obj, self.accessor)
         if value in (None, ''):
             return self.placeholder
+        if self.format_string:
+            value = self.format_string.format(value)
         return render_to_string(self.template_name, {
             **context,
             'value': value,
@@ -55,6 +60,28 @@ class TextAttr(Attr):
         })
 
 
+class NumericAttr(Attr):
+    template_name = 'ui/attrs/numeric.html'
+
+    def __init__(self, *args, unit_accessor=None, copy_button=False, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.unit_accessor = unit_accessor
+        self.copy_button = copy_button
+
+    def render(self, obj, context=None):
+        context = context or {}
+        value = self._resolve_attr(obj, self.accessor)
+        if value in (None, ''):
+            return self.placeholder
+        unit = self._resolve_attr(obj, self.unit_accessor) if self.unit_accessor else None
+        return render_to_string(self.template_name, {
+            **context,
+            'value': value,
+            'unit': unit,
+            'copy_button': self.copy_button,
+        })
+
+
 class ChoiceAttr(Attr):
     template_name = 'ui/attrs/choice.html'
 
@@ -77,6 +104,37 @@ class ChoiceAttr(Attr):
         })
 
 
+class BooleanAttr(Attr):
+    template_name = 'ui/attrs/boolean.html'
+
+    def __init__(self, *args, display_false=True, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.display_false = display_false
+
+    def render(self, obj, context=None):
+        context = context or {}
+        value = self._resolve_attr(obj, self.accessor)
+        if value in (None, '') and not self.display_false:
+            return self.placeholder
+        return render_to_string(self.template_name, {
+            **context,
+            'value': value,
+        })
+
+
+class ColorAttr(Attr):
+    template_name = 'ui/attrs/color.html'
+    label = _('Color')
+
+    def render(self, obj, context=None):
+        context = context or {}
+        value = self._resolve_attr(obj, self.accessor)
+        return render_to_string(self.template_name, {
+            **context,
+            'color': value,
+        })
+
+
 class ObjectAttr(Attr):
     template_name = 'ui/attrs/object.html'
 
@@ -149,9 +207,9 @@ class AddressAttr(Attr):
 
 class GPSCoordinatesAttr(Attr):
     template_name = 'ui/attrs/gps_coordinates.html'
+    label = _('GPS Coordinates')
 
     def __init__(self, latitude_attr='latitude', longitude_attr='longitude', map_url=True, **kwargs):
-        kwargs.setdefault('label', _('GPS Coordinates'))
         super().__init__(accessor=None, **kwargs)
         self.latitude_attr = latitude_attr
         self.longitude_attr = longitude_attr

+ 6 - 2
netbox/netbox/ui/panels.py

@@ -19,6 +19,7 @@ __all__ = (
     'NestedGroupObjectPanel',
     'ObjectPanel',
     'ObjectsTablePanel',
+    'OrganizationalObjectPanel',
     'RelatedObjectsPanel',
     'Panel',
     'PluginContentPanel',
@@ -45,7 +46,7 @@ class Panel(ABC):
         return render_to_string(self.template_name, {
             'request': context.get('request'),
             'object': obj,
-            'title': self.title,
+            'title': self.title or title(obj._meta.verbose_name),
             'actions': [action.get_context(obj) for action in self.actions],
             **self.get_context(obj),
         })
@@ -93,9 +94,12 @@ class ObjectPanel(Panel, metaclass=ObjectPanelMeta):
         }
 
 
-class NestedGroupObjectPanel(ObjectPanel, metaclass=ObjectPanelMeta):
+class OrganizationalObjectPanel(ObjectPanel, metaclass=ObjectPanelMeta):
     name = attrs.TextAttr('name', label=_('Name'))
     description = attrs.TextAttr('description', label=_('Description'))
+
+
+class NestedGroupObjectPanel(OrganizationalObjectPanel, metaclass=ObjectPanelMeta):
     parent = attrs.NestedObjectAttr('parent', label=_('Parent'), linkify=True)
 
 

+ 1 - 0
netbox/templates/ui/attrs/boolean.html

@@ -0,0 +1 @@
+{% checkmark object.desc_units %}

+ 1 - 0
netbox/templates/ui/attrs/color.html

@@ -0,0 +1 @@
+<span class="badge color-label" style="background-color: #{{ color }}">&nbsp;</span>

+ 12 - 0
netbox/templates/ui/attrs/numeric.html

@@ -0,0 +1,12 @@
+{% load i18n %}
+<span{% if style %} class="{{ style }}"{% endif %}>
+  <span{% if name %} id="attr_{{ name }}"{% endif %}>{{ value }}</span>
+  {% if unit %}
+    {{ unit|lower }}
+  {% endif %}
+</span>
+{% if copy_button %}
+  <a class="btn btn-sm btn-primary copy-content" data-clipboard-target="#attr_{{ name }}" title="{% trans "Copy to clipboard" %}">
+    <i class="mdi mdi-content-copy"></i>
+  </a>
+{% endif %}