1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

More power work

This commit is contained in:
Jeremy Stretch
2019-03-12 11:36:29 -04:00
parent 3b9c0e4c67
commit e06dece00c
11 changed files with 230 additions and 91 deletions

View File

@ -3,8 +3,8 @@ from rest_framework import serializers
from dcim.constants import CONNECTION_STATUS_CHOICES from dcim.constants import CONNECTION_STATUS_CHOICES
from dcim.models import ( from dcim.models import (
Cable, ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceType, DeviceRole, FrontPort, FrontPortTemplate, Cable, ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceType, DeviceRole, FrontPort, FrontPortTemplate,
Interface, Manufacturer, Platform, PowerOutlet, PowerPort, Rack, RackGroup, RackRole, RearPort, RearPortTemplate, Interface, Manufacturer, Platform, PowerFeed, PowerOutlet, PowerPanel, PowerPort, Rack, RackGroup, RackRole,
Region, Site, VirtualChassis, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
) )
from utilities.api import ChoiceField, WritableNestedSerializer from utilities.api import ChoiceField, WritableNestedSerializer
@ -21,7 +21,9 @@ __all__ = [
'NestedInterfaceSerializer', 'NestedInterfaceSerializer',
'NestedManufacturerSerializer', 'NestedManufacturerSerializer',
'NestedPlatformSerializer', 'NestedPlatformSerializer',
'NestedPowerFeedSerializer',
'NestedPowerOutletSerializer', 'NestedPowerOutletSerializer',
'NestedPowerPanelSerializer',
'NestedPowerPortSerializer', 'NestedPowerPortSerializer',
'NestedRackGroupSerializer', 'NestedRackGroupSerializer',
'NestedRackRoleSerializer', 'NestedRackRoleSerializer',
@ -247,3 +249,23 @@ class NestedVirtualChassisSerializer(WritableNestedSerializer):
class Meta: class Meta:
model = VirtualChassis model = VirtualChassis
fields = ['id', 'url', 'master'] fields = ['id', 'url', 'master']
#
# Power panels/feeds
#
class NestedPowerPanelSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerpanel-detail')
class Meta:
model = PowerPanel
fields = ['id', 'url', 'name']
class NestedPowerFeedSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerfeed-detail')
class Meta:
model = PowerFeed
fields = ['id', 'url', 'name']

View File

@ -7,8 +7,9 @@ from dcim.constants import *
from dcim.models import ( from dcim.models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceType, DeviceRole, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, DeviceBayTemplate, DeviceType, DeviceRole, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, Manufacturer, InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis,
) )
from extras.api.customfields import CustomFieldModelSerializer from extras.api.customfields import CustomFieldModelSerializer
from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer
@ -587,3 +588,56 @@ class VirtualChassisSerializer(TaggitSerializer, ValidatedModelSerializer):
class Meta: class Meta:
model = VirtualChassis model = VirtualChassis
fields = ['id', 'master', 'domain', 'tags'] fields = ['id', 'master', 'domain', 'tags']
#
# Power panels
#
class PowerPanelSerializer(ValidatedModelSerializer):
site = NestedSiteSerializer()
rack_group = NestedRackGroupSerializer(
required=False,
allow_null=True,
default=None
)
class Meta:
model = PowerPanel
fields = ['id', 'site', 'rack_group', 'name']
class PowerFeedSerializer(TaggitSerializer, CustomFieldModelSerializer):
power_panel = NestedPowerPanelSerializer()
rack = NestedRackSerializer(
required=False,
allow_null=True,
default=None
)
type = ChoiceField(
choices=POWERFEED_TYPE_CHOICES,
default=POWERFEED_TYPE_PRIMARY
)
status = ChoiceField(
choices=POWERFEED_STATUS_CHOICES,
default=POWERFEED_STATUS_ACTIVE
)
supply = ChoiceField(
choices=POWERFEED_SUPPLY_CHOICES,
default=POWERFEED_SUPPLY_AC
)
phase = ChoiceField(
choices=POWERFEED_PHASE_CHOICES,
default=POWERFEED_PHASE_SINGLE
)
tags = TagListSerializerField(
required=False
)
class Meta:
model = PowerFeed
fields = [
'id', 'power_panel', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage', 'amperage',
'max_utilization', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
]

