Initial work on power modeling (WIP)

This commit is contained in:
Jeremy Stretch 2019-03-11 22:40:52 -04:00
parent 0b95016e00
commit 5b753923b6
12 changed files with 804 additions and 7 deletions

View File

@ -443,3 +443,33 @@ RACK_DIMENSION_UNIT_CHOICES = (
(LENGTH_UNIT_MILLIMETER, 'Millimeters'),
(LENGTH_UNIT_INCH, 'Inches'),
)
# Power feeds
POWERFEED_TYPE_PRIMARY = 1
POWERFEED_TYPE_REDUNDANT = 2
POWERFEED_TYPE_CHOICES = (
(POWERFEED_TYPE_PRIMARY, 'AC'),
(POWERFEED_TYPE_REDUNDANT, 'DC'),
)
POWERFEED_SUPPLY_AC = 1
POWERFEED_SUPPLY_DC = 2
POWERFEED_SUPPLY_CHOICES = (
(POWERFEED_SUPPLY_AC, 'AC'),
(POWERFEED_SUPPLY_DC, 'DC'),
)
POWERFEED_PHASE_SINGLE = 1
POWERFEED_PHASE_3PHASE = 3
POWERFEED_PHASE_CHOICES = (
(POWERFEED_PHASE_SINGLE, 'Single phase'),
(POWERFEED_PHASE_3PHASE, 'Three-phase'),
)
POWERFEED_STATUS_OFFLINE = 0
POWERFEED_STATUS_ACTIVE = 1
POWERFEED_STATUS_PLANNED = 2
POWERFEED_STATUS_FAILED = 4
POWERFEED_STATUS_CHOICES = (
(POWERFEED_STATUS_ACTIVE, 'Active'),
(POWERFEED_STATUS_OFFLINE, 'Offline'),
(POWERFEED_STATUS_PLANNED, 'Planned'),
(POWERFEED_STATUS_FAILED, 'Failed'),
)

View File

@ -26,8 +26,8 @@ from .constants import *
from .models import (
Cable, DeviceBay, DeviceBayTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate,
Device, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, Manufacturer,
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis
InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort, PowerPortTemplate,
Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
)
DEVICE_BY_PK_RE = r'{\d+\}'
@ -3156,3 +3156,169 @@ class VirtualChassisFilterForm(BootstrapMixin, CustomFieldFilterForm):
to_field_name='slug',
null_label='-- None --',
)
#
# Power panels
#
class PowerPanelForm(BootstrapMixin, forms.ModelForm):
rackgroup = ChainedModelChoiceField(
queryset=RackGroup.objects.all(),
chains=(
('site', 'site'),
),
required=False,
widget=APISelect(
api_url='/api/dcim/rack-groups/',
)
)
class Meta:
model = PowerPanel
fields = [
'site', 'rackgroup', 'name',
]
widgets = {
'site': APISelect(
api_url="/api/dcim/sites/",
filter_for={
'rackgroup': 'site_id',
}
),
}
class PowerPanelCSVForm(forms.ModelForm):
site = forms.ModelChoiceField(
queryset=Site.objects.all(),
to_field_name='name',
help_text='Name of parent site',
error_messages={
'invalid_choice': 'Site not found.',
}
)
group_name = forms.CharField(
help_text='Name of rack group',
required=False
)
class Meta:
model = PowerPanel
fields = PowerPanel.csv_headers
#
# Power feeds
#
class PowerFeedForm(BootstrapMixin, CustomFieldForm):
tags = TagField(
required=False
)
class Meta:
model = PowerFeed
fields = [
'powerpanel', 'rack', 'name', 'type', 'status', 'supply', 'voltage', 'amperage', 'phase', 'max_utilization',
'comments', 'tags',
]
widgets = {
'site': APISelect(
api_url="/api/dcim/sites/",
filter_for={
'rackgroup': 'site_id',
}
),
'type': StaticSelect2(),
'status': StaticSelect2(),
'supply': StaticSelect2(),
'phase': StaticSelect2(),
}
class PowerFeedCSVForm(forms.ModelForm):
type = CSVChoiceField(
choices=POWERFEED_TYPE_CHOICES,
required=False,
help_text='Primary or redundant'
)
status = CSVChoiceField(
choices=POWERFEED_STATUS_CHOICES,
required=False,
help_text='Operational status'
)
supply = CSVChoiceField(
choices=POWERFEED_SUPPLY_CHOICES,
required=False,
help_text='AC/DC'
)
class Meta:
model = PowerFeed
fields = PowerFeed.csv_headers
class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=PowerFeed.objects.all(),
widget=forms.MultipleHiddenInput
)
powerpanel = forms.ModelChoiceField(
queryset=PowerPanel.objects.all(),
required=False,
widget=APISelect(
api_url="/api/dcim/sites",
filter_for={
'rackgroup': 'site_id',
}
)
)
rackgroup = forms.ModelChoiceField(
queryset=RackGroup.objects.all(),
required=False,
widget=APISelect(
api_url="/api/dcim/rack-groups",
)
)
type = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_TYPE_CHOICES),
required=False,
initial='',
widget=StaticSelect2()
)
status = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_STATUS_CHOICES),
required=False,
initial='',
widget=StaticSelect2()
)
supply = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_SUPPLY_CHOICES),
required=False,
initial='',
widget=StaticSelect2()
)
voltage = forms.IntegerField(
required=False
)
amperage = forms.IntegerField(
required=False
)
phase = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_PHASE_CHOICES),
required=False,
initial='',
widget=StaticSelect2()
)
max_utilization = forms.IntegerField(
required=False
)
comments = forms.CharField(
required=False
)
class Meta:
nullable_fields = [
'rackgroup', 'comments',
]

