فهرست منبع

Merge pull request #5251 from netbox-community/4786-device-component-tables

#4786: Convert device component templates to tables
Jeremy Stretch 5 سال پیش
والد
کامیت
9cbfc0ce9a

+ 283 - 19
netbox/dcim/tables/devices.py

@@ -10,15 +10,28 @@ from utilities.tables import (
     BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn,
     TagColumn, ToggleColumn,
 )
-from .template_code import DEVICE_LINK, INTERFACE_IPADDRESSES, INTERFACE_TAGGED_VLANS
+from .template_code import (
+    CABLETERMINATION, CONSOLEPORT_BUTTONS, CONSOLESERVERPORT_BUTTONS, DEVICE_LINK, DEVICEBAY_BUTTONS, DEVICEBAY_STATUS,
+    FRONTPORT_BUTTONS, INTERFACE_BUTTONS, INTERFACE_IPADDRESSES, INTERFACE_TAGGED_VLANS, POWEROUTLET_BUTTONS,
+    POWERPORT_BUTTONS, REARPORT_BUTTONS,
+)
 
 __all__ = (
     'ConsolePortTable',
     'ConsoleServerPortTable',
-    'DeviceImportTable',
-    'DeviceTable',
     'DeviceBayTable',
+    'DeviceConsolePortTable',
+    'DeviceConsoleServerPortTable',
+    'DeviceDeviceBayTable',
+    'DeviceFrontPortTable',
+    'DeviceImportTable',
+    'DeviceInterfaceTable',
+    'DeviceInventoryItemTable',
+    'DevicePowerPortTable',
+    'DevicePowerOutletTable',
+    'DeviceRearPortTable',
     'DeviceRoleTable',
+    'DeviceTable',
     'FrontPortTable',
     'InterfaceTable',
     'InventoryItemTable',
@@ -204,29 +217,93 @@ class DeviceComponentTable(BaseTable):
         order_by = ('device', 'name')
 
 
-class ConsolePortTable(DeviceComponentTable):
+class CableTerminationTable(BaseTable):
+    cable = tables.Column(
+        linkify=True
+    )
+    cable_peer = tables.TemplateColumn(
+        accessor='get_cable_peer',
+        template_code=CABLETERMINATION,
+        orderable=False
+    )
+
+
+class PathEndpointTable(CableTerminationTable):
+    connection = tables.TemplateColumn(
+        accessor='_path.destination',
+        template_code=CABLETERMINATION,
+        verbose_name='Connection',
+        orderable=False
+    )
+
+
+class ConsolePortTable(DeviceComponentTable, PathEndpointTable):
     tags = TagColumn(
         url_name='dcim:consoleport_list'
     )
 
     class Meta(DeviceComponentTable.Meta):
         model = ConsolePort
-        fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'cable', 'tags')
+        fields = (
+            'pk', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'connection', 'tags',
+        )
         default_columns = ('pk', 'device', 'name', 'label', 'type', 'description')
 
 
-class ConsoleServerPortTable(DeviceComponentTable):
+class DeviceConsolePortTable(ConsolePortTable):
+    name = tables.TemplateColumn(
+        template_code='<i class="fa fa-keyboard-o"></i> <a href="{{ record.get_absolute_url }}">{{ value }}</a>'
+    )
+    actions = ButtonsColumn(
+        model=ConsolePort,
+        buttons=('edit', 'delete'),
+        prepend_template=CONSOLEPORT_BUTTONS
+    )
+
+    class Meta(DeviceComponentTable.Meta):
+        model = ConsolePort
+        fields = (
+            'pk', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'connection', 'tags', 'actions'
+        )
+        default_columns = ('pk', 'name', 'label', 'type', 'description', 'cable', 'connection', 'actions')
+        row_attrs = {
+            'class': lambda record: record.cable.get_status_class() if record.cable else ''
+        }
+
+
+class ConsoleServerPortTable(DeviceComponentTable, PathEndpointTable):
     tags = TagColumn(
         url_name='dcim:consoleserverport_list'
     )
 
     class Meta(DeviceComponentTable.Meta):
         model = ConsoleServerPort
-        fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'cable', 'tags')
+        fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'connection', 'tags')
         default_columns = ('pk', 'device', 'name', 'label', 'type', 'description')
 
 
-class PowerPortTable(DeviceComponentTable):
+class DeviceConsoleServerPortTable(ConsoleServerPortTable):
+    name = tables.TemplateColumn(
+        template_code='<i class="fa fa-keyboard-o"></i> <a href="{{ record.get_absolute_url }}">{{ value }}</a>'
+    )
+    actions = ButtonsColumn(
+        model=ConsoleServerPort,
+        buttons=('edit', 'delete'),
+        prepend_template=CONSOLESERVERPORT_BUTTONS
+    )
+
+    class Meta(DeviceComponentTable.Meta):
+        model = ConsoleServerPort
+        fields = (
+            'pk', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'connection', 'tags', 'actions'
+        )
+        default_columns = ('pk', 'name', 'label', 'type', 'description', 'cable', 'connection', 'actions')
+        row_attrs = {
+            'class': lambda record: record.cable.get_status_class() if record.cable else ''
+        }
+
+
+class PowerPortTable(DeviceComponentTable, PathEndpointTable):
     tags = TagColumn(
         url_name='dcim:powerport_list'
     )