View File

@ -68,6 +68,10 @@ router.register(r'cables', views.CableViewSet)
# Virtual chassis # Virtual chassis
router.register(r'virtual-chassis', views.VirtualChassisViewSet) router.register(r'virtual-chassis', views.VirtualChassisViewSet)
# Power
router.register(r'power-panels', views.PowerPanelViewSet)
router.register(r'power-feeds', views.PowerFeedViewSet)
# Miscellaneous # Miscellaneous
router.register(r'connected-device', views.ConnectedDeviceViewSet, basename='connected-device') router.register(r'connected-device', views.ConnectedDeviceViewSet, basename='connected-device')

View File

@ -16,8 +16,9 @@ from dcim import filters
from dcim.models import ( from dcim.models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, Manufacturer, InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis,
) )
from extras.api.serializers import RenderedGraphSerializer from extras.api.serializers import RenderedGraphSerializer
from extras.api.views import CustomFieldModelViewSet from extras.api.views import CustomFieldModelViewSet
@ -534,6 +535,26 @@ class VirtualChassisViewSet(ModelViewSet):
serializer_class = serializers.VirtualChassisSerializer serializer_class = serializers.VirtualChassisSerializer
#
# Power panels
#
class PowerPanelViewSet(ModelViewSet):
queryset = PowerPanel.objects.all()
serializer_class = serializers.PowerPanelSerializer
# filterset_class = filters.PowerPanelFilter
#
# Power feeds
#
class PowerFeedViewSet(ModelViewSet):
queryset = PowerFeed.objects.all()
serializer_class = serializers.PowerFeedSerializer
# filterset_class = filters.PowerFeedFilter
# #
# Miscellaneous # Miscellaneous
# #

View File