View File

@ -0,0 +1,74 @@
# Generated by Django 2.1.7 on 2019-03-12 02:29
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import taggit.managers
class Migration(migrations.Migration):
dependencies = [
('extras', '0020_add_color_comments_changelog_to_tag'),
('dcim', '0071_device_components_add_description'),
]
operations = [
migrations.CreateModel(
name='PowerFeed',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('name', models.CharField(max_length=50)),
('type', models.PositiveSmallIntegerField(default=1)),
('status', models.PositiveSmallIntegerField(default=1)),
('supply', models.PositiveSmallIntegerField(default=1)),
('voltage', models.PositiveSmallIntegerField(default=120, validators=[django.core.validators.MinValueValidator(1)])),
('amperage', models.PositiveSmallIntegerField(default=20, validators=[django.core.validators.MinValueValidator(1)])),
('phase', models.PositiveSmallIntegerField(default=1)),
('max_utilization', models.PositiveSmallIntegerField(default=80, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)])),
('comments', models.TextField(blank=True)),
],
options={
'ordering': ['powerpanel', 'name'],
},
),
migrations.CreateModel(
name='PowerPanel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('name', models.CharField(max_length=50)),
('rackgroup', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dcim.RackGroup')),
('site', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='dcim.Site')),
],
options={
'ordering': ['site', 'name'],
},
),
migrations.AddField(
model_name='powerfeed',
name='powerpanel',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='powerfeeds', to='dcim.PowerPanel'),
),
migrations.AddField(
model_name='powerfeed',
name='rack',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dcim.Rack'),
),
migrations.AddField(
model_name='powerfeed',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterUniqueTogether(
name='powerpanel',
unique_together={('site', 'name')},
),
migrations.AlterUniqueTogether(
name='powerfeed',
unique_together={('powerpanel', 'name')},
),
]

View File