@@ -234,22 +311,78 @@ class PowerPortTable(DeviceComponentTable):
     class Meta(DeviceComponentTable.Meta):
         model = PowerPort
         fields = (
-            'pk', 'device', 'name', 'label', 'type', 'description', 'maximum_draw', 'allocated_draw', 'cable', 'tags',
+            'pk', 'device', 'name', 'label', 'type', 'description', 'maximum_draw', 'allocated_draw', 'cable',
+            'cable_peer', 'connection', 'tags',
         )
         default_columns = ('pk', 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description')
 
 
-class PowerOutletTable(DeviceComponentTable):
+class DevicePowerPortTable(PowerPortTable):
+    name = tables.TemplateColumn(
+        template_code='<i class="fa fa-bolt"></i> <a href="{{ record.get_absolute_url }}">{{ value }}</a>'
+    )
+    actions = ButtonsColumn(
+        model=PowerPort,
+        buttons=('edit', 'delete'),
+        prepend_template=POWERPORT_BUTTONS
+    )
+
+    class Meta(DeviceComponentTable.Meta):
+        model = PowerPort
+        fields = (
+            'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable', 'cable_peer',
+            'connection', 'tags', 'actions',
+        )
+        default_columns = (
+            'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable', 'connection',
+            'actions',
+        )
+        row_attrs = {
+            'class': lambda record: record.cable.get_status_class() if record.cable else ''
+        }
+
+
+class PowerOutletTable(DeviceComponentTable, PathEndpointTable):
+    power_port = tables.Column(
+        linkify=True
+    )
     tags = TagColumn(
         url_name='dcim:poweroutlet_list'
     )
 
     class Meta(DeviceComponentTable.Meta):
         model = PowerOutlet
-        fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'power_port', 'feed_leg', 'cable', 'tags')
+        fields = (
+            'pk', 'device', 'name', 'label', 'type', 'description', 'power_port', 'feed_leg', 'cable', 'cable_peer',
+            'connection', 'tags',
+        )
         default_columns = ('pk', 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description')
 
 
+class DevicePowerOutletTable(PowerOutletTable):
+    name = tables.TemplateColumn(
+        template_code='<i class="fa fa-bolt"></i> <a href="{{ record.get_absolute_url }}">{{ value }}</a>'
+    )
+    actions = ButtonsColumn(
+        model=PowerOutlet,
+        buttons=('edit', 'delete'),
+        prepend_template=POWEROUTLET_BUTTONS
+    )
+
+    class Meta(DeviceComponentTable.Meta):
+        model = PowerOutlet
+        fields = (
+            'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable', 'cable_peer', 'connection',
+            'tags', 'actions',
+        )
+        default_columns = (
+            'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable', 'connection', 'actions',
+        )
+        row_attrs = {
+            'class': lambda record: record.cable.get_status_class() if record.cable else ''
+        }
+
+
 class BaseInterfaceTable(BaseTable):
     enabled = BooleanColumn()
     ip_addresses = tables.TemplateColumn(
@@ -265,7 +398,7 @@ class BaseInterfaceTable(BaseTable):
     )
 
 
-class InterfaceTable(DeviceComponentTable, BaseInterfaceTable):
+class InterfaceTable(DeviceComponentTable, BaseInterfaceTable, PathEndpointTable):
     tags = TagColumn(
         url_name='dcim:interface_list'
     )
@@ -274,15 +407,49 @@ class InterfaceTable(DeviceComponentTable, BaseInterfaceTable):
         model = Interface
         fields = (
             'pk', 'device', 'name', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'mode', 'mac_address',
-            'description', 'cable', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans',
+            'description', 'cable', 'cable_peer', 'connection', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans',
         )
         default_columns = ('pk', 'device', 'name', 'label', 'enabled', 'type', 'description')
 
 
-class FrontPortTable(DeviceComponentTable):
+class DeviceInterfaceTable(InterfaceTable):
+    name = tables.TemplateColumn(
+        template_code='<i class="fa fa-{% if iface.mgmt_only %}wrench{% elif iface.is_lag %}align-justify'
+                      '{% elif iface.is_virtual %}circle{% elif iface.is_wireless %}wifi{% else %}exchange'
+                      '{% endif %}"></i> <a href="{{ record.get_absolute_url }}">{{ value }}</a>'
+    )
+    lag = tables.Column(
+        linkify=True,
+        verbose_name='LAG'
+    )
+    actions = ButtonsColumn(
+        model=Interface,
+        buttons=('edit', 'delete'),
+        prepend_template=INTERFACE_BUTTONS
+    )
+
+    class Meta(DeviceComponentTable.Meta):
+        model = Interface
+        fields = (
+            'pk', 'name', 'label', 'enabled', 'type', 'lag', 'mgmt_only', 'mtu', 'mode', 'mac_address', 'description',
+            'cable', 'cable_peer', 'connection', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans', 'actions',
+        )
+        default_columns = (
+            'pk', 'name', 'label', 'enabled', 'type', 'lag', 'mtu', 'mode', 'description', 'ip_addresses', 'cable',
+            'connection', 'actions',
+        )
+        row_attrs = {
+            'class': lambda record: record.cable.get_status_class() if record.cable else ''
+        }
+
+
+class FrontPortTable(DeviceComponentTable, CableTerminationTable):
     rear_port_position = tables.Column(
         verbose_name='Position'
     )
+    rear_port = tables.Column(
+        linkify=True
+    )
     tags = TagColumn(
         url_name='dcim:frontport_list'
     )
@@ -290,23 +457,77 @@ class FrontPortTable(DeviceComponentTable):
     class Meta(DeviceComponentTable.Meta):
         model = FrontPort
         fields = (
-            'pk', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'tags',
+            'pk', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable',
+            'cable_peer', 'tags',
         )
         default_columns = ('pk', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description')
 
 
-class RearPortTable(DeviceComponentTable):
+class DeviceFrontPortTable(FrontPortTable):
+    name = tables.TemplateColumn(
+        template_code='<i class="fa fa-square{% if not record.cable %}-o{% endif %}"></i> '
+                      '<a href="{{ record.get_absolute_url }}">{{ value }}</a>'
+    )
+    actions = ButtonsColumn(
+        model=FrontPort,
+        buttons=('edit', 'delete'),
+        prepend_template=FRONTPORT_BUTTONS
+    )
+
+    class Meta(DeviceComponentTable.Meta):
+        model = FrontPort
+        fields = (
+            'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'cable_peer',
+            'tags', 'actions',
+        )
+        default_columns = (
+            'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'cable_peer',
+            'actions',
+        )
+        row_attrs = {
+            'class': lambda record: record.cable.get_status_class() if record.cable else ''
+        }
+
+
+class RearPortTable(DeviceComponentTable, CableTerminationTable):
     tags = TagColumn(
         url_name='dcim:rearport_list'
     )
 
     class Meta(DeviceComponentTable.Meta):
         model = RearPort
-        fields = ('pk', 'device', 'name', 'label', 'type', 'positions', 'description', 'cable', 'tags')
+        fields = ('pk', 'device', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_peer', 'tags')
         default_columns = ('pk', 'device', 'name', 'label', 'type', 'description')
 
 
+class DeviceRearPortTable(RearPortTable):
+    name = tables.TemplateColumn(
+        template_code='<i class="fa fa-square{% if not record.cable %}-o{% endif %}"></i> '
+                      '<a href="{{ record.get_absolute_url }}">{{ value }}</a>'
+    )
+    actions = ButtonsColumn(
+        model=RearPort,
+        buttons=('edit', 'delete'),
+        prepend_template=REARPORT_BUTTONS
+    )
+
+    class Meta(DeviceComponentTable.Meta):
+        model = RearPort
+        fields = (
+            'pk', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_peer', 'tags', 'actions',
+        )
+        default_columns = (
+            'pk', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_peer', 'actions',
+        )
+        row_attrs = {
+            'class': lambda record: record.cable.get_status_class() if record.cable else ''
+        }
+
+
 class DeviceBayTable(DeviceComponentTable):
+    status = tables.TemplateColumn(
+        template_code=DEVICEBAY_STATUS
+    )
     installed_device = tables.Column(
         linkify=True
     )
@@ -316,8 +537,29 @@ class DeviceBayTable(DeviceComponentTable):
 
     class Meta(DeviceComponentTable.Meta):
         model = DeviceBay
-        fields = ('pk', 'device', 'name', 'label', 'installed_device', 'description', 'tags')
-        default_columns = ('pk', 'device', 'name', 'label', 'installed_device', 'description')
+        fields = ('pk', 'device', 'name', 'label', 'status', 'installed_device', 'description', 'tags')
+        default_columns = ('pk', 'device', 'name', 'label', 'status', 'installed_device', 'description')
+
+
+class DeviceDeviceBayTable(DeviceBayTable):
+    name = tables.TemplateColumn(
+        template_code='<i class="fa fa-square{% if record.installed_device %}dot-circle-o{% else %}circle-o{% endif %}'
+                      '"></i> <a href="{{ record.get_absolute_url }}">{{ value }}</a>'
+    )
+    actions = ButtonsColumn(
+        model=DeviceBay,
+        buttons=('edit', 'delete'),
+        prepend_template=DEVICEBAY_BUTTONS
+    )
+
+    class Meta(DeviceComponentTable.Meta):
+        model = DeviceBay
+        fields = (
+            'pk', 'name', 'label', 'status', 'installed_device', 'description', 'tags', 'actions',
+        )
+        default_columns = (
+            'pk', 'name', 'label', 'status', 'installed_device', 'description', 'actions',
+        )
 
 
 class InventoryItemTable(DeviceComponentTable):
@@ -339,6 +581,28 @@ class InventoryItemTable(DeviceComponentTable):
         default_columns = ('pk', 'device', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag')
 
 
+class DeviceInventoryItemTable(InventoryItemTable):
+    name = tables.TemplateColumn(
+        template_code='<a href="{{ record.get_absolute_url }}" style="padding-left: {{ record.level }}0px">'
+                      '{{ value }}</a>'
+    )
+    actions = ButtonsColumn(
+        model=InventoryItem,
+        buttons=('edit', 'delete')
+    )
+
+    class Meta(DeviceComponentTable.Meta):
+        model = InventoryItem
+        fields = (
+            'pk', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'discovered',
+            'tags', 'actions',
+        )
+        default_columns = (
+            'pk', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'discovered',
+            'actions',
+        )
+
+
 #
 # Virtual chassis
 #

+ 6 - 12
netbox/dcim/tables/power.py

@@ -3,6 +3,7 @@ from django_tables2.utils import Accessor
 
 from dcim.models import PowerFeed, PowerPanel
 from utilities.tables import BaseTable, ChoiceFieldColumn, LinkedCountColumn, TagColumn, ToggleColumn
+from .devices import CableTerminationTable
 from .template_code import POWERFEED_CABLE, POWERFEED_CABLETERMINATION
 
 __all__ = (
@@ -41,7 +42,9 @@ class PowerPanelTable(BaseTable):
 # Power feeds
 #
 
-class PowerFeedTable(BaseTable):
+# We're not using PathEndpointTable for PowerFeed because power connections
+# cannot traverse pass-through ports.
+class PowerFeedTable(CableTerminationTable):
     pk = ToggleColumn()
     name = tables.LinkColumn()
     power_panel = tables.Column(
@@ -55,15 +58,6 @@ class PowerFeedTable(BaseTable):
     max_utilization = tables.TemplateColumn(
         template_code="{{ value }}%"
     )
-    cable = tables.TemplateColumn(
-        template_code=POWERFEED_CABLE,
-        orderable=False
-    )
-    connection = tables.TemplateColumn(
-        accessor='get_cable_peer',
-        template_code=POWERFEED_CABLETERMINATION,
-        orderable=False
-    )
     available_power = tables.Column(
         verbose_name='Available power (VA)'
     )
@@ -75,9 +69,9 @@ class PowerFeedTable(BaseTable):
         model = PowerFeed
         fields = (
             'pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase',
-            'max_utilization', 'cable', 'connection', 'available_power', 'tags',
+            'max_utilization', 'cable', 'cable_peer', 'connection', 'available_power', 'tags',
         )
         default_columns = (
             'pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase', 'cable',
-            'connection',
+            'cable_peer',
         )

+ 167 - 1
netbox/dcim/tables/template_code.py

@@ -1,3 +1,13 @@
+CABLETERMINATION = """
+{% if value %}
+    <a href="{{ value.parent.get_absolute_url }}">{{ value.parent }}</a>
+    <i class="fa fa-caret-right"></i>
+    <a href="{{ value.get_absolute_url }}">{{ value }}</a>
+{% else %}
+    &mdash;
+{% endif %}
+"""
+
 CABLE_LENGTH = """
 {% if record.length %}{{ record.length }} {{ record.get_length_unit_display }}{% else %}&mdash;{% endif %}
 """
@@ -18,8 +28,18 @@ DEVICE_LINK = """
 </a>
 """
 
+DEVICEBAY_STATUS = """
+{% if record.installed_device_id %}
+    <span class="label label-{{ record.installed_device.get_status_class }}">
+        {{ record.installed_device.get_status_display }}
+    </span>
+{% else %}
+    <span class="label label-default">Vacant</span>
+{% endif %}
+"""
+
 INTERFACE_IPADDRESSES = """
-{% for ip in record.ip_addresses.unrestricted %}
+{% for ip in record.ip_addresses.all %}
     <a href="{{ ip.get_absolute_url }}">{{ ip }}</a><br />
 {% endfor %}
 """
@@ -63,3 +83,149 @@ UTILIZATION_GRAPH = """
 {% load helpers %}
 {% utilization_graph value %}
 """
+
+#
+# Device component buttons
+#
+
+CONSOLEPORT_BUTTONS = """
+{% if record.cable %}
+    <a href="{% url 'dcim:consoleport_trace' pk=record.pk %}" class="btn btn-primary btn-xs" title="Trace"><i class="fa fa-share-alt"></i></a>
+    {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %}
+{% elif perms.dcim.add_cable %}
+    <span class="dropdown">
+        <button type="button" class="btn btn-success btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+            <span class="glyphicon glyphicon-resize-small" aria-hidden="true"></span>
+        </button>
+        <ul class="dropdown-menu dropdown-menu-right">
+            <li><a href="{% url 'dcim:consoleport_connect' termination_a_id=record.pk termination_b_type='console-server-port' %}?return_url={{ device.get_absolute_url }}">Console Server Port</a></li>
+            <li><a href="{% url 'dcim:consoleport_connect' termination_a_id=record.pk termination_b_type='front-port' %}?return_url={{ device.get_absolute_url }}">Front Port</a></li>
+            <li><a href="{% url 'dcim:consoleport_connect' termination_a_id=record.pk termination_b_type='rear-port' %}?return_url={{ device.get_absolute_url }}">Rear Port</a></li>
+        </ul>
+    </span>
+{% endif %}
+"""
+
+CONSOLESERVERPORT_BUTTONS = """
+{% if record.cable %}
+    <a href="{% url 'dcim:consoleserverport_trace' pk=record.pk %}" class="btn btn-primary btn-xs" title="Trace"><i class="fa fa-share-alt"></i></a>
+    {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %}
+{% elif perms.dcim.add_cable %}
+    <span class="dropdown">
+        <button type="button" class="btn btn-success btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+            <span class="glyphicon glyphicon-resize-small" aria-hidden="true"></span>
+        </button>
+        <ul class="dropdown-menu dropdown-menu-right">
+            <li><a href="{% url 'dcim:consoleserverport_connect' termination_a_id=record.pk termination_b_type='console-port' %}?return_url={{ device.get_absolute_url }}">Console Port</a></li>
+            <li><a href="{% url 'dcim:consoleserverport_connect' termination_a_id=record.pk termination_b_type='front-port' %}?return_url={{ device.get_absolute_url }}">Front Port</a></li>
+            <li><a href="{% url 'dcim:consoleserverport_connect' termination_a_id=record.pk termination_b_type='rear-port' %}?return_url={{ device.get_absolute_url }}">Rear Port</a></li>
+        </ul>
+    </span>
+{% endif %}
+"""
+
+POWERPORT_BUTTONS = """
+{% if record.cable %}
+    <a href="{% url 'dcim:powerport_trace' pk=record.pk %}" class="btn btn-primary btn-xs" title="Trace"><i class="fa fa-share-alt"></i></a>
+    {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %}
+{% elif perms.dcim.add_cable %}
+    <span class="dropdown">
+        <button type="button" class="btn btn-success btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+            <span class="glyphicon glyphicon-resize-small" aria-hidden="true"></span>
+        </button>
+        <ul class="dropdown-menu dropdown-menu-right">
+            <li><a href="{% url 'dcim:powerport_connect' termination_a_id=record.pk termination_b_type='power-outlet' %}?return_url={{ device.get_absolute_url }}">Power Outlet</a></li>
+            <li><a href="{% url 'dcim:powerport_connect' termination_a_id=record.pk termination_b_type='power-feed' %}?return_url={{ device.get_absolute_url }}">Power Feed</a></li>
+        </ul>
+    </span>
+{% endif %}
+"""
+
+POWEROUTLET_BUTTONS = """
+{% if record.cable %}
+    <a href="{% url 'dcim:poweroutlet_trace' pk=record.pk %}" class="btn btn-primary btn-xs" title="Trace"><i class="fa fa-share-alt"></i></a>
+    {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %}
+{% elif perms.dcim.add_cable %}
+    <a href="{% url 'dcim:poweroutlet_connect' termination_a_id=record.pk termination_b_type='power-port' %}?return_url={{ device.get_absolute_url }}" title="Connect" class="btn btn-success btn-xs">
+        <i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
+    </a>
+{% endif %}
+"""
+
+INTERFACE_BUTTONS = """
+{% if perms.ipam.add_ipaddress %}
+    <a href="{% url 'ipam:ipaddress_add' %}?interface={{ record.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-xs btn-success" title="Add IP address">
+        <i class="glyphicon glyphicon-plus" aria-hidden="true"></i>
+    </a>
+{% endif %}
+{% if record.cable %}
+    <a href="{% url 'dcim:interface_trace' pk=record.pk %}" class="btn btn-primary btn-xs" title="Trace"><i class="fa fa-share-alt"></i></a>
+    {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %}
+{% elif record.is_connectable and perms.dcim.add_cable %}
+    <span class="dropdown">
+        <button type="button" class="btn btn-success btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+            <span class="glyphicon glyphicon-resize-small" aria-hidden="true"></span>
+        </button>
+        <ul class="dropdown-menu dropdown-menu-right">
+            <li><a href="{% url 'dcim:interface_connect' termination_a_id=record.pk termination_b_type='interface' %}?return_url={{ device.get_absolute_url }}">Interface</a></li>
+            <li><a href="{% url 'dcim:interface_connect' termination_a_id=record.pk termination_b_type='front-port' %}?return_url={{ device.get_absolute_url }}">Front Port</a></li>
+            <li><a href="{% url 'dcim:interface_connect' termination_a_id=record.pk termination_b_type='rear-port' %}?return_url={{ device.get_absolute_url }}">Rear Port</a></li>
+            <li><a href="{% url 'dcim:interface_connect' termination_a_id=record.pk termination_b_type='circuit-termination' %}?return_url={{ device.get_absolute_url }}">Circuit Termination</a></li>
+        </ul>
+    </span>
+{% endif %}
+"""
+
+FRONTPORT_BUTTONS = """
+{% if record.cable %}
+    <a href="{% url 'dcim:frontport_trace' pk=record.pk %}" class="btn btn-primary btn-xs" title="Trace"><i class="fa fa-share-alt"></i></a>
+    {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %}
+{% elif perms.dcim.add_cable %}
+    <span class="dropdown">
+        <button type="button" class="btn btn-success btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+            <span class="glyphicon glyphicon-resize-small" aria-hidden="true"></span>
+        </button>
+        <ul class="dropdown-menu dropdown-menu-right">
+            <li><a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='interface' %}?return_url={{ device.get_absolute_url }}">Interface</a></li>
+            <li><a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='console-server-port' %}?return_url={{ device.get_absolute_url }}">Console Server Port</a></li>
+            <li><a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='console-port' %}?return_url={{ device.get_absolute_url }}">Console Port</a></li>
+            <li><a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='front-port' %}?return_url={{ device.get_absolute_url }}">Front Port</a></li>
+            <li><a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='rear-port' %}?return_url={{ device.get_absolute_url }}">Rear Port</a></li>
+            <li><a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='circuit-termination' %}?return_url={{ device.get_absolute_url }}">Circuit Termination</a></li>
+        </ul>
+    </span>
+{% endif %}
+"""
+
+REARPORT_BUTTONS = """
+{% if record.cable %}
+    <a href="{% url 'dcim:rearport_trace' pk=record.pk %}" class="btn btn-primary btn-xs" title="Trace"><i class="fa fa-share-alt"></i></a>
+    {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %}
+{% elif perms.dcim.add_cable %}
+    <span class="dropdown">
+        <button type="button" class="btn btn-success btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+            <span class="glyphicon glyphicon-resize-small" aria-hidden="true"></span>
+        </button>
+        <ul class="dropdown-menu dropdown-menu-right">
+            <li><a href="{% url 'dcim:rearport_connect' termination_a_id=record.pk termination_b_type='interface' %}?return_url={{ device.get_absolute_url }}">Interface</a></li>
+            <li><a href="{% url 'dcim:rearport_connect' termination_a_id=record.pk termination_b_type='front-port' %}?return_url={{ device.get_absolute_url }}">Front Port</a></li>
+            <li><a href="{% url 'dcim:rearport_connect' termination_a_id=record.pk termination_b_type='rear-port' %}?return_url={{ device.get_absolute_url }}">Rear Port</a></li>
+            <li><a href="{% url 'dcim:rearport_connect' termination_a_id=record.pk termination_b_type='circuit-termination' %}?return_url={{ device.get_absolute_url }}">Circuit Termination</a></li>
+        </ul>
+    </span>
+{% endif %}
+"""
+
+DEVICEBAY_BUTTONS = """
+{% if perms.dcim.change_devicebay %}
+    {% if record.installed_device %}
+        <a href="{% url 'dcim:devicebay_depopulate' pk=record.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=record.pk %}" class="btn btn-success btn-xs">
+            <i class="glyphicon glyphicon-plus" aria-hidden="true" title="Install device"></i>
+        </a>
+    {% endif %}
+{% endif %}
+"""

+ 37 - 9
netbox/dcim/views.py

@@ -1019,6 +1019,9 @@ class DeviceView(ObjectView):
         consoleports = ConsolePort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
             'cable', '_path__destination',
         )
+        consoleport_table = tables.DeviceConsolePortTable(consoleports, orderable=False)
+        if request.user.has_perm('dcim.change_consoleport') or request.user.has_perm('dcim.delete_consoleport'):
+            consoleport_table.columns.show('pk')
 
         # Console server ports
         consoleserverports = ConsoleServerPort.objects.restrict(request.user, 'view').filter(
@@ -1026,16 +1029,26 @@ class DeviceView(ObjectView):
         ).prefetch_related(
             'cable', '_path__destination',
         )
+        consoleserverport_table = tables.DeviceConsoleServerPortTable(consoleserverports, orderable=False)
+        if request.user.has_perm('dcim.change_consoleserverport') or \
+                request.user.has_perm('dcim.delete_consoleserverport'):
+            consoleserverport_table.columns.show('pk')
 
         # Power ports
         powerports = PowerPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
             'cable', '_path__destination',
         )
+        powerport_table = tables.DevicePowerPortTable(powerports, orderable=False)
+        if request.user.has_perm('dcim.change_powerport') or request.user.has_perm('dcim.delete_powerport'):
+            powerport_table.columns.show('pk')
 
         # Power outlets
         poweroutlets = PowerOutlet.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
             'cable', 'power_port', '_path__destination',
         )
+        poweroutlet_table = tables.DevicePowerOutletTable(poweroutlets, orderable=False)
+        if request.user.has_perm('dcim.change_poweroutlet') or request.user.has_perm('dcim.delete_poweroutlet'):
+            poweroutlet_table.columns.show('pk')
 
         # Interfaces
         interfaces = device.vc_interfaces.restrict(request.user, 'view').prefetch_related(
@@ -1043,24 +1056,39 @@ class DeviceView(ObjectView):
             Prefetch('member_interfaces', queryset=Interface.objects.restrict(request.user)),
             'lag', 'cable', '_path__destination', 'tags',
         )
+        interface_table = tables.DeviceInterfaceTable(interfaces, orderable=False)
+        if request.user.has_perm('dcim.change_interface') or request.user.has_perm('dcim.delete_interface'):
+            interface_table.columns.show('pk')
 
         # Front ports
         frontports = FrontPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
             'rear_port', 'cable',
         )
+        frontport_table = tables.DeviceFrontPortTable(frontports, orderable=False)
+        if request.user.has_perm('dcim.change_frontport') or request.user.has_perm('dcim.delete_frontport'):
+            frontport_table.columns.show('pk')
 
         # Rear ports
         rearports = RearPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related('cable')
+        rearport_table = tables.DeviceRearPortTable(rearports, orderable=False)
+        if request.user.has_perm('dcim.change_rearport') or request.user.has_perm('dcim.delete_rearport'):
+            rearport_table.columns.show('pk')
 
         # Device bays
         devicebays = DeviceBay.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
             'installed_device__device_type__manufacturer',
         )
+        devicebay_table = tables.DeviceDeviceBayTable(devicebays, orderable=False)
+        if request.user.has_perm('dcim.change_devicebay') or request.user.has_perm('dcim.delete_devicebay'):
+            devicebay_table.columns.show('pk')
 
         # Inventory items
         inventoryitems = InventoryItem.objects.restrict(request.user, 'view').filter(
             device=device
         ).prefetch_related('manufacturer')
+        inventoryitem_table = tables.DeviceInventoryItemTable(inventoryitems, orderable=False)
+        if request.user.has_perm('dcim.change_inventoryitem') or request.user.has_perm('dcim.delete_inventoryitem'):
+            devicebay_table.columns.show('pk')
 
         # Services
         services = Service.objects.restrict(request.user, 'view').filter(device=device)
@@ -1079,15 +1107,15 @@ class DeviceView(ObjectView):
 
         return render(request, 'dcim/device.html', {
             'device': device,
-            'consoleports': consoleports,
-            'consoleserverports': consoleserverports,
-            'powerports': powerports,
-            'poweroutlets': poweroutlets,
-            'interfaces': interfaces,
-            'frontports': frontports,
-            'rearports': rearports,
-            'devicebays': devicebays,
-            'inventoryitems': inventoryitems,
+            'consoleport_table': consoleport_table,
+            'consoleserverport_table': consoleserverport_table,
+            'powerport_table': powerport_table,
+            'poweroutlet_table': poweroutlet_table,
+            'interface_table': interface_table,
+            'frontport_table': frontport_table,
+            'rearport_table': rearport_table,
+            'devicebay_table': devicebay_table,
+            'inventoryitem_table': inventoryitem_table,
             'services': services,
             'secrets': secrets,
             'vc_members': vc_members,

+ 0 - 13
netbox/project-static/js/interface_toggles.js

@@ -1,16 +1,3 @@
-// Toggle the display of IP addresses under interfaces
-$('button.toggle-ips').click(function() {
-    var selected = $(this).attr('selected');
-    if (selected) {
-        $('#interfaces_table tr.interface:visible + tr.ipaddresses').hide();
-    } else {
-        $('#interfaces_table tr.interface:visible + tr.ipaddresses').show();
-    }
-    $(this).attr('selected', !selected);
-    $(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked');
-    return false;
-});
-
 // Inteface filtering
 $('input.interface-filter').on('input', function() {
     var filter = new RegExp(this.value);

+ 40 - 225
netbox/templates/dcim/device.html

@@ -122,31 +122,31 @@
                     <a href="#details" role="tab" data-toggle="tab">Details</a>
                 </li>
                 <li role="presentation">
-                    <a href="#interfaces" role="tab" data-toggle="tab">Interfaces {% badge interfaces|length %}</a>
+                    <a href="#interfaces" role="tab" data-toggle="tab">Interfaces {% badge interface_table.rows|length %}</a>
                 </li>
                 <li role="presentation">
-                    <a href="#frontports" role="tab" data-toggle="tab">Front Ports {% badge frontports|length %}</a>
+                    <a href="#frontports" role="tab" data-toggle="tab">Front Ports {% badge frontport_table.rows|length %}</a>
                 </li>
                 <li role="presentation">
-                    <a href="#rearports" role="tab" data-toggle="tab">Rear Ports {% badge rearports|length %}</a>
+                    <a href="#rearports" role="tab" data-toggle="tab">Rear Ports {% badge rearport_table.rows|length %}</a>
                 </li>
                 <li role="presentation">
-                    <a href="#consoleports" role="tab" data-toggle="tab">Console Ports {% badge consoleports|length %}</a>
+                    <a href="#consoleports" role="tab" data-toggle="tab">Console Ports {% badge consoleport_table.rows|length %}</a>
                 </li>
                 <li role="presentation">
-                    <a href="#consoleserverports" role="tab" data-toggle="tab">Console Server Ports {% badge consoleserverports|length %}</a>
+                    <a href="#consoleserverports" role="tab" data-toggle="tab">Console Server Ports {% badge consoleserverport_table.rows|length %}</a>
                 </li>
                 <li role="presentation">
-                    <a href="#powerports" role="tab" data-toggle="tab">Power Ports {% badge powerports|length %}</a>
+                    <a href="#powerports" role="tab" data-toggle="tab">Power Ports {% badge powerport_table.rows|length %}</a>
                 </li>
                 <li role="presentation">
-                    <a href="#poweroutlets" role="tab" data-toggle="tab">Power Outlets {% badge poweroutlets|length %}</a>
+                    <a href="#poweroutlets" role="tab" data-toggle="tab">Power Outlets {% badge poweroutlet_table.rows|length %}</a>
                 </li>
                 <li role="presentation">
-                    <a href="#devicebays" role="tab" data-toggle="tab">Device Bays {% badge devicebays|length %}</a>
+                    <a href="#devicebays" role="tab" data-toggle="tab">Device Bays {% badge devicebay_table.rows|length %}</a>
                 </li>
                 <li role="presentation">
-                    <a href="#inventoryitems" role="tab" data-toggle="tab">Inventory {% badge inventoryitems|length %}</a>
+                    <a href="#inventoryitems" role="tab" data-toggle="tab">Inventory {% badge inventoryitem_table.rows|length %}</a>
                 </li>
             </ul>
             <div class="tab-content">
@@ -485,38 +485,11 @@
                         <div class="panel panel-default">
                             <div class="panel-heading">
                                 <strong>Interfaces</strong>
-                                <div class="pull-right noprint">
-                                    <button class="btn btn-default btn-xs toggle-ips" selected="selected">
-                                        <span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
-                                    </button>
-                                </div>
                                 <div class="col-md-2 pull-right noprint">
                                     <input class="form-control interface-filter" type="text" placeholder="Filter" title="Filter text (regular expressions supported)" style="height: 23px" />
                                 </div>
                             </div>
-                            <table id="interfaces_table" class="table table-hover table-headings panel-body component-list">
-                                <thead>
-                                    <tr>
-                                        {% if perms.dcim.change_interface or perms.dcim.delete_interface %}
-                                            <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
-                                        {% endif %}
-                                        <th>Name</th>
-                                        <th>LAG</th>
-                                        <th>Description</th>
-                                        <th>MTU</th>
-                                        <th>Mode</th>
-                                        <th>Cable</th>
-                                        <th colspan="2">Cable Termination</th>
-                                        <th colspan="2">Connection</th>
-                                        <th></th>
-                                    </tr>
-                                </thead>
-                                <tbody>
-                                    {% for iface in interfaces %}
-                                        {% include 'dcim/inc/interface.html' %}
-                                    {% endfor %}
-                                </tbody>
-                            </table>
+                            {% include 'responsive_table.html' with table=interface_table %}
                             <div class="panel-footer noprint">
                                 {% if interfaces and perms.dcim.change_interface %}
                                     <button type="submit" name="_rename" formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
@@ -542,8 +515,8 @@
                                             <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add interfaces
                                         </a>
                                     </div>
-                                    <div class="clearfix"></div>
                                 {% endif %}
+                                <div class="clearfix"></div>
                              </div>
                         </div>
                     </form>
@@ -555,30 +528,9 @@
                             <div class="panel-heading">
                                 <strong>Front Ports</strong>
                             </div>
-                            <table class="table table-hover table-headings panel-body component-list">
-                                <thead>
-                                    <tr>
-                                        {% if perms.dcim.change_frontport or perms.dcim.delete_frontport %}
-                                            <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
-                                        {% endif %}
-                                        <th>Name</th>
-                                        <th>Type</th>
-                                        <th>Rear Port</th>
-                                        <th>Position</th>
-                                        <th>Description</th>
-                                        <th>Cable</th>
-                                        <th colspan="2">Cable Termination</th>
-                                        <th></th>
-                                    </tr>
-                                </thead>
-                                <tbody>
-                                    {% for frontport in frontports %}
-                                        {% include 'dcim/inc/frontport.html' %}
-                                    {% endfor %}
-                                </tbody>
-                            </table>
+                            {% include 'responsive_table.html' with table=frontport_table %}
                             <div class="panel-footer noprint">
-                                {% if frontports and perms.dcim.change_frontport %}
+                                {% if perms.dcim.change_frontport %}
                                     <button type="submit" name="_rename" formaction="{% url 'dcim:frontport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
                                         <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
                                     </button>
@@ -589,7 +541,7 @@
                                         <span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
                                     </button>
                                 {% endif %}
-                                {% if frontports and perms.dcim.delete_frontport %}
+                                {% if perms.dcim.delete_frontport %}
                                     <button type="submit" formaction="{% url 'dcim:frontport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
                                         <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
                                     </button>
@@ -600,8 +552,8 @@
                                             <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add front ports
                                         </a>
                                     </div>
-                                    <div class="clearfix"></div>
                                 {% endif %}
+                                <div class="clearfix"></div>
                             </div>
                         </div>
                     </form>
@@ -613,29 +565,9 @@
                             <div class="panel-heading">
                                 <strong>Rear Ports</strong>
                             </div>
-                            <table class="table table-hover table-headings panel-body component-list">
-                                <thead>
-                                    <tr>
-                                        {% if perms.dcim.change_rearport or perms.dcim.delete_rearport %}
-                                            <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
-                                        {% endif %}
-                                        <th>Name</th>
-                                        <th>Type</th>
-                                        <th>Positions</th>
-                                        <th>Description</th>
-                                        <th>Cable</th>
-                                        <th colspan="2">Cable Termination</th>
-                                        <th></th>
-                                    </tr>
-                                </thead>
-                                <tbody>
-                                    {% for rearport in rearports %}
-                                        {% include 'dcim/inc/rearport.html' %}
-                                    {% endfor %}
-                                </tbody>
-                            </table>
+                            {% include 'responsive_table.html' with table=rearport_table %}
                             <div class="panel-footer noprint">
-                                {% if rearports and perms.dcim.change_rearport %}
+                                {% if perms.dcim.change_rearport %}
                                     <button type="submit" name="_rename" formaction="{% url 'dcim:rearport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
                                         <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
                                     </button>
@@ -646,7 +578,7 @@
                                         <span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
                                     </button>
                                 {% endif %}
-                                {% if rearports and perms.dcim.delete_rearport %}
+                                {% if perms.dcim.delete_rearport %}
                                     <button type="submit" formaction="{% url 'dcim:rearport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
                                         <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
                                     </button>
@@ -657,8 +589,8 @@
                                             <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add rear ports
                                         </a>
                                     </div>
-                                    <div class="clearfix"></div>
                                 {% endif %}
+                                <div class="clearfix"></div>
                             </div>
                         </div>
                     </form>
@@ -670,27 +602,9 @@
                             <div class="panel-heading">
                                 <strong>Console Ports</strong>
                             </div>
-                            <table class="table table-hover panel-body component-list">
-                                <thead>
-                                    <tr>
-                                        {% if perms.dcim.change_consoleport or perms.dcim.delete_consoleport %}
-                                            <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
-                                        {% endif %}
-                                        <th>Name</th>
-                                        <th>Type</th>
-                                        <th>Description</th>
-                                        <th>Cable</th>
-                                        <th colspan="2">Cable Termination</th>
-                                        <th colspan="2">Connection</th>
-                                        <th></th>
-                                    </tr>
-                                </thead>
-                                {% for cp in consoleports %}
-                                    {% include 'dcim/inc/consoleport.html' %}
-                                {% endfor %}
-                            </table>
+                            {% include 'responsive_table.html' with table=consoleport_table %}
                             <div class="panel-footer noprint">
-                                {% if consoleports and perms.dcim.change_consoleport %}
+                                {% if perms.dcim.change_consoleport %}
                                     <button type="submit" name="_rename" formaction="{% url 'dcim:consoleport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
                                         <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
                                     </button>
@@ -701,7 +615,7 @@
                                         <span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
                                     </button>
                                 {% endif %}
-                                {% if consoleports and perms.dcim.delete_consoleport %}
+                                {% if perms.dcim.delete_consoleport %}
                                     <button type="submit" name="_delete" formaction="{% url 'dcim:consoleport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
                                         <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
                                     </button>
@@ -713,6 +627,7 @@
                                         </a>
                                     </div>
                                 {% endif %}
+                                <div class="clearfix"></div>
                             </div>
                         </div>
                     </form>
@@ -724,29 +639,9 @@
                             <div class="panel-heading">
                                 <strong>Console Server Ports</strong>
                             </div>
-                            <table class="table table-hover table-headings panel-body component-list">
-                                <thead>
-                                    <tr>
-                                        {% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %}
-                                            <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
-                                        {% endif %}
-                                        <th>Name</th>
-                                        <th>Type</th>
-                                        <th>Description</th>
-                                        <th>Cable</th>
-                                        <th colspan="2">Cable Termination</th>
-                                        <th colspan="2">Connection</th>
-                                        <th></th>
-                                    </tr>
-                                </thead>
-                                <tbody>
-                                    {% for csp in consoleserverports %}
-                                        {% include 'dcim/inc/consoleserverport.html' %}
-                                    {% endfor %}
-                                </tbody>
-                            </table>
+                            {% include 'responsive_table.html' with table=consoleserverport_table %}
                             <div class="panel-footer noprint">
-                                {% if consoleserverports and perms.dcim.change_consoleport %}
+                                {% if perms.dcim.change_consoleport %}
                                     <button type="submit" name="_rename" formaction="{% url 'dcim:consoleserverport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
                                         <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
                                     </button>
@@ -757,7 +652,7 @@
                                         <span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
                                     </button>
                                 {% endif %}
-                                {% if consoleserverports and perms.dcim.delete_consoleserverport %}
+                                {% if perms.dcim.delete_consoleserverport %}
                                     <button type="submit" formaction="{% url 'dcim:consoleserverport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
                                         <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
                                     </button>
@@ -768,8 +663,8 @@
                                             <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console server ports
                                         </a>
                                     </div>
-                                    <div class="clearfix"></div>
                                 {% endif %}
+                                <div class="clearfix"></div>
                             </div>
                         </div>
                     </form>
@@ -781,27 +676,9 @@
                             <div class="panel-heading">
                                 <strong>Power Ports</strong>
                             </div>
-                            <table class="table table-hover panel-body component-list">
-                                <thead>
-                                    <tr>
-                                        {% if perms.dcim.change_consoleport or perms.dcim.delete_consoleport %}
-                                            <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
-                                        {% endif %}
-                                        <th>Name</th>
-                                        <th>Type</th>
-                                        <th>Draw</th>
-                                        <th>Description</th>
-                                        <th>Cable</th>
-                                        <th colspan="2">Connection</th>
-                                        <th></th>
-                                    </tr>
-                                </thead>
-                                {% for pp in powerports %}
-                                    {% include 'dcim/inc/powerport.html' %}
-                                {% endfor %}
-                            </table>
+                            {% include 'responsive_table.html' with table=powerport_table %}
                             <div class="panel-footer noprint">
-                                {% if powerports and perms.dcim.change_powerport %}
+                                {% if perms.dcim.change_powerport %}
                                     <button type="submit" name="_rename" formaction="{% url 'dcim:powerport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
                                         <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
                                     </button>
@@ -812,7 +689,7 @@
                                         <span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
                                     </button>
                                 {% endif %}
-                                {% if powerports and perms.dcim.delete_powerport %}
+                                {% if perms.dcim.delete_powerport %}
                                     <button type="submit" name="_delete" formaction="{% url 'dcim:powerport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
                                         <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
                                     </button>
@@ -835,29 +712,9 @@
                             <div class="panel-heading">
                                 <strong>Power Outlets</strong>
                             </div>
-                            <table class="table table-hover table-headings panel-body component-list">
-                                <thead>
-                                    <tr>
-                                        {% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %}
-                                            <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
-                                        {% endif %}
-                                        <th>Name</th>
-                                    <th>Type</th>
-                                        <th>Input/Leg</th>
-                                        <th>Description</th>
-                                        <th>Cable</th>
-                                        <th colspan="3">Connection</th>
-                                        <th></th>
-                                    </tr>
-                                </thead>
-                                <tbody>
-                                    {% for po in poweroutlets %}
-                                        {% include 'dcim/inc/poweroutlet.html' %}
-                                    {% endfor %}
-                                </tbody>
-                            </table>
+                            {% include 'responsive_table.html' with table=poweroutlet_table %}
                             <div class="panel-footer noprint">
-                                {% if poweroutlets and perms.dcim.change_powerport %}
+                                {% if perms.dcim.change_powerport %}
                                     <button type="submit" name="_rename" formaction="{% url 'dcim:poweroutlet_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
                                         <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
                                     </button>
@@ -868,7 +725,7 @@
                                         <span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
                                     </button>
                                 {% endif %}
-                                {% if poweroutlets and perms.dcim.delete_poweroutlet %}
+                                {% if perms.dcim.delete_poweroutlet %}
                                     <button type="submit" formaction="{% url 'dcim:poweroutlet_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
                                         <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
                                     </button>
@@ -879,8 +736,8 @@
                                             <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add power outlets
                                         </a>
                                     </div>
-                                    <div class="clearfix"></div>
                                 {% endif %}
+                                <div class="clearfix"></div>
                             </div>
                         </div>
                     </form>
@@ -892,36 +749,14 @@
                             <div class="panel-heading">
                                 <strong>Device Bays</strong>
                             </div>
-                            <table class="table table-hover table-headings panel-body component-list">
-                                <thead>
-                                    <tr>
-                                        {% if perms.dcim.change_devicebay or perms.dcim.delete_devicebay %}
-                                            <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
-                                        {% endif %}
-                                        <th>Name</th>
-                                        <th>Status</th>
-                                        <th>Description</th>
-                                        <th colspan="2">Installed Device</th>
-                                        <th></th>
-                                    </tr>
-                                </thead>
-                                <tbody>
-                                    {% for devicebay in devicebays %}
-                                        {% include 'dcim/inc/devicebay.html' %}
-                                    {% empty %}
-                                        <tr>
-                                            <td colspan="5" class="text-center text-muted">&mdash; No device bays defined &mdash;</td>
-                                        </tr>
-                                    {% endfor %}
-                                </tbody>
-                            </table>
+                            {% include 'responsive_table.html' with table=devicebay_table %}
                             <div class="panel-footer noprint">
-                                {% if devicebays and perms.dcim.change_devicebay %}
+                                {% if perms.dcim.change_devicebay %}
                                     <button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
                                         <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
                                     </button>
                                 {% endif %}
-                                {% if devicebays and perms.dcim.delete_devicebay %}
+                                {% if perms.dcim.delete_devicebay %}
                                     <button type="submit" formaction="{% url 'dcim:devicebay_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
                                         <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete selected
                                     </button>
@@ -932,8 +767,8 @@
                                             <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add device bays
                                         </a>
                                     </div>
-                                    <div class="clearfix"></div>
                                 {% endif %}
+                                <div class="clearfix"></div>
                              </div>
                         </div>
                     </form>
@@ -945,28 +780,7 @@
                             <div class="panel-heading">
                                 <strong>Inventory Items</strong>
                             </div>
-                            <table class="table table-hover table-condensed panel-body" id="hardware">
-                                <thead>
-                                    <tr>
-                                        {% if perms.dcim.change_inventoryitem or perms.dcim.delete_inventoryitem %}
-                                            <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
-                                        {% endif %}
-                                        <th>Name</th>
-                                        <th>Manufacturer</th>
-                                        <th>Part ID</th>
-                                        <th>Serial Number</th>
-                                        <th>Asset Tag</th>
-                                        <th>Discovered</th>
-                                        <th>Description</th>
-                                        <th></th>
-                                    </tr>
-                                </thead>
-                                <tbody>
-                                    {% for item in inventoryitems %}
-                                        {% include 'dcim/inc/inventoryitem.html' %}
-                                    {% endfor %}
-                                </tbody>
-                            </table>
+                            {% include 'responsive_table.html' with table=inventoryitem_table %}
                             <div class="panel-footer noprint">
                                 {% if inventoryitems and perms.dcim.change_inventoryitem %}
                                     <button type="submit" name="_rename" formaction="{% url 'dcim:inventoryitem_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
@@ -988,6 +802,7 @@
                                         </a>
                                     </div>
                                 {% endif %}
+                                <div class="clearfix"></div>
                             </div>
                         </div>
                     </form>

+ 0 - 77
netbox/templates/dcim/inc/consoleport.html

@@ -1,77 +0,0 @@
-<tr class="consoleport{% if cp.cable %} {{ cp.cable.get_status_class }}{% endif %}">
-
-    {# Checkbox #}
-    {% if perms.dcim.change_consoleport or perms.dcim.delete_consoleport %}
-        <td class="pk">
-            <input name="pk" type="checkbox" value="{{ cp.pk }}" />
-        </td>
-    {% endif %}
-
-    {# Name #}
-    <td>
-        <i class="fa fa-fw fa-keyboard-o"></i>
-        <a href="{{ cp.get_absolute_url }}">{{ cp }}</a>
-    </td>
-
-    {# Type #}
-    <td>
-        {% if cp.type %}{{ cp.get_type_display }}{% else %}&mdash;{% endif %}
-    </td>
-
-    {# Description #}
-    <td>
-        {{ cp.description }}
-    </td>
-
-    {# Cable #}
-    {% if cp.cable %}
-        <td>
-            <a href="{{ cp.cable.get_absolute_url }}">{{ cp.cable }}</a>
-            <a href="{% url 'dcim:consoleport_trace' pk=cp.pk %}" class="btn btn-primary btn-xs" title="Trace">
-                <i class="fa fa-share-alt" aria-hidden="true"></i>
-            </a>
-        </td>
-        {% include 'dcim/inc/cabletermination.html' with termination=cp.get_cable_peer %}
-    {% else %}
-        <td colspan="3">
-            <span class="text-muted">Not connected</span>
-        </td>
-    {% endif %}
-
-    {# Connection #}
-    {% include 'dcim/inc/endpoint_connection.html' with path=cp.path %}
-
-    {# Actions #}
-    <td class="text-right noprint">
-        {% if cp.cable %}
-            {% include 'dcim/inc/cable_toggle_buttons.html' with cable=cp.cable %}
-        {% elif perms.dcim.add_cable %}
-            <span class="dropdown">
-                <button type="button" class="btn btn-success btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                    <span class="glyphicon glyphicon-resize-small" aria-hidden="true"></span>
-                </button>
-                <ul class="dropdown-menu dropdown-menu-right">
-                    <li><a href="{% url 'dcim:consoleport_connect' termination_a_id=cp.pk termination_b_type='console-server-port' %}?return_url={{ device.get_absolute_url }}">Console Server Port</a></li>
-                    <li><a href="{% url 'dcim:consoleport_connect' termination_a_id=cp.pk termination_b_type='front-port' %}?return_url={{ device.get_absolute_url }}">Front Port</a></li>
-                    <li><a href="{% url 'dcim:consoleport_connect' termination_a_id=cp.pk termination_b_type='rear-port' %}?return_url={{ device.get_absolute_url }}">Rear Port</a></li>
-                </ul>
-            </span>
-        {% endif %}
-        {% if perms.dcim.change_consoleport %}
-            <a href="{% url 'dcim:consoleport_edit' pk=cp.pk %}?return_url={{ device.get_absolute_url }}" title="Edit port" class="btn btn-info btn-xs">
-                <i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
-            </a>
-        {% endif %}
-        {% if perms.dcim.delete_consoleport %}
-            {% if cp.connected_endpoint %}
-                <button class="btn btn-danger btn-xs" disabled="disabled">
-                    <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
-                </button>
-            {% else %}
-                <a href="{% url 'dcim:consoleport_delete' pk=cp.pk %}?return_url={{ device.get_absolute_url }}" title="Delete port" class="btn btn-danger btn-xs">
-                    <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
-                </a>
-            {% endif %}
-        {% endif %}
-    </td>
-</tr>

+ 0 - 79
netbox/templates/dcim/inc/consoleserverport.html

@@ -1,79 +0,0 @@
-{% load helpers %}
-
-<tr class="consoleserverport{% if csp.cable %} {{ csp.cable.get_status_class }}{% endif %}">
-
-    {# Checkbox #}
-    {% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %}
-        <td class="pk">
-            <input name="pk" type="checkbox" value="{{ csp.pk }}" />
-        </td>
-    {% endif %}
-
-    {# Name #}
-    <td>
-        <i class="fa fa-fw fa-keyboard-o"></i>
-        <a href="{{ csp.get_absolute_url }}">{{ csp }}</a>
-    </td>
-
-    {# Type #}
-    <td>
-        {% if csp.type %}{{ csp.get_type_display }}{% else %}&mdash;{% endif %}
-    </td>
-
-    {# Description #}
-    <td>
-        {{ csp.description|placeholder }}
-    </td>
-
-    {# Cable #}
-    {% if csp.cable %}
-        <td>
-            <a href="{{ csp.cable.get_absolute_url }}">{{ csp.cable }}</a>
-            <a href="{% url 'dcim:consoleserverport_trace' pk=csp.pk %}" class="btn btn-primary btn-xs" title="Trace">
-                <i class="fa fa-share-alt" aria-hidden="true"></i>
-            </a>
-        </td>
-        {% include 'dcim/inc/cabletermination.html' with termination=csp.get_cable_peer %}
-    {% else %}
-        <td colspan="3">
-            <span class="text-muted">Not connected</span>
-        </td>
-    {% endif %}
-
-    {# Connection #}
-    {% include 'dcim/inc/endpoint_connection.html' with path=csp.path %}
-
-    {# Actions #}
-    <td class="text-right noprint">
-        {% if csp.cable %}
-            {% include 'dcim/inc/cable_toggle_buttons.html' with cable=csp.cable %}
-        {% elif perms.dcim.add_cable %}
-            <span class="dropdown">
-                <button type="button" class="btn btn-success btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                    <span class="glyphicon glyphicon-resize-small" aria-hidden="true"></span>
-                </button>
-                <ul class="dropdown-menu dropdown-menu-right">
-                    <li><a href="{% url 'dcim:consoleserverport_connect' termination_a_id=csp.pk termination_b_type='console-port' %}?return_url={{ device.get_absolute_url }}">Console Port</a></li>
-                    <li><a href="{% url 'dcim:consoleserverport_connect' termination_a_id=csp.pk termination_b_type='front-port' %}?return_url={{ device.get_absolute_url }}">Front Port</a></li>
-                    <li><a href="{% url 'dcim:consoleserverport_connect' termination_a_id=csp.pk termination_b_type='rear-port' %}?return_url={{ device.get_absolute_url }}">Rear Port</a></li>
-                </ul>
-            </span>
-        {% endif %}
-        {% if perms.dcim.change_consoleserverport %}
-            <a href="{% url 'dcim:consoleserverport_edit' pk=csp.pk %}?return_url={{ device.get_absolute_url }}" title="Edit port" class="btn btn-info btn-xs">
-                <i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
-            </a>
-        {% endif %}
-        {% if perms.dcim.delete_consoleserverport %}
-            {% if csp.connected_endpoint %}
-                <button class="btn btn-danger btn-xs" disabled="disabled">
-                    <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
-                </button>
-            {% else %}
-                <a href="{% url 'dcim:consoleserverport_delete' pk=csp.pk %}?return_url={{ device.get_absolute_url }}" title="Delete port" class="btn btn-danger btn-xs">
-                    <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
-                </a>
-            {% endif %}
-        {% endif %}
-    </td>
-</tr>

+ 0 - 71
netbox/templates/dcim/inc/devicebay.html

@@ -1,71 +0,0 @@
-{% load helpers %}
-
-<tr class="devicebay">
-    {% if perms.dcim.change_devicebay or perms.dcim.delete_devicebay %}
-        <td class="pk">
-            <input name="pk" type="checkbox" value="{{ devicebay.pk }}" />
-        </td>
-    {% endif %}
-
-    {# Name #}
-    <td>
-        <i class="fa fa-fw fa-{% if devicebay.installed_device %}dot-circle-o{% else %}circle-o{% endif %}"></i>
-        <a href="{{ devicebay.get_absolute_url }}">{{ devicebay.name }}</a>
-    </td>
-
-    {# Status #}
-    <td>
-        {% if devicebay.installed_device %}
-            <span class="label label-{{ devicebay.installed_device.get_status_class }}">
-                {{ devicebay.installed_device.get_status_display }}
-            </span>
-        {% else %}
-            <span class="label label-default">Vacant</span>
-        {% endif %}
-    </td>
-
-    {# Description #}
-    <td>
-        {{ devicebay.description|placeholder }}
-    </td>
-
-    {# Installed device #}
-    {% if devicebay.installed_device %}
-        <td>
-            <a href="{% url 'dcim:device' pk=devicebay.installed_device.pk %}">{{ devicebay.installed_device }}</a>
-        </td>
-        <td>
-            <span>{{ devicebay.installed_device.device_type.display_name }}</span>
-        </td>
-    {% else %}
-        <td colspan="2"></td>
-    {% endif %}
-
-    <td class="text-right noprint">
-        {% 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 %}?return_url={{ device.get_absolute_url }}" 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 %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
-                    <i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete device bay"></i>
-                </a>
-            {% endif %}
-        {% endif %}
-    </td>
-</tr>

+ 0 - 72
netbox/templates/dcim/inc/frontport.html

@@ -1,72 +0,0 @@
-{% load helpers %}
-<tr class="frontport{% if frontport.cable %} {{ frontport.cable.get_status_class }}{% endif %}">
-
-    {# Checkbox #}
-    {% if perms.dcim.change_frontport or perms.dcim.delete_frontport %}
-        <td class="pk">
-            <input name="pk" type="checkbox" value="{{ frontport.pk }}" />
-        </td>
-    {% endif %}
-
-    {# Name #}
-    <td>
-        <i class="fa fa-fw fa-square{% if not frontport.cable %}-o{% endif %}"></i>
-        <a href="{{ frontport.get_absolute_url }}">{{ frontport }}</a>
-    </td>
-
-    {# Type #}
-    <td>{{ frontport.get_type_display }}</td>
-
-    {# Rear port #}
-    <td>{{ frontport.rear_port }}</td>
-    <td>{{ frontport.rear_port_position }}</td>
-
-    {# Description #}
-    <td>{{ frontport.description|placeholder }}</td>
-
-    {# Cable #}
-    {% if frontport.cable %}
-        <td>
-            <a href="{{ frontport.cable.get_absolute_url }}">{{ frontport.cable }}</a>
-            <a href="{% url 'dcim:frontport_trace' pk=frontport.pk %}" class="btn btn-primary btn-xs" title="Trace">
-                <i class="fa fa-share-alt" aria-hidden="true"></i>
-            </a>
-        </td>
-        {% include 'dcim/inc/cabletermination.html' with termination=frontport.get_cable_peer %}
-    {% else %}
-        <td colspan="3">
-            <span class="text-muted">Not connected</span>
-        </td>
-    {% endif %}
-
-    {# Actions #}
-    <td class="text-right noprint">
-        {% if frontport.cable %}
-            {% include 'dcim/inc/cable_toggle_buttons.html' with cable=frontport.cable %}
-        {% elif perms.dcim.add_cable %}
-            <span class="dropdown">
-                <button type="button" class="btn btn-success btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                    <span class="glyphicon glyphicon-resize-small" aria-hidden="true"></span>
-                </button>
-                <ul class="dropdown-menu dropdown-menu-right">
-                    <li><a href="{% url 'dcim:frontport_connect' termination_a_id=frontport.pk termination_b_type='interface' %}?return_url={{ device.get_absolute_url }}">Interface</a></li>
-                    <li><a href="{% url 'dcim:frontport_connect' termination_a_id=frontport.pk termination_b_type='console-server-port' %}?return_url={{ device.get_absolute_url }}">Console Server Port</a></li>
-                    <li><a href="{% url 'dcim:frontport_connect' termination_a_id=frontport.pk termination_b_type='console-port' %}?return_url={{ device.get_absolute_url }}">Console Port</a></li>
-                    <li><a href="{% url 'dcim:frontport_connect' termination_a_id=frontport.pk termination_b_type='front-port' %}?return_url={{ device.get_absolute_url }}">Front Port</a></li>
-                    <li><a href="{% url 'dcim:frontport_connect' termination_a_id=frontport.pk termination_b_type='rear-port' %}?return_url={{ device.get_absolute_url }}">Rear Port</a></li>
-                    <li><a href="{% url 'dcim:frontport_connect' termination_a_id=frontport.pk termination_b_type='circuit-termination' %}?return_url={{ device.get_absolute_url }}">Circuit Termination</a></li>
-                </ul>
-            </span>
-        {% endif %}
-        {% if perms.dcim.change_frontport %}
-            <a href="{% url 'dcim:frontport_edit' pk=frontport.pk %}?return_url={{ device.get_absolute_url }}" title="Edit port" class="btn btn-info btn-xs">
-                <i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
-            </a>
-        {% endif %}
-        {% if perms.dcim.delete_frontport %}
-            <a href="{% url 'dcim:frontport_delete' pk=frontport.pk %}?return_url={{ device.get_absolute_url }}" title="Delete port" class="btn btn-danger btn-xs">
-                <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
-            </a>
-        {% endif %}
-    </td>
-</tr>

+ 0 - 200
netbox/templates/dcim/inc/interface.html

@@ -1,200 +0,0 @@
-{% load helpers %}
-<tr class="interface{% if not iface.enabled %} danger{% elif iface.cable %} {{ iface.cable.get_status_class }}{% elif iface.is_virtual %} warning{% endif %}" id="interface_{{ iface.name }}">
-
-    {# Checkbox #}
-    {% if perms.dcim.change_interface or perms.dcim.delete_interface %}
-        <td class="pk">
-            <input name="pk" type="checkbox" value="{{ iface.pk }}" />
-        </td>
-    {% endif %}
-
-    {# Icon and name #}
-    <td class="text-nowrap">
-        <span title="{{ iface.get_type_display }}">
-            <i class="fa fa-fw fa-{% if iface.mgmt_only %}wrench{% elif iface.is_lag %}align-justify{% elif iface.is_virtual %}circle{% elif iface.is_wireless %}wifi{% else %}exchange{% endif %}"></i>
-            <a href="{{ iface.get_absolute_url }}">{{ iface }}</a>
-        </span>
-        {% if iface.mac_address %}
-            <br/><small class="text-muted">{{ iface.mac_address }}</small>
-        {% endif %}
-    </td>
-
-    {# LAG #}
-    <td>
-        {% if iface.lag %}
-            <a href="{{ iface.lag.device.get_absolute_url }}#interface_{{ iface.lag }}" class="label label-primary" title="{{ iface.lag.description }}">{{ iface.lag }}</a>
-        {% endif %}
-    </td>
-
-    {# Description/tags #}
-    <td>
-        {% if iface.description %}
-            {{ iface.description }}<br/>
-        {% endif %}
-        {% for tag in iface.tags.all %}
-            {% tag tag %}
-        {% empty %}
-            {% if not iface.description %}&mdash;{% endif %}
-        {% endfor %}
-    </td>
-
-    {# MTU #}
-    <td>{{ iface.mtu|default:"&mdash;" }}</td>
-
-    {# 802.1Q mode #}
-    <td>{{ iface.get_mode_display|default:"&mdash;" }}</td>
-
-    {# Cable #}
-    {% if iface.cable %}
-        <td>
-            <a href="{{ iface.cable.get_absolute_url }}">{{ iface.cable }}</a>
-            <a href="{% url 'dcim:interface_trace' pk=iface.pk %}" class="btn btn-primary btn-xs" title="Trace">
-                <i class="fa fa-share-alt" aria-hidden="true"></i>
-            </a>
-        </td>
-        {% include 'dcim/inc/cabletermination.html' with termination=iface.get_cable_peer %}
-    {% else %}
-        <td colspan="3">
-            <span class="text-muted">Not connected</span>
-        </td>
-    {% endif %}
-
-    {# Connection or type #}
-    {% if iface.is_lag %}
-        <td colspan="2" class="text-muted">
-            LAG interface<br />
-            <small class="text-muted">
-                {% for member in iface.member_interfaces.all %}
-                    <a href="{{ member.get_absolute_url }}">{{ member }}</a>{% if not forloop.last %}, {% endif %}
-                {% empty %}
-                    No members
-                {% endfor %}
-            </small>
-        </td>
-    {% elif iface.is_virtual %}
-        <td colspan="2" class="text-muted">Virtual interface</td>
-    {% elif iface.is_wireless %}
-        <td colspan="2" class="text-muted">Wireless interface</td>
-    {% else %}
-        {% include 'dcim/inc/endpoint_connection.html' with path=iface.path %}
-    {% endif %}
-
-    {# Buttons #}
-    <td class="text-right text-nowrap noprint">
-        {% if perms.ipam.add_ipaddress %}
-            <a href="{% url 'ipam:ipaddress_add' %}?interface={{ iface.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-xs btn-success" title="Add IP address">
-                <i class="glyphicon glyphicon-plus" aria-hidden="true"></i>
-            </a>
-        {% endif %}
-        {% if perms.dcim.change_interface %}
-            {% if iface.cable %}
-                {% include 'dcim/inc/cable_toggle_buttons.html' with cable=iface.cable %}
-            {% elif iface.is_connectable and perms.dcim.add_cable %}
-                <span class="dropdown">
-                    <button type="button" class="btn btn-success btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                        <span class="glyphicon glyphicon-resize-small" aria-hidden="true"></span>
-                    </button>
-                    <ul class="dropdown-menu dropdown-menu-right">
-                        <li><a href="{% url 'dcim:interface_connect' termination_a_id=iface.pk termination_b_type='interface' %}?return_url={{ device.get_absolute_url }}">Interface</a></li>
-                        <li><a href="{% url 'dcim:interface_connect' termination_a_id=iface.pk termination_b_type='front-port' %}?return_url={{ device.get_absolute_url }}">Front Port</a></li>
-                        <li><a href="{% url 'dcim:interface_connect' termination_a_id=iface.pk termination_b_type='rear-port' %}?return_url={{ device.get_absolute_url }}">Rear Port</a></li>
-                        <li><a href="{% url 'dcim:interface_connect' termination_a_id=iface.pk termination_b_type='circuit-termination' %}?return_url={{ device.get_absolute_url }}">Circuit Termination</a></li>
-                    </ul>
-                </span>
-            {% endif %}
-            <a href="{% url 'dcim:interface_edit' pk=iface.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-info btn-xs" title="Edit interface">
-                <i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
-            </a>
-        {% endif %}
-        {% if perms.dcim.delete_interface %}
-            {% if iface.connection or iface.circuit_termination %}
-                <button class="btn btn-danger btn-xs" disabled="disabled">
-                    <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
-                </button>
-            {% else %}
-                <a href="{% url 'dcim:interface_delete' pk=iface.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs" title="Delete interface">
-                    <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
-                </a>
-            {% endif %}
-        {% endif %}
-    </td>
-</tr>
-
-{% with ipaddresses=iface.ip_addresses.all %}
-    {% if ipaddresses %}
-        <tr class="ipaddresses">
-            {# Placeholder #}
-            {% if perms.dcim.change_interface or perms.dcim.delete_interface %}
-                <td></td>
-            {% endif %}
-
-            {# IP addresses table #}
-            <td colspan="9" style="padding: 0">
-                <table class="table table-condensed interface-ips">
-                    <thead>
-                        <tr class="text-muted">
-                            <th class="col-md-3">IP Address</th>
-                            <th class="col-md-2">Status/Role</th>
-                            <th class="col-md-3">VRF</th>
-                            <th class="col-md-3">Description</th>
-                            <th class="col-md-1"></th>
-                        </tr>
-                    </thead>
-                    {% for ip in iface.ip_addresses.all %}
-                        <tr>
-
-                            {# IP address #}
-                            <td>
-                                <a href="{% url 'ipam:ipaddress' pk=ip.pk %}">{{ ip }}</a>
-                            </td>
-
-                            {# Primary/status/role #}
-                            <td>
-                                {% if device.primary_ip4 == ip or device.primary_ip6 == ip %}
-                                    <span class="label label-success">Primary</span>
-                                {% endif %}
-                                <span class="label label-{{ ip.get_status_class }}">{{ ip.get_status_display }}</span>
-                                {% if ip.role %}
-                                    <span class="label label-{{ ip.get_role_class }}">{{ ip.get_role_display }}</span>
-                                {% endif %}
-                            </td>
-
-                            {# VRF #}
-                            <td>
-                                {% if ip.vrf %}
-                                    <a href="{% url 'ipam:vrf' pk=ip.vrf.pk %}" title="{{ ip.vrf.rd }}">{{ ip.vrf.name }}</a>
-                                {% else %}
-                                    <span class="text-muted">Global</span>
-                                {% endif %}
-                            </td>
-
-                            {# Description #}
-                            <td>
-                                {% if ip.description %}
-                                    {{ ip.description }}
-                                {% else %}
-                                    <span class="text-muted">&mdash;</span>
-                                {% endif %}
-                            </td>
-
-                            {# Buttons #}
-                            <td class="text-right text-nowrap noprint">
-                                {% if perms.ipam.change_ipaddress %}
-                                    <a href="{% url 'ipam:ipaddress_edit' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-info btn-xs">
-                                        <i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit IP address"></i>
-                                    </a>
-                                {% endif %}
-                                {% if perms.ipam.delete_ipaddress %}
-                                    <a href="{% url 'ipam:ipaddress_delete' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
-                                        <i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete IP address"></i>
-                                    </a>
-                                {% endif %}
-                            </td>
-
-                        </tr>
-                    {% endfor %}
-                </table>
-            </td>
-        </tr>
-    {% endif %}
-{% endwith %}

+ 0 - 40
netbox/templates/dcim/inc/inventoryitem.html

@@ -1,40 +0,0 @@
-{% load helpers %}
-<tr>
-
-    {# Checkbox #}
-    {% if perms.dcim.change_inventoryitem or perms.dcim.delete_inventoryitem %}
-        <td class="pk">
-            <input name="pk" type="checkbox" value="{{ item.pk }}" />
-        </td>
-    {% endif %}
-
-    <td style="padding-left: {{ item.level }}0px">
-        <a href="{{ item.get_absolute_url }}">{{ item }}</a>
-    </td>
-    <td>
-        {% if item.manufacturer %}
-            <a href="{{ item.manufacturer.get_absolute_url }}">{{ item.manufacturer }}</a>
-        {% else %}
-            <span class="text-muted">&mdash;</span>
-        {% endif %}
-    </td>
-    <td>{{ item.part_id|placeholder }}</td>
-    <td>{{ item.serial|placeholder }}</td>
-    <td>{{ item.asset_tag|placeholder }}</td>
-    <td>
-        {% if item.discovered %}
-            <span class="text-success"><i class="fa fa-check"></i></span>
-        {% else %}
-            <span class="text-muted">&mdash;</span>
-        {% endif %}
-    </td>
-    <td>{{ item.description|placeholder }}</td>
-    <td class="text-right noprint">
-        {% if perms.dcim.change_inventoryitem %}
-            <a href="{% url 'dcim:inventoryitem_edit' pk=item.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-xs btn-warning"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span></a>
-        {% endif %}
-        {% if perms.dcim.delete_inventoryitem %}
-            <a href="{% url 'dcim:inventoryitem_delete' pk=item.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></a>
-        {% endif %}
-    </td>
-</tr>

+ 0 - 93
netbox/templates/dcim/inc/poweroutlet.html

@@ -1,93 +0,0 @@
-{% load helpers %}
-
-<tr class="poweroutlet{% if po.cable %} {{ po.cable.get_status_class }}{% endif %}">
-
-    {# Checkbox #}
-    {% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %}
-        <td class="pk">
-            <input name="pk" type="checkbox" value="{{ po.pk }}" />
-        </td>
-    {% endif %}
-
-    {# Name #}
-    <td>
-        <i class="fa fa-fw fa-bolt"></i>
-        <a href="{{ po.get_absolute_url }}">{{ po }}</a>
-    </td>
-
-    {# Type #}
-    <td>
-        {{ po.get_type_display }}
-    </td>
-
-    {# Input/leg #}
-    <td>
-        {% if po.power_port %}
-            {{ po.power_port }}{% if po.feed_leg %} / {{ po.get_feed_leg_display }}{% endif %}
-        {% elif po.feed_leg %}
-            {{ po.get_feed_leg_display }}
-        {% else %}
-            <span class="text-warning">None</span>
-        {% endif %}
-    </td>
-
-    {# Description #}
-    <td>
-        {{ po.description|placeholder }}
-    </td>
-
-    {# Cable #}
-    {% if po.cable %}
-        <td>
-            <a href="{{ po.cable.get_absolute_url }}">{{ po.cable }}</a>
-            <a href="{% url 'dcim:poweroutlet_trace' pk=po.pk %}" class="btn btn-primary btn-xs" title="Trace">
-                <i class="fa fa-share-alt" aria-hidden="true"></i>
-            </a>
-        </td>
-    {% else %}
-        <td><span class="text-muted">Not connected</span></td>
-    {% endif %}
-
-    {# Connection #}
-    {% with path=po.path %}
-        {% include 'dcim/inc/endpoint_connection.html' %}
-        <td>
-            {% if paths|length == 1 %}
-                {% with pp=paths.0.destination %}
-                    {% if pp.allocated_draw %}
-                        {{ pp.allocated_draw }}W{% if pp.maximum_draw %} ({{ pp.maximum_draw }}W max){% endif %}
-                    {% elif pp.maximum_draw %}
-                        {{ pp.maximum_draw }}W
-                    {% endif %}
-                {% endwith %}
-            {% endif %}
-        </td>
-    {% endwith %}
-
-    {# Actions #}
-    <td class="text-right noprint">
-        {% if po.cable %}
-            {% include 'dcim/inc/cable_toggle_buttons.html' with cable=po.cable %}
-        {% elif perms.dcim.add_cable %}
-            <a href="{% url 'dcim:poweroutlet_connect' termination_a_id=po.pk termination_b_type='power-port' %}?return_url={{ device.get_absolute_url }}" title="Connect" class="btn btn-success btn-xs">
-                <i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
-            </a>
-        {% endif %}
-        {% if perms.dcim.change_poweroutlet %}
-            <a href="{% url 'dcim:poweroutlet_edit' pk=po.pk %}?return_url={{ device.get_absolute_url }}" title="Edit outlet" class="btn btn-info btn-xs">
-                <i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
-            </a>
-        {% endif %}
-        {% if perms.dcim.delete_poweroutlet %}
-            {% if po.connected_endpoint %}
-                <button class="btn btn-danger btn-xs" disabled="disabled">
-                    <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
-                </button>
-            {% else %}
-                <a href="{% url 'dcim:poweroutlet_delete' pk=po.pk %}?return_url={{ device.get_absolute_url }}" title="Delete outlet" class="btn btn-danger btn-xs">
-                    <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
-                </a>
-            {% endif %}
-        {% endif %}
-    </td>
-</tr>

+ 0 - 82
netbox/templates/dcim/inc/powerport.html

@@ -1,82 +0,0 @@
-<tr class="powerport{% if pp.cable %} {{ pp.cable.get_status_class }}{% endif %}">
-
-    {# Checkbox #}
-    {% if perms.dcim.change_powerport or perms.dcim.delete_powerport %}
-        <td class="pk">
-            <input name="pk" type="checkbox" value="{{ pp.pk }}" />
-        </td>
-    {% endif %}
-
-    {# Name #}
-    <td>
-        <i class="fa fa-fw fa-bolt"></i>
-        <a href="{{ pp.get_absolute_url }}">{{ pp }}</a>
-    </td>
-
-    {# Type #}
-    <td>
-        {{ pp.get_type_display }}
-    </td>
-
-    {# Current draw #}
-    <td>
-        {% if pp.allocated_draw %}
-            {{ pp.allocated_draw }}W{% if pp.maximum_draw %} ({{ pp.maximum_draw }}W max){% endif %}
-        {% elif pp.maximum_draw %}
-            {{ pp.maximum_draw }}W
-        {% endif %}
-    </td>
-
-    {# Description #}
-    <td>
-        {{ pp.description }}
-    </td>
-
-    {# Cable #}
-    {% if pp.cable %}
-        <td>
-            <a href="{{ pp.cable.get_absolute_url }}">{{ pp.cable }}</a>
-            <a href="{% url 'dcim:powerport_trace' pk=pp.pk %}" class="btn btn-primary btn-xs" title="Trace">
-                <i class="fa fa-share-alt" aria-hidden="true"></i>
-            </a>
-        </td>
-    {% else %}
-        <td><span class="text-muted">Not connected</span></td>
-    {% endif %}
-
-    {# Connection #}
-    {% include 'dcim/inc/endpoint_connection.html' with path=pp.path %}
-
-    {# Actions #}
-    <td class="text-right noprint">
-        {% if pp.cable %}
-            {% include 'dcim/inc/cable_toggle_buttons.html' with cable=pp.cable %}
-        {% elif perms.dcim.add_cable %}
-            <span class="dropdown">
-                <button type="button" class="btn btn-success btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                    <span class="glyphicon glyphicon-resize-small" aria-hidden="true"></span>
-                </button>
-                <ul class="dropdown-menu dropdown-menu-right">
-                    <li><a href="{% url 'dcim:powerport_connect' termination_a_id=pp.pk termination_b_type='power-outlet' %}?return_url={{ device.get_absolute_url }}">Power Outlet</a></li>
-                    <li><a href="{% url 'dcim:powerport_connect' termination_a_id=pp.pk termination_b_type='power-feed' %}?return_url={{ device.get_absolute_url }}">Power Feed</a></li>
-                </ul>
-            </span>
-        {% endif %}
-        {% if perms.dcim.change_powerport %}
-            <a href="{% url 'dcim:powerport_edit' pk=pp.pk %}?return_url={{ device.get_absolute_url }}" title="Edit port" class="btn btn-info btn-xs">
-                <i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
-            </a>
-        {% endif %}
-        {% if perms.dcim.delete_powerport %}
-            {% if pp.connected_endpoint %}
-                <button class="btn btn-danger btn-xs" disabled="disabled">
-                    <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
-                </button>
-            {% else %}
-                <a href="{% url 'dcim:powerport_delete' pk=pp.pk %}?return_url={{ device.get_absolute_url }}" title="Delete port" class="btn btn-danger btn-xs">
-                    <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
-                </a>
-            {% endif %}
-        {% endif %}
-    </td>
-</tr>

+ 0 - 69
netbox/templates/dcim/inc/rearport.html

@@ -1,69 +0,0 @@
-{% load helpers %}
-<tr class="rearport{% if rearport.cable %} {{ rearport.cable.get_status_class }}{% endif %}">
-
-    {# Checkbox #}
-    {% if perms.dcim.change_rearport or perms.dcim.delete_rearport %}
-        <td class="pk">
-            <input name="pk" type="checkbox" value="{{ rearport.pk }}" />
-        </td>
-    {% endif %}
-
-    {# Name #}
-    <td>
-        <i class="fa fa-fw fa-square{% if not rearport.cable %}-o{% endif %}"></i>
-        <a href="{{ rearport.get_absolute_url }}">{{ rearport }}</a>
-    </td>
-
-    {# Type #}
-    <td>{{ rearport.get_type_display }}</td>
-
-    {# Positions #}
-    <td>{{ rearport.positions }}</td>
-
-    {# Description #}
-    <td>{{ rearport.description|placeholder }}</td>
-
-    {# Cable #}
-    {% if rearport.cable %}
-        <td>
-            <a href="{{ rearport.cable.get_absolute_url }}">{{ rearport.cable }}</a>
-            <a href="{% url 'dcim:rearport_trace' pk=rearport.pk %}" class="btn btn-primary btn-xs" title="Trace">
-                <i class="fa fa-share-alt" aria-hidden="true"></i>
-            </a>
-        </td>
-        {% include 'dcim/inc/cabletermination.html' with termination=rearport.get_cable_peer %}
-    {% else %}
-        <td colspan="3">
-            <span class="text-muted">Not connected</span>
-        </td>
-    {% endif %}
-
-    {# Actions #}
-    <td class="text-right noprint">
-        {% if rearport.cable %}
-            {% include 'dcim/inc/cable_toggle_buttons.html' with cable=rearport.cable %}
-        {% elif perms.dcim.add_cable %}
-            <span class="dropdown">
-                <button type="button" class="btn btn-success btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                    <span class="glyphicon glyphicon-resize-small" aria-hidden="true"></span>
-                </button>
-                <ul class="dropdown-menu dropdown-menu-right">
-                    <li><a href="{% url 'dcim:rearport_connect' termination_a_id=rearport.pk termination_b_type='interface' %}?return_url={{ device.get_absolute_url }}">Interface</a></li>
-                    <li><a href="{% url 'dcim:rearport_connect' termination_a_id=rearport.pk termination_b_type='front-port' %}?return_url={{ device.get_absolute_url }}">Front Port</a></li>
-                    <li><a href="{% url 'dcim:rearport_connect' termination_a_id=rearport.pk termination_b_type='rear-port' %}?return_url={{ device.get_absolute_url }}">Rear Port</a></li>
-                    <li><a href="{% url 'dcim:rearport_connect' termination_a_id=rearport.pk termination_b_type='circuit-termination' %}?return_url={{ device.get_absolute_url }}">Circuit Termination</a></li>
-                </ul>
-            </span>
-        {% endif %}
-        {% if perms.dcim.change_rearport %}
-            <a href="{% url 'dcim:rearport_edit' pk=rearport.pk %}?return_url={{ device.get_absolute_url }}" title="Edit port" class="btn btn-info btn-xs">
-                <i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
-            </a>
-        {% endif %}
-        {% if perms.dcim.delete_rearport %}
-            <a href="{% url 'dcim:rearport_delete' pk=rearport.pk %}?return_url={{ device.get_absolute_url }}" title="Delete port" class="btn btn-danger btn-xs">
-                <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
-            </a>
-        {% endif %}
-    </td>
-</tr>

+ 0 - 136
netbox/templates/virtualization/inc/vminterface.html

@@ -1,136 +0,0 @@
-{% load helpers %}
-<tr class="interface{% if not iface.enabled %} danger{% endif %}" id="interface_{{ iface.name }}">
-
-    {# Checkbox #}
-    {% if perms.virtualization.change_vminterface or perms.virtualization.delete_vminterface %}
-        <td class="pk">
-            <input name="pk" type="checkbox" value="{{ iface.pk }}" />
-        </td>
-    {% endif %}
-
-    {# Name #}
-    <td>
-        <a href="{{ iface.get_absolute_url }}">{{ iface }}</a>
-    </td>
-
-    {# MAC address #}
-    <td class="text-monospace">
-        {{ iface.mac_address|default:"&mdash;" }}
-    </td>
-
-    {# MTU #}
-    <td>{{ iface.mtu|default:"&mdash;" }}</td>
-
-    {# 802.1Q mode #}
-    <td>{{ iface.get_mode_display|default:"&mdash;" }}</td>
-
-    {# Description/tags #}
-    <td>
-        {% if iface.description %}
-            {{ iface.description }}<br/>
-        {% endif %}
-        {% for tag in iface.tags.all %}
-            {% tag tag %}
-        {% empty %}
-            {% if not iface.description %}&mdash;{% endif %}
-        {% endfor %}
-    </td>
-
-    {# Buttons #}
-    <td class="text-right text-nowrap noprint">
-        {% if perms.ipam.add_ipaddress %}
-            <a href="{% url 'ipam:ipaddress_add' %}?vminterface={{ iface.pk }}&return_url={{ virtualmachine.get_absolute_url }}" class="btn btn-xs btn-success" title="Add IP address">
-                <i class="glyphicon glyphicon-plus" aria-hidden="true"></i>
-            </a>
-        {% endif %}
-        {% if perms.virtualization.change_vminterface %}
-            <a href="{% url 'virtualization:vminterface_edit' pk=iface.pk %}?return_url={{ virtualmachine.get_absolute_url }}" class="btn btn-info btn-xs" title="Edit interface">
-                <i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
-            </a>
-        {% endif %}
-        {% if perms.virtualization.delete_vminterface %}
-            <a href="{% url 'virtualization:vminterface_delete' pk=iface.pk %}?return_url={{ virtualmachine.get_absolute_url }}" class="btn btn-danger btn-xs" title="Delete interface">
-                <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
-            </a>
-        {% endif %}
-    </td>
-</tr>
-
-{% with ipaddresses=iface.ip_addresses.all %}
-    {% if ipaddresses %}
-        <tr class="ipaddresses">
-            {# Placeholder #}
-            {% if perms.virtualization.change_vminterface or perms.virtualization.delete_vminterface %}
-                <td></td>
-            {% endif %}
-
-            {# IP addresses table #}
-            <td colspan="9" style="padding: 0">
-                <table class="table table-condensed interface-ips">
-                    <thead>
-                        <tr class="text-muted">
-                            <th class="col-md-3">IP Address</th>
-                            <th class="col-md-2">Status/Role</th>
-                            <th class="col-md-3">VRF</th>
-                            <th class="col-md-3">Description</th>
-                            <th class="col-md-1"></th>
-                        </tr>
-                    </thead>
-                    {% for ip in iface.ip_addresses.all %}
-                        <tr>
-
-                            {# IP address #}
-                            <td>
-                                <a href="{% url 'ipam:ipaddress' pk=ip.pk %}">{{ ip }}</a>
-                            </td>
-
-                            {# Primary/status/role #}
-                            <td>
-                                {% if virtualmachine.primary_ip4 == ip or virtualmachine.primary_ip6 == ip %}
-                                    <span class="label label-success">Primary</span>
-                                {% endif %}
-                                <span class="label label-{{ ip.get_status_class }}">{{ ip.get_status_display }}</span>
-                                {% if ip.role %}
-                                    <span class="label label-{{ ip.get_role_class }}">{{ ip.get_role_display }}</span>
-                                {% endif %}
-                            </td>
-
-                            {# VRF #}
-                            <td>
-                                {% if ip.vrf %}
-                                    <a href="{% url 'ipam:vrf' pk=ip.vrf.pk %}" title="{{ ip.vrf.rd }}">{{ ip.vrf.name }}</a>
-                                {% else %}
-                                    <span class="text-muted">Global</span>
-                                {% endif %}
-                            </td>
-
-                            {# Description #}
-                            <td>
-                                {% if ip.description %}
-                                    {{ ip.description }}
-                                {% else %}
-                                    <span class="text-muted">&mdash;</span>
-                                {% endif %}
-                            </td>
-
-                            {# Buttons #}
-                            <td class="text-right text-nowrap noprint">
-                                {% if perms.ipam.change_ipaddress %}
-                                    <a href="{% url 'ipam:ipaddress_edit' pk=ip.pk %}?return_url={{ virtualmachine.get_absolute_url }}" class="btn btn-info btn-xs">
-                                        <i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit IP address"></i>
-                                    </a>
-                                {% endif %}
-                                {% if perms.ipam.delete_ipaddress %}
-                                    <a href="{% url 'ipam:ipaddress_delete' pk=ip.pk %}?return_url={{ virtualmachine.get_absolute_url }}" class="btn btn-danger btn-xs">
-                                        <i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete IP address"></i>
-                                    </a>
-                                {% endif %}
-                            </td>
-
-                        </tr>
-                    {% endfor %}
-                </table>
-            </td>
-        </tr>
-    {% endif %}
-{% endwith %}

+ 1 - 29
netbox/templates/virtualization/virtualmachine.html

@@ -276,39 +276,11 @@
         <div class="panel panel-default">
             <div class="panel-heading">
                 <strong>Interfaces</strong>
-                <div class="pull-right">
-                    <button class="btn btn-default btn-xs toggle-ips" selected="selected">
-                        <span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
-                    </button>
-                </div>
                 <div class="col-md-2 pull-right noprint">
                     <input class="form-control interface-filter" type="text" placeholder="Filter" title="RegEx-enabled" style="height: 23px" />
                 </div>
             </div>
-            <table id="interfaces_table" class="table table-hover table-headings panel-body component-list">
-                <thead>
-                    <tr>
-                        {% if perms.virtualization.change_vminterface or perms.virtualization.delete_vminterface %}
-                            <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
-                        {% endif %}
-                        <th>Name</th>
-                        <th>MAC Address</th>
-                        <th>MTU</th>
-                        <th>Mode</th>
-                        <th>Description</th>
-                        <th></th>
-                    </tr>
-                </thead>
-                <tbody>
-                    {% for iface in interfaces %}
-                        {% include 'virtualization/inc/vminterface.html' %}
-                    {% empty %}
-                        <tr>
-                            <td colspan="8" class="text-center text-muted">&mdash; No interfaces defined &mdash;</td>
-                        </tr>
-                    {% endfor %}
-                </tbody>
-            </table>
+            {% include 'responsive_table.html' with table=vminterface_table %}
             {% if perms.virtualization.add_vminterface or perms.virtualization.delete_vminterface %}
                 <div class="panel-footer noprint">
                     {% if interfaces and perms.virtualization.change_vminterface %}

+ 1 - 1
netbox/utilities/tables.py

@@ -142,7 +142,7 @@ class ButtonsColumn(tables.TemplateColumn):
         </a>
     {{% endif %}}
     {{% if "edit" in buttons and perms.{app_label}.change_{model_name} %}}
-        <a href="{{% url '{app_label}:{model_name}_edit' {pk_field}=record.{pk_field} %}}?return_url={{{{ request.path }}}}" class="btn btn-xs btn-warning" title="Edit">
+        <a href="{{% url '{app_label}:{model_name}_edit' {pk_field}=record.{pk_field} %}}?return_url={{{{ request.path }}}}" class="btn btn-xs btn-info" title="Edit">
             <i class="fa fa-pencil"></i>
         </a>
     {{% endif %}}

+ 34 - 0
netbox/virtualization/tables.py

@@ -7,6 +7,24 @@ from utilities.tables import (
 )
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 
+__all__ = (
+    'ClusterTable',
+    'ClusterGroupTable',
+    'ClusterTypeTable',
+    'VirtualMachineDetailTable',
+    'VirtualMachineTable',
+    'VirtualMachineVMInterfaceTable',
+    'VMInterfaceTable',
+)
+
+VMINTERFACE_BUTTONS = """
+{% if perms.ipam.add_ipaddress %}
+    <a href="{% url 'ipam:ipaddress_add' %}?vminterface={{ record.pk }}&return_url={{ virtualmachine.get_absolute_url }}" class="btn btn-xs btn-success" title="Add IP address">
+        <i class="glyphicon glyphicon-plus" aria-hidden="true"></i>
+    </a>
+{% endif %}
+"""
+
 
 #
 # Cluster types
@@ -147,3 +165,19 @@ class VMInterfaceTable(BaseInterfaceTable):
             'untagged_vlan', 'tagged_vlans',
         )
         default_columns = ('pk', 'virtual_machine', 'name', 'enabled', 'description')
+
+
+class VirtualMachineVMInterfaceTable(VMInterfaceTable):
+    actions = ButtonsColumn(
+        model=VMInterface,
+        buttons=('edit', 'delete'),
+        prepend_template=VMINTERFACE_BUTTONS
+    )
+
+    class Meta(BaseTable.Meta):
+        model = VMInterface
+        fields = (
+            'pk', 'name', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'tags', 'ip_addresses',
+            'untagged_vlan', 'tagged_vlans', 'actions',
+        )
+        default_columns = ('pk', 'name', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'actions')

+ 6 - 2
netbox/virtualization/views.py

@@ -244,11 +244,15 @@ class VirtualMachineView(ObjectView):
         virtualmachine = get_object_or_404(self.queryset, pk=pk)
 
         # Interfaces
-        interfaces = VMInterface.objects.restrict(request.user, 'view').filter(
+        vminterfaces = VMInterface.objects.restrict(request.user, 'view').filter(
             virtual_machine=virtualmachine
         ).prefetch_related(
             Prefetch('ip_addresses', queryset=IPAddress.objects.restrict(request.user))
         )
+        vminterface_table = tables.VirtualMachineVMInterfaceTable(vminterfaces, orderable=False)
+        if request.user.has_perm('virtualization.change_vminterface') or \
+                request.user.has_perm('virtualization.delete_vminterface'):
+            vminterface_table.columns.show('pk')
 
         # Services
         services = Service.objects.restrict(request.user, 'view').filter(
@@ -262,7 +266,7 @@ class VirtualMachineView(ObjectView):
 
         return render(request, 'virtualization/virtualmachine.html', {
             'virtualmachine': virtualmachine,
-            'interfaces': interfaces,
+            'vminterface_table': vminterface_table,
             'services': services,
             'secrets': secrets,
         })