diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index eb1d452fa..e1d98a2d4 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -1962,48 +1962,48 @@ class PowerPort(CableTermination, ComponentModel): "Connected endpoint must be a PowerOutlet or PowerFeed, not {}.".format(type(value)) ) - def get_power_stats(self): + def get_power_draw(self): """ - Return utilization statistics for this PowerPort. + Return the allocated and maximum power draw (in VA) and child PowerOutlet count for this PowerPort. """ - feed = self._connected_powerfeed - if not feed or not self.poweroutlets.count(): - return None + # Calculate aggregate draw of all child power outlets if no numbers have been defined manually + if self.allocated_draw is None and self.maximum_draw is None: + outlet_ids = PowerOutlet.objects.filter(power_port=self).values_list('pk', flat=True) + utilization = PowerPort.objects.filter(_connected_poweroutlet_id__in=outlet_ids).aggregate( + maximum_draw_total=Sum('maximum_draw'), + allocated_draw_total=Sum('allocated_draw'), + ) + ret = { + 'allocated': utilization['allocated_draw_total'] or 0, + 'maximum': utilization['maximum_draw_total'] or 0, + 'outlet_count': len(outlet_ids), + 'legs': [], + } - stats = [] - powerfeed_available = self._connected_powerfeed.available_power + # Calculate per-leg aggregates for three-phase feeds + if self._connected_powerfeed and self._connected_powerfeed.phase == POWERFEED_PHASE_3PHASE: + for leg, leg_name in POWERFEED_LEG_CHOICES: + outlet_ids = PowerOutlet.objects.filter(power_port=self, feed_leg=leg).values_list('pk', flat=True) + utilization = PowerPort.objects.filter(_connected_poweroutlet_id__in=outlet_ids).aggregate( + maximum_draw_total=Sum('maximum_draw'), + allocated_draw_total=Sum('allocated_draw'), + ) + ret['legs'].append({ + 'name': leg_name, + 'allocated': utilization['allocated_draw_total'] or 0, + 'maximum': utilization['maximum_draw_total'] or 0, + 'outlet_count': len(outlet_ids), + }) - outlet_ids = PowerOutlet.objects.filter(power_port=self).values_list('pk', flat=True) - utilization = PowerPort.objects.filter(_connected_poweroutlet_id__in=outlet_ids).aggregate( - maximum_draw_total=Sum('maximum_draw'), - allocated_draw_total=Sum('allocated_draw'), - ) - utilization['outlets'] = len(outlet_ids) - utilization['available_power'] = powerfeed_available - allocated_utilization = int( - float(utilization['allocated_draw_total'] or 0) / powerfeed_available * 100 - ) - utilization['allocated_utilization'] = allocated_utilization - stats.append(utilization) + return ret - # Per-leg stats for three-phase feeds - if feed.phase == POWERFEED_PHASE_3PHASE: - for leg, leg_name in POWERFEED_LEG_CHOICES: - outlet_ids = PowerOutlet.objects.filter(power_port=self, feed_leg=leg).values_list('pk', flat=True) - utilization = PowerPort.objects.filter(_connected_poweroutlet_id__in=outlet_ids).aggregate( - maximum_draw_total=Sum('maximum_draw'), - allocated_draw_total=Sum('allocated_draw'), - ) - utilization['name'] = 'Leg {}'.format(leg_name) - utilization['outlets'] = len(outlet_ids) - utilization['available_power'] = round(powerfeed_available / 3) - allocated_utilization = int( - float(utilization['allocated_draw_total'] or 0) / powerfeed_available * 100 - ) - utilization['allocated_utilization'] = allocated_utilization - stats.append(utilization) - - return stats + # Default to administratively defined values + return { + 'allocated': self.allocated_draw or 0, + 'maximum': self.maximum_draw or 0, + 'outlet_count': PowerOutlet.objects.filter(power_port=self).count(), + 'legs': [], + } # @@ -3037,14 +3037,3 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel): def get_status_class(self): return STATUS_CLASSES[self.status] - - def get_power_stats(self): - """ - Return power utilization statistics - """ - power_port = self.connected_endpoint - if not power_port: - # Nothing is connected to the feed so it is not being utilized - return None - - return power_port.get_power_stats() diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index bbd30ac0b..aafb35a0f 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -299,8 +299,16 @@ class RackDetailTable(RackTable): template_code=RACK_DEVICE_COUNT, verbose_name='Devices' ) - get_utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='Utilization') - get_power_utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='Power Utilization') + get_utilization = tables.TemplateColumn( + template_code=UTILIZATION_GRAPH, + orderable=False, + verbose_name='Space' + ) + get_power_utilization = tables.TemplateColumn( + template_code=UTILIZATION_GRAPH, + orderable=False, + verbose_name='Power' + ) class Meta(RackTable.Meta): fields = ( diff --git a/netbox/project-static/css/base.css b/netbox/project-static/css/base.css index e87811bc1..fcee05e12 100644 --- a/netbox/project-static/css/base.css +++ b/netbox/project-static/css/base.css @@ -586,6 +586,7 @@ ul.nav-tabs, ul.nav-pills { /* Fix progress bar margin inside table cells */ td .progress { margin-bottom: 0; + min-width: 100px; } textarea { font-family: Consolas, Lucida Console, monospace; diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 5a0515b06..5f0bbdfc0 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -349,24 +349,36 @@ Input Outlets - Allocated/Max (W) - Available (VA) - Utilization (Allocated) + Allocated + Available + Utilization {% for pp in power_ports %} - {% for leg in pp.get_power_stats %} + {% with utilization=pp.get_power_draw powerfeed=pp.connected_endpoint %} - {% if leg.name %} - {{ leg.name }} + {{ pp }} + {{ utilization.outlet_count }} + {{ utilization.allocated }}VA + {% if powerfeed %} + {{ powerfeed.available_power }}VA + {% utilization_graph utilization.allocated|percentage:powerfeed.available_power %} {% else %} - {{ pp }} + — + — {% endif %} - {{ leg.outlets|placeholder }} - {{ leg.allocated_draw_total }} / {{ leg.maximum_draw_total }} - {{ leg.available_power }} - {% utilization_graph leg.allocated_utilization %} - {% endfor %} + {% for leg in utilization.legs %} + + Leg {{ leg.name }} + {{ leg.outlet_count }} + {{ leg.allocated }} + {{ powerfeed.available_power|divide:3 }}VA + {% with phase_available=powerfeed.available_power|divide:3 %} + {% utilization_graph leg.allocated|percentage:phase_available %} + {% endwith %} + + {% endfor %} + {% endwith %} {% endfor %} diff --git a/netbox/templates/dcim/powerfeed.html b/netbox/templates/dcim/powerfeed.html index c036c1666..8589524c9 100644 --- a/netbox/templates/dcim/powerfeed.html +++ b/netbox/templates/dcim/powerfeed.html @@ -106,6 +106,19 @@ {% endif %} + + Utilization (Allocated) + {% with utilization=powerfeed.connected_endpoint.get_power_draw %} + {% if utilization %} + + {{ utilization.allocated }}VA / {{ powerfeed.available_power }}VA + {% utilization_graph utilization.allocated|percentage:powerfeed.available_power %} + + {% else %} + N/A + {% endif %} + {% endwith %} + @@ -138,30 +151,5 @@ -
-
-
- Power Utilization -
- - - - - - - - {% for leg in powerfeed.get_power_stats %} - - - - - - - {% endfor %} -
OutletsAllocated/Max (W)Available (VA)Utilization (Allocated)
{{ leg.outlets|placeholder }}{{ leg.allocated_draw_total }} / {{ leg.maximum_draw_total }}{{ leg.available_power }}{% utilization_graph leg.allocated_utilization %}
-
-
-
-
{% endblock %} diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index 300b6ee80..60a70c36c 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -223,7 +223,13 @@ {{ powerfeed.get_type_display }} - {% utilization_graph powerfeed.get_power_stats.0.allocated_utilization %} + {% with power_port=powerfeed.connected_endpoint %} + {% if power_port %} + {% utilization_graph power_port.get_power_draw.allocated|percentage:powerfeed.available_power %} + {% else %} + N/A + {% endif %} + {% endwith %} {% endfor %} diff --git a/netbox/utilities/templatetags/helpers.py b/netbox/utilities/templatetags/helpers.py index 2b465d54a..e6616d888 100644 --- a/netbox/utilities/templatetags/helpers.py +++ b/netbox/utilities/templatetags/helpers.py @@ -166,6 +166,26 @@ def fgcolor(value): return '#{}'.format(foreground_color(value)) +@register.filter() +def divide(x, y): + """ + Return x/y (rounded). + """ + if x is None or y is None: + return None + return round(x / y) + + +@register.filter() +def percentage(x, y): + """ + Return x/y as a percentage. + """ + if x is None or y is None: + return None + return round(x / y * 100) + + # # Tags #