@ -2668,3 +2668,134 @@ class Cable(ChangeLoggedModel):
b_endpoint = b_path[-1][2]
return a_endpoint, b_endpoint, path_status
#
# Power
#
class PowerPanel(ChangeLoggedModel):
"""
A distribution point for electrical power; e.g. a data center RPP.
"""
site = models.ForeignKey(
to='Site',
on_delete=models.PROTECT
)
rackgroup = models.ForeignKey(
to='RackGroup',
on_delete=models.PROTECT,
blank=True,
null=True
)
name = models.CharField(
max_length=50
)
csv_headers = ['site', 'rackgroup', 'name']
class Meta:
ordering = ['site', 'name']
unique_together = ['site', 'name']
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('dcim:powerpanel', args=[self.pk])
def to_csv(self):
return (
self.site.name,
self.rackgroup.name if self.rackgroup else None,
self.name,
)
class PowerFeed(ChangeLoggedModel, CustomFieldModel):
"""
An electrical circuit delivered from a PowerPanel.
"""
powerpanel = models.ForeignKey(
to='PowerPanel',
on_delete=models.PROTECT,
related_name='powerfeeds'
)
rack = models.ForeignKey(
to='Rack',
on_delete=models.PROTECT,
blank=True,
null=True
)
name = models.CharField(
max_length=50
)
type = models.PositiveSmallIntegerField(
choices=POWERFEED_TYPE_CHOICES,
default=POWERFEED_TYPE_PRIMARY
)
status = models.PositiveSmallIntegerField(
choices=POWERFEED_STATUS_CHOICES,
default=POWERFEED_STATUS_ACTIVE
)
supply = models.PositiveSmallIntegerField(
choices=POWERFEED_SUPPLY_CHOICES,
default=POWERFEED_SUPPLY_AC
)
voltage = models.PositiveSmallIntegerField(
validators=[MinValueValidator(1)],
default=120
)
amperage = models.PositiveSmallIntegerField(
validators=[MinValueValidator(1)],
default=20
)
phase = models.PositiveSmallIntegerField(
choices=POWERFEED_PHASE_CHOICES,
default=POWERFEED_PHASE_SINGLE
)
max_utilization = models.PositiveSmallIntegerField(
validators=[MinValueValidator(1), MaxValueValidator(100)],
default=80,
help_text="Maximum permissible draw (percentage)"
)
comments = models.TextField(
blank=True
)
custom_field_values = GenericRelation(
to='extras.CustomFieldValue',
content_type_field='obj_type',
object_id_field='obj_id'
)
tags = TaggableManager(through=TaggedItem)
csv_headers = [
'powerpanel', 'rack', 'name', 'type', 'status', 'supply', 'voltage', 'amperage', 'phase', 'max_utilization',
'comments',
]
class Meta:
ordering = ['powerpanel', 'name']
unique_together = ['powerpanel', 'name']
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('dcim:powerfeed', args=[self.pk])
def to_csv(self):
return (
self.powerpanel.name,
self.rack.name if self.rack else None,
self.name,
self.get_type_display(),
self.get_status_display(),
self.get_supply_display(),
self.voltage,
self.amperage,
self.get_phase_display(),
self.max_utilization,
self.comments,
)

View File

