瀏覽代碼

Merge branch 'develop' into feature

jeremystretch 3 年之前
父節點
當前提交
a36294e209

+ 7 - 1
docs/release-notes/version-3.2.md

@@ -1,15 +1,21 @@
 # NetBox v3.2
 
-## v3.2.9 (FUTURE)
+## v3.2.9 (2022-08-16)
 
 ### Enhancements
 
+* [#8595](https://github.com/netbox-community/netbox/issues/8595) - Add PON interface types
+* [#8723](https://github.com/netbox-community/netbox/issues/8723) - Enable bulk renaming of devices
 * [#9161](https://github.com/netbox-community/netbox/issues/9161) - Pretty print JSON custom field data when editing
+* [#9505](https://github.com/netbox-community/netbox/issues/9505) - Display extra addressing details for IPv4 prefixes
 * [#9625](https://github.com/netbox-community/netbox/issues/9625) - Add phone & email details to contacts panel
 * [#9857](https://github.com/netbox-community/netbox/issues/9857) - Add clear button to quick search fields
+* [#9933](https://github.com/netbox-community/netbox/issues/9933) - Add DOCSIS interface type
 
 ### Bug Fixes
 
+* [#9491](https://github.com/netbox-community/netbox/issues/9491) - Remove button for adding inventory item templates to module type components
+* [#9979](https://github.com/netbox-community/netbox/issues/9979) - Fix Markdown rendering for custom fields in table columns
 * [#9986](https://github.com/netbox-community/netbox/issues/9986) - Workaround for upstream timezone data bug
 
 ---

+ 28 - 0
netbox/dcim/choices.py

@@ -836,6 +836,17 @@ class InterfaceTypeChoices(ChoiceSet):
     # ATM/DSL
     TYPE_XDSL = 'xdsl'
 
+    # Coaxial
+    TYPE_DOCSIS = 'docsis'
+
+    # PON
+    TYPE_GPON = 'gpon'
+    TYPE_XG_PON = 'xg-pon'
+    TYPE_XGS_PON = 'xgs-pon'
+    TYPE_NG_PON2 = 'ng-pon2'
+    TYPE_EPON = 'epon'
+    TYPE_10G_EPON = '10g-epon'
+
     # Stacking
     TYPE_STACKWISE = 'cisco-stackwise'
     TYPE_STACKWISE_PLUS = 'cisco-stackwise-plus'
@@ -972,6 +983,23 @@ class InterfaceTypeChoices(ChoiceSet):
                 (TYPE_XDSL, 'xDSL'),
             )
         ),
+        (
+            'Coaxial',
+            (
+                (TYPE_DOCSIS, 'DOCSIS'),
+            )
+        ),
+        (
+            'PON',
+            (
+                (TYPE_GPON, 'GPON (2.5 Gbps / 1.25 Gps)'),
+                (TYPE_XG_PON, 'XG-PON (10 Gbps / 2.5 Gbps)'),
+                (TYPE_XGS_PON, 'XGS-PON (10 Gbps)'),
+                (TYPE_NG_PON2, 'NG-PON2 (TWDM-PON) (4x10 Gbps)'),
+                (TYPE_EPON, 'EPON (1 Gbps)'),
+                (TYPE_10G_EPON, '10G-EPON (10 Gbps)'),
+            )
+        ),
         (
             'Stacking',
             (

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

@@ -88,7 +88,7 @@ LOCATION_BUTTONS = """
 
 MODULAR_COMPONENT_TEMPLATE_BUTTONS = """
 {% load helpers %}
-{% if perms.dcim.add_inventoryitemtemplate %}
+{% if perms.dcim.add_inventoryitemtemplate and record.device_type_id %}
 <a href="{% url 'dcim:inventoryitemtemplate_add' %}?device_type={{ record.device_type_id }}&component_type={{ record|content_type_id }}&component_id={{ record.pk }}&return_url={{ request.path }}" title="Add inventory item" class="btn btn-primary btn-sm">
   <i class="mdi mdi-plus-thick" aria-hidden="true"></i>
 </a>

+ 1 - 0
netbox/dcim/urls.py

@@ -248,6 +248,7 @@ urlpatterns = [
     path('devices/import/', views.DeviceBulkImportView.as_view(), name='device_import'),
     path('devices/import/child-devices/', views.ChildDeviceBulkImportView.as_view(), name='device_import_child'),
     path('devices/edit/', views.DeviceBulkEditView.as_view(), name='device_bulk_edit'),
+    path('devices/rename/', views.DeviceBulkRenameView.as_view(), name='device_bulk_rename'),
     path('devices/delete/', views.DeviceBulkDeleteView.as_view(), name='device_bulk_delete'),
     path('devices/<int:pk>/', views.DeviceView.as_view(), name='device'),
     path('devices/<int:pk>/edit/', views.DeviceEditView.as_view(), name='device_edit'),

+ 6 - 0
netbox/dcim/views.py

@@ -1798,6 +1798,12 @@ class DeviceBulkDeleteView(generic.BulkDeleteView):
     table = tables.DeviceTable
 
 
+class DeviceBulkRenameView(generic.BulkRenameView):
+    queryset = Device.objects.all()
+    filterset = filtersets.DeviceFilterSet
+    table = tables.DeviceTable
+
+
 #
 # Devices
 #

+ 6 - 3
netbox/netbox/tables/columns.py

@@ -14,6 +14,7 @@ from django_tables2.columns import library
 from django_tables2.utils import Accessor
 
 from extras.choices import CustomFieldTypeChoices
+from utilities.templatetags.builtins.filters import render_markdown
 from utilities.utils import content_type_identifier, content_type_name, get_viewname
 
 __all__ = (
@@ -427,7 +428,7 @@ class CustomFieldColumn(tables.Column):
         super().__init__(*args, **kwargs)
 
     @staticmethod
-    def _likify_item(item):
+    def _linkify_item(item):
         if hasattr(item, 'get_absolute_url'):
             return f'<a href="{item.get_absolute_url()}">{escape(item)}</a>'
         return escape(item)
@@ -443,11 +444,13 @@ class CustomFieldColumn(tables.Column):
             return ', '.join(v for v in value)
         if self.customfield.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT:
             return mark_safe(', '.join(
-                self._likify_item(obj) for obj in self.customfield.deserialize(value)
+                self._linkify_item(obj) for obj in self.customfield.deserialize(value)
             ))
+        if self.customfield.type == CustomFieldTypeChoices.TYPE_LONGTEXT and value:
+            return render_markdown(value)
         if value is not None:
             obj = self.customfield.deserialize(value)
-            return mark_safe(self._likify_item(obj))
+            return mark_safe(self._linkify_item(obj))
         return self.default
 
     def value(self, value):

+ 1 - 1
netbox/netbox/views/generic/bulk_views.py

@@ -603,7 +603,7 @@ class BulkRenameView(GetReturnURLMixin, BaseMultiObjectView):
             replace = form.cleaned_data['replace']
             if form.cleaned_data['use_regex']:
                 try:
-                    obj.new_name = re.sub(find, replace, obj.name)
+                    obj.new_name = re.sub(find, replace, obj.name or '')
                 # Catch regex group reference errors
                 except re.error:
                     obj.new_name = obj.name

+ 29 - 23
netbox/templates/dcim/device/consoleports.html

@@ -16,31 +16,37 @@
     </div>
 
     <div class="noprint bulk-buttons">
+      <div class="bulk-button-group">
+        {% if 'bulk_edit' in actions %}
+          <div class="btn-group" role="group">
+            <button type="submit" name="_edit" formaction="{% url 'dcim:consoleport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-warning btn-sm">
+              <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
+            </button>
+            <button type="submit" name="_rename" formaction="{% url 'dcim:consoleport_bulk_rename' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
+              <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
+            </button>
+          </div>
+        {% endif %}
+        <div class="btn-group" role="group">
+          {% if 'bulk_delete' in actions %}
+            <button type="submit" name="_delete" formaction="{% url 'dcim:consoleport_bulk_delete' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-danger btn-sm">
+              <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
+            </button>
+          {% endif %}
+          {% if 'bulk_edit' in actions %}
+            <button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleport_bulk_disconnect' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
+              <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
+            </button>
+          {% endif %}
+        </div>
+      </div>
+      {% if perms.dcim.add_consoleport %}
         <div class="bulk-button-group">
-            {% if 'bulk_edit' in actions %}
-                <button type="submit" name="_rename" formaction="{% url 'dcim:consoleport_bulk_rename' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
-                    <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
-                </button>
-                <button type="submit" name="_edit" formaction="{% url 'dcim:consoleport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-warning btn-sm">
-                    <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
-                </button>
-                <button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleport_bulk_disconnect' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
-                    <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
-                </button>
-            {% endif %}
-            {% if 'bulk_delete' in actions %}
-                <button type="submit" name="_delete" formaction="{% url 'dcim:consoleport_bulk_delete' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-danger btn-sm">
-                    <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
-                </button>
-            {% endif %}
+          <a href="{% url 'dcim:consoleport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-sm btn-primary">
+            <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Console Port
+          </a>
         </div>
-        {% if perms.dcim.add_consoleport %}
-            <div class="bulk-button-group">
-                <a href="{% url 'dcim:consoleport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-sm btn-primary">
-                    <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Console Port
-                </a>
-            </div>
-        {% endif %}
+      {% endif %}
     </div>
   </form>
 {% endblock %}

+ 29 - 23
netbox/templates/dcim/device/consoleserverports.html

@@ -16,31 +16,37 @@
     </div>
 
     <div class="noprint bulk-buttons">
+      <div class="bulk-button-group">
+        {% if 'bulk_edit' in actions %}
+          <div class="btn-group" role="group">
+            <button type="submit" name="_edit" formaction="{% url 'dcim:consoleserverport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-warning btn-sm">
+              <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
+            </button>
+            <button type="submit" name="_rename" formaction="{% url 'dcim:consoleserverport_bulk_rename' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
+              <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
+            </button>
+          </div>
+        {% endif %}
+        <div class="btn-group" role="group">
+          {% if 'bulk_delete' in actions %}
+            <button type="submit" formaction="{% url 'dcim:consoleserverport_bulk_delete' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-danger btn-sm">
+              <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
+            </button>
+          {% endif %}
+          {% if 'bulk_edit' in actions %}
+            <button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleserverport_bulk_disconnect' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
+              <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
+            </button>
+          {% endif %}
+        </div>
+      </div>
+      {% if perms.dcim.add_consoleserverport %}
         <div class="bulk-button-group">
-            {% if 'bulk_edit' in actions %}
-                <button type="submit" name="_rename" formaction="{% url 'dcim:consoleserverport_bulk_rename' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
-                    <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
-                </button>
-                <button type="submit" name="_edit" formaction="{% url 'dcim:consoleserverport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-warning btn-sm">
-                    <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
-                </button>
-                <button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleserverport_bulk_disconnect' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
-                    <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
-                </button>
-            {% endif %}
-            {% if 'bulk_delete' in actions %}
-                <button type="submit" formaction="{% url 'dcim:consoleserverport_bulk_delete' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-danger btn-sm">
-                    <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
-                </button>
-            {% endif %}
+          <a href="{% url 'dcim:consoleserverport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-primary btn-sm">
+            <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Console Server Ports
+          </a>
         </div>
-        {% if perms.dcim.add_consoleserverport %}
-            <div class="bulk-button-group">
-                <a href="{% url 'dcim:consoleserverport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-primary btn-sm">
-                    <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Console Server Ports
-                </a>
-            </div>
-        {% endif %}
+      {% endif %}
     </div>
   </form>
 {% endblock %}

+ 22 - 20
netbox/templates/dcim/device/devicebays.html

@@ -16,28 +16,30 @@
     </div>
 
     <div class="noprint bulk-buttons">
+      <div class="bulk-button-group">
+        {% if 'bulk_edit' in actions %}
+          <div class="btn-group" role="group">
+            <button type="submit" name="_edit" formaction="{% url 'dcim:devicebay_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-warning btn-sm">
+              <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
+            </button>
+            <button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
+              <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
+            </button>
+          </div>
+        {% endif %}
+        {% if 'bulk_delete' in actions %}
+          <button type="submit" name="_delete" formaction="{% url 'dcim:devicebay_bulk_delete' %}?return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-danger btn-sm">
+            <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
+          </button>
+        {% endif %}
+      </div>
+      {% if perms.dcim.add_devicebay %}
         <div class="bulk-button-group">
-            {% if 'bulk_edit' in actions %}
-                <button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
-                    <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
-                </button>
-                <button type="submit" name="_edit" formaction="{% url 'dcim:devicebay_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-warning btn-sm">
-                    <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
-                </button>
-            {% endif %}
-            {% if 'bulk_delete' in actions %}
-                <button type="submit" formaction="{% url 'dcim:devicebay_bulk_delete' %}?return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
-                    <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete selected
-                </button>
-            {% endif %}
+          <a href="{% url 'dcim:devicebay_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-primary btn-sm">
+            <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Device Bays
+          </a>
         </div>
-        {% if perms.dcim.add_devicebay %}
-            <div class="bulk-button-group">
-                <a href="{% url 'dcim:devicebay_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-primary btn-sm">
-                    <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Device Bays
-                </a>
-            </div>
-        {% endif %}
+      {% endif %}
     </div>
   </form>
 {% endblock %}

+ 29 - 23
netbox/templates/dcim/device/frontports.html

@@ -16,31 +16,37 @@
     </div>
 
     <div class="noprint bulk-buttons">
+      <div class="bulk-button-group">
+        {% if 'bulk_edit' in actions %}
+          <div class="btn-group" role="group">
+            <button type="submit" name="_edit" formaction="{% url 'dcim:frontport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-warning btn-sm">
+              <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
+            </button>
+            <button type="submit" name="_rename" formaction="{% url 'dcim:frontport_bulk_rename' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
+              <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
+            </button>
+          </div>
+        {% endif %}
+        <div class="btn-group" role="group">
+          {% if 'bulk_delete' in actions %}
+            <button type="submit" formaction="{% url 'dcim:frontport_bulk_delete' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-danger btn-sm">
+              <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
+            </button>
+          {% endif %}
+          {% if 'bulk_edit' in actions %}
+            <button type="submit" name="_disconnect" formaction="{% url 'dcim:frontport_bulk_disconnect' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
+              <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
+            </button>
+          {% endif %}
+        </div>
+      </div>
+      {% if perms.dcim.add_frontport %}
         <div class="bulk-button-group">
-            {% if 'bulk_edit' in actions %}
-                <button type="submit" name="_rename" formaction="{% url 'dcim:frontport_bulk_rename' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
-                    <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
-                </button>
-                <button type="submit" name="_edit" formaction="{% url 'dcim:frontport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-warning btn-sm">
-                    <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
-                </button>
-                <button type="submit" name="_disconnect" formaction="{% url 'dcim:frontport_bulk_disconnect' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
-                    <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
-                </button>
-            {% endif %}
-            {% if 'bulk_delete' in actions %}
-                <button type="submit" formaction="{% url 'dcim:frontport_bulk_delete' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-danger btn-sm">
-                    <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
-                </button>
-            {% endif %}
+          <a href="{% url 'dcim:frontport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-primary btn-sm">
+            <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add front ports
+          </a>
         </div>
-        {% if perms.dcim.add_frontport %}
-            <div class="bulk-button-group">
-                <a href="{% url 'dcim:frontport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-primary btn-sm">
-                    <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add front ports
-                </a>
-            </div>
-        {% endif %}
+      {% endif %}
     </div>
   </form>
 {% endblock %}

+ 11 - 0
netbox/templates/dcim/device/inc/interface_table_controls.html

@@ -0,0 +1,11 @@
+{% extends 'inc/table_controls_htmx.html' %}
+
+{% block extra_table_controls %}
+  <button class="btn btn-sm btn-outline-dark dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
+    <i class="mdi mdi-eye"></i>
+  </button>
+  <ul class="dropdown-menu">
+    <button type="button" class="dropdown-item toggle-enabled" data-state="show">Hide Enabled</button>
+    <button type="button" class="dropdown-item toggle-disabled" data-state="show">Hide Disabled</button>
+  </ul>
+{% endblock extra_table_controls %}

+ 30 - 52
netbox/templates/dcim/device/interfaces.html

@@ -4,39 +4,11 @@
 {% load static %}
 
 {% block content %}
-<div class="row mb-3 justify-content-between">
-  <div class="col col-12 col-lg-4 my-3 my-lg-0 d-flex noprint table-controls">
-    <div class="input-group input-group-sm quicksearch hide-last-child">
-      <input type="search" results=5 name="q" id="quicksearch" class="form-control" placeholder="Quick search"
-        hx-get="{{ request.full_path }}" hx-target="#object_list" hx-trigger="keyup changed delay:500ms, search" />
-      <button class="btn bg-transparent" type="button" id="quicksearch_clear"><i
-          class="mdi mdi-close-circle"></i></button>
-    </div>
-  </div>
-  <div class="col col-md-3 mb-0 d-flex noprint table-controls">
-    <div class="input-group input-group-sm justify-content-end">
-      {% if request.user.is_authenticated %}
-      <button type="button" class="btn btn-sm btn-outline-dark" data-bs-toggle="modal"
-        data-bs-target="#DeviceInterfaceTable_config" title="Configure Table">
-        <i class="mdi mdi-cog"></i> Configure Table
-      </button>
-      {% endif %}
-      <button class="btn btn-sm btn-outline-dark dropdown-toggle" type="button" data-bs-toggle="dropdown"
-        aria-expanded="false">
-        <i class="mdi mdi-eye"></i>
-      </button>
-      <ul class="dropdown-menu">
-        <button type="button" class="dropdown-item toggle-enabled" data-state="show">Hide Enabled</button>
-        <button type="button" class="dropdown-item toggle-disabled" data-state="show">Hide Disabled</button>
-      </ul>
-    </div>
-  </div>
-</div>
+  {% include 'dcim/device/inc/interface_table_controls.html' with table_modal="DeviceInterfaceTable_config" %}
 
 <form method="post">
   {% csrf_token %}
 
-
   <div class="card">
     <div class="card-body" id="object_list">
       {% include 'htmx/table.html' %}
@@ -45,30 +17,36 @@
 
   <div class="noprint bulk-buttons">
     <div class="bulk-button-group">
-      {% if perms.dcim.change_interface %}
-        <button type="submit" name="_rename"
-          formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
-          class="btn btn-outline-warning btn-sm">
-          <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
-        </button>
-        <button type="submit" name="_edit"
-          formaction="{% url 'dcim:interface_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
-          class="btn btn-warning btn-sm">
-          <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
-        </button>
-        <button type="submit" name="_disconnect"
-          formaction="{% url 'dcim:interface_bulk_disconnect' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
-          class="btn btn-outline-danger btn-sm">
-          <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
-        </button>
-      {% endif %}
-      {% if perms.dcim.delete_interface %}
-        <button type="submit" name="_delete"
-          formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
-          class="btn btn-danger btn-sm">
-          <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
-        </button>
+      {% if 'bulk_edit' in actions %}
+        <div class="btn-group" role="group">
+          <button type="submit" name="_edit"
+            formaction="{% url 'dcim:interface_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
+            class="btn btn-warning btn-sm">
+            <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
+          </button>
+          <button type="submit" name="_rename"
+            formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
+            class="btn btn-outline-warning btn-sm">
+            <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
+          </button>
+        </div>
       {% endif %}
+      <div class="btn-group" role="group">
+        {% if 'bulk_delete' in actions %}
+          <button type="submit" name="_delete"
+            formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
+            class="btn btn-danger btn-sm">
+            <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
+          </button>
+        {% endif %}
+        {% if 'bulk_edit' in actions %}
+          <button type="submit" name="_disconnect"
+            formaction="{% url 'dcim:interface_bulk_disconnect' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
+            class="btn btn-outline-danger btn-sm">
+            <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
+          </button>
+        {% endif %}
+      </div>
     </div>
     {% if perms.dcim.add_interface %}
       <div class="bulk-button-group">

+ 22 - 20
netbox/templates/dcim/device/inventory.html

@@ -16,28 +16,30 @@
     </div>
 
     <div class="noprint bulk-buttons">
+      <div class="bulk-button-group">
+        {% if 'bulk_edit' in actions %}
+          <div class="btn-group" role="group">
+            <button type="submit" name="_edit" formaction="{% url 'dcim:inventoryitem_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-warning btn-sm">
+              <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
+            </button>
+            <button type="submit" name="_rename" formaction="{% url 'dcim:inventoryitem_bulk_rename' %}?return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
+              <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
+            </button>
+          </div>
+        {% endif %}
+        {% if 'bulk_delete' in actions %}
+          <button type="submit" name="_delete" formaction="{% url 'dcim:inventoryitem_bulk_delete' %}?return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-danger btn-sm">
+            <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
+          </button>
+        {% endif %}
+      </div>
+      {% if perms.dcim.add_inventoryitem %}
         <div class="bulk-button-group">
-            {% if 'bulk_edit' in actions %}
-                <button type="submit" name="_rename" formaction="{% url 'dcim:inventoryitem_bulk_rename' %}?return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-warning btn-sm">
-                    <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
-                </button>
-                <button type="submit" name="_edit" formaction="{% url 'dcim:inventoryitem_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-warning btn-sm">
-                    <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
-                </button>
-            {% endif %}
-            {% if 'bulk_delete' in actions %}
-                <button type="submit" name="_delete" formaction="{% url 'dcim:inventoryitem_bulk_delete' %}?return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-danger btn-sm">
-                    <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
-                </button>
-            {% endif %}
+          <a href="{% url 'dcim:inventoryitem_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-primary btn-sm">
+            <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Inventory Item
+          </a>
         </div>
-        {% if perms.dcim.add_inventoryitem %}
-            <div class="bulk-button-group">
-                <a href="{% url 'dcim:inventoryitem_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-primary btn-sm">
-                    <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Inventory Item
-                </a>
-            </div>
-        {% endif %}
+      {% endif %}
     </div>
   </form>
 {% endblock %}

+ 22 - 20
netbox/templates/dcim/device/modulebays.html

@@ -16,28 +16,30 @@
     </div>
 
     <div class="noprint bulk-buttons">
+      <div class="bulk-button-group">
+        {% if 'bulk_edit' in actions %}
+          <div class="btn-group" role="group">
+            <button type="submit" name="_edit" formaction="{% url 'dcim:modulebay_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-warning btn-sm">
+              <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
+            </button>
+            <button type="submit" name="_rename" formaction="{% url 'dcim:modulebay_bulk_rename' %}?return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
+              <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
+            </button>
+          </div>
+        {% endif %}
+        {% if 'bulk_delete' in actions %}
+          <button type="submit" formaction="{% url 'dcim:modulebay_bulk_delete' %}?return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-danger btn-sm">
+            <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
+          </button>
+        {% endif %}
+      </div>
+      {% if perms.dcim.add_modulebay %}
         <div class="bulk-button-group">
-            {% if 'bulk_edit' in actions %}
-                <button type="submit" name="_rename" formaction="{% url 'dcim:modulebay_bulk_rename' %}?return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
-                    <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
-                </button>
-                <button type="submit" name="_edit" formaction="{% url 'dcim:modulebay_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-warning btn-sm">
-                    <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
-                </button>
-            {% endif %}
-            {% if 'bulk_delete' in actions %}
-                <button type="submit" formaction="{% url 'dcim:modulebay_bulk_delete' %}?return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
-                    <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete selected
-                </button>
-            {% endif %}
+          <a href="{% url 'dcim:modulebay_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-primary btn-sm">
+            <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Module Bays
+          </a>
         </div>
-        {% if perms.dcim.add_modulebay %}
-            <div class="bulk-button-group">
-                <a href="{% url 'dcim:modulebay_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-primary btn-sm">
-                    <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Module Bays
-                </a>
-            </div>
-        {% endif %}
+      {% endif %}
     </div>
   </form>
   {% table_config_form table %}

+ 29 - 23
netbox/templates/dcim/device/poweroutlets.html

@@ -16,31 +16,37 @@
     </div>
 
     <div class="noprint bulk-buttons">
+      <div class="bulk-button-group">
+        {% if 'bulk_edit' in actions %}
+          <div class="btn-group" role="group">
+            <button type="submit" name="_edit" formaction="{% url 'dcim:poweroutlet_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-warning btn-sm">
+              <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
+            </button>
+            <button type="submit" name="_rename" formaction="{% url 'dcim:poweroutlet_bulk_rename' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
+              <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
+            </button>
+          </div>
+        {% endif %}
+        <div class="btn-group" role="group">
+          {% if 'bulk_delete' in actions %}
+            <button type="submit" formaction="{% url 'dcim:poweroutlet_bulk_delete' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-danger btn-sm">
+              <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
+            </button>
+          {% endif %}
+          {% if 'bulk_edit' in actions %}
+            <button type="submit" name="_disconnect" formaction="{% url 'dcim:poweroutlet_bulk_disconnect' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
+              <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
+            </button>
+          {% endif %}
+        </div>
+      </div>
+      {% if perms.dcim.add_poweroutlet %}
         <div class="bulk-button-group">
-            {% if 'bulk_edit' in actions %}
-                <button type="submit" name="_rename" formaction="{% url 'dcim:poweroutlet_bulk_rename' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
-                    <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
-                </button>
-                <button type="submit" name="_edit" formaction="{% url 'dcim:poweroutlet_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-warning btn-sm">
-                    <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
-                </button>
-                <button type="submit" name="_disconnect" formaction="{% url 'dcim:poweroutlet_bulk_disconnect' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
-                    <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
-                </button>
-            {% endif %}
-            {% if 'bulk_delete' in actions %}
-                <button type="submit" formaction="{% url 'dcim:poweroutlet_bulk_delete' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-danger btn-sm">
-                    <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
-                </button>
-            {% endif %}
+          <a href="{% url 'dcim:poweroutlet_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-primary btn-sm">
+            <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Power Outlets
+          </a>
         </div>
-        {% if perms.dcim.add_poweroutlet %}
-            <div class="bulk-button-group">
-                <a href="{% url 'dcim:poweroutlet_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-primary btn-sm">
-                    <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Power Outlets
-                </a>
-            </div>
-        {% endif %}
+      {% endif %}
     </div>
   </form>
 {% endblock %}

+ 29 - 23
netbox/templates/dcim/device/powerports.html

@@ -16,31 +16,37 @@
     </div>
 
     <div class="noprint bulk-buttons">
+      <div class="bulk-button-group">
+        {% if 'bulk_edit' in actions %}
+          <div class="btn-group" role="group">
+            <button type="submit" name="_edit" formaction="{% url 'dcim:powerport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-warning btn-sm">
+              <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
+            </button>
+            <button type="submit" name="_rename" formaction="{% url 'dcim:powerport_bulk_rename' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
+              <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
+            </button>
+          </div>
+        {% endif %}
+        <div class="btn-group" role="group">
+          {% if 'bulk_delete' in actions %}
+            <button type="submit" name="_delete" formaction="{% url 'dcim:powerport_bulk_delete' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-danger btn-sm">
+              <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
+            </button>
+          {% endif %}
+          {% if 'bulk_edit' in actions %}
+            <button type="submit" name="_disconnect" formaction="{% url 'dcim:powerport_bulk_disconnect' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
+              <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
+            </button>
+          {% endif %}
+        </div>
+      </div>
+      {% if perms.dcim.add_powerport %}
         <div class="bulk-button-group">
-            {% if 'bulk_edit' in actions %}
-                <button type="submit" name="_rename" formaction="{% url 'dcim:powerport_bulk_rename' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
-                    <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
-                </button>
-                <button type="submit" name="_edit" formaction="{% url 'dcim:powerport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-warning btn-sm">
-                    <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
-                </button>
-                <button type="submit" name="_disconnect" formaction="{% url 'dcim:powerport_bulk_disconnect' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
-                    <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
-                </button>
-            {% endif %}
-            {% if 'bulk_delete' in actions %}
-                <button type="submit" name="_delete" formaction="{% url 'dcim:powerport_bulk_delete' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-danger btn-sm">
-                    <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
-                </button>
-            {% endif %}
+          <a href="{% url 'dcim:powerport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-sm btn-primary">
+            <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Power Port
+          </a>
         </div>
-        {% if perms.dcim.add_powerport %}
-            <div class="bulk-button-group">
-                <a href="{% url 'dcim:powerport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-sm btn-primary">
-                    <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Power Port
-                </a>
-            </div>
-        {% endif %}
+      {% endif %}
     </div>
   </form>
 {% endblock %}

+ 29 - 23
netbox/templates/dcim/device/rearports.html

@@ -16,31 +16,37 @@
     </div>
 
     <div class="noprint bulk-buttons">
+      <div class="bulk-button-group">
+        {% if 'bulk_edit' in actions %}
+          <div class="btn-group" role="group">
+            <button type="submit" name="_edit" formaction="{% url 'dcim:rearport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-warning btn-sm">
+              <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
+            </button>
+            <button type="submit" name="_rename" formaction="{% url 'dcim:rearport_bulk_rename' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
+              <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
+            </button>
+          </div>
+        {% endif %}
+        <div class="btn-group" role="group">
+          {% if 'bulk_delete' in actions %}
+            <button type="submit" formaction="{% url 'dcim:rearport_bulk_delete' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-danger btn-sm">
+              <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
+            </button>
+          {% endif %}
+          {% if 'bulk_edit' in actions %}
+            <button type="submit" name="_disconnect" formaction="{% url 'dcim:rearport_bulk_disconnect' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
+              <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
+            </button>
+          {% endif %}
+        </div>
+      </div>
+      {% if perms.dcim.add_rearport %}
         <div class="bulk-button-group">
-            {% if 'bulk_edit' in actions %}
-                <button type="submit" name="_rename" formaction="{% url 'dcim:rearport_bulk_rename' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
-                    <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
-                </button>
-                <button type="submit" name="_edit" formaction="{% url 'dcim:rearport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-warning btn-sm">
-                    <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
-                </button>
-                <button type="submit" name="_disconnect" formaction="{% url 'dcim:rearport_bulk_disconnect' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
-                    <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
-                </button>
-            {% endif %}
-            {% if 'bulk_delete' in actions %}
-                <button type="submit" formaction="{% url 'dcim:rearport_bulk_delete' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-danger btn-sm">
-                    <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
-                </button>
-            {% endif %}
+          <a href="{% url 'dcim:rearport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-primary btn-sm">
+            <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add rear ports
+          </a>
         </div>
-        {% if perms.dcim.add_rearport %}
-            <div class="bulk-button-group">
-                <a href="{% url 'dcim:rearport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-primary btn-sm">
-                    <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add rear ports
-                </a>
-            </div>
-        {% endif %}
+      {% endif %}
     </div>
   </form>
 {% endblock %}

+ 12 - 1
netbox/templates/dcim/device_list.html

@@ -1,4 +1,5 @@
 {% extends 'generic/object_list.html' %}
+{% load buttons %}
 
 {% block bulk_buttons %}
   {% if perms.dcim.change_device %}
@@ -73,5 +74,15 @@
       </ul>
     </div>
   {% endif %}
-  {{ block.super }}
+  {% if 'bulk_edit' in actions %}
+    <div class="btn-group" role="group">
+      {% bulk_edit_button model query_params=request.GET %}
+      <button type="submit" name="_rename" formaction="{% url 'dcim:device_bulk_rename' %}?return_url={% url 'dcim:device_list' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-outline-warning btn-sm">
+        <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
+      </button>
+    </div>
+  {% endif %}
+  {% if 'bulk_delete' in actions %}
+    {% bulk_delete_button model query_params=request.GET %}
+  {% endif %}
 {% endblock %}

+ 13 - 13
netbox/templates/inc/table_controls_htmx.html

@@ -1,22 +1,22 @@
 {% load helpers %}
 
-<div class="row mb-3 justify-content-between">
-  <div class="table-controls noprint col col-12 col-md-8 col-lg-4">
-    <div class="input-group input-group-sm quicksearch hide-last-child">
+<div class="row mb-3">
+  <div class="col-auto table-controls noprint">
+    <div class="input-group input-group-sm me-2 quicksearch hide-last-child">
       <input type="search" results=5 name="q" id="quicksearch" class="form-control" placeholder="Quick search"
         hx-get="{{ request.full_path }}" hx-target="#object_list" hx-trigger="keyup changed delay:500ms, search" />
-      <button class="btn bg-transparent" type="button" id="quicksearch_clear"><i
-          class="mdi mdi-close-circle"></i></button>
+      <button class="btn bg-transparent" type="button" id="quicksearch_clear"><i class="mdi mdi-close-circle"></i></button>
     </div>
+    {% block extra_table_controls %}{% endblock %}
   </div>
-  <div class="table-controls noprint col col-md-3 mb-0">
+  <div class="col-auto ms-auto table-controls noprint">
     {% if request.user.is_authenticated and table_modal %}
-    <div class="table-configure input-group input-group-sm">
-      <button type="button" data-bs-toggle="modal" title="Configure Table" data-bs-target="#{{ table_modal }}"
-        class="btn btn-sm btn-outline-dark">
-        <i class="mdi mdi-cog"></i> Configure Table
-      </button>
-    </div>
+      <div class="table-configure input-group input-group-sm">
+        <button type="button" data-bs-toggle="modal" title="Configure Table" data-bs-target="#{{ table_modal }}"
+          class="btn btn-sm btn-outline-dark">
+          <i class="mdi mdi-cog"></i> Configure Table
+        </button>
+      </div>
     {% endif %}
   </div>
-</div>
+</div>

+ 43 - 0
netbox/templates/ipam/prefix.html

@@ -144,6 +144,13 @@
             </td>
           </tr>
         </table>
+        {% if object.prefix.version == 4 %}
+          <div class="float-end">
+            <a class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#prefix-modal">
+              <i class="mdi mdi-information-outline" aria-hidden="true"></i> Addressing Details
+            </a>
+          </div>
+        {% endif %}
       </div>
     </div>
     {% include 'inc/panels/custom_fields.html' %}
@@ -161,3 +168,39 @@
     </div>
 </div>
 {% endblock %}
+
+{% block modals %}
+  {{ block.super }}
+  {% if object.prefix.version == 4 %}
+    <div class="modal fade" id="prefix-modal" tabindex="-1" aria-hidden="true">
+      <div class="modal-dialog modal-dialog-centered">
+        <div class="modal-content">
+          <div class="modal-header">
+            <h5 class="modal-title">Prefix Details</h5>
+            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+          </div>
+          <div class="modal-body">
+            <table class="table table-hover attr-table">
+              <tr>
+                <td>Network Address</td>
+                <td>{{ object.prefix.network }}</td>
+              </tr>
+              <tr>
+                <td>Network Mask</td>
+                <td>{{ object.prefix.netmask }}</td>
+              </tr>
+              <tr>
+                <td>Wildcard Mask</td>
+                <td>{{ object.prefix.hostmask }}</td>
+              </tr>
+              <tr>
+                <td>Broadcast Address</td>
+                <td>{{ object.prefix.broadcast }}</td>
+              </tr>
+            </table>
+          </div>
+        </div>
+      </div>
+    </div>
+  {% endif %}
+{% endblock %}

+ 22 - 21
netbox/templates/virtualization/virtualmachine/interfaces.html

@@ -15,27 +15,28 @@
     </div>
 
     <div class="noprint">
-        {% if perms.virtualization.change_vminterface %}
-            <button type="submit" name="_rename" formaction="{% url 'virtualization:vminterface_bulk_rename' %}?return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-warning btn-sm">
-                <span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
-            </button>
-            <button type="submit" name="_edit" formaction="{% url 'virtualization:vminterface_bulk_edit' %}?virtual_machine={{ object.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-warning btn-sm">
-                <span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
-            </button>
-        {% endif %}
-        {% if perms.virtualization.delete_vminterface %}
-            <button type="submit" name="_delete" formaction="{% url 'virtualization:vminterface_bulk_delete' %}?return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-danger btn-sm">
-                <span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
-            </button>
-        {% endif %}
-        {% if perms.virtualization.add_vminterface %}
-            <div class="float-end">
-                <a href="{% url 'virtualization:vminterface_add' %}?virtual_machine={{ object.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-primary btn-sm">
-                    <span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add interfaces
-                </a>
-            </div>
-        {% endif %}
-        <div class="clearfix"></div>
+      {% if perms.virtualization.change_vminterface %}
+        <div class="btn-group" role="group">
+          <button type="submit" name="_edit" formaction="{% url 'virtualization:vminterface_bulk_edit' %}?virtual_machine={{ object.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-warning btn-sm">
+            <span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
+          </button>
+          <button type="submit" name="_rename" formaction="{% url 'virtualization:vminterface_bulk_rename' %}?return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
+            <span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
+          </button>
+        </div>
+      {% endif %}
+      {% if perms.virtualization.delete_vminterface %}
+        <button type="submit" name="_delete" formaction="{% url 'virtualization:vminterface_bulk_delete' %}?return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-danger btn-sm">
+          <span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
+        </button>
+      {% endif %}
+      {% if perms.virtualization.add_vminterface %}
+        <div class="float-end">
+          <a href="{% url 'virtualization:vminterface_add' %}?virtual_machine={{ object.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-primary btn-sm">
+            <span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add interfaces
+          </a>
+        </div>
+      {% endif %}
      </div>
   </form>
 {% endblock content %}

+ 2 - 2
requirements.txt

@@ -20,13 +20,13 @@ gunicorn==20.1.0
 Jinja2==3.1.2
 Markdown==3.4.1
 markdown-include==0.7.0
-mkdocs-material==8.3.9
+mkdocs-material==8.4.0
 mkdocstrings[python-legacy]==0.19.0
 netaddr==0.8.0
 Pillow==9.2.0
 psycopg2-binary==2.9.3
 PyYAML==6.0
-sentry-sdk==1.9.2
+sentry-sdk==1.9.5
 social-auth-app-django==5.0.0
 social-auth-core==4.3.0
 svgwrite==1.4.3