mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-15 11:42:52 -06:00
Initial work on #91: Support for subdevices
This commit is contained in:
parent
35c5423127
commit
0123dbcf5f
@ -2,9 +2,9 @@ from django.contrib import admin
|
|||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
Interface, InterfaceTemplate, Manufacturer, Module, Platform, PowerOutlet, PowerOutletTemplate, PowerPort,
|
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceTemplate, Manufacturer, Module, Platform,
|
||||||
PowerPortTemplate, Rack, RackGroup, Site,
|
PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, Site,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -61,6 +61,10 @@ class InterfaceTemplateAdmin(admin.TabularInline):
|
|||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceBayTemplateAdmin(admin.TabularInline):
|
||||||
|
model = DeviceBayTemplate
|
||||||
|
|
||||||
|
|
||||||
@admin.register(DeviceType)
|
@admin.register(DeviceType)
|
||||||
class DeviceTypeAdmin(admin.ModelAdmin):
|
class DeviceTypeAdmin(admin.ModelAdmin):
|
||||||
prepopulated_fields = {
|
prepopulated_fields = {
|
||||||
@ -72,9 +76,10 @@ class DeviceTypeAdmin(admin.ModelAdmin):
|
|||||||
PowerPortTemplateAdmin,
|
PowerPortTemplateAdmin,
|
||||||
PowerOutletTemplateAdmin,
|
PowerOutletTemplateAdmin,
|
||||||
InterfaceTemplateAdmin,
|
InterfaceTemplateAdmin,
|
||||||
|
DeviceBayTemplateAdmin,
|
||||||
]
|
]
|
||||||
list_display = ['model', 'manufacturer', 'slug', 'u_height', 'console_ports', 'console_server_ports', 'power_ports',
|
list_display = ['model', 'manufacturer', 'slug', 'u_height', 'console_ports', 'console_server_ports', 'power_ports',
|
||||||
'power_outlets', 'interfaces']
|
'power_outlets', 'interfaces', 'device_bays']
|
||||||
list_filter = ['manufacturer']
|
list_filter = ['manufacturer']
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
@ -84,6 +89,7 @@ class DeviceTypeAdmin(admin.ModelAdmin):
|
|||||||
power_port_count=Count('power_port_templates', distinct=True),
|
power_port_count=Count('power_port_templates', distinct=True),
|
||||||
power_outlet_count=Count('power_outlet_templates', distinct=True),
|
power_outlet_count=Count('power_outlet_templates', distinct=True),
|
||||||
interface_count=Count('interface_templates', distinct=True),
|
interface_count=Count('interface_templates', distinct=True),
|
||||||
|
devicebay_count=Count('devicebay_templates', distinct=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
def console_ports(self, instance):
|
def console_ports(self, instance):
|
||||||
@ -101,6 +107,9 @@ class DeviceTypeAdmin(admin.ModelAdmin):
|
|||||||
def interfaces(self, instance):
|
def interfaces(self, instance):
|
||||||
return instance.interface_count
|
return instance.interface_count
|
||||||
|
|
||||||
|
def device_bays(self, instance):
|
||||||
|
return instance.devicebay_count
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Devices
|
# Devices
|
||||||
@ -144,6 +153,11 @@ class InterfaceAdmin(admin.TabularInline):
|
|||||||
model = Interface
|
model = Interface
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceBayAdmin(admin.TabularInline):
|
||||||
|
model = DeviceBay
|
||||||
|
fk_name = 'device'
|
||||||
|
|
||||||
|
|
||||||
class ModuleAdmin(admin.TabularInline):
|
class ModuleAdmin(admin.TabularInline):
|
||||||
model = Module
|
model = Module
|
||||||
readonly_fields = ['parent', 'discovered']
|
readonly_fields = ['parent', 'discovered']
|
||||||
@ -157,6 +171,7 @@ class DeviceAdmin(admin.ModelAdmin):
|
|||||||
PowerPortAdmin,
|
PowerPortAdmin,
|
||||||
PowerOutletAdmin,
|
PowerOutletAdmin,
|
||||||
InterfaceAdmin,
|
InterfaceAdmin,
|
||||||
|
DeviceBayAdmin,
|
||||||
ModuleAdmin,
|
ModuleAdmin,
|
||||||
]
|
]
|
||||||
list_display = ['display_name', 'device_type', 'device_role', 'primary_ip', 'rack', 'position', 'serial']
|
list_display = ['display_name', 'device_type', 'device_role', 'primary_ip', 'rack', 'position', 'serial']
|
||||||
|
@ -10,10 +10,10 @@ from utilities.forms import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_PLANNED, CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate,
|
DeviceBay, DeviceBayTemplate, CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_PLANNED, CONNECTION_STATUS_CONNECTED,
|
||||||
ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType, Interface, IFACE_FF_VIRTUAL,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType,
|
||||||
InterfaceConnection, InterfaceTemplate, Manufacturer, Module, Platform, PowerOutlet, PowerOutletTemplate, PowerPort,
|
Interface, IFACE_FF_VIRTUAL, InterfaceConnection, InterfaceTemplate, Manufacturer, Module, Platform, PowerOutlet,
|
||||||
PowerPortTemplate, Rack, RackGroup, Site, STATUS_CHOICES
|
PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -216,7 +216,7 @@ class DeviceTypeForm(forms.ModelForm, BootstrapMixin):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceType
|
model = DeviceType
|
||||||
fields = ['manufacturer', 'model', 'slug', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu',
|
fields = ['manufacturer', 'model', 'slug', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu',
|
||||||
'is_network_device']
|
'is_network_device', 'subdevice_role']
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeBulkEditForm(forms.Form, BootstrapMixin):
|
class DeviceTypeBulkEditForm(forms.Form, BootstrapMixin):
|
||||||
@ -283,6 +283,14 @@ class InterfaceTemplateForm(forms.ModelForm, BootstrapMixin):
|
|||||||
fields = ['name_pattern', 'form_factor', 'mgmt_only']
|
fields = ['name_pattern', 'form_factor', 'mgmt_only']
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceBayTemplateForm(forms.ModelForm, BootstrapMixin):
|
||||||
|
name_pattern = ExpandableNameField(label='Name')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = DeviceBayTemplate
|
||||||
|
fields = ['name_pattern']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Device roles
|
# Device roles
|
||||||
#
|
#
|
||||||
@ -1080,6 +1088,41 @@ class InterfaceConnectionDeletionForm(forms.Form, BootstrapMixin):
|
|||||||
device = forms.ModelChoiceField(queryset=Device.objects.all(), widget=forms.HiddenInput(), required=False)
|
device = forms.ModelChoiceField(queryset=Device.objects.all(), widget=forms.HiddenInput(), required=False)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Device bays
|
||||||
|
#
|
||||||
|
|
||||||
|
class DeviceBayForm(forms.ModelForm, BootstrapMixin):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = DeviceBay
|
||||||
|
fields = ['device', 'name']
|
||||||
|
widgets = {
|
||||||
|
'device': forms.HiddenInput(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceBayCreateForm(forms.Form, BootstrapMixin):
|
||||||
|
name_pattern = ExpandableNameField(label='Name')
|
||||||
|
|
||||||
|
|
||||||
|
class PopulateDeviceBayForm(forms.Form, BootstrapMixin):
|
||||||
|
installed_device = forms.ModelChoiceField(queryset=Device.objects.all(), label='Child Device',
|
||||||
|
help_text="Child devices must first be created within the rack occupied "
|
||||||
|
"by the parent device. Then they can be assigned to a bay.")
|
||||||
|
|
||||||
|
def __init__(self, device_bay, *args, **kwargs):
|
||||||
|
|
||||||
|
super(PopulateDeviceBayForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
children_queryset = Device.objects.filter(rack=device_bay.device.rack,
|
||||||
|
parent_bay__isnull=True,
|
||||||
|
device_type__u_height=0,
|
||||||
|
device_type__subdevice_role=SUBDEVICE_ROLE_CHILD)\
|
||||||
|
.exclude(pk=device_bay.device.pk)
|
||||||
|
self.fields['installed_device'].queryset = children_queryset
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Connections
|
# Connections
|
||||||
#
|
#
|
||||||
|
56
netbox/dcim/migrations/0004_auto_20160701_2049.py
Normal file
56
netbox/dcim/migrations/0004_auto_20160701_2049.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.7 on 2016-07-01 20:49
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0003_auto_20160628_1721'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DeviceBay',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=50, verbose_name=b'Name')),
|
||||||
|
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='device_bays', to='dcim.Device')),
|
||||||
|
('installed_device', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='parent_bay', to='dcim.Device')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['device', 'name'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DeviceBayTemplate',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=30)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['device_type', 'name'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='devicetype',
|
||||||
|
name='subdevice_role',
|
||||||
|
field=models.NullBooleanField(choices=[(None, b'N/A'), (True, b'Parent'), (False, b'Child')], default=None, help_text=b'Parent devices house child devices in device bays. Select "None" if this device type is neither a parent nor a child.', verbose_name=b'Parent/child status'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='devicebaytemplate',
|
||||||
|
name='device_type',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='device_bay_templates', to='dcim.DeviceType'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='devicebaytemplate',
|
||||||
|
unique_together=set([('device_type', 'name')]),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='devicebay',
|
||||||
|
unique_together=set([('device', 'name')]),
|
||||||
|
),
|
||||||
|
]
|
@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q, ObjectDoesNotExist
|
from django.db.models import Count, Q, ObjectDoesNotExist
|
||||||
|
|
||||||
from extras.rpc import RPC_CLIENTS
|
from extras.rpc import RPC_CLIENTS
|
||||||
from utilities.fields import NullableCharField
|
from utilities.fields import NullableCharField
|
||||||
@ -18,6 +18,14 @@ RACK_FACE_CHOICES = [
|
|||||||
[RACK_FACE_REAR, 'Rear'],
|
[RACK_FACE_REAR, 'Rear'],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
SUBDEVICE_ROLE_PARENT = True
|
||||||
|
SUBDEVICE_ROLE_CHILD = False
|
||||||
|
SUBDEVICE_ROLE_CHOICES = (
|
||||||
|
(None, 'None'),
|
||||||
|
(SUBDEVICE_ROLE_PARENT, 'Parent'),
|
||||||
|
(SUBDEVICE_ROLE_CHILD, 'Child'),
|
||||||
|
)
|
||||||
|
|
||||||
COLOR_TEAL = 'teal'
|
COLOR_TEAL = 'teal'
|
||||||
COLOR_GREEN = 'green'
|
COLOR_GREEN = 'green'
|
||||||
COLOR_BLUE = 'blue'
|
COLOR_BLUE = 'blue'
|
||||||
@ -274,6 +282,7 @@ class Rack(CreatedUpdatedModel):
|
|||||||
# Add devices to rack units list
|
# Add devices to rack units list
|
||||||
if self.pk:
|
if self.pk:
|
||||||
for device in Device.objects.select_related('device_type__manufacturer', 'device_role')\
|
for device in Device.objects.select_related('device_type__manufacturer', 'device_role')\
|
||||||
|
.annotate(devicebay_count=Count('device_bays'))\
|
||||||
.exclude(pk=exclude)\
|
.exclude(pk=exclude)\
|
||||||
.filter(rack=self, position__gt=0)\
|
.filter(rack=self, position__gt=0)\
|
||||||
.filter(Q(face=face) | Q(device_type__is_full_depth=True)):
|
.filter(Q(face=face) | Q(device_type__is_full_depth=True)):
|
||||||
@ -380,6 +389,10 @@ class DeviceType(models.Model):
|
|||||||
help_text="This type of device has power outlets")
|
help_text="This type of device has power outlets")
|
||||||
is_network_device = models.BooleanField(default=True, verbose_name='Is a network device',
|
is_network_device = models.BooleanField(default=True, verbose_name='Is a network device',
|
||||||
help_text="This type of device has network interfaces")
|
help_text="This type of device has network interfaces")
|
||||||
|
subdevice_role = models.NullBooleanField(default=None, verbose_name='Parent/child status',
|
||||||
|
choices=SUBDEVICE_ROLE_CHOICES,
|
||||||
|
help_text="Parent devices house child devices in device bays. Select "
|
||||||
|
"\"None\" if this device type is neither a parent nor a child.")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['manufacturer', 'model']
|
ordering = ['manufacturer', 'model']
|
||||||
@ -389,11 +402,24 @@ class DeviceType(models.Model):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return "{0} {1}".format(self.manufacturer, self.model)
|
return "{} {}".format(self.manufacturer, self.model)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('dcim:devicetype', args=[self.pk])
|
return reverse('dcim:devicetype', args=[self.pk])
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
|
||||||
|
if self.u_height and self.subdevice_role == SUBDEVICE_ROLE_CHILD:
|
||||||
|
raise ValidationError("Child device types must be 0U.")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_parent_device(self):
|
||||||
|
return bool(self.subdevice_role)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_child_device(self):
|
||||||
|
return bool(self.subdevice_role is False)
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortTemplate(models.Model):
|
class ConsolePortTemplate(models.Model):
|
||||||
"""
|
"""
|
||||||
@ -481,6 +507,21 @@ class InterfaceTemplate(models.Model):
|
|||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceBayTemplate(models.Model):
|
||||||
|
"""
|
||||||
|
A template for a DeviceBay to be created for a new parent Device.
|
||||||
|
"""
|
||||||
|
device_type = models.ForeignKey('DeviceType', related_name='device_bay_templates', on_delete=models.CASCADE)
|
||||||
|
name = models.CharField(max_length=30)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['device_type', 'name']
|
||||||
|
unique_together = ['device_type', 'name']
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Devices
|
# Devices
|
||||||
#
|
#
|
||||||
@ -563,6 +604,10 @@ class Device(CreatedUpdatedModel):
|
|||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
|
||||||
|
# Child devices cannot be assigned to a rack face/unit
|
||||||
|
if self.device_type.is_child_device and (self.face is not None or self.position):
|
||||||
|
raise ValidationError("Child device types cannot be assigned a rack face or position.")
|
||||||
|
|
||||||
# Validate position/face combination
|
# Validate position/face combination
|
||||||
if self.position and self.face is None:
|
if self.position and self.face is None:
|
||||||
raise ValidationError("Must specify rack face with rack position.")
|
raise ValidationError("Must specify rack face with rack position.")
|
||||||
@ -610,6 +655,10 @@ class Device(CreatedUpdatedModel):
|
|||||||
[Interface(device=self, name=template.name, form_factor=template.form_factor,
|
[Interface(device=self, name=template.name, form_factor=template.form_factor,
|
||||||
mgmt_only=template.mgmt_only) for template in self.device_type.interface_templates.all()]
|
mgmt_only=template.mgmt_only) for template in self.device_type.interface_templates.all()]
|
||||||
)
|
)
|
||||||
|
DeviceBay.objects.bulk_create(
|
||||||
|
[DeviceBay(device=self, name=template.name) for template in
|
||||||
|
self.device_type.device_bay_templates.all()]
|
||||||
|
)
|
||||||
|
|
||||||
def to_csv(self):
|
def to_csv(self):
|
||||||
return ','.join([
|
return ','.join([
|
||||||
@ -643,6 +692,12 @@ class Device(CreatedUpdatedModel):
|
|||||||
return self.name
|
return self.name
|
||||||
return '{{{}}}'.format(self.pk)
|
return '{{{}}}'.format(self.pk)
|
||||||
|
|
||||||
|
def get_children(self):
|
||||||
|
"""
|
||||||
|
Return the set of child Devices installed in DeviceBays within this Device.
|
||||||
|
"""
|
||||||
|
return Device.objects.filter(parent_bay__device=self.pk)
|
||||||
|
|
||||||
def get_rpc_client(self):
|
def get_rpc_client(self):
|
||||||
"""
|
"""
|
||||||
Return the appropriate RPC (e.g. NETCONF, ssh, etc.) client for this device's platform, if one is defined.
|
Return the appropriate RPC (e.g. NETCONF, ssh, etc.) client for this device's platform, if one is defined.
|
||||||
@ -860,6 +915,33 @@ class InterfaceConnection(models.Model):
|
|||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceBay(models.Model):
|
||||||
|
"""
|
||||||
|
An empty space within a Device which can house a child device
|
||||||
|
"""
|
||||||
|
device = models.ForeignKey('Device', related_name='device_bays', on_delete=models.CASCADE)
|
||||||
|
name = models.CharField(max_length=50, verbose_name='Name')
|
||||||
|
installed_device = models.OneToOneField('Device', related_name='parent_bay', blank=True, null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['device', 'name']
|
||||||
|
unique_together = ['device', 'name']
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return '{} - {}'.format(self.device.name, self.name)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
|
||||||
|
# Validate that the parent Device can have DeviceBays
|
||||||
|
if not self.device.device_type.is_parent_device:
|
||||||
|
raise ValidationError("This type of device ({}) does not support device bays."
|
||||||
|
.format(self.device.device_type))
|
||||||
|
|
||||||
|
# Cannot install a device into itself, obviously
|
||||||
|
if self.device == self.installed_device:
|
||||||
|
raise ValidationError("Cannot install a device into itself.")
|
||||||
|
|
||||||
|
|
||||||
class Module(models.Model):
|
class Module(models.Model):
|
||||||
"""
|
"""
|
||||||
A Module represents a piece of hardware within a Device, such as a line card or power supply. Modules are used only
|
A Module represents a piece of hardware within a Device, such as a line card or power supply. Modules are used only
|
||||||
|
@ -4,8 +4,9 @@ from django_tables2.utils import Accessor
|
|||||||
from utilities.tables import BaseTable, ToggleColumn
|
from utilities.tables import BaseTable, ToggleColumn
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType, InterfaceTemplate,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPortTemplate, Device, DeviceBayTemplate, DeviceRole, DeviceType,
|
||||||
Interface, Manufacturer, Platform, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, Site,
|
Interface, InterfaceTemplate, Manufacturer, Platform, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
|
||||||
|
RackGroup, Site,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -201,6 +202,19 @@ class InterfaceTemplateTable(tables.Table):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceBayTemplateTable(tables.Table):
|
||||||
|
pk = ToggleColumn()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = DeviceBayTemplate
|
||||||
|
fields = ('pk', 'name')
|
||||||
|
empty_text = "None"
|
||||||
|
show_header = False
|
||||||
|
attrs = {
|
||||||
|
'class': 'table table-hover panel-body',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Device roles
|
# Device roles
|
||||||
#
|
#
|
||||||
|
@ -4,7 +4,8 @@ from secrets.views import secret_add
|
|||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
from .models import (
|
from .models import (
|
||||||
ConsolePortTemplate, ConsoleServerPortTemplate, PowerPortTemplate, PowerOutletTemplate, InterfaceTemplate,
|
ConsolePortTemplate, ConsoleServerPortTemplate, DeviceBayTemplate, PowerPortTemplate, PowerOutletTemplate,
|
||||||
|
InterfaceTemplate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -70,6 +71,10 @@ urlpatterns = [
|
|||||||
name='devicetype_add_interface'),
|
name='devicetype_add_interface'),
|
||||||
url(r'^device-types/(?P<pk>\d+)/interfaces/delete/$', views.component_template_delete,
|
url(r'^device-types/(?P<pk>\d+)/interfaces/delete/$', views.component_template_delete,
|
||||||
{'model': InterfaceTemplate}, name='devicetype_delete_interface'),
|
{'model': InterfaceTemplate}, name='devicetype_delete_interface'),
|
||||||
|
url(r'^device-types/(?P<pk>\d+)/device-bays/add/$', views.DeviceBayTemplateAddView.as_view(),
|
||||||
|
name='devicetype_add_devicebay'),
|
||||||
|
url(r'^device-types/(?P<pk>\d+)/device-bays/delete/$', views.component_template_delete,
|
||||||
|
{'model': DeviceBayTemplate}, name='devicetype_delete_devicebay'),
|
||||||
|
|
||||||
# Device roles
|
# Device roles
|
||||||
url(r'^device-roles/$', views.DeviceRoleListView.as_view(), name='devicerole_list'),
|
url(r'^device-roles/$', views.DeviceRoleListView.as_view(), name='devicerole_list'),
|
||||||
@ -125,6 +130,13 @@ urlpatterns = [
|
|||||||
url(r'^power-outlets/(?P<pk>\d+)/edit/$', views.poweroutlet_edit, name='poweroutlet_edit'),
|
url(r'^power-outlets/(?P<pk>\d+)/edit/$', views.poweroutlet_edit, name='poweroutlet_edit'),
|
||||||
url(r'^power-outlets/(?P<pk>\d+)/delete/$', views.poweroutlet_delete, name='poweroutlet_delete'),
|
url(r'^power-outlets/(?P<pk>\d+)/delete/$', views.poweroutlet_delete, name='poweroutlet_delete'),
|
||||||
|
|
||||||
|
# Device bays
|
||||||
|
url(r'^devices/(?P<pk>\d+)/bays/add/$', views.devicebay_add, name='devicebay_add'),
|
||||||
|
url(r'^device-bays/(?P<pk>\d+)/edit/$', views.devicebay_edit, name='devicebay_edit'),
|
||||||
|
url(r'^device-bays/(?P<pk>\d+)/delete/$', views.devicebay_delete, name='devicebay_delete'),
|
||||||
|
url(r'^device-bays/(?P<pk>\d+)/populate/$', views.devicebay_populate, name='devicebay_populate'),
|
||||||
|
url(r'^device-bays/(?P<pk>\d+)/depopulate/$', views.devicebay_depopulate, name='devicebay_depopulate'),
|
||||||
|
|
||||||
# Console/power/interface connections
|
# Console/power/interface connections
|
||||||
url(r'^console-connections/$', views.ConsoleConnectionsListView.as_view(), name='console_connections_list'),
|
url(r'^console-connections/$', views.ConsoleConnectionsListView.as_view(), name='console_connections_list'),
|
||||||
url(r'^console-connections/import/$', views.ConsoleConnectionsBulkImportView.as_view(), name='console_connections_import'),
|
url(r'^console-connections/import/$', views.ConsoleConnectionsBulkImportView.as_view(), name='console_connections_import'),
|
||||||
|
@ -24,8 +24,9 @@ from utilities.views import (
|
|||||||
from . import filters, forms, tables
|
from . import filters, forms, tables
|
||||||
from .models import (
|
from .models import (
|
||||||
CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device,
|
CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device,
|
||||||
DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, Module, Platform,
|
DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate,
|
||||||
PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, Site,
|
Manufacturer, Module, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
||||||
|
Site,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -153,7 +154,8 @@ def rack(request, pk):
|
|||||||
|
|
||||||
rack = get_object_or_404(Rack, pk=pk)
|
rack = get_object_or_404(Rack, pk=pk)
|
||||||
|
|
||||||
nonracked_devices = Device.objects.filter(rack=rack, position__isnull=True)
|
nonracked_devices = Device.objects.filter(rack=rack, position__isnull=True)\
|
||||||
|
.select_related('device_type__manufacturer')
|
||||||
next_rack = Rack.objects.filter(site=rack.site, name__gt=rack.name).order_by('name').first()
|
next_rack = Rack.objects.filter(site=rack.site, name__gt=rack.name).order_by('name').first()
|
||||||
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()
|
||||||
|
|
||||||
@ -263,12 +265,14 @@ def devicetype(request, pk):
|
|||||||
powerport_table = tables.PowerPortTemplateTable(PowerPortTemplate.objects.filter(device_type=devicetype))
|
powerport_table = tables.PowerPortTemplateTable(PowerPortTemplate.objects.filter(device_type=devicetype))
|
||||||
poweroutlet_table = tables.PowerOutletTemplateTable(PowerOutletTemplate.objects.filter(device_type=devicetype))
|
poweroutlet_table = tables.PowerOutletTemplateTable(PowerOutletTemplate.objects.filter(device_type=devicetype))
|
||||||
interface_table = tables.InterfaceTemplateTable(InterfaceTemplate.objects.filter(device_type=devicetype))
|
interface_table = tables.InterfaceTemplateTable(InterfaceTemplate.objects.filter(device_type=devicetype))
|
||||||
|
devicebay_table = tables.DeviceBayTemplateTable(DeviceBayTemplate.objects.filter(device_type=devicetype))
|
||||||
if request.user.has_perm('dcim.change_devicetype'):
|
if request.user.has_perm('dcim.change_devicetype'):
|
||||||
consoleport_table.base_columns['pk'].visible = True
|
consoleport_table.base_columns['pk'].visible = True
|
||||||
consoleserverport_table.base_columns['pk'].visible = True
|
consoleserverport_table.base_columns['pk'].visible = True
|
||||||
powerport_table.base_columns['pk'].visible = True
|
powerport_table.base_columns['pk'].visible = True
|
||||||
poweroutlet_table.base_columns['pk'].visible = True
|
poweroutlet_table.base_columns['pk'].visible = True
|
||||||
interface_table.base_columns['pk'].visible = True
|
interface_table.base_columns['pk'].visible = True
|
||||||
|
devicebay_table.base_columns['pk'].visible = True
|
||||||
|
|
||||||
return render(request, 'dcim/devicetype.html', {
|
return render(request, 'dcim/devicetype.html', {
|
||||||
'devicetype': devicetype,
|
'devicetype': devicetype,
|
||||||
@ -277,6 +281,7 @@ def devicetype(request, pk):
|
|||||||
'powerport_table': powerport_table,
|
'powerport_table': powerport_table,
|
||||||
'poweroutlet_table': poweroutlet_table,
|
'poweroutlet_table': poweroutlet_table,
|
||||||
'interface_table': interface_table,
|
'interface_table': interface_table,
|
||||||
|
'devicebay_table': devicebay_table,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -395,6 +400,11 @@ class InterfaceTemplateAddView(ComponentTemplateCreateView):
|
|||||||
form = forms.InterfaceTemplateForm
|
form = forms.InterfaceTemplateForm
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceBayTemplateAddView(ComponentTemplateCreateView):
|
||||||
|
model = DeviceBayTemplate
|
||||||
|
form = forms.DeviceBayTemplateForm
|
||||||
|
|
||||||
|
|
||||||
def component_template_delete(request, pk, model):
|
def component_template_delete(request, pk, model):
|
||||||
|
|
||||||
devicetype = get_object_or_404(DeviceType, pk=pk)
|
devicetype = get_object_or_404(DeviceType, pk=pk)
|
||||||
@ -510,6 +520,7 @@ def device(request, pk):
|
|||||||
.select_related('connected_as_a', 'connected_as_b', 'circuit')
|
.select_related('connected_as_a', 'connected_as_b', 'circuit')
|
||||||
mgmt_interfaces = Interface.objects.filter(device=device, mgmt_only=True)\
|
mgmt_interfaces = Interface.objects.filter(device=device, mgmt_only=True)\
|
||||||
.select_related('connected_as_a', 'connected_as_b', 'circuit')
|
.select_related('connected_as_a', 'connected_as_b', 'circuit')
|
||||||
|
device_bays = DeviceBay.objects.filter(device=device).select_related('installed_device')
|
||||||
|
|
||||||
# Gather any secrets which belong to this device
|
# Gather any secrets which belong to this device
|
||||||
secrets = device.secrets.all()
|
secrets = device.secrets.all()
|
||||||
@ -540,6 +551,7 @@ def device(request, pk):
|
|||||||
'power_outlets': power_outlets,
|
'power_outlets': power_outlets,
|
||||||
'interfaces': interfaces,
|
'interfaces': interfaces,
|
||||||
'mgmt_interfaces': mgmt_interfaces,
|
'mgmt_interfaces': mgmt_interfaces,
|
||||||
|
'device_bays': device_bays,
|
||||||
'ip_addresses': ip_addresses,
|
'ip_addresses': ip_addresses,
|
||||||
'secrets': secrets,
|
'secrets': secrets,
|
||||||
'related_devices': related_devices,
|
'related_devices': related_devices,
|
||||||
@ -550,7 +562,7 @@ class DeviceEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
permission_required = 'dcim.change_device'
|
permission_required = 'dcim.change_device'
|
||||||
model = Device
|
model = Device
|
||||||
form_class = forms.DeviceForm
|
form_class = forms.DeviceForm
|
||||||
fields_initial = ['site', 'rack', 'position', 'face']
|
fields_initial = ['site', 'rack', 'position', 'face', 'device_bay']
|
||||||
template_name = 'dcim/device_edit.html'
|
template_name = 'dcim/device_edit.html'
|
||||||
cancel_url = 'dcim:device_list'
|
cancel_url = 'dcim:device_list'
|
||||||
|
|
||||||
@ -1342,6 +1354,143 @@ class InterfaceBulkAddView(PermissionRequiredMixin, BulkEditView):
|
|||||||
len(selected_devices)))
|
len(selected_devices)))
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Device bays
|
||||||
|
#
|
||||||
|
|
||||||
|
@permission_required('dcim.add_devicebay')
|
||||||
|
def devicebay_add(request, pk):
|
||||||
|
|
||||||
|
device = get_object_or_404(Device, pk=pk)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = forms.DeviceBayCreateForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
|
||||||
|
device_bays = []
|
||||||
|
for name in form.cleaned_data['name_pattern']:
|
||||||
|
devicebay_form = forms.DeviceBayForm({
|
||||||
|
'device': device.pk,
|
||||||
|
'name': name,
|
||||||
|
})
|
||||||
|
if devicebay_form.is_valid():
|
||||||
|
device_bays.append(devicebay_form.save(commit=False))
|
||||||
|
else:
|
||||||
|
for err in devicebay_form.errors.get('__all__', []):
|
||||||
|
form.add_error('name_pattern', err)
|
||||||
|
|
||||||
|
if not form.errors:
|
||||||
|
DeviceBay.objects.bulk_create(device_bays)
|
||||||
|
messages.success(request, "Added {} device bay(s) to {}".format(len(device_bays), device))
|
||||||
|
if '_addanother' in request.POST:
|
||||||
|
return redirect('dcim:devicebay_add', pk=device.pk)
|
||||||
|
else:
|
||||||
|
return redirect('dcim:device', pk=device.pk)
|
||||||
|
|
||||||
|
else:
|
||||||
|
form = forms.DeviceBayCreateForm()
|
||||||
|
|
||||||
|
return render(request, 'dcim/devicebay_edit.html', {
|
||||||
|
'device': device,
|
||||||
|
'form': form,
|
||||||
|
'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@permission_required('dcim.change_devicebay')
|
||||||
|
def devicebay_edit(request, pk):
|
||||||
|
|
||||||
|
devicebay = get_object_or_404(DeviceBay, pk=pk)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = forms.DeviceBayForm(request.POST, instance=devicebay)
|
||||||
|
if form.is_valid():
|
||||||
|
devicebay = form.save()
|
||||||
|
messages.success(request, "Modified {} bay {}".format(devicebay.device.name, devicebay.name))
|
||||||
|
return redirect('dcim:device', pk=devicebay.device.pk)
|
||||||
|
|
||||||
|
else:
|
||||||
|
form = forms.DeviceBayForm(instance=devicebay)
|
||||||
|
|
||||||
|
return render(request, 'dcim/devicebay_edit.html', {
|
||||||
|
'devicebay': devicebay,
|
||||||
|
'form': form,
|
||||||
|
'cancel_url': reverse('dcim:device', kwargs={'pk': devicebay.device.pk}),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@permission_required('dcim.delete_devicebay')
|
||||||
|
def devicebay_delete(request, pk):
|
||||||
|
|
||||||
|
devicebay = get_object_or_404(DeviceBay, pk=pk)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = ConfirmationForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
devicebay.delete()
|
||||||
|
messages.success(request, "Device bay {} has been deleted from {}".format(devicebay, devicebay.device))
|
||||||
|
return redirect('dcim:device', pk=devicebay.device.pk)
|
||||||
|
|
||||||
|
else:
|
||||||
|
form = ConfirmationForm()
|
||||||
|
|
||||||
|
return render(request, 'dcim/devicebay_delete.html', {
|
||||||
|
'devicebay': devicebay,
|
||||||
|
'form': form,
|
||||||
|
'cancel_url': reverse('dcim:device', kwargs={'pk': devicebay.device.pk}),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@permission_required('dcim.change_devicebay')
|
||||||
|
def devicebay_populate(request, pk):
|
||||||
|
|
||||||
|
device_bay = get_object_or_404(DeviceBay, pk=pk)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = forms.PopulateDeviceBayForm(device_bay, request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
|
||||||
|
device_bay.installed_device = form.cleaned_data['installed_device']
|
||||||
|
device_bay.save()
|
||||||
|
|
||||||
|
if not form.errors:
|
||||||
|
messages.success(request, "Added {} to {}".format(device_bay.installed_device, device_bay))
|
||||||
|
return redirect('dcim:device', pk=device_bay.device.pk)
|
||||||
|
|
||||||
|
else:
|
||||||
|
form = forms.PopulateDeviceBayForm(device_bay)
|
||||||
|
|
||||||
|
return render(request, 'dcim/devicebay_populate.html', {
|
||||||
|
'device_bay': device_bay,
|
||||||
|
'form': form,
|
||||||
|
'cancel_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@permission_required('dcim.change_devicebay')
|
||||||
|
def devicebay_depopulate(request, pk):
|
||||||
|
|
||||||
|
device_bay = get_object_or_404(DeviceBay, pk=pk)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = ConfirmationForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
removed_device = device_bay.installed_device
|
||||||
|
device_bay.installed_device = None
|
||||||
|
device_bay.save()
|
||||||
|
messages.success(request, "{} has been removed from {}".format(removed_device, device_bay))
|
||||||
|
return redirect('dcim:device', pk=device_bay.device.pk)
|
||||||
|
|
||||||
|
else:
|
||||||
|
form = ConfirmationForm()
|
||||||
|
|
||||||
|
return render(request, 'dcim/devicebay_depopulate.html', {
|
||||||
|
'device_bay': device_bay,
|
||||||
|
'form': form,
|
||||||
|
'cancel_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Interface connections
|
# Interface connections
|
||||||
#
|
#
|
||||||
|
@ -31,7 +31,12 @@
|
|||||||
<li class="occupied h{{ u.device.device_type.u_height }}u{% ifequal u.device.face face_id %} {{ u.device.device_role.color }}{% endifequal %}">
|
<li class="occupied h{{ u.device.device_type.u_height }}u{% ifequal u.device.face face_id %} {{ u.device.device_role.color }}{% endifequal %}">
|
||||||
{% ifequal u.device.face face_id %}
|
{% ifequal u.device.face face_id %}
|
||||||
<a href="{% url 'dcim:device' pk=u.device.pk %}" data-toggle="popover" data-trigger="hover" data-container="body" data-html="true"
|
<a href="{% url 'dcim:device' pk=u.device.pk %}" data-toggle="popover" data-trigger="hover" data-container="body" data-html="true"
|
||||||
data-content="{{ u.device.device_role }}<br />{{ u.device.device_type }} ({{ u.device.device_type.u_height }}U)">{{ u.device.name|default:u.device.device_role }}</a>
|
data-content="{{ u.device.device_role }}<br />{{ u.device.device_type }} ({{ u.device.device_type.u_height }}U)">
|
||||||
|
{{ u.device.name|default:u.device.device_role }}
|
||||||
|
{% if u.device.devicebay_count %}
|
||||||
|
({{ u.device.get_children.count }}/{{ u.device.devicebay_count }})
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span>{{ u.device.name|default:u.device.device_role }}</span>
|
<span>{{ u.device.name|default:u.device.device_role }}</span>
|
||||||
{% endifequal %}
|
{% endifequal %}
|
||||||
|
@ -29,7 +29,12 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Position</td>
|
<td>Position</td>
|
||||||
<td>
|
<td>
|
||||||
{% if device.position %}
|
{% if device.parent_bay %}
|
||||||
|
{% with device.parent_bay.device as parent %}
|
||||||
|
<span>U{{ parent.position }} / {{ parent.get_face_display }}
|
||||||
|
(<a href="{{ parent.get_absolute_url }}">{{ parent }}</a> - {{ device.parent_bay.name }})</span>
|
||||||
|
{% endwith %}
|
||||||
|
{% elif device.position %}
|
||||||
<span>U{{ device.position }} / {{ device.get_face_display }}</span>
|
<span>U{{ device.position }} / {{ device.get_face_display }}</span>
|
||||||
{% elif device.device_type.u_height %}
|
{% elif device.device_type.u_height %}
|
||||||
<span class="label label-warning">Not racked</span>
|
<span class="label label-warning">Not racked</span>
|
||||||
@ -268,6 +273,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
{% if device_bays or device.device_type.is_parent_device %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
{% if perms.dcim.add_devicebay %}
|
||||||
|
<a href="{% url 'dcim:devicebay_add' pk=device.pk %}" class="btn btn-primary btn-xs pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add Device Bays</a>
|
||||||
|
{% endif %}
|
||||||
|
<strong>Device Bays</strong>
|
||||||
|
</div>
|
||||||
|
<table class="table table-hover panel-body">
|
||||||
|
{% for devicebay in device_bays %}
|
||||||
|
{% include 'dcim/inc/_devicebay.html' %}
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4">No device bays defined</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if interfaces or device.device_type.is_network_device %}
|
{% if interfaces or device.device_type.is_network_device %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
|
8
netbox/templates/dcim/devicebay_delete.html
Normal file
8
netbox/templates/dcim/devicebay_delete.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{% extends 'utilities/confirmation_form.html' %}
|
||||||
|
{% load form_helpers %}
|
||||||
|
|
||||||
|
{% block title %}Delete device bay {{ devicebay }}?{% endblock %}
|
||||||
|
|
||||||
|
{% block message %}
|
||||||
|
<p>Are you sure you want to delete this device bay from <strong>{{ devicebay.device }}</strong>?</p>
|
||||||
|
{% endblock %}
|
8
netbox/templates/dcim/devicebay_depopulate.html
Normal file
8
netbox/templates/dcim/devicebay_depopulate.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{% extends 'utilities/confirmation_form.html' %}
|
||||||
|
{% load form_helpers %}
|
||||||
|
|
||||||
|
{% block title %}Remove {{ device_bay.installed_device }} from {{ device_bay }}?{% endblock %}
|
||||||
|
|
||||||
|
{% block message %}
|
||||||
|
<p>Are you sure you want to remove <strong>{{ device_bay.installed_device }}</strong> from <strong>{{ device_bay }}</strong>?</p>
|
||||||
|
{% endblock %}
|
51
netbox/templates/dcim/devicebay_edit.html
Normal file
51
netbox/templates/dcim/devicebay_edit.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{% extends '_base.html' %}
|
||||||
|
{% load form_helpers %}
|
||||||
|
|
||||||
|
{% block title %}{% if devicebay.pk %}Editing {{ devicebay.device }} {{ devicebay }}{% else %}Add a Device Bay ({{ device }}){% endif %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form action="." method="post" class="form form-horizontal">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 col-md-offset-3">
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<div class="panel panel-danger">
|
||||||
|
<div class="panel-heading"><strong>Errors</strong></div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
{% if poweroutlet.pk %}
|
||||||
|
<strong>Editing {{ devicebay }}</strong>
|
||||||
|
{% else %}
|
||||||
|
<strong>Add a Device Bay</strong>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label required">Device</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<p class="form-control-static">{% if devicebay %}{{ devicebay.device }}{% else %}{{ device }}{% endif %}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% render_form form %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-md-9 col-md-offset-3">
|
||||||
|
{% if devicebay.pk %}
|
||||||
|
<button type="submit" name="_update" class="btn btn-primary">Save</button>
|
||||||
|
{% else %}
|
||||||
|
<button type="submit" name="_create" class="btn btn-primary">Create</button>
|
||||||
|
<button type="submit" name="_addanother" class="btn btn-primary">Create and Add More</button>
|
||||||
|
{% endif %}
|
||||||
|
<a href="{{ cancel_url }}" class="btn btn-default">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
46
netbox/templates/dcim/devicebay_populate.html
Normal file
46
netbox/templates/dcim/devicebay_populate.html
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{% extends '_base.html' %}
|
||||||
|
{% load form_helpers %}
|
||||||
|
|
||||||
|
{% block title %}Populate {{ device_bay }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form action="." method="post" class="form form-horizontal">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 col-md-offset-3">
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<div class="panel panel-danger">
|
||||||
|
<div class="panel-heading"><strong>Errors</strong></div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">Populate {{ device_bay }}</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label required">Parent Device</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<p class="form-control-static">{{ device_bay.device }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label required">Bay</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<p class="form-control-static">{{ device_bay.name }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% render_form form %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-md-9 col-md-offset-3">
|
||||||
|
<button type="submit" name="_update" class="btn btn-primary">Save</button>
|
||||||
|
<a href="{{ cancel_url }}" class="btn btn-default">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
@ -77,6 +77,7 @@
|
|||||||
{% include 'dcim/inc/devicetype_component_table.html' with table=powerport_table title='Power Ports' add_url='dcim:devicetype_add_powerport' delete_url='dcim:devicetype_delete_powerport' %}
|
{% include 'dcim/inc/devicetype_component_table.html' with table=powerport_table title='Power Ports' add_url='dcim:devicetype_add_powerport' delete_url='dcim:devicetype_delete_powerport' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
{% include 'dcim/inc/devicetype_component_table.html' with table=devicebay_table title='Device Bays' add_url='dcim:devicetype_add_devicebay' delete_url='dcim:devicetype_delete_devicebay' %}
|
||||||
{% include 'dcim/inc/devicetype_component_table.html' with table=interface_table title='Interfaces' add_url='dcim:devicetype_add_interface' delete_url='dcim:devicetype_delete_interface' %}
|
{% include 'dcim/inc/devicetype_component_table.html' with table=interface_table title='Interfaces' add_url='dcim:devicetype_add_interface' delete_url='dcim:devicetype_delete_interface' %}
|
||||||
{% include 'dcim/inc/devicetype_component_table.html' with table=consoleserverport_table title='Console Server Ports' add_url='dcim:devicetype_add_consoleserverport' delete_url='dcim:devicetype_delete_consoleserverport' %}
|
{% include 'dcim/inc/devicetype_component_table.html' with table=consoleserverport_table title='Console Server Ports' add_url='dcim:devicetype_add_consoleserverport' delete_url='dcim:devicetype_delete_consoleserverport' %}
|
||||||
{% include 'dcim/inc/devicetype_component_table.html' with table=poweroutlet_table title='Power Outlets' add_url='dcim:devicetype_add_poweroutlet' delete_url='dcim:devicetype_delete_poweroutlet' %}
|
{% include 'dcim/inc/devicetype_component_table.html' with table=poweroutlet_table title='Power Outlets' add_url='dcim:devicetype_add_poweroutlet' delete_url='dcim:devicetype_delete_poweroutlet' %}
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
<li><a href="{% url 'dcim:site' slug=device.rack.site.slug %}">{{ device.rack.site }}</a></li>
|
<li><a href="{% url 'dcim:site' slug=device.rack.site.slug %}">{{ device.rack.site }}</a></li>
|
||||||
<li><a href="{% url 'dcim:rack_list' %}?site={{ device.rack.site.slug }}">Racks</a></li>
|
<li><a href="{% url 'dcim:rack_list' %}?site={{ device.rack.site.slug }}">Racks</a></li>
|
||||||
<li><a href="{% url 'dcim:rack' pk=device.rack.pk %}">{{ device.rack }}</a></li>
|
<li><a href="{% url 'dcim:rack' pk=device.rack.pk %}">{{ device.rack }}</a></li>
|
||||||
|
{% if device.parent_bay %}
|
||||||
|
<li><a href="{% url 'dcim:device' pk=device.parent_bay.device.pk %}">{{ device.parent_bay.device }}</a></li>
|
||||||
|
<li>{{ device.parent_bay.name }}</li>
|
||||||
|
{% endif %}
|
||||||
<li>{{ device }}</li>
|
<li>{{ device }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
39
netbox/templates/dcim/inc/_devicebay.html
Normal file
39
netbox/templates/dcim/inc/_devicebay.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<i class="fa fa-fw fa-{% if devicebay.installed_device %}dot-circle-o{% else %}circle-o{% endif %}"></i> {{ devicebay.name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if devicebay.installed_device %}
|
||||||
|
<a href="{% url 'dcim:device' pk=devicebay.installed_device.pk %}">{{ devicebay.installed_device }}</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">Vacant</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{% if perms.dcim.change_devicebay %}
|
||||||
|
{% if devicebay.installed_device %}
|
||||||
|
<a href="{% url 'dcim:devicebay_depopulate' pk=devicebay.pk %}" class="btn btn-danger btn-xs">
|
||||||
|
<i class="glyphicon glyphicon-remove" aria-hidden="true" title="Remove device"></i>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'dcim:devicebay_populate' pk=devicebay.pk %}" class="btn btn-success btn-xs">
|
||||||
|
<i class="glyphicon glyphicon-plus" aria-hidden="true" title="Install device"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="{% url 'dcim:devicebay_edit' pk=devicebay.pk %}" class="btn btn-info btn-xs">
|
||||||
|
<i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit device bay"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.dcim.delete_devicebay %}
|
||||||
|
{% if devicebay.installed_device %}
|
||||||
|
<button class="btn btn-danger btn-xs" disabled="disabled">
|
||||||
|
<i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'dcim:devicebay_delete' pk=devicebay.pk %}" class="btn btn-danger btn-xs">
|
||||||
|
<i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete device bay"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
@ -112,6 +112,12 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if nonracked_devices %}
|
{% if nonracked_devices %}
|
||||||
<table class="table table-hover panel-body">
|
<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 %}
|
{% for device in nonracked_devices %}
|
||||||
<tr{% if device.device_type.u_height %} class="warning"{% endif %}>
|
<tr{% if device.device_type.u_height %} class="warning"{% endif %}>
|
||||||
<td>
|
<td>
|
||||||
@ -119,6 +125,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>{{ device.device_role }}</td>
|
<td>{{ device.device_role }}</td>
|
||||||
<td>{{ device.device_type }}</td>
|
<td>{{ device.device_type }}</td>
|
||||||
|
<td>{% if device.parent_bay %}<a href="{{ device.parent_bay.device.get_absolute_url }}">{{ device.parent_bay }}</a>{% endif %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
Loading…
Reference in New Issue
Block a user