@ -6,8 +6,9 @@ from utilities.tables import BaseTable, BooleanColumn, ColorColumn, ToggleColumn
from .models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
InventoryItem, Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
InventoryItem, Manufacturer, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis,
)
REGION_LINK = """
@ -786,3 +787,48 @@ class VirtualChassisTable(BaseTable):
class Meta(BaseTable.Meta):
model = VirtualChassis
fields = ('pk', 'master', 'domain', 'member_count', 'actions')
#
# Power panels
#
class PowerPanelTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn()
powerfeed_count = tables.Column(
verbose_name='Feeds'
)
actions = tables.TemplateColumn(
template_code=RACKROLE_ACTIONS,
attrs={
'td': {'class': 'text-right noprint'}
},
verbose_name=''
)
class Meta(BaseTable.Meta):
model = PowerPanel
fields = ('pk', 'name', 'site', 'rackgroup', 'powerfeed_count', 'actions')
#
# Power feeds
#
class PowerFeedTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn()
powerpanel = tables.LinkColumn(
viewname='dcim:powerpanel',
args=[Accessor('powerpanel.pk')],
)
rack = tables.LinkColumn(
viewname='dcim:rack',
accessor=Accessor('rack.pk')
)
class Meta(BaseTable.Meta):
model = PowerFeed
fields = ('pk', 'name', 'powerpanel', 'rack', 'type', 'status', 'supply', 'voltage', 'amperage', 'phase')

View File

@ -6,7 +6,8 @@ from secrets.views import secret_add
from . import views
from .models import (
Cable, ConsolePort, ConsoleServerPort, Device, DeviceRole, DeviceType, FrontPort, Interface, Manufacturer, Platform,
PowerPort, PowerOutlet, Rack, RackGroup, RackReservation, RackRole, RearPort, Region, Site, VirtualChassis,
PowerFeed, PowerPanel, PowerPort, PowerOutlet, Rack, RackGroup, RackReservation, RackRole, RearPort, Region, Site,
VirtualChassis,
)
app_name = 'dcim'
@ -279,4 +280,23 @@ urlpatterns = [
url(r'^virtual-chassis/(?P<pk>\d+)/add-member/$', views.VirtualChassisAddMemberView.as_view(), name='virtualchassis_add_member'),
url(r'^virtual-chassis-members/(?P<pk>\d+)/delete/$', views.VirtualChassisRemoveMemberView.as_view(), name='virtualchassis_remove_member'),
# Power panels
url(r'^power-panels/$', views.PowerPanelListView.as_view(), name='powerpanel_list'),
url(r'^power-panels/add/$', views.PowerPanelCreateView.as_view(), name='powerpanel_add'),
url(r'^power-panels/import/$', views.PowerPanelBulkImportView.as_view(), name='powerpanel_import'),
url(r'^power-panels/delete/$', views.PowerPanelBulkDeleteView.as_view(), name='powerpanel_bulk_delete'),
url(r'^power-panels/(?P<pk>\d+)/edit/$', views.PowerPanelEditView.as_view(), name='powerpanel_edit'),
url(r'^power-panels/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='powerpanel_changelog', kwargs={'model': PowerPanel}),
# Racks
url(r'^power-feeds/$', views.PowerFeedListView.as_view(), name='powerfeed_list'),
url(r'^power-feeds/add/$', views.PowerFeedEditView.as_view(), name='powerfeed_add'),
url(r'^power-feeds/import/$', views.PowerFeedBulkImportView.as_view(), name='powerfeed_import'),
url(r'^power-feeds/edit/$', views.PowerFeedBulkEditView.as_view(), name='powerfeed_bulk_edit'),
url(r'^power-feeds/delete/$', views.PowerFeedBulkDeleteView.as_view(), name='powerfeed_bulk_delete'),
url(r'^power-feeds/(?P<pk>\d+)/$', views.PowerFeedView.as_view(), name='powerfeed'),
url(r'^power-feeds/(?P<pk>\d+)/edit/$', views.PowerFeedEditView.as_view(), name='powerfeed_edit'),
url(r'^power-feeds/(?P<pk>\d+)/delete/$', views.PowerFeedDeleteView.as_view(), name='powerfeed_delete'),
url(r'^power-feeds/(?P<pk>\d+)/changelog/$', ObjectChangeLogView.as_view(), name='powerfeed_changelog', kwargs={'model': PowerFeed}),
]

View File

@ -30,8 +30,9 @@ from . import filters, forms, tables
from .models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
InventoryItem, Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
InventoryItem, Manufacturer, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis,
)
@ -2114,3 +2115,113 @@ class VirtualChassisRemoveMemberView(PermissionRequiredMixin, GetReturnURLMixin,
'form': form,
'return_url': self.get_return_url(request, device),
})
#
# Power panels
#
class PowerPanelListView(ObjectListView):
queryset = PowerPanel.objects.select_related(
'site', 'rackgroup'
).annotate(
rack_count=Count('powerfeeds')
)
table = tables.PowerPanelTable
template_name = 'dcim/powerpanel_list.html'
class PowerPanelCreateView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'dcim.add_powerpanel'
model = PowerPanel
model_form = forms.PowerPanelForm
default_return_url = 'dcim:powerpanel_list'
class PowerPanelEditView(PowerPanelCreateView):
permission_required = 'dcim.change_powerpanel'
class PowerPanelBulkImportView(PermissionRequiredMixin, BulkImportView):
permission_required = 'dcim.add_powerpanel'
model_form = forms.PowerPanelCSVForm
table = tables.PowerPanelTable
default_return_url = 'dcim:powerpanel_list'
class PowerPanelBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_powerpanel'
queryset = PowerPanel.objects.select_related(
'site', 'rack_group'
).annotate(
rack_count=Count('powerfeeds')
)
table = tables.PowerPanelTable
default_return_url = 'dcim:powerpanel_list'
#
# Power feeds
#
class PowerFeedListView(ObjectListView):
queryset = PowerFeed.objects.select_related(
'powerpanel', 'rack'
)
# filter = filters.PowerFeedFilter
# filter_form = forms.PowerFeedFilterForm
table = tables.PowerFeedTable
template_name = 'dcim/powerfeed_list.html'
class PowerFeedView(View):
def get(self, request, pk):
powerfeed = get_object_or_404(PowerFeed.objects.select_related('panel', 'rack'), pk=pk)
return render(request, 'dcim/powerfeed.html', {
'powerfeed': powerfeed,
})
class PowerFeedCreateView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'dcim.add_powerfeed'
model = PowerFeed
model_form = forms.PowerFeedForm
template_name = 'dcim/powerfeed_edit.html'
default_return_url = 'dcim:powerfeed_list'
class PowerFeedEditView(PowerFeedCreateView):
permission_required = 'dcim.change_powerfeed'
class PowerFeedDeleteView(PermissionRequiredMixin, ObjectDeleteView):
permission_required = 'dcim.delete_powerfeed'
model = PowerFeed
default_return_url = 'dcim:powerfeed_list'
class PowerFeedBulkImportView(PermissionRequiredMixin, BulkImportView):
permission_required = 'dcim.add_powerfeed'
model_form = forms.PowerFeedCSVForm
table = tables.PowerFeedTable
default_return_url = 'dcim:powerfeed_list'
class PowerFeedBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'dcim.change_powerfeed'
queryset = PowerFeed.objects.select_related('powerpanel', 'rack')
# filter = filters.PowerFeedFilter
table = tables.PowerFeedTable
form = forms.PowerFeedBulkEditForm
default_return_url = 'dcim:powerfeed_list'
class PowerFeedBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_powerfeed'
queryset = PowerFeed.objects.select_related('powerpanel', 'rack')
# filter = filters.PowerFeedFilter
table = tables.PowerFeedTable
default_return_url = 'dcim:powerfeed_list'

View File

@ -0,0 +1,111 @@
{% extends '_base.html' %}
{% load static %}
{% load tz %}
{% load helpers %}
{% block header %}
<div class="row noprint">
<div class="col-sm-8 col-md-9">
<ol class="breadcrumb">
<li><a href="{% url 'dcim:powerfeed_list' %}">Power Feeds</a></li>
<li><a href="{{ powerfeed.site.get_absolute_url }}">{{ powerfeed.site }}</a></li>
{% if powerfeed.rackgroup %}
<li><a href="{{ powerfeed.rackgroup.get_absolute_url }}">{{ powerfeed.rackgroup }}</a></li>
{% endif %}
<li>{{ site }}</li>
</ol>
</div>
<div class="col-sm-4 col-md-3">
<form action="{% url 'dcim:powerfeed_list' %}" method="get">
<div class="input-group">
<input type="text" name="q" class="form-control" placeholder="Search power feeds" />
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">
<span class="fa fa-search" aria-hidden="true"></span>
</button>
</span>
</div>
</form>
</div>
</div>
<div class="pull-right noprint">
{% if perms.dcim.change_powerfeed %}
<a href="{% url 'dcim:powerfeed_edit' pk=powerfeed.pk %}" class="btn btn-warning">
<span class="fa fa-pencil" aria-hidden="true"></span>
Edit this power feed
</a>
{% endif %}
{% if perms.dcim.delete_powerfeed %}
<a href="{% url 'dcim:powerfeed_delete' pk=powerfeed.pk %}" class="btn btn-danger">
<span class="fa fa-trash" aria-hidden="true"></span>
Delete this power feed
</a>
{% endif %}
</div>
<h1>{% block title %}{{ powerfeed }}{% endblock %}</h1>
{% include 'inc/created_updated.html' with obj=powerfeed %}
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-7">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Power Feed</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Power Panel</td>
<td>
<a href="{{ powerfeed.powerpanel.get_absolute_url }}">{{ powerfeed.powerpanel }}</a>
</td>
</tr>
<tr>
<td>Rack</td>
<td>
{% if powerfeed.rack %}
<a href="{{ powerfeed.rack.get_absolute_url }}">{{ powerfeed.rack }}</a>
{% else %}
<span class="text-muted">None</span>
{% endif %}
</td>
</tr>
<tr>
<td>Type</td>
<td>
<span class="label label-{{ powerfeed.get_type_class }}">{{ powerfeed.get_type_display }}</span>
</td>
</tr>
<tr>
<td>Status</td>
<td>
<span class="label label-{{ powerfeed.get_status_class }}">{{ powerfeed.get_status_display }}</span>
</td>
</tr>
<tr>
<td>Supply</td>
<td>{{ powerfeed.get_supply_display }}</td>
</tr>
<tr>
<td>Voltage</td>
<td>{{ powerfeed.voltage }}V</td>
</tr>
<tr>
<td>Amperage</td>
<td>{{ powerfeed.amperage }}A</td>
</tr>
<tr>
<td>Phase</td>
<td>{{ powerfeed.get_phase_display }}</td>
</tr>
<tr>
<td>Max Utilization</td>
<td>{{ powerfeed.max_utilization }}%</td>
</tr>
</table>
</div>
</div>
<div class="col-md-5">
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,45 @@
{% extends 'utilities/obj_edit.html' %}
{% load form_helpers %}
{% block form %}
<div class="panel panel-default">
<div class="panel-heading"><strong>Power Feed</strong></div>
<div class="panel-body">
{% render_field form.powerpanel %}
{% render_field form.rack %}
{% render_field form.name %}
{% render_field form.type %}
{% render_field form.status %}
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Characteristics</strong></div>
<div class="panel-body">
{% render_field form.supply %}
{% render_field form.voltage %}
{% render_field form.amperage %}
{% render_field form.phase %}
{% render_field form.max_utilization %}
</div>
</div>
{% if form.custom_fields %}
<div class="panel panel-default">
<div class="panel-heading"><strong>Custom Fields</strong></div>
<div class="panel-body">
{% render_custom_fields form %}
</div>
</div>
{% endif %}
<div class="panel panel-default">
<div class="panel-heading"><strong>Tags</strong></div>
<div class="panel-body">
{% render_field form.tags %}
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Comments</strong></div>
<div class="panel-body">
{% render_field form.comments %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,22 @@
{% extends '_base.html' %}
{% load buttons %}
{% block content %}
<div class="pull-right noprint">
{% if perms.dcim.add_powerfeed %}
{% add_button 'dcim:powerfeed_add' %}
{% import_button 'dcim:powerfeed_import' %}
{% endif %}
{% export_button content_type %}
</div>
<h1>{% block title %}Power Feeds{% endblock %}</h1>
<div class="row">
<div class="col-md-9">
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:powerfeed_bulk_edit' bulk_delete_url='dcim:powerfeed_bulk_delete' %}
</div>
<div class="col-md-3 noprint">
{% include 'inc/search_panel.html' %}
{% include 'inc/tags_panel.html' %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,18 @@
{% extends '_base.html' %}
{% load buttons %}
{% block content %}
<div class="pull-right noprint">
{% if perms.dcim.add_powerpanel %}
{% add_button 'dcim:powerpanel_add' %}
{% import_button 'dcim:powerpanel_import' %}
{% endif %}
{% export_button content_type %}
</div>
<h1>{% block title %}Power Panels{% endblock %}</h1>
<div class="row">
<div class="col-md-12">
{% include 'utilities/obj_table.html' with bulk_delete_url='dcim:powerpanel_bulk_delete' %}
</div>
</div>
{% endblock %}

View File

@ -368,6 +368,29 @@
</li>
</ul>
</li>
<li class="dropdown{% if request.path|contains:'/dcim/power' %} active{% endif %}">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Power <span class="caret"></span></a>
<ul class="dropdown-menu">
<li>
{% if perms.dcim.add_powerfeed %}
<div class="buttons pull-right">
<a href="{% url 'dcim:powerfeed_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
<a href="{% url 'dcim:powerfeed_import' %}" class="btn btn-xs btn-info" title="Import"><i class="fa fa-download"></i></a>
</div>
{% endif %}
<a href="{% url 'dcim:powerfeed_list' %}">Power Feeds</a>
</li>
<li>
{% if perms.dcim.add_powerpanel %}
<div class="buttons pull-right">
<a href="{% url 'dcim:powerpanel_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
<a href="{% url 'dcim:powerpanel_import' %}" class="btn btn-xs btn-info" title="Import"><i class="fa fa-download"></i></a>
</div>
{% endif %}
<a href="{% url 'dcim:powerpanel_list' %}">Power Panels</a>
</li>
</ul>
</li>
{% if request.user.is_authenticated %}
<li class="dropdown{% if request.path|contains:'/secrets/' %} active{% endif %}">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Secrets <span class="caret"></span></a>