diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index fcf3252cf..d1f9f59f2 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -9,7 +9,7 @@ from django.contrib.postgres.fields import ArrayField, JSONField from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models -from django.db.models import Count, Q +from django.db.models import Count, Q, Sum from django.urls import reverse from mptt.models import MPTTModel, TreeForeignKey from taggit.managers import TaggableManager @@ -1938,6 +1938,41 @@ class PowerPort(CableTermination, ComponentModel): "Connected endpoint must be a PowerOutlet or PowerFeed, not {}.".format(type(value)) ) + def get_power_stats(self): + """ + Return power utilization statistics + """ + feed = self._connected_powerfeed + if not feed or not self.poweroutlets.count(): + return None + + stats = [] + powerfeed_available = self._connected_powerfeed.available_power + + 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=Sum('maximum_draw'), + allocated_draw=Sum('allocated_draw'), + ) + utilization['outlets'] = len(outlet_ids) + utilization['available_power'] = powerfeed_available + stats.append(utilization) + + # 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=Sum('maximum_draw'), + allocated_draw=Sum('allocated_draw'), + ) + utilization['name'] = 'Leg {}'.format(leg_name) + utilization['outlets'] = len(outlet_ids) + utilization['available_power'] = powerfeed_available / 3 + stats.append(utilization) + + return stats + # # Power outlets @@ -2923,3 +2958,10 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel): def get_status_class(self): return STATUS_CLASSES[self.status] + + @property + def available_power(self): + kva = self.voltage * self.amperage * self.max_utilization + if self.phase == POWERFEED_PHASE_3PHASE: + return kva * 1.732 + return kva diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 34811c4c0..b9f283837 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -332,6 +332,49 @@ {% endif %} {% endif %} + {% if power_ports and poweroutlets %} +
+
+ Power Utilization +
+ + + + + + + + {% for pp in power_ports %} + {% for leg in pp.get_power_stats %} + + {% if leg.name %} + + {% else %} + + {% endif %} + + + + + {% endfor %} + {% endfor %} +
InputOutletsAllocated/Max (W)Available (VA)
{{ leg.name }}{{ pp }}{{ leg.outlets|placeholder }}{{ leg.allocated_draw }} / {{ leg.maximum_draw }}{{ leg.available_power }}
+ {% if perms.dcim.add_interface or perms.dcim.add_consoleport or perms.dcim.add_powerport %} + + {% endif %} +
+ {% endif %} {% if request.user.is_authenticated %}