@ -448,8 +448,8 @@ RACK_DIMENSION_UNIT_CHOICES = (
POWERFEED_TYPE_PRIMARY = 1 POWERFEED_TYPE_PRIMARY = 1
POWERFEED_TYPE_REDUNDANT = 2 POWERFEED_TYPE_REDUNDANT = 2
POWERFEED_TYPE_CHOICES = ( POWERFEED_TYPE_CHOICES = (
(POWERFEED_TYPE_PRIMARY, 'AC'), (POWERFEED_TYPE_PRIMARY, 'Primary'),
(POWERFEED_TYPE_REDUNDANT, 'DC'), (POWERFEED_TYPE_REDUNDANT, 'Redundant'),
) )
POWERFEED_SUPPLY_AC = 1 POWERFEED_SUPPLY_AC = 1
POWERFEED_SUPPLY_DC = 2 POWERFEED_SUPPLY_DC = 2

View File

@ -3183,7 +3183,7 @@ class PowerPanelForm(BootstrapMixin, forms.ModelForm):
'site': APISelect( 'site': APISelect(
api_url="/api/dcim/sites/", api_url="/api/dcim/sites/",
filter_for={ filter_for={
'rackgroup': 'site_id', 'rack_group': 'site_id',
} }
), ),
} }
@ -3231,7 +3231,7 @@ class PowerFeedForm(BootstrapMixin, CustomFieldForm):
class Meta: class Meta:
model = PowerFeed model = PowerFeed
fields = [ fields = [
'site', 'power_panel', 'rack', 'name', 'type', 'status', 'supply', 'voltage', 'amperage', 'phase', 'site', 'power_panel', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage', 'amperage',
'max_utilization', 'comments', 'tags', 'max_utilization', 'comments', 'tags',
] ]
widgets = { widgets = {
@ -3241,24 +3241,24 @@ class PowerFeedForm(BootstrapMixin, CustomFieldForm):
'rack': APISelect( 'rack': APISelect(
api_url="/api/dcim/racks/" api_url="/api/dcim/racks/"
), ),
'type': StaticSelect2(),
'status': StaticSelect2(), 'status': StaticSelect2(),
'type': StaticSelect2(),
'supply': StaticSelect2(), 'supply': StaticSelect2(),
'phase': StaticSelect2(), 'phase': StaticSelect2(),
} }
class PowerFeedCSVForm(forms.ModelForm): class PowerFeedCSVForm(forms.ModelForm):
type = CSVChoiceField(
choices=POWERFEED_TYPE_CHOICES,
required=False,
help_text='Primary or redundant'
)
status = CSVChoiceField( status = CSVChoiceField(
choices=POWERFEED_STATUS_CHOICES, choices=POWERFEED_STATUS_CHOICES,
required=False, required=False,
help_text='Operational status' help_text='Operational status'
) )
type = CSVChoiceField(
choices=POWERFEED_TYPE_CHOICES,
required=False,
help_text='Primary or redundant'
)
supply = CSVChoiceField( supply = CSVChoiceField(
choices=POWERFEED_SUPPLY_CHOICES, choices=POWERFEED_SUPPLY_CHOICES,
required=False, required=False,
@ -3292,14 +3292,14 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
api_url="/api/dcim/rack-groups", api_url="/api/dcim/rack-groups",
) )
) )
type = forms.ChoiceField( status = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_TYPE_CHOICES), choices=add_blank_choice(POWERFEED_STATUS_CHOICES),
required=False, required=False,
initial='', initial='',
widget=StaticSelect2() widget=StaticSelect2()
) )
status = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_STATUS_CHOICES), choices=add_blank_choice(POWERFEED_TYPE_CHOICES),
required=False, required=False,
initial='', initial='',
widget=StaticSelect2() widget=StaticSelect2()
@ -3310,18 +3310,18 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
initial='', initial='',
widget=StaticSelect2() widget=StaticSelect2()
) )
voltage = forms.IntegerField(
required=False
)
amperage = forms.IntegerField(
required=False
)
phase = forms.ChoiceField( phase = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_PHASE_CHOICES), choices=add_blank_choice(POWERFEED_PHASE_CHOICES),
required=False, required=False,
initial='', initial='',
widget=StaticSelect2() widget=StaticSelect2()
) )
voltage = forms.IntegerField(
required=False
)
amperage = forms.IntegerField(
required=False
)
max_utilization = forms.IntegerField( max_utilization = forms.IntegerField(
required=False required=False
) )

View File

@ -2730,18 +2730,22 @@ class PowerFeed(ChangeLoggedModel, CustomFieldModel):
name = models.CharField( name = models.CharField(
max_length=50 max_length=50
) )
type = models.PositiveSmallIntegerField(
choices=POWERFEED_TYPE_CHOICES,
default=POWERFEED_TYPE_PRIMARY
)
status = models.PositiveSmallIntegerField( status = models.PositiveSmallIntegerField(
choices=POWERFEED_STATUS_CHOICES, choices=POWERFEED_STATUS_CHOICES,
default=POWERFEED_STATUS_ACTIVE default=POWERFEED_STATUS_ACTIVE
) )
type = models.PositiveSmallIntegerField(
choices=POWERFEED_TYPE_CHOICES,
default=POWERFEED_TYPE_PRIMARY
)
supply = models.PositiveSmallIntegerField( supply = models.PositiveSmallIntegerField(
choices=POWERFEED_SUPPLY_CHOICES, choices=POWERFEED_SUPPLY_CHOICES,
default=POWERFEED_SUPPLY_AC default=POWERFEED_SUPPLY_AC
) )
phase = models.PositiveSmallIntegerField(
choices=POWERFEED_PHASE_CHOICES,
default=POWERFEED_PHASE_SINGLE
)
voltage = models.PositiveSmallIntegerField( voltage = models.PositiveSmallIntegerField(
validators=[MinValueValidator(1)], validators=[MinValueValidator(1)],
default=120 default=120
@ -2750,10 +2754,6 @@ class PowerFeed(ChangeLoggedModel, CustomFieldModel):
validators=[MinValueValidator(1)], validators=[MinValueValidator(1)],
default=20 default=20
) )
phase = models.PositiveSmallIntegerField(
choices=POWERFEED_PHASE_CHOICES,
default=POWERFEED_PHASE_SINGLE
)
max_utilization = models.PositiveSmallIntegerField( max_utilization = models.PositiveSmallIntegerField(
validators=[MinValueValidator(1), MaxValueValidator(100)], validators=[MinValueValidator(1), MaxValueValidator(100)],
default=80, default=80,
@ -2771,7 +2771,7 @@ class PowerFeed(ChangeLoggedModel, CustomFieldModel):
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
csv_headers = [ csv_headers = [
'power_panel', 'rack', 'name', 'type', 'status', 'supply', 'voltage', 'amperage', 'phase', 'max_utilization', 'power_panel', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization',
'comments', 'comments',
] ]
@ -2790,12 +2790,18 @@ class PowerFeed(ChangeLoggedModel, CustomFieldModel):
self.power_panel.name, self.power_panel.name,
self.rack.name if self.rack else None, self.rack.name if self.rack else None,
self.name, self.name,
self.get_type_display(),
self.get_status_display(), self.get_status_display(),
self.get_type_display(),
self.get_supply_display(), self.get_supply_display(),
self.get_phase_display(),
self.voltage, self.voltage,
self.amperage, self.amperage,
self.get_phase_display(),
self.max_utilization, self.max_utilization,
self.comments, self.comments,
) )
def get_type_class(self):
return STATUS_CLASSES[self.type]
def get_status_class(self):
return STATUS_CLASSES[self.status]

View File

@ -145,6 +145,10 @@ STATUS_LABEL = """
<span class="label label-{{ record.get_status_class }}">{{ record.get_status_display }}</span> <span class="label label-{{ record.get_status_class }}">{{ record.get_status_display }}</span>
""" """
TYPE_LABEL = """
<span class="label label-{{ record.get_type_class }}">{{ record.get_type_display }}</span>
"""
DEVICE_PRIMARY_IP = """ DEVICE_PRIMARY_IP = """
{{ record.primary_ip6.address.ip|default:"" }} {{ record.primary_ip6.address.ip|default:"" }}
{% if record.primary_ip6 and record.primary_ip4 %}<br />{% endif %} {% if record.primary_ip6 and record.primary_ip4 %}<br />{% endif %}
@ -799,17 +803,10 @@ class PowerPanelTable(BaseTable):
powerfeed_count = tables.Column( powerfeed_count = tables.Column(
verbose_name='Feeds' verbose_name='Feeds'
) )
actions = tables.TemplateColumn(
template_code=RACKROLE_ACTIONS,
attrs={
'td': {'class': 'text-right noprint'}
},
verbose_name=''
)
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
model = PowerPanel model = PowerPanel
fields = ('pk', 'name', 'site', 'rackgroup', 'powerfeed_count', 'actions') fields = ('pk', 'name', 'site', 'rack_group', 'powerfeed_count')
# #
@ -819,16 +816,18 @@ class PowerPanelTable(BaseTable):
class PowerFeedTable(BaseTable): class PowerFeedTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = tables.LinkColumn() name = tables.LinkColumn()
powerpanel = tables.LinkColumn( power_panel = tables.LinkColumn(
viewname='dcim:powerpanel', viewname='dcim:powerpanel',
args=[Accessor('powerpanel.pk')], args=[Accessor('power_panel.pk')],
) )
rack = tables.LinkColumn( status = tables.TemplateColumn(
viewname='dcim:rack', template_code=STATUS_LABEL
accessor=Accessor('rack.pk') )
type = tables.TemplateColumn(
template_code=TYPE_LABEL
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
model = PowerFeed model = PowerFeed
fields = ('pk', 'name', 'powerpanel', 'rack', 'type', 'status', 'supply', 'voltage', 'amperage', 'phase') fields = ('pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase')

View File

@ -392,10 +392,12 @@ class RackView(View):
prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name').first() prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name').first()
reservations = RackReservation.objects.filter(rack=rack) reservations = RackReservation.objects.filter(rack=rack)
power_feeds = PowerFeed.objects.filter(rack=rack).select_related('power_panel')
return render(request, 'dcim/rack.html', { return render(request, 'dcim/rack.html', {
'rack': rack, 'rack': rack,
'reservations': reservations, 'reservations': reservations,
'power_feeds': power_feeds,
'nonracked_devices': nonracked_devices, 'nonracked_devices': nonracked_devices,
'next_rack': next_rack, 'next_rack': next_rack,
'prev_rack': prev_rack, 'prev_rack': prev_rack,
@ -2123,9 +2125,9 @@ class VirtualChassisRemoveMemberView(PermissionRequiredMixin, GetReturnURLMixin,
class PowerPanelListView(ObjectListView): class PowerPanelListView(ObjectListView):
queryset = PowerPanel.objects.select_related( queryset = PowerPanel.objects.select_related(
'site', 'rackgroup' 'site', 'rack_group'
).annotate( ).annotate(
rack_count=Count('powerfeeds') powerfeed_count=Count('powerfeeds')
) )
table = tables.PowerPanelTable table = tables.PowerPanelTable
template_name = 'dcim/powerpanel_list.html' template_name = 'dcim/powerpanel_list.html'
@ -2183,7 +2185,7 @@ class PowerPanelBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
class PowerFeedListView(ObjectListView): class PowerFeedListView(ObjectListView):
queryset = PowerFeed.objects.select_related( queryset = PowerFeed.objects.select_related(
'powerpanel', 'rack' 'power_panel', 'rack'
) )
# filter = filters.PowerFeedFilter # filter = filters.PowerFeedFilter
# filter_form = forms.PowerFeedFilterForm # filter_form = forms.PowerFeedFilterForm
@ -2229,7 +2231,7 @@ class PowerFeedBulkImportView(PermissionRequiredMixin, BulkImportView):
class PowerFeedBulkEditView(PermissionRequiredMixin, BulkEditView): class PowerFeedBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'dcim.change_powerfeed' permission_required = 'dcim.change_powerfeed'
queryset = PowerFeed.objects.select_related('powerpanel', 'rack') queryset = PowerFeed.objects.select_related('power_panel', 'rack')
# filter = filters.PowerFeedFilter # filter = filters.PowerFeedFilter
table = tables.PowerFeedTable table = tables.PowerFeedTable
form = forms.PowerFeedBulkEditForm form = forms.PowerFeedBulkEditForm
@ -2238,7 +2240,7 @@ class PowerFeedBulkEditView(PermissionRequiredMixin, BulkEditView):
class PowerFeedBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class PowerFeedBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_powerfeed' permission_required = 'dcim.delete_powerfeed'
queryset = PowerFeed.objects.select_related('powerpanel', 'rack') queryset = PowerFeed.objects.select_related('power_panel', 'rack')
# filter = filters.PowerFeedFilter # filter = filters.PowerFeedFilter
table = tables.PowerFeedTable table = tables.PowerFeedTable
default_return_url = 'dcim:powerfeed_list' default_return_url = 'dcim:powerfeed_list'

View File

@ -9,13 +9,13 @@
{% render_field form.power_panel %} {% render_field form.power_panel %}
{% render_field form.rack %} {% render_field form.rack %}
{% render_field form.name %} {% render_field form.name %}
{% render_field form.type %}
{% render_field form.status %} {% render_field form.status %}
</div> </div>
</div> </div>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>Characteristics</strong></div> <div class="panel-heading"><strong>Characteristics</strong></div>
<div class="panel-body"> <div class="panel-body">
{% render_field form.type %}
{% render_field form.supply %} {% render_field form.supply %}
{% render_field form.voltage %} {% render_field form.voltage %}
{% render_field form.amperage %} {% render_field form.amperage %}

View File

@ -190,47 +190,37 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="panel panel-default"> {% if power_feeds %}
<div class="panel-heading"> <div class="panel panel-default">
<strong>Non-Racked Devices</strong> <div class="panel-heading">
</div> <strong>Power Feeds</strong>
{% if nonracked_devices %} </div>
<table class="table table-hover panel-body"> <table class="table panel-body">
<tr> <tr>
<th>Name</th> <th>Panel</th>
<th>Role</th> <th>Feed</th>
<th>Status</th>
<th>Type</th> <th>Type</th>
<th>Parent</th>
</tr> </tr>
{% for device in nonracked_devices %} {% for powerfeed in power_feeds %}
<tr{% if device.device_type.u_height %} class="warning"{% endif %}> <tr>
<td> <td>
<a href="{% url 'dcim:device' pk=device.pk %}">{{ device }}</a> <a href="{{ powerfeed.power_panel.get_absolute_url }}">{{ powerfeed.power_panel.name }}</a>
<td>
<a href="{{ powerfeed.get_absolute_url }}">{{ powerfeed.name }}</a>
</td> </td>
<td>{{ device.device_role }}</td>
<td>{{ device.device_type.display_name }}</td>
<td> <td>
{% if device.parent_bay %} <span class="label label-{{ powerfeed.get_status_class }}">{{ powerfeed.get_status_display }}</span>
<a href="{{ device.parent_bay.device.get_absolute_url }}">{{ device.parent_bay }}</a> </td>
{% else %} <td>
<span class="text-muted">&mdash;</span> <span class="label label-{{ powerfeed.get_type_class }}">{{ powerfeed.get_type_display }}</span>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
{% else %} </div>
<div class="panel-body text-muted">None</div> {% endif %}
{% endif %}
{% if perms.dcim.add_device %}
<div class="panel-footer text-right noprint">
<a href="{% url 'dcim:device_add' %}?site={{ rack.site.pk }}&rack={{ rack.pk }}" class="btn btn-primary btn-xs">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
Add a non-racked device
</a>
</div>
{% endif %}
</div>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<strong>Images</strong> <strong>Images</strong>
@ -307,11 +297,52 @@
{% include 'dcim/inc/rack_elevation.html' with primary_face=front_elevation secondary_face=rear_elevation face_id=0 reserved_units=rack.get_reserved_units %} {% include 'dcim/inc/rack_elevation.html' with primary_face=front_elevation secondary_face=rear_elevation face_id=0 reserved_units=rack.get_reserved_units %}
</div> </div>
<div class="col-md-6 col-sm-6 col-xs-12"> <div class="col-md-6 col-sm-6 col-xs-12">
<div class="rack_header"> <div class="rack_header">
<h4>Rear</h4> <h4>Rear</h4>
</div> </div>
{% include 'dcim/inc/rack_elevation.html' with primary_face=rear_elevation secondary_face=front_elevation face_id=1 reserved_units=rack.get_reserved_units %} {% include 'dcim/inc/rack_elevation.html' with primary_face=rear_elevation secondary_face=front_elevation face_id=1 reserved_units=rack.get_reserved_units %}
</div> </div>
<div class="panel panel-default">
<div class="panel-heading">
<strong>Non-Racked Devices</strong>
</div>
{% if nonracked_devices %}
<table class="table table-hover panel-body">
<tr>
<th>Name</th>
<th>Role</th>
<th>Type</th>
<th>Parent</th>
</tr>
{% for device in nonracked_devices %}
<tr{% if device.device_type.u_height %} class="warning"{% endif %}>
<td>
<a href="{% url 'dcim:device' pk=device.pk %}">{{ device }}</a>
</td>
<td>{{ device.device_role }}</td>
<td>{{ device.device_type.display_name }}</td>
<td>
{% if device.parent_bay %}
<a href="{{ device.parent_bay.device.get_absolute_url }}">{{ device.parent_bay }}</a>
{% else %}
<span class="text-muted">&mdash;</span>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% else %}
<div class="panel-body text-muted">None</div>
{% endif %}
{% if perms.dcim.add_device %}
<div class="panel-footer text-right noprint">
<a href="{% url 'dcim:device_add' %}?site={{ rack.site.pk }}&rack={{ rack.pk }}" class="btn btn-primary btn-xs">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
Add a non-racked device
</a>
</div>
{% endif %}
</div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}