mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 04:22:01 -06:00
Merge pull request #8073 from netbox-community/8057-htmx-tables
Closes #8057: Dynamic object tables using HTMX
This commit is contained in:
commit
57d3bfcfc9
@ -797,41 +797,49 @@ class DeviceTypeView(generic.ObjectView):
|
|||||||
class DeviceTypeConsolePortsView(DeviceTypeComponentsView):
|
class DeviceTypeConsolePortsView(DeviceTypeComponentsView):
|
||||||
child_model = ConsolePortTemplate
|
child_model = ConsolePortTemplate
|
||||||
table = tables.ConsolePortTemplateTable
|
table = tables.ConsolePortTemplateTable
|
||||||
|
filterset = filtersets.ConsolePortTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeConsoleServerPortsView(DeviceTypeComponentsView):
|
class DeviceTypeConsoleServerPortsView(DeviceTypeComponentsView):
|
||||||
child_model = ConsoleServerPortTemplate
|
child_model = ConsoleServerPortTemplate
|
||||||
table = tables.ConsoleServerPortTemplateTable
|
table = tables.ConsoleServerPortTemplateTable
|
||||||
|
filterset = filtersets.ConsoleServerPortTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypePowerPortsView(DeviceTypeComponentsView):
|
class DeviceTypePowerPortsView(DeviceTypeComponentsView):
|
||||||
child_model = PowerPortTemplate
|
child_model = PowerPortTemplate
|
||||||
table = tables.PowerPortTemplateTable
|
table = tables.PowerPortTemplateTable
|
||||||
|
filterset = filtersets.PowerPortTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypePowerOutletsView(DeviceTypeComponentsView):
|
class DeviceTypePowerOutletsView(DeviceTypeComponentsView):
|
||||||
child_model = PowerOutletTemplate
|
child_model = PowerOutletTemplate
|
||||||
table = tables.PowerOutletTemplateTable
|
table = tables.PowerOutletTemplateTable
|
||||||
|
filterset = filtersets.PowerOutletTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeInterfacesView(DeviceTypeComponentsView):
|
class DeviceTypeInterfacesView(DeviceTypeComponentsView):
|
||||||
child_model = InterfaceTemplate
|
child_model = InterfaceTemplate
|
||||||
table = tables.InterfaceTemplateTable
|
table = tables.InterfaceTemplateTable
|
||||||
|
filterset = filtersets.InterfaceTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeFrontPortsView(DeviceTypeComponentsView):
|
class DeviceTypeFrontPortsView(DeviceTypeComponentsView):
|
||||||
child_model = FrontPortTemplate
|
child_model = FrontPortTemplate
|
||||||
table = tables.FrontPortTemplateTable
|
table = tables.FrontPortTemplateTable
|
||||||
|
filterset = filtersets.FrontPortTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeRearPortsView(DeviceTypeComponentsView):
|
class DeviceTypeRearPortsView(DeviceTypeComponentsView):
|
||||||
child_model = RearPortTemplate
|
child_model = RearPortTemplate
|
||||||
table = tables.RearPortTemplateTable
|
table = tables.RearPortTemplateTable
|
||||||
|
filterset = filtersets.RearPortTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeDeviceBaysView(DeviceTypeComponentsView):
|
class DeviceTypeDeviceBaysView(DeviceTypeComponentsView):
|
||||||
child_model = DeviceBayTemplate
|
child_model = DeviceBayTemplate
|
||||||
table = tables.DeviceBayTemplateTable
|
table = tables.DeviceBayTemplateTable
|
||||||
|
filterset = filtersets.DeviceBayTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeEditView(generic.ObjectEditView):
|
class DeviceTypeEditView(generic.ObjectEditView):
|
||||||
@ -1328,30 +1336,35 @@ class DeviceView(generic.ObjectView):
|
|||||||
class DeviceConsolePortsView(DeviceComponentsView):
|
class DeviceConsolePortsView(DeviceComponentsView):
|
||||||
child_model = ConsolePort
|
child_model = ConsolePort
|
||||||
table = tables.DeviceConsolePortTable
|
table = tables.DeviceConsolePortTable
|
||||||
|
filterset = filtersets.ConsolePortFilterSet
|
||||||
template_name = 'dcim/device/consoleports.html'
|
template_name = 'dcim/device/consoleports.html'
|
||||||
|
|
||||||
|
|
||||||
class DeviceConsoleServerPortsView(DeviceComponentsView):
|
class DeviceConsoleServerPortsView(DeviceComponentsView):
|
||||||
child_model = ConsoleServerPort
|
child_model = ConsoleServerPort
|
||||||
table = tables.DeviceConsoleServerPortTable
|
table = tables.DeviceConsoleServerPortTable
|
||||||
|
filterset = filtersets.ConsoleServerPortFilterSet
|
||||||
template_name = 'dcim/device/consoleserverports.html'
|
template_name = 'dcim/device/consoleserverports.html'
|
||||||
|
|
||||||
|
|
||||||
class DevicePowerPortsView(DeviceComponentsView):
|
class DevicePowerPortsView(DeviceComponentsView):
|
||||||
child_model = PowerPort
|
child_model = PowerPort
|
||||||
table = tables.DevicePowerPortTable
|
table = tables.DevicePowerPortTable
|
||||||
|
filterset = filtersets.PowerPortFilterSet
|
||||||
template_name = 'dcim/device/powerports.html'
|
template_name = 'dcim/device/powerports.html'
|
||||||
|
|
||||||
|
|
||||||
class DevicePowerOutletsView(DeviceComponentsView):
|
class DevicePowerOutletsView(DeviceComponentsView):
|
||||||
child_model = PowerOutlet
|
child_model = PowerOutlet
|
||||||
table = tables.DevicePowerOutletTable
|
table = tables.DevicePowerOutletTable
|
||||||
|
filterset = filtersets.PowerOutletFilterSet
|
||||||
template_name = 'dcim/device/poweroutlets.html'
|
template_name = 'dcim/device/poweroutlets.html'
|
||||||
|
|
||||||
|
|
||||||
class DeviceInterfacesView(DeviceComponentsView):
|
class DeviceInterfacesView(DeviceComponentsView):
|
||||||
child_model = Interface
|
child_model = Interface
|
||||||
table = tables.DeviceInterfaceTable
|
table = tables.DeviceInterfaceTable
|
||||||
|
filterset = filtersets.InterfaceFilterSet
|
||||||
template_name = 'dcim/device/interfaces.html'
|
template_name = 'dcim/device/interfaces.html'
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
@ -1364,24 +1377,28 @@ class DeviceInterfacesView(DeviceComponentsView):
|
|||||||
class DeviceFrontPortsView(DeviceComponentsView):
|
class DeviceFrontPortsView(DeviceComponentsView):
|
||||||
child_model = FrontPort
|
child_model = FrontPort
|
||||||
table = tables.DeviceFrontPortTable
|
table = tables.DeviceFrontPortTable
|
||||||
|
filterset = filtersets.FrontPortFilterSet
|
||||||
template_name = 'dcim/device/frontports.html'
|
template_name = 'dcim/device/frontports.html'
|
||||||
|
|
||||||
|
|
||||||
class DeviceRearPortsView(DeviceComponentsView):
|
class DeviceRearPortsView(DeviceComponentsView):
|
||||||
child_model = RearPort
|
child_model = RearPort
|
||||||
table = tables.DeviceRearPortTable
|
table = tables.DeviceRearPortTable
|
||||||
|
filterset = filtersets.RearPortFilterSet
|
||||||
template_name = 'dcim/device/rearports.html'
|
template_name = 'dcim/device/rearports.html'
|
||||||
|
|
||||||
|
|
||||||
class DeviceDeviceBaysView(DeviceComponentsView):
|
class DeviceDeviceBaysView(DeviceComponentsView):
|
||||||
child_model = DeviceBay
|
child_model = DeviceBay
|
||||||
table = tables.DeviceDeviceBayTable
|
table = tables.DeviceDeviceBayTable
|
||||||
|
filterset = filtersets.DeviceBayFilterSet
|
||||||
template_name = 'dcim/device/devicebays.html'
|
template_name = 'dcim/device/devicebays.html'
|
||||||
|
|
||||||
|
|
||||||
class DeviceInventoryView(DeviceComponentsView):
|
class DeviceInventoryView(DeviceComponentsView):
|
||||||
child_model = InventoryItem
|
child_model = InventoryItem
|
||||||
table = tables.DeviceInventoryItemTable
|
table = tables.DeviceInventoryItemTable
|
||||||
|
filterset = filtersets.InventoryItemFilterSet
|
||||||
template_name = 'dcim/device/inventory.html'
|
template_name = 'dcim/device/inventory.html'
|
||||||
|
|
||||||
|
|
||||||
|
@ -195,6 +195,12 @@ class Aggregate(PrimaryModel):
|
|||||||
return self.prefix.version
|
return self.prefix.version
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_child_prefixes(self):
|
||||||
|
"""
|
||||||
|
Return all Prefixes within this Aggregate
|
||||||
|
"""
|
||||||
|
return Prefix.objects.filter(prefix__net_contained=str(self.prefix))
|
||||||
|
|
||||||
def get_utilization(self):
|
def get_utilization(self):
|
||||||
"""
|
"""
|
||||||
Determine the prefix utilization of the aggregate and return it as a percentage.
|
Determine the prefix utilization of the aggregate and return it as a percentage.
|
||||||
|
@ -61,6 +61,7 @@ urlpatterns = [
|
|||||||
path('aggregates/edit/', views.AggregateBulkEditView.as_view(), name='aggregate_bulk_edit'),
|
path('aggregates/edit/', views.AggregateBulkEditView.as_view(), name='aggregate_bulk_edit'),
|
||||||
path('aggregates/delete/', views.AggregateBulkDeleteView.as_view(), name='aggregate_bulk_delete'),
|
path('aggregates/delete/', views.AggregateBulkDeleteView.as_view(), name='aggregate_bulk_delete'),
|
||||||
path('aggregates/<int:pk>/', views.AggregateView.as_view(), name='aggregate'),
|
path('aggregates/<int:pk>/', views.AggregateView.as_view(), name='aggregate'),
|
||||||
|
path('aggregates/<int:pk>/prefixes/', views.AggregatePrefixesView.as_view(), name='aggregate_prefixes'),
|
||||||
path('aggregates/<int:pk>/edit/', views.AggregateEditView.as_view(), name='aggregate_edit'),
|
path('aggregates/<int:pk>/edit/', views.AggregateEditView.as_view(), name='aggregate_edit'),
|
||||||
path('aggregates/<int:pk>/delete/', views.AggregateDeleteView.as_view(), name='aggregate_delete'),
|
path('aggregates/<int:pk>/delete/', views.AggregateDeleteView.as_view(), name='aggregate_delete'),
|
||||||
path('aggregates/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='aggregate_changelog', kwargs={'model': Aggregate}),
|
path('aggregates/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='aggregate_changelog', kwargs={'model': Aggregate}),
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Prefetch
|
from django.db.models import Prefetch
|
||||||
from django.db.models.expressions import RawSQL
|
from django.db.models.expressions import RawSQL
|
||||||
from django.http import Http404
|
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from dcim.filtersets import InterfaceFilterSet
|
||||||
from dcim.models import Device, Interface, Site
|
from dcim.models import Device, Interface, Site
|
||||||
from dcim.tables import SiteTable
|
from dcim.tables import SiteTable
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.tables import paginate_table
|
from utilities.tables import paginate_table
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
|
from virtualization.filtersets import VMInterfaceFilterSet
|
||||||
from virtualization.models import VirtualMachine, VMInterface
|
from virtualization.models import VirtualMachine, VMInterface
|
||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .models import *
|
from .models import *
|
||||||
from .models import ASN
|
from .models import ASN
|
||||||
from .utils import add_available_ipaddresses, add_requested_prefixes, add_available_vlans
|
from .utils import add_requested_prefixes, add_available_vlans
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -274,39 +275,32 @@ class AggregateListView(generic.ObjectListView):
|
|||||||
class AggregateView(generic.ObjectView):
|
class AggregateView(generic.ObjectView):
|
||||||
queryset = Aggregate.objects.all()
|
queryset = Aggregate.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
|
||||||
# Find all child prefixes contained in this aggregate
|
|
||||||
prefix_list = Prefix.objects.restrict(request.user, 'view').filter(
|
|
||||||
prefix__net_contained_or_equal=str(instance.prefix)
|
|
||||||
).prefetch_related(
|
|
||||||
'site', 'role'
|
|
||||||
).order_by(
|
|
||||||
'prefix'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Return List of requested Prefixes
|
class AggregatePrefixesView(generic.ObjectChildrenView):
|
||||||
|
queryset = Aggregate.objects.all()
|
||||||
|
child_model = Prefix
|
||||||
|
table = tables.PrefixTable
|
||||||
|
filterset = filtersets.PrefixFilterSet
|
||||||
|
template_name = 'ipam/aggregate/prefixes.html'
|
||||||
|
|
||||||
|
def get_children(self, request, parent):
|
||||||
|
return Prefix.objects.restrict(request.user, 'view').filter(
|
||||||
|
prefix__net_contained_or_equal=str(parent.prefix)
|
||||||
|
).prefetch_related('site', 'role', 'tenant', 'vlan')
|
||||||
|
|
||||||
|
def prep_table_data(self, request, queryset, parent):
|
||||||
|
# Determine whether to show assigned prefixes, available prefixes, or both
|
||||||
show_available = bool(request.GET.get('show_available', 'true') == 'true')
|
show_available = bool(request.GET.get('show_available', 'true') == 'true')
|
||||||
show_assigned = bool(request.GET.get('show_assigned', 'true') == 'true')
|
show_assigned = bool(request.GET.get('show_assigned', 'true') == 'true')
|
||||||
child_prefixes = add_requested_prefixes(instance.prefix, prefix_list, show_available, show_assigned)
|
|
||||||
|
|
||||||
prefix_table = tables.PrefixTable(child_prefixes, exclude=('utilization',))
|
return add_requested_prefixes(parent.prefix, queryset, show_available, show_assigned)
|
||||||
if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
|
|
||||||
prefix_table.columns.show('pk')
|
|
||||||
paginate_table(prefix_table, request)
|
|
||||||
|
|
||||||
# Compile permissions list for rendering the object table
|
|
||||||
permissions = {
|
|
||||||
'add': request.user.has_perm('ipam.add_prefix'),
|
|
||||||
'change': request.user.has_perm('ipam.change_prefix'),
|
|
||||||
'delete': request.user.has_perm('ipam.delete_prefix'),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
return {
|
return {
|
||||||
'prefix_table': prefix_table,
|
|
||||||
'permissions': permissions,
|
|
||||||
'bulk_querystring': f'within={instance.prefix}',
|
'bulk_querystring': f'within={instance.prefix}',
|
||||||
'show_available': show_available,
|
'active_tab': 'prefixes',
|
||||||
'show_assigned': show_assigned,
|
'show_available': bool(request.GET.get('show_available', 'true') == 'true'),
|
||||||
|
'show_assigned': bool(request.GET.get('show_assigned', 'true') == 'true'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -457,17 +451,18 @@ class PrefixPrefixesView(generic.ObjectChildrenView):
|
|||||||
queryset = Prefix.objects.all()
|
queryset = Prefix.objects.all()
|
||||||
child_model = Prefix
|
child_model = Prefix
|
||||||
table = tables.PrefixTable
|
table = tables.PrefixTable
|
||||||
|
filterset = filtersets.PrefixFilterSet
|
||||||
template_name = 'ipam/prefix/prefixes.html'
|
template_name = 'ipam/prefix/prefixes.html'
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
child_prefixes = parent.get_child_prefixes().restrict(request.user, 'view')
|
return parent.get_child_prefixes().restrict(request.user, 'view')
|
||||||
|
|
||||||
# Add available prefixes if requested
|
def prep_table_data(self, request, queryset, parent):
|
||||||
|
# Determine whether to show assigned prefixes, available prefixes, or both
|
||||||
show_available = bool(request.GET.get('show_available', 'true') == 'true')
|
show_available = bool(request.GET.get('show_available', 'true') == 'true')
|
||||||
show_assigned = bool(request.GET.get('show_assigned', 'true') == 'true')
|
show_assigned = bool(request.GET.get('show_assigned', 'true') == 'true')
|
||||||
child_prefixes = add_requested_prefixes(parent.prefix, child_prefixes, show_available, show_assigned)
|
|
||||||
|
|
||||||
return child_prefixes
|
return add_requested_prefixes(parent.prefix, queryset, show_available, show_assigned)
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
return {
|
return {
|
||||||
@ -483,6 +478,7 @@ class PrefixIPRangesView(generic.ObjectChildrenView):
|
|||||||
queryset = Prefix.objects.all()
|
queryset = Prefix.objects.all()
|
||||||
child_model = IPRange
|
child_model = IPRange
|
||||||
table = tables.IPRangeTable
|
table = tables.IPRangeTable
|
||||||
|
filterset = filtersets.IPRangeFilterSet
|
||||||
template_name = 'ipam/prefix/ip_ranges.html'
|
template_name = 'ipam/prefix/ip_ranges.html'
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
@ -499,6 +495,7 @@ class PrefixIPAddressesView(generic.ObjectChildrenView):
|
|||||||
queryset = Prefix.objects.all()
|
queryset = Prefix.objects.all()
|
||||||
child_model = IPAddress
|
child_model = IPAddress
|
||||||
table = tables.IPAddressTable
|
table = tables.IPAddressTable
|
||||||
|
filterset = filtersets.IPAddressFilterSet
|
||||||
template_name = 'ipam/prefix/ip_addresses.html'
|
template_name = 'ipam/prefix/ip_addresses.html'
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
@ -560,6 +557,7 @@ class IPRangeIPAddressesView(generic.ObjectChildrenView):
|
|||||||
queryset = IPRange.objects.all()
|
queryset = IPRange.objects.all()
|
||||||
child_model = IPAddress
|
child_model = IPAddress
|
||||||
table = tables.IPAddressTable
|
table = tables.IPAddressTable
|
||||||
|
filterset = filtersets.IPAddressFilterSet
|
||||||
template_name = 'ipam/iprange/ip_addresses.html'
|
template_name = 'ipam/iprange/ip_addresses.html'
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
@ -959,6 +957,7 @@ class VLANInterfacesView(generic.ObjectChildrenView):
|
|||||||
queryset = VLAN.objects.all()
|
queryset = VLAN.objects.all()
|
||||||
child_model = Interface
|
child_model = Interface
|
||||||
table = tables.VLANDevicesTable
|
table = tables.VLANDevicesTable
|
||||||
|
filterset = InterfaceFilterSet
|
||||||
template_name = 'ipam/vlan/interfaces.html'
|
template_name = 'ipam/vlan/interfaces.html'
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
@ -974,6 +973,7 @@ class VLANVMInterfacesView(generic.ObjectChildrenView):
|
|||||||
queryset = VLAN.objects.all()
|
queryset = VLAN.objects.all()
|
||||||
child_model = VMInterface
|
child_model = VMInterface
|
||||||
table = tables.VLANVirtualMachinesTable
|
table = tables.VLANVirtualMachinesTable
|
||||||
|
filterset = VMInterfaceFilterSet
|
||||||
template_name = 'ipam/vlan/vminterfaces.html'
|
template_name = 'ipam/vlan/vminterfaces.html'
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
|
@ -23,6 +23,7 @@ from utilities.exceptions import AbortTransaction, PermissionsViolation
|
|||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
BootstrapMixin, BulkRenameForm, ConfirmationForm, CSVDataField, CSVFileField, ImportForm, restrict_form_fields,
|
BootstrapMixin, BulkRenameForm, ConfirmationForm, CSVDataField, CSVFileField, ImportForm, restrict_form_fields,
|
||||||
)
|
)
|
||||||
|
from utilities.htmx import is_htmx
|
||||||
from utilities.permissions import get_permission_for_model
|
from utilities.permissions import get_permission_for_model
|
||||||
from utilities.tables import paginate_table
|
from utilities.tables import paginate_table
|
||||||
from utilities.utils import normalize_querydict, prepare_cloned_fields
|
from utilities.utils import normalize_querydict, prepare_cloned_fields
|
||||||
@ -83,17 +84,28 @@ class ObjectChildrenView(ObjectView):
|
|||||||
queryset = None
|
queryset = None
|
||||||
child_model = None
|
child_model = None
|
||||||
table = None
|
table = None
|
||||||
|
filterset = None
|
||||||
template_name = None
|
template_name = None
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
"""
|
"""
|
||||||
Return a QuerySet or iterable of child objects.
|
Return a QuerySet of child objects.
|
||||||
|
|
||||||
request: The current request
|
request: The current request
|
||||||
parent: The parent object
|
parent: The parent object
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError(f'{self.__class__.__name__} must implement get_children()')
|
raise NotImplementedError(f'{self.__class__.__name__} must implement get_children()')
|
||||||
|
|
||||||
|
def prep_table_data(self, request, queryset, parent):
|
||||||
|
"""
|
||||||
|
Provides a hook for subclassed views to modify data before initializing the table.
|
||||||
|
|
||||||
|
:param request: The current request
|
||||||
|
:param queryset: The filtered queryset of child objects
|
||||||
|
:param parent: The parent object
|
||||||
|
"""
|
||||||
|
return queryset
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
GET handler for rendering child objects.
|
GET handler for rendering child objects.
|
||||||
@ -101,17 +113,27 @@ class ObjectChildrenView(ObjectView):
|
|||||||
instance = get_object_or_404(self.queryset, **kwargs)
|
instance = get_object_or_404(self.queryset, **kwargs)
|
||||||
child_objects = self.get_children(request, instance)
|
child_objects = self.get_children(request, instance)
|
||||||
|
|
||||||
|
if self.filterset:
|
||||||
|
child_objects = self.filterset(request.GET, child_objects).qs
|
||||||
|
|
||||||
permissions = {}
|
permissions = {}
|
||||||
for action in ('change', 'delete'):
|
for action in ('change', 'delete'):
|
||||||
perm_name = get_permission_for_model(self.child_model, action)
|
perm_name = get_permission_for_model(self.child_model, action)
|
||||||
permissions[action] = request.user.has_perm(perm_name)
|
permissions[action] = request.user.has_perm(perm_name)
|
||||||
|
|
||||||
table = self.table(child_objects, user=request.user)
|
table = self.table(self.prep_table_data(request, child_objects, instance), user=request.user)
|
||||||
# Determine whether to display bulk action checkboxes
|
# Determine whether to display bulk action checkboxes
|
||||||
if 'pk' in table.base_columns and (permissions['change'] or permissions['delete']):
|
if 'pk' in table.base_columns and (permissions['change'] or permissions['delete']):
|
||||||
table.columns.show('pk')
|
table.columns.show('pk')
|
||||||
paginate_table(table, request)
|
paginate_table(table, request)
|
||||||
|
|
||||||
|
# If this is an HTMX request, return only the rendered table HTML
|
||||||
|
if is_htmx(request):
|
||||||
|
return render(request, 'htmx/table.html', {
|
||||||
|
'object': instance,
|
||||||
|
'table': table,
|
||||||
|
})
|
||||||
|
|
||||||
return render(request, self.get_template_name(), {
|
return render(request, self.get_template_name(), {
|
||||||
'object': instance,
|
'object': instance,
|
||||||
'table': table,
|
'table': table,
|
||||||
@ -233,6 +255,12 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|||||||
table = self.get_table(request, permissions)
|
table = self.get_table(request, permissions)
|
||||||
paginate_table(table, request)
|
paginate_table(table, request)
|
||||||
|
|
||||||
|
# If this is an HTMX request, return only the rendered table HTML
|
||||||
|
if is_htmx(request):
|
||||||
|
return render(request, 'htmx/table.html', {
|
||||||
|
'table': table,
|
||||||
|
})
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'content_type': content_type,
|
'content_type': content_type,
|
||||||
'table': table,
|
'table': table,
|
||||||
|
BIN
netbox/project-static/dist/netbox-dark.css
vendored
BIN
netbox/project-static/dist/netbox-dark.css
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox-light.css
vendored
BIN
netbox/project-static/dist/netbox-light.css
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox-print.css
vendored
BIN
netbox/project-static/dist/netbox-print.css
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox.js
vendored
BIN
netbox/project-static/dist/netbox.js
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox.js.map
vendored
BIN
netbox/project-static/dist/netbox.js.map
vendored
Binary file not shown.
@ -30,6 +30,7 @@
|
|||||||
"cookie": "^0.4.1",
|
"cookie": "^0.4.1",
|
||||||
"dayjs": "^1.10.4",
|
"dayjs": "^1.10.4",
|
||||||
"flatpickr": "4.6.3",
|
"flatpickr": "4.6.3",
|
||||||
|
"htmx.org": "^1.6.1",
|
||||||
"just-debounce-it": "^1.4.0",
|
"just-debounce-it": "^1.4.0",
|
||||||
"masonry-layout": "^4.2.2",
|
"masonry-layout": "^4.2.2",
|
||||||
"query-string": "^6.14.1",
|
"query-string": "^6.14.1",
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { initConnectionToggle } from './connectionToggle';
|
import { initConnectionToggle } from './connectionToggle';
|
||||||
import { initDepthToggle } from './depthToggle';
|
import { initDepthToggle } from './depthToggle';
|
||||||
import { initMoveButtons } from './moveOptions';
|
import { initMoveButtons } from './moveOptions';
|
||||||
import { initPerPage } from './pagination';
|
|
||||||
import { initPreferenceUpdate } from './preferences';
|
import { initPreferenceUpdate } from './preferences';
|
||||||
import { initReslug } from './reslug';
|
import { initReslug } from './reslug';
|
||||||
import { initSelectAll } from './selectAll';
|
import { initSelectAll } from './selectAll';
|
||||||
@ -13,7 +12,6 @@ export function initButtons(): void {
|
|||||||
initReslug,
|
initReslug,
|
||||||
initSelectAll,
|
initSelectAll,
|
||||||
initPreferenceUpdate,
|
initPreferenceUpdate,
|
||||||
initPerPage,
|
|
||||||
initMoveButtons,
|
initMoveButtons,
|
||||||
]) {
|
]) {
|
||||||
func();
|
func();
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import { getElements } from '../util';
|
|
||||||
|
|
||||||
function handlePerPageSelect(event: Event): void {
|
|
||||||
const select = event.currentTarget as HTMLSelectElement;
|
|
||||||
if (select.form !== null) {
|
|
||||||
select.form.submit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initPerPage(): void {
|
|
||||||
for (const element of getElements<HTMLSelectElement>('select.per-page')) {
|
|
||||||
element.addEventListener('change', handlePerPageSelect);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,5 @@
|
|||||||
import '@popperjs/core';
|
import '@popperjs/core';
|
||||||
import 'bootstrap';
|
import 'bootstrap';
|
||||||
|
import 'htmx.org';
|
||||||
import 'simplebar';
|
import 'simplebar';
|
||||||
import './netbox';
|
import './netbox';
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import debounce from 'just-debounce-it';
|
import { getElements, findFirstAdjacent, isTruthy } from './util';
|
||||||
import { getElements, getRowValues, findFirstAdjacent, isTruthy } from './util';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the display value and hidden input values of the search filter based on dropdown
|
* Change the display value and hidden input values of the search filter based on dropdown
|
||||||
@ -41,109 +40,8 @@ function initSearchBar(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize Interface Table Filter Elements.
|
|
||||||
*/
|
|
||||||
function initInterfaceFilter(): void {
|
|
||||||
for (const input of getElements<HTMLInputElement>('input.interface-filter')) {
|
|
||||||
const table = findFirstAdjacent<HTMLTableElement>(input, 'table');
|
|
||||||
const rows = Array.from(
|
|
||||||
table?.querySelectorAll<HTMLTableRowElement>('tbody > tr') ?? [],
|
|
||||||
).filter(r => r !== null);
|
|
||||||
/**
|
|
||||||
* Filter on-page table by input text.
|
|
||||||
*/
|
|
||||||
function handleInput(event: Event): void {
|
|
||||||
const target = event.target as HTMLInputElement;
|
|
||||||
// Create a regex pattern from the input search text to match against.
|
|
||||||
const filter = new RegExp(target.value.toLowerCase().trim());
|
|
||||||
|
|
||||||
// Each row represents an interface and its attributes.
|
|
||||||
for (const row of rows) {
|
|
||||||
// Find the row's checkbox and deselect it, so that it is not accidentally included in form
|
|
||||||
// submissions.
|
|
||||||
const checkBox = row.querySelector<HTMLInputElement>('input[type="checkbox"][name="pk"]');
|
|
||||||
if (checkBox !== null) {
|
|
||||||
checkBox.checked = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The data-name attribute's value contains the interface name.
|
|
||||||
const name = row.getAttribute('data-name');
|
|
||||||
|
|
||||||
if (typeof name === 'string') {
|
|
||||||
if (filter.test(name.toLowerCase().trim())) {
|
|
||||||
// If this row matches the search pattern, but is already hidden, unhide it.
|
|
||||||
if (row.classList.contains('d-none')) {
|
|
||||||
row.classList.remove('d-none');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If this row doesn't match the search pattern, hide it.
|
|
||||||
row.classList.add('d-none');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
input.addEventListener('keyup', debounce(handleInput, 300));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initTableFilter(): void {
|
|
||||||
for (const input of getElements<HTMLInputElement>('input.object-filter')) {
|
|
||||||
// Find the first adjacent table element.
|
|
||||||
const table = findFirstAdjacent<HTMLTableElement>(input, 'table');
|
|
||||||
|
|
||||||
// Build a valid array of <tr/> elements that are children of the adjacent table.
|
|
||||||
const rows = Array.from(
|
|
||||||
table?.querySelectorAll<HTMLTableRowElement>('tbody > tr') ?? [],
|
|
||||||
).filter(r => r !== null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter table rows by matched input text.
|
|
||||||
* @param event
|
|
||||||
*/
|
|
||||||
function handleInput(event: Event): void {
|
|
||||||
const target = event.target as HTMLInputElement;
|
|
||||||
|
|
||||||
// Create a regex pattern from the input search text to match against.
|
|
||||||
const filter = new RegExp(target.value.toLowerCase().trim());
|
|
||||||
|
|
||||||
// List of which rows which match the query
|
|
||||||
const matchedRows: Array<HTMLTableRowElement> = [];
|
|
||||||
|
|
||||||
for (const row of rows) {
|
|
||||||
// Find the row's checkbox and deselect it, so that it is not accidentally included in form
|
|
||||||
// submissions.
|
|
||||||
const checkBox = row.querySelector<HTMLInputElement>('input[type="checkbox"][name="pk"]');
|
|
||||||
if (checkBox !== null) {
|
|
||||||
checkBox.checked = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate through each row's cell values
|
|
||||||
for (const value of getRowValues(row)) {
|
|
||||||
if (filter.test(value.toLowerCase())) {
|
|
||||||
// If this row matches the search pattern, add it to the list.
|
|
||||||
matchedRows.push(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate the rows again to set visibility.
|
|
||||||
// This results in a single reflow instead of one for each row.
|
|
||||||
for (const row of rows) {
|
|
||||||
if (matchedRows.indexOf(row) >= 0) {
|
|
||||||
row.classList.remove('d-none');
|
|
||||||
} else {
|
|
||||||
row.classList.add('d-none');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
input.addEventListener('keyup', debounce(handleInput, 300));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initSearch(): void {
|
export function initSearch(): void {
|
||||||
for (const func of [initSearchBar, initTableFilter, initInterfaceFilter]) {
|
for (const func of [initSearchBar]) {
|
||||||
func();
|
func();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -737,10 +737,6 @@ nav.breadcrumb-container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div.paginator > form > div.input-group {
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
label.required {
|
label.required {
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
|
|
||||||
@ -900,14 +896,6 @@ div.card-overlay {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Right-align the paginator element.
|
|
||||||
.paginator {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-end;
|
|
||||||
padding: $spacer 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tabbed content
|
// Tabbed content
|
||||||
.nav-tabs {
|
.nav-tabs {
|
||||||
.nav-link {
|
.nav-link {
|
||||||
|
@ -1688,6 +1688,11 @@ hosted-git-info@^2.1.4, hosted-git-info@^2.8.9:
|
|||||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||||
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
|
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
|
||||||
|
|
||||||
|
htmx.org@^1.6.1:
|
||||||
|
version "1.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/htmx.org/-/htmx.org-1.6.1.tgz#6f0d59a93fa61cbaa15316c134a2f179045a5778"
|
||||||
|
integrity sha512-i+1k5ee2eFWaZbomjckyrDjUpa3FMDZWufatUSBmmsjXVksn89nsXvr1KLGIdAajiz+ZSL7TE4U/QaZVd2U2sA==
|
||||||
|
|
||||||
ignore@^4.0.6:
|
ignore@^4.0.6:
|
||||||
version "4.0.6"
|
version "4.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
|
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
|
||||||
|
@ -8,19 +8,14 @@
|
|||||||
{% block content-wrapper %}
|
{% block content-wrapper %}
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
|
|
||||||
{# Conncetions list #}
|
{# Connections list #}
|
||||||
<div class="tab-pane show active" id="object-list" role="tabpanel" aria-labelledby="object-list-tab">
|
<div class="tab-pane show active" id="object-list" role="tabpanel" aria-labelledby="object-list-tab">
|
||||||
{% include 'inc/table_controls.html' %}
|
{% include 'inc/table_controls_htmx.html' %}
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body" id="object_list">
|
||||||
<div class="table-responsive">
|
{% include 'htmx/table.html' %}
|
||||||
{% render_table table 'inc/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Filter form #}
|
{# Filter form #}
|
||||||
|
@ -6,10 +6,14 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% include 'inc/table_controls.html' with table_modal="DeviceConsolePortTable_config" %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceConsolePortTable_config" %}
|
||||||
<div class="table-responsive">
|
|
||||||
{% render_table table 'inc/table.html' %}
|
<div class="card">
|
||||||
|
<div class="card-body" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
<div class="noprint bulk-buttons">
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
{% if perms.dcim.change_consoleport %}
|
{% if perms.dcim.change_consoleport %}
|
||||||
@ -38,6 +42,5 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
|
||||||
{% table_config_form table %}
|
{% table_config_form table %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -6,10 +6,14 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% include 'inc/table_controls.html' with table_modal="DeviceConsoleServerPortTable_config" %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceConsoleServerPortTable_config" %}
|
||||||
<div class="table-responsive">
|
|
||||||
{% render_table table 'inc/table.html' %}
|
<div class="card">
|
||||||
|
<div class="card-body" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
<div class="noprint bulk-buttons">
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
{% if perms.dcim.change_consoleserverport %}
|
{% if perms.dcim.change_consoleserverport %}
|
||||||
|
@ -6,10 +6,14 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% include 'inc/table_controls.html' with table_modal="DeviceDeviceBayTable_config" %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceDeviceBayTable_config" %}
|
||||||
<div class="table-responsive">
|
|
||||||
{% render_table table 'inc/table.html' %}
|
<div class="card">
|
||||||
|
<div class="card-body" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
<div class="noprint bulk-buttons">
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
{% if perms.dcim.change_devicebay %}
|
{% if perms.dcim.change_devicebay %}
|
||||||
|
@ -6,10 +6,14 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% include 'inc/table_controls.html' with table_modal="DeviceFrontPortTable_config" %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceFrontPortTable_config" %}
|
||||||
<div class="table-responsive">
|
|
||||||
{% render_table table 'inc/table.html' %}
|
<div class="card">
|
||||||
|
<div class="card-body" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
<div class="noprint bulk-buttons">
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
{% if perms.dcim.change_frontport %}
|
{% if perms.dcim.change_frontport %}
|
||||||
|
@ -9,7 +9,15 @@
|
|||||||
<div class="row mb-3 justify-content-between">
|
<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="col col-12 col-lg-4 my-3 my-lg-0 d-flex noprint table-controls">
|
||||||
<div class="input-group input-group-sm">
|
<div class="input-group input-group-sm">
|
||||||
<input type="text" class="form-control interface-filter" placeholder="Filter" title="Filter text (regular expressions supported)" />
|
<input
|
||||||
|
type="text"
|
||||||
|
name="q"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="Quick search"
|
||||||
|
hx-get="{{ request.full_path }}"
|
||||||
|
hx-target="#object_list"
|
||||||
|
hx-trigger="keyup changed delay:500ms"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-md-3 mb-0 d-flex noprint table-controls">
|
<div class="col col-md-3 mb-0 d-flex noprint table-controls">
|
||||||
@ -34,9 +42,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-responsive">
|
|
||||||
{% render_table table 'inc/table.html' %}
|
<div class="card">
|
||||||
|
<div class="card-body" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
<div class="noprint bulk-buttons">
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
{% if perms.dcim.change_interface %}
|
{% if perms.dcim.change_interface %}
|
||||||
|
@ -6,10 +6,14 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% include 'inc/table_controls.html' with table_modal="DeviceInventoryItemTable_config" %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceInventoryItemTable_config" %}
|
||||||
<div class="table-responsive">
|
|
||||||
{% render_table table 'inc/table.html' %}
|
<div class="card">
|
||||||
|
<div class="card-body" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
<div class="noprint bulk-buttons">
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
{% if perms.dcim.change_inventoryitem %}
|
{% if perms.dcim.change_inventoryitem %}
|
||||||
|
@ -6,10 +6,14 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% include 'inc/table_controls.html' with table_modal="DevicePowerOutletTable_config" %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="DevicePowerOutletTable_config" %}
|
||||||
<div class="table-responsive">
|
|
||||||
{% render_table table 'inc/table.html' %}
|
<div class="card">
|
||||||
|
<div class="card-body" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
<div class="noprint bulk-buttons">
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
{% if perms.dcim.change_powerport %}
|
{% if perms.dcim.change_powerport %}
|
||||||
|
@ -6,10 +6,14 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% include 'inc/table_controls.html' with table_modal="DevicePowerPortTable_config" %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="DevicePowerPortTable_config" %}
|
||||||
<div class="table-responsive">
|
|
||||||
{% render_table table 'inc/table.html' %}
|
<div class="card">
|
||||||
|
<div class="card-body" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
<div class="noprint bulk-buttons">
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
{% if perms.dcim.change_powerport %}
|
{% if perms.dcim.change_powerport %}
|
||||||
|
@ -6,10 +6,14 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% include 'inc/table_controls.html' with table_modal="DeviceRearPortTable_config" %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceRearPortTable_config" %}
|
||||||
<div class="table-responsive">
|
|
||||||
{% render_table table 'inc/table.html' %}
|
<div class="card">
|
||||||
|
<div class="card-body" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="noprint bulk-buttons">
|
<div class="noprint bulk-buttons">
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
{% if perms.dcim.change_rearport %}
|
{% if perms.dcim.change_rearport %}
|
||||||
|
@ -7,11 +7,9 @@
|
|||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">{{ title }}</h5>
|
||||||
{{ title }}
|
<div class="card-body" id="object_list">
|
||||||
</h5>
|
{% include 'htmx/table.html' %}
|
||||||
<div class="card-body table-responsive">
|
|
||||||
{% render_table table 'inc/table.html' %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer noprint">
|
<div class="card-footer noprint">
|
||||||
{% if table.rows %}
|
{% if table.rows %}
|
||||||
@ -37,12 +35,10 @@
|
|||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">{{ title }}</h5>
|
||||||
{{ title }}
|
<div class="card-body" id="object_list">
|
||||||
</h5>
|
{% include 'htmx/table.html' %}
|
||||||
<div class="card-body table-responsive">
|
</div>
|
||||||
{% render_table table 'inc/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -87,7 +87,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Object table controls #}
|
{# Object table controls #}
|
||||||
{% include 'inc/table_controls.html' with table_modal="ObjectTable_config" %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="ObjectTable_config" %}
|
||||||
|
|
||||||
<form method="post" class="form form-horizontal">
|
<form method="post" class="form form-horizontal">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@ -95,10 +95,8 @@
|
|||||||
|
|
||||||
{# Object table #}
|
{# Object table #}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body" id="object_list">
|
||||||
<div class="table-responsive">
|
{% include 'htmx/table.html' %}
|
||||||
{% render_table table 'inc/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -125,8 +123,6 @@
|
|||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{# Paginator #}
|
|
||||||
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Filter form #}
|
{# Filter form #}
|
||||||
|
5
netbox/templates/htmx/table.html
Normal file
5
netbox/templates/htmx/table.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{# Render an HTMX-enabled table with paginator #}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
|
{% render_table table 'inc/table_htmx.html' %}
|
||||||
|
{% include 'inc/paginator_htmx.html' with paginator=table.paginator page=table.page %}
|
@ -1,51 +1,52 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
<div class="paginator float-end text-end">
|
<div class="row">
|
||||||
|
<div class="col col-md-6 mb-0">
|
||||||
|
{# Page number carousel #}
|
||||||
{% if paginator.num_pages > 1 %}
|
{% if paginator.num_pages > 1 %}
|
||||||
<div class="btn-group btn-group-sm mb-3" role="group" aria-label="Pages">
|
<div class="btn-group btn-group-sm mb-3" role="group" aria-label="Pages">
|
||||||
{% if page.has_previous %}
|
{% if page.has_previous %}
|
||||||
<a href="{% querystring request page=page.previous_page_number %}" class="btn btn-outline-secondary">
|
<a href="{% querystring request page=page.previous_page_number %}" class="btn btn-outline-secondary">
|
||||||
<i class="mdi mdi-chevron-double-left"></i>
|
<i class="mdi mdi-chevron-double-left"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
|
||||||
{% for p in page.smart_pages %}
|
|
||||||
{% if p %}
|
|
||||||
<a href="{% querystring request page=p %}" class="btn btn-outline-secondary{% if page.number == p %} active{% endif %}">
|
|
||||||
{{ p }}
|
|
||||||
</a>
|
|
||||||
{% else %}
|
|
||||||
<button type="button" class="btn btn-outline-secondary" disabled>
|
|
||||||
<span>…</span>
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% for p in page.smart_pages %}
|
||||||
{% if page.has_next %}
|
{% if p %}
|
||||||
<a href="{% querystring request page=page.next_page_number %}" class="btn btn-outline-secondary">
|
<a href="{% querystring request page=p %}" class="btn btn-outline-secondary{% if page.number == p %} active{% endif %}">
|
||||||
<i class="mdi mdi-chevron-double-right"></i>
|
{{ p }}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% else %}
|
||||||
</div>
|
<button type="button" class="btn btn-outline-secondary" disabled>
|
||||||
{% endif %}
|
<span>…</span>
|
||||||
<form method="get" class="mb-2">
|
</button>
|
||||||
{% for k, v_list in request.GET.lists %}
|
{% endif %}
|
||||||
{% if k != 'per_page' %}
|
|
||||||
{% for v in v_list %}
|
|
||||||
<input type="hidden" name="{{ k }}" value="{{ v }}" />
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class="input-group input-group-sm">
|
{% if page.has_next %}
|
||||||
<select name="per_page" class="form-select per-page">
|
<a href="{% querystring request page=page.next_page_number %}" class="btn btn-outline-secondary">
|
||||||
{% for n in page.paginator.get_page_lengths %}
|
<i class="mdi mdi-chevron-double-right"></i>
|
||||||
<option value="{{ n }}"{% if page.paginator.per_page == n %} selected="selected"{% endif %}>{{ n }}</option>
|
</a>
|
||||||
{% endfor %}
|
{% endif %}
|
||||||
</select>
|
</div>
|
||||||
<label class="input-group-text" for="per_page">Per Page</label>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% if page %}
|
|
||||||
<small class="text-end text-muted">
|
|
||||||
Showing {{ page.start_index }}-{{ page.end_index }} of {{ page.paginator.count }}
|
|
||||||
</small>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-6 mb-0 text-end">
|
||||||
|
{# Per-page count selector #}
|
||||||
|
<div class="dropdown dropup">
|
||||||
|
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||||
|
Per Page
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
{% for n in page.paginator.get_page_lengths %}
|
||||||
|
<li>
|
||||||
|
<a href="{% querystring request per_page=n %}" class="dropdown-item">{{ n }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% if page %}
|
||||||
|
<small class="text-end text-muted">
|
||||||
|
Showing {{ page.start_index }}-{{ page.end_index }} of {{ page.paginator.count }}
|
||||||
|
</small>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
72
netbox/templates/inc/paginator_htmx.html
Normal file
72
netbox/templates/inc/paginator_htmx.html
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
{% load helpers %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-6 mb-0">
|
||||||
|
{# Page number carousel #}
|
||||||
|
{% if paginator.num_pages > 1 %}
|
||||||
|
<div class="btn-group btn-group-sm" role="group" aria-label="Pages">
|
||||||
|
{% if page.has_previous %}
|
||||||
|
<a href="#"
|
||||||
|
hx-get="{% querystring request page=page.previous_page_number %}"
|
||||||
|
hx-target="#object_list"
|
||||||
|
hx-push-url="true"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-chevron-double-left"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% for p in page.smart_pages %}
|
||||||
|
{% if p %}
|
||||||
|
<a href="#"
|
||||||
|
hx-get="{% querystring request page=p %}"
|
||||||
|
hx-target="#object_list"
|
||||||
|
hx-push-url="true"
|
||||||
|
class="btn btn-outline-secondary{% if page.number == p %} active{% endif %}"
|
||||||
|
>
|
||||||
|
{{ p }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<button type="button" class="btn btn-outline-secondary" disabled>
|
||||||
|
<span>…</span>
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% if page.has_next %}
|
||||||
|
<a href="#"
|
||||||
|
hx-get="{% querystring request page=page.next_page_number %}"
|
||||||
|
hx-target="#object_list"
|
||||||
|
hx-push-url="true"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-chevron-double-right"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-6 mb-0 text-end">
|
||||||
|
{# Per-page count selector #}
|
||||||
|
<div class="dropdown dropup">
|
||||||
|
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||||
|
Per Page
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
{% for n in page.paginator.get_page_lengths %}
|
||||||
|
<li>
|
||||||
|
<a href="#"
|
||||||
|
hx-get="{% querystring request per_page=n %}"
|
||||||
|
hx-target="#object_list"
|
||||||
|
hx-push-url="true"
|
||||||
|
class="dropdown-item"
|
||||||
|
>{{ n }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% if page %}
|
||||||
|
<small class="text-end text-muted">
|
||||||
|
Showing {{ page.start_index }}-{{ page.end_index }} of {{ page.paginator.count }}
|
||||||
|
</small>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,11 +1,16 @@
|
|||||||
|
{% load helpers %}
|
||||||
|
|
||||||
<div class="row mb-3 justify-content-between">
|
<div class="row mb-3 justify-content-between">
|
||||||
<div class="table-controls noprint col col-12 col-md-8 col-lg-4">
|
<div class="table-controls noprint col col-12 col-md-8 col-lg-4">
|
||||||
<div class="input-group input-group-sm">
|
<div class="input-group input-group-sm">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="form-control object-filter"
|
name="q"
|
||||||
placeholder="Quick find"
|
class="form-control"
|
||||||
title="Find in the results below (regular expressions supported)"
|
placeholder="Quick search"
|
||||||
|
hx-get="{{ request.full_path }}"
|
||||||
|
hx-target="#object_list"
|
||||||
|
hx-trigger="keyup changed delay:500ms"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
49
netbox/templates/inc/table_htmx.html
Normal file
49
netbox/templates/inc/table_htmx.html
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{% load django_tables2 %}
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table{% if table.attrs %} {{ table.attrs.as_html }}{% endif %}>
|
||||||
|
{% if table.show_header %}
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{% for column in table.columns %}
|
||||||
|
{% if column.orderable %}
|
||||||
|
<th {{ column.attrs.th.as_html }}>
|
||||||
|
<a href="#"
|
||||||
|
hx-get="{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}"
|
||||||
|
hx-target="#object_list"
|
||||||
|
hx-push-url="true"
|
||||||
|
>{{ column.header }}</a>
|
||||||
|
</th>
|
||||||
|
{% else %}
|
||||||
|
<th {{ column.attrs.th.as_html }}>{{ column.header }}</th>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{% endif %}
|
||||||
|
<tbody>
|
||||||
|
{% for row in table.page.object_list|default:table.rows %}
|
||||||
|
<tr {{ row.attrs.as_html }}>
|
||||||
|
{% for column, cell in row.items %}
|
||||||
|
<td {{ column.attrs.td.as_html }}>{{ cell }}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
{% if table.empty_text %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="{{ table.columns|length }}" class="text-center text-muted">— {{ table.empty_text }} —</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
{% if table.has_footer %}
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
{% for column in table.columns %}
|
||||||
|
<td>{{ column.footer }}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
{% endif %}
|
||||||
|
</table>
|
||||||
|
</div>
|
@ -1,82 +1,66 @@
|
|||||||
{% extends 'generic/object.html' %}
|
{% extends 'ipam/aggregate/base.html' %}
|
||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load plugins %}
|
{% load plugins %}
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
|
||||||
{{ block.super }}
|
|
||||||
<li class="breadcrumb-item"><a href="{% url 'ipam:aggregate_list' %}?rir_id={{ object.rir.pk }}">{{ object.rir }}</a></li>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_controls %}
|
|
||||||
{% include 'ipam/inc/toggle_available.html' %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">Aggregate</h5>
|
||||||
Aggregate
|
<div class="card-body">
|
||||||
</h5>
|
<table class="table table-hover attr-table">
|
||||||
<div class="card-body">
|
<tr>
|
||||||
<table class="table table-hover attr-table">
|
<td>Family</td>
|
||||||
<tr>
|
<td>IPv{{ object.family }}</td>
|
||||||
<td>Family</td>
|
</tr>
|
||||||
<td>IPv{{ object.family }}</td>
|
<tr>
|
||||||
</tr>
|
<td>RIR</td>
|
||||||
<tr>
|
<td>
|
||||||
<td>RIR</td>
|
<a href="{% url 'ipam:aggregate_list' %}?rir={{ object.rir.slug }}">{{ object.rir }}</a>
|
||||||
<td>
|
</td>
|
||||||
<a href="{% url 'ipam:aggregate_list' %}?rir={{ object.rir.slug }}">{{ object.rir }}</a>
|
</tr>
|
||||||
</td>
|
<tr>
|
||||||
</tr>
|
<td>Utilization</td>
|
||||||
<tr>
|
<td>
|
||||||
<td>Utilization</td>
|
{% utilization_graph object.get_utilization %}
|
||||||
<td>
|
</td>
|
||||||
{% utilization_graph object.get_utilization %}
|
</tr>
|
||||||
</td>
|
<tr>
|
||||||
</tr>
|
<td>Tenant</td>
|
||||||
<tr>
|
<td>
|
||||||
<td>Tenant</td>
|
{% if object.tenant %}
|
||||||
<td>
|
{% if prefix.object.group %}
|
||||||
{% if object.tenant %}
|
<a href="{{ object.tenant.group.get_absolute_url }}">{{ object.tenant.group }}</a> /
|
||||||
{% if prefix.object.group %}
|
{% endif %}
|
||||||
<a href="{{ object.tenant.group.get_absolute_url }}">{{ object.tenant.group }}</a> /
|
<a href="{{ object.tenant.get_absolute_url }}">{{ object.tenant }}</a>
|
||||||
{% endif %}
|
{% else %}
|
||||||
<a href="{{ object.tenant.get_absolute_url }}">{{ object.tenant }}</a>
|
<span class="text-muted">None</span>
|
||||||
{% else %}
|
{% endif %}
|
||||||
<span class="text-muted">None</span>
|
</td>
|
||||||
{% endif %}
|
</tr>
|
||||||
</td>
|
<tr>
|
||||||
</tr>
|
<td>Date Added</td>
|
||||||
<tr>
|
<td>{{ object.date_added|annotated_date|placeholder }}</td>
|
||||||
<td>Date Added</td>
|
</tr>
|
||||||
<td>{{ object.date_added|annotated_date|placeholder }}</td>
|
<tr>
|
||||||
</tr>
|
<td>Description</td>
|
||||||
<tr>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
<td>Description</td>
|
</tr>
|
||||||
<td>{{ object.description|placeholder }}</td>
|
</table>
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% plugin_left_page object %}
|
</div>
|
||||||
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
{% include 'inc/panels/tags.html' %}
|
{% include 'inc/panels/tags.html' %}
|
||||||
{% plugin_right_page object %}
|
{% plugin_right_page object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col col-md-12">
|
||||||
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col col-md-12">
|
|
||||||
{% plugin_full_width_page object %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col col-md-12">
|
|
||||||
{% include 'utilities/obj_table.html' with table=prefix_table heading='Child Prefixes' bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
23
netbox/templates/ipam/aggregate/base.html
Normal file
23
netbox/templates/ipam/aggregate/base.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{% extends 'generic/object.html' %}
|
||||||
|
{% load buttons %}
|
||||||
|
{% load helpers %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
{{ block.super }}
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'ipam:aggregate_list' %}?rir_id={{ object.rir.pk }}">{{ object.rir }}</a></li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block tab_items %}
|
||||||
|
<li role="presentation" class="nav-item">
|
||||||
|
<a class="nav-link{% if not active_tab %} active{% endif %}" href="{{ object.get_absolute_url }}">
|
||||||
|
Aggregate
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% if perms.ipam.view_prefix %}
|
||||||
|
<li role="presentation" class="nav-item">
|
||||||
|
<a class="nav-link{% if active_tab == 'prefixes' %} active{% endif %}" href="{% url 'ipam:aggregate_prefixes' pk=object.pk %}">
|
||||||
|
Prefixes {% badge object.get_child_prefixes.count %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
36
netbox/templates/ipam/aggregate/prefixes.html
Normal file
36
netbox/templates/ipam/aggregate/prefixes.html
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{% extends 'ipam/aggregate/base.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
|
||||||
|
{% block extra_controls %}
|
||||||
|
{% include 'ipam/inc/toggle_available.html' %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% include 'inc/table_controls_htmx.html' with table_modal="PrefixTable_config" %}
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="noprint bulk-buttons">
|
||||||
|
<div class="bulk-button-group">
|
||||||
|
{% if perms.ipam.change_prefix %}
|
||||||
|
<button type="submit" name="_edit" formaction="{% url 'ipam:prefix_bulk_edit' %}?return_url={% url 'ipam:prefix_prefixes' pk=object.pk %}" class="btn btn-warning btn-sm">
|
||||||
|
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.ipam.delete_prefix %}
|
||||||
|
<button type="submit" name="_delete" formaction="{% url 'ipam:prefix_bulk_delete' %}?return_url={% url 'ipam:prefix_prefixes' pk=object.pk %}" class="btn btn-danger btn-sm">
|
||||||
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% table_config_form table %}
|
||||||
|
{% endblock %}
|
@ -2,6 +2,7 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load form_helpers %}
|
{% load form_helpers %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
{% block title %}Assign an IP Address{% endblock title %}
|
{% block title %}Assign an IP Address{% endblock title %}
|
||||||
|
|
||||||
@ -35,7 +36,9 @@
|
|||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col col-md-12">
|
<div class="col col-md-12">
|
||||||
<h3>Search Results</h3>
|
<h3>Search Results</h3>
|
||||||
{% include 'utilities/obj_table.html' %}
|
<div class="table-responsive">
|
||||||
|
{% render_table table 'inc/table.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{% extends 'ipam/iprange/base.html' %}
|
{% extends 'ipam/iprange/base.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
|
||||||
{% block extra_controls %}
|
{% block extra_controls %}
|
||||||
{% if perms.ipam.add_ipaddress and active_tab == 'ip-addresses' and object.first_available_ip %}
|
{% if perms.ipam.add_ipaddress and active_tab == 'ip-addresses' and object.first_available_ip %}
|
||||||
@ -9,9 +10,30 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<form method="post">
|
||||||
<div class="col col-md-12">
|
{% csrf_token %}
|
||||||
{% include 'utilities/obj_table.html' with heading='IP Addresses' bulk_edit_url='ipam:ipaddress_bulk_edit' bulk_delete_url='ipam:ipaddress_bulk_delete' %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="IPAddressTable_config" %}
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="noprint bulk-buttons">
|
||||||
|
<div class="bulk-button-group">
|
||||||
|
{% if perms.ipam.change_ipaddress %}
|
||||||
|
<button type="submit" name="_edit" formaction="{% url 'ipam:ipaddress_bulk_edit' %}?return_url={% url 'ipam:iprange_ipaddresses' pk=object.pk %}" class="btn btn-warning btn-sm">
|
||||||
|
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.ipam.delete_ipaddress %}
|
||||||
|
<button type="submit" name="_delete" formaction="{% url 'ipam:ipaddress_bulk_delete' %}?return_url={% url 'ipam:iprange_ipaddresses' pk=object.pk %}" class="btn btn-danger btn-sm">
|
||||||
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% table_config_form table %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{% extends 'ipam/prefix/base.html' %}
|
{% extends 'ipam/prefix/base.html' %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block extra_controls %}
|
{% block extra_controls %}
|
||||||
{% if perms.ipam.add_ipaddress and first_available_ip %}
|
{% if perms.ipam.add_ipaddress and first_available_ip %}
|
||||||
@ -11,11 +10,30 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<form method="post">
|
||||||
<div class="col col-md-12">
|
{% csrf_token %}
|
||||||
{% include 'inc/table_controls.html' with table_modal="IPAddressTable_config" %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="IPAddressTable_config" %}
|
||||||
{% include 'utilities/obj_table.html' with heading='IP Addresses' bulk_edit_url='ipam:ipaddress_bulk_edit' bulk_delete_url='ipam:ipaddress_bulk_delete' %}
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% table_config_form table table_name="IPAddressTable" %}
|
<div class="noprint bulk-buttons">
|
||||||
|
<div class="bulk-button-group">
|
||||||
|
{% if perms.ipam.change_ipaddress %}
|
||||||
|
<button type="submit" name="_edit" formaction="{% url 'ipam:ipaddress_bulk_edit' %}?return_url={% url 'ipam:prefix_ipaddresses' pk=object.pk %}" class="btn btn-warning btn-sm">
|
||||||
|
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.ipam.delete_ipaddress %}
|
||||||
|
<button type="submit" name="_delete" formaction="{% url 'ipam:ipaddress_bulk_delete' %}?return_url={% url 'ipam:prefix_ipaddresses' pk=object.pk %}" class="btn btn-danger btn-sm">
|
||||||
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% table_config_form table %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,13 +1,31 @@
|
|||||||
{% extends 'ipam/prefix/base.html' %}
|
{% extends 'ipam/prefix/base.html' %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<form method="post">
|
||||||
<div class="col col-md-12">
|
{% csrf_token %}
|
||||||
{% include 'inc/table_controls.html' with table_modal="IPRangeTable_config" %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="IPRangeTable_config" %}
|
||||||
{% include 'utilities/obj_table.html' with heading='Child IP Ranges' bulk_edit_url='ipam:iprange_bulk_edit' bulk_delete_url='ipam:iprange_bulk_delete' parent=prefix %}
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% table_config_form table table_name="IPRangeTable" %}
|
<div class="noprint bulk-buttons">
|
||||||
|
<div class="bulk-button-group">
|
||||||
|
{% if perms.ipam.change_iprange %}
|
||||||
|
<button type="submit" name="_edit" formaction="{% url 'ipam:iprange_bulk_edit' %}?return_url={% url 'ipam:prefix_ipranges' pk=object.pk %}" class="btn btn-warning btn-sm">
|
||||||
|
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.ipam.delete_iprange %}
|
||||||
|
<button type="submit" name="_delete" formaction="{% url 'ipam:iprange_bulk_delete' %}?return_url={% url 'ipam:prefix_ipranges' pk=object.pk %}" class="btn btn-danger btn-sm">
|
||||||
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% table_config_form table %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{% extends 'ipam/prefix/base.html' %}
|
{% extends 'ipam/prefix/base.html' %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block extra_controls %}
|
{% block extra_controls %}
|
||||||
{% include 'ipam/inc/toggle_available.html' %}
|
{% include 'ipam/inc/toggle_available.html' %}
|
||||||
@ -13,11 +12,30 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<form method="post">
|
||||||
<div class="col col-md-12">
|
{% csrf_token %}
|
||||||
{% include 'inc/table_controls.html' with table_modal="PrefixTable_config" %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="PrefixTable_config" %}
|
||||||
{% include 'utilities/obj_table.html' with heading='Child Prefixes' bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' parent=prefix %}
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% table_config_form table table_name="PrefixTable" %}
|
<div class="noprint bulk-buttons">
|
||||||
|
<div class="bulk-button-group">
|
||||||
|
{% if perms.ipam.change_prefix %}
|
||||||
|
<button type="submit" name="_edit" formaction="{% url 'ipam:prefix_bulk_edit' %}?return_url={% url 'ipam:prefix_prefixes' pk=object.pk %}" class="btn btn-warning btn-sm">
|
||||||
|
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.ipam.delete_prefix %}
|
||||||
|
<button type="submit" name="_delete" formaction="{% url 'ipam:prefix_bulk_delete' %}?return_url={% url 'ipam:prefix_prefixes' pk=object.pk %}" class="btn btn-danger btn-sm">
|
||||||
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% table_config_form table %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
{% extends 'ipam/vlan/base.html' %}
|
{% extends 'ipam/vlan/base.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<form method="post">
|
||||||
<div class="col col-md-12">
|
{% csrf_token %}
|
||||||
{% include 'utilities/obj_table.html' with heading='Device Interfaces' parent=vlan %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="VLANDevicesTable_config" %}
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
</form>
|
||||||
|
{% table_config_form table %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
{% extends 'ipam/vlan/base.html' %}
|
{% extends 'ipam/vlan/base.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<form method="post">
|
||||||
<div class="col col-md-12">
|
{% csrf_token %}
|
||||||
{% include 'utilities/obj_table.html' with heading='Virtual Machine Interfaces' parent=vlan %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="VLANVirtualMachinesTable_config" %}
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
</form>
|
||||||
|
{% table_config_form table %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
{% load helpers %}
|
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
|
|
||||||
{% if permissions.change or permissions.delete %}
|
|
||||||
<form method="post" class="form form-horizontal">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="return_url" value="{% if return_url %}{{ return_url }}{% else %}{{ request.path }}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}{% endif %}" />
|
|
||||||
|
|
||||||
{% if table.paginator.num_pages > 1 %}
|
|
||||||
<div id="select-all-box" class="d-none card noprint">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="float-end">
|
|
||||||
{% if bulk_edit_url and permissions.change %}
|
|
||||||
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if bulk_querystring %}?{{ bulk_querystring }}{% elif request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm" disabled="disabled">
|
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit All
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if bulk_delete_url and permissions.delete %}
|
|
||||||
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if bulk_querystring %}?{{ bulk_querystring }}{% elif request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm" disabled="disabled">
|
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete All
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input type="checkbox" id="select-all" name="_all" class="form-check-input" />
|
|
||||||
<label for="select-all" class="form-check-label">
|
|
||||||
Select <strong>all {{ table.objects_count }} {{ table.data.verbose_name_plural }}</strong> matching query
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
|
||||||
{% render_table table 'inc/table.html' %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="float-start noprint">
|
|
||||||
{% block extra_actions %}{% endblock %}
|
|
||||||
|
|
||||||
{% if bulk_edit_url and permissions.change %}
|
|
||||||
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit Selected
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if bulk_delete_url and permissions.delete %}
|
|
||||||
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete Selected
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
|
||||||
{% render_table table 'inc/table.html' %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
|
@ -3,26 +3,25 @@
|
|||||||
{% load render_table from django_tables2 %}
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<form action="{% url 'virtualization:cluster_remove_devices' pk=object.pk %}" method="post">
|
||||||
<div class="col col-md-12">
|
{% csrf_token %}
|
||||||
|
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceTable_config" %}
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<div class="card-body" id="object_list">
|
||||||
Host Devices
|
{% include 'htmx/table.html' %}
|
||||||
</h5>
|
|
||||||
<form action="{% url 'virtualization:cluster_remove_devices' pk=object.pk %}" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="card-body table-responsive">
|
|
||||||
{% render_table table 'inc/table.html' %}
|
|
||||||
</div>
|
</div>
|
||||||
{% if perms.virtualization.change_cluster %}
|
</div>
|
||||||
<div class="card-footer noprint">
|
|
||||||
|
<div class="noprint bulk-buttons">
|
||||||
|
<div class="bulk-button-group">
|
||||||
|
{% if perms.virtualization.change_cluster %}
|
||||||
<button type="submit" name="_remove" class="btn btn-danger btn-sm">
|
<button type="submit" name="_remove" class="btn btn-danger btn-sm">
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Remove Devices
|
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Remove Devices
|
||||||
</button>
|
</button>
|
||||||
</div>
|
{% endif %}
|
||||||
{% endif %}
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</div>
|
{% table_config_form table %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -3,16 +3,30 @@
|
|||||||
{% load render_table from django_tables2 %}
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<form method="post">
|
||||||
<div class="col col-md-12">
|
{% csrf_token %}
|
||||||
|
{% include 'inc/table_controls_htmx.html' with table_modal="VirtualMachineTable_config" %}
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<div class="card-body" id="object_list">
|
||||||
Virtual Machines
|
{% include 'htmx/table.html' %}
|
||||||
</h5>
|
|
||||||
<div class="card-body table-responsive">
|
|
||||||
{% render_table table 'inc/table.html' %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
<div class="noprint bulk-buttons">
|
||||||
|
<div class="bulk-button-group">
|
||||||
|
{% if perms.virtualization.change_virtualmachine %}
|
||||||
|
<button type="submit" name="_edit" formaction="{% url 'virtualization:virtualmachine_bulk_edit' %}?return_url={% url 'virtualization:cluster_virtualmachines' pk=object.pk %}" class="btn btn-warning btn-sm">
|
||||||
|
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.virtualization.delete_virtualmachine %}
|
||||||
|
<button type="submit" name="_delete" formaction="{% url 'virtualization:virtualmachine_bulk_delete' %}?return_url={% url 'virtualization:cluster_virtualmachines' pk=object.pk %}" class="btn btn-danger btn-sm">
|
||||||
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% table_config_form table %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
{% extends 'virtualization/virtualmachine/base.html' %}
|
{% extends 'virtualization/virtualmachine/base.html' %}
|
||||||
{% load render_table from django_tables2 %}
|
{% load render_table from django_tables2 %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% include 'inc/table_controls.html' with table_modal="VirtualMachineVMInterfaceTable_config" %}
|
{% include 'inc/table_controls_htmx.html' with table_modal="VMInterfaceTable_config" %}
|
||||||
<div class="table-responsive">
|
|
||||||
{% render_table table 'inc/table.html' %}
|
<div class="card">
|
||||||
|
<div class="card-body" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="noprint">
|
<div class="noprint">
|
||||||
{% if perms.virtualization.change_vminterface %}
|
{% 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">
|
<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">
|
||||||
|
5
netbox/utilities/htmx.py
Normal file
5
netbox/utilities/htmx.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
def is_htmx(request):
|
||||||
|
"""
|
||||||
|
Returns True if the request was made by HTMX; False otherwise.
|
||||||
|
"""
|
||||||
|
return 'Hx-Request' in request.headers
|
@ -4,6 +4,7 @@ from django.db.models import Prefetch
|
|||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from dcim.filtersets import DeviceFilterSet
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from dcim.tables import DeviceTable
|
from dcim.tables import DeviceTable
|
||||||
from extras.views import ObjectConfigContextView
|
from extras.views import ObjectConfigContextView
|
||||||
@ -165,6 +166,7 @@ class ClusterVirtualMachinesView(generic.ObjectChildrenView):
|
|||||||
queryset = Cluster.objects.all()
|
queryset = Cluster.objects.all()
|
||||||
child_model = VirtualMachine
|
child_model = VirtualMachine
|
||||||
table = tables.VirtualMachineTable
|
table = tables.VirtualMachineTable
|
||||||
|
filterset = filtersets.VirtualMachineFilterSet
|
||||||
template_name = 'virtualization/cluster/virtual_machines.html'
|
template_name = 'virtualization/cluster/virtual_machines.html'
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
@ -180,6 +182,7 @@ class ClusterDevicesView(generic.ObjectChildrenView):
|
|||||||
queryset = Cluster.objects.all()
|
queryset = Cluster.objects.all()
|
||||||
child_model = Device
|
child_model = Device
|
||||||
table = DeviceTable
|
table = DeviceTable
|
||||||
|
filterset = DeviceFilterSet
|
||||||
template_name = 'virtualization/cluster/devices.html'
|
template_name = 'virtualization/cluster/devices.html'
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
@ -345,6 +348,7 @@ class VirtualMachineInterfacesView(generic.ObjectChildrenView):
|
|||||||
queryset = VirtualMachine.objects.all()
|
queryset = VirtualMachine.objects.all()
|
||||||
child_model = VMInterface
|
child_model = VMInterface
|
||||||
table = tables.VMInterfaceTable
|
table = tables.VMInterfaceTable
|
||||||
|
filterset = filtersets.VMInterfaceFilterSet
|
||||||
template_name = 'virtualization/virtualmachine/interfaces.html'
|
template_name = 'virtualization/virtualmachine/interfaces.html'
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
|
Loading…
Reference in New Issue
Block a user