diff --git a/netbox/circuits/models.py b/netbox/circuits/models.py index 73df7f2d4..b163834e6 100644 --- a/netbox/circuits/models.py +++ b/netbox/circuits/models.py @@ -175,7 +175,7 @@ class CircuitType(OrganizationalModel): return self.name def get_absolute_url(self): - return "{}?type={}".format(reverse('circuits:circuit_list'), self.slug) + return reverse('circuits:circuittype', args=[self.pk]) def to_csv(self): return ( diff --git a/netbox/circuits/urls.py b/netbox/circuits/urls.py index acc3baac5..58f52bb42 100644 --- a/netbox/circuits/urls.py +++ b/netbox/circuits/urls.py @@ -38,6 +38,7 @@ urlpatterns = [ path('circuit-types/import/', views.CircuitTypeBulkImportView.as_view(), name='circuittype_import'), path('circuit-types/edit/', views.CircuitTypeBulkEditView.as_view(), name='circuittype_bulk_edit'), path('circuit-types/delete/', views.CircuitTypeBulkDeleteView.as_view(), name='circuittype_bulk_delete'), + path('circuit-types//', views.CircuitTypeView.as_view(), name='circuittype'), path('circuit-types//edit/', views.CircuitTypeEditView.as_view(), name='circuittype_edit'), path('circuit-types//delete/', views.CircuitTypeDeleteView.as_view(), name='circuittype_delete'), path('circuit-types//changelog/', ObjectChangeLogView.as_view(), name='circuittype_changelog', kwargs={'model': CircuitType}), diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index 1c0a152b2..6f3c5b2be 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -147,6 +147,23 @@ class CircuitTypeListView(generic.ObjectListView): table = tables.CircuitTypeTable +class CircuitTypeView(generic.ObjectView): + queryset = CircuitType.objects.all() + + def get_extra_context(self, request, instance): + circuits = Circuit.objects.restrict(request.user, 'view').filter( + type=instance + ) + + circuits_table = tables.CircuitTable(circuits) + circuits_table.columns.hide('type') + paginate_table(circuits_table, request) + + return { + 'circuits_table': circuits_table, + } + + class CircuitTypeEditView(generic.ObjectEditView): queryset = CircuitType.objects.all() model_form = forms.CircuitTypeForm diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 7f22d9325..a5efadac5 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -65,7 +65,7 @@ class Manufacturer(OrganizationalModel): return self.name def get_absolute_url(self): - return "{}?manufacturer={}".format(reverse('dcim:devicetype_list'), self.slug) + return reverse('dcim:manufacturer', args=[self.pk]) def to_csv(self): return ( @@ -375,6 +375,9 @@ class DeviceRole(OrganizationalModel): def __str__(self): return self.name + def get_absolute_url(self): + return reverse('dcim:devicerole', args=[self.pk]) + def to_csv(self): return ( self.name, @@ -436,7 +439,7 @@ class Platform(OrganizationalModel): return self.name def get_absolute_url(self): - return "{}?platform={}".format(reverse('dcim:device_list'), self.slug) + return reverse('dcim:platform', args=[self.pk]) def to_csv(self): return ( diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index 1942e3cb0..2869c4265 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -67,7 +67,7 @@ class RackRole(OrganizationalModel): return self.name def get_absolute_url(self): - return "{}?role={}".format(reverse('dcim:rack_list'), self.slug) + return reverse('dcim:rackrole', args=[self.pk]) def to_csv(self): return ( diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index c4204dd4a..c6ad3ad6d 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -50,6 +50,9 @@ __all__ = ( class DeviceRoleTable(BaseTable): pk = ToggleColumn() + name = tables.Column( + linkify=True + ) device_count = LinkedCountColumn( viewname='dcim:device_list', url_params={'role': 'slug'}, @@ -76,6 +79,9 @@ class DeviceRoleTable(BaseTable): class PlatformTable(BaseTable): pk = ToggleColumn() + name = tables.Column( + linkify=True + ) device_count = LinkedCountColumn( viewname='dcim:device_list', url_params={'platform': 'slug'}, diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index e7c29ae9f..f07e1911a 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -57,6 +57,7 @@ urlpatterns = [ path('rack-roles/import/', views.RackRoleBulkImportView.as_view(), name='rackrole_import'), path('rack-roles/edit/', views.RackRoleBulkEditView.as_view(), name='rackrole_bulk_edit'), path('rack-roles/delete/', views.RackRoleBulkDeleteView.as_view(), name='rackrole_bulk_delete'), + path('rack-roles//', views.RackRoleView.as_view(), name='rackrole'), path('rack-roles//edit/', views.RackRoleEditView.as_view(), name='rackrole_edit'), path('rack-roles//delete/', views.RackRoleDeleteView.as_view(), name='rackrole_delete'), path('rack-roles//changelog/', ObjectChangeLogView.as_view(), name='rackrole_changelog', kwargs={'model': RackRole}), @@ -93,6 +94,7 @@ urlpatterns = [ path('manufacturers/import/', views.ManufacturerBulkImportView.as_view(), name='manufacturer_import'), path('manufacturers/edit/', views.ManufacturerBulkEditView.as_view(), name='manufacturer_bulk_edit'), path('manufacturers/delete/', views.ManufacturerBulkDeleteView.as_view(), name='manufacturer_bulk_delete'), + path('manufacturers//', views.ManufacturerView.as_view(), name='manufacturer'), path('manufacturers//edit/', views.ManufacturerEditView.as_view(), name='manufacturer_edit'), path('manufacturers//delete/', views.ManufacturerDeleteView.as_view(), name='manufacturer_delete'), path('manufacturers//changelog/', ObjectChangeLogView.as_view(), name='manufacturer_changelog', kwargs={'model': Manufacturer}), @@ -179,6 +181,7 @@ urlpatterns = [ path('device-roles/import/', views.DeviceRoleBulkImportView.as_view(), name='devicerole_import'), path('device-roles/edit/', views.DeviceRoleBulkEditView.as_view(), name='devicerole_bulk_edit'), path('device-roles/delete/', views.DeviceRoleBulkDeleteView.as_view(), name='devicerole_bulk_delete'), + path('device-roles//', views.DeviceRoleView.as_view(), name='devicerole'), path('device-roles//edit/', views.DeviceRoleEditView.as_view(), name='devicerole_edit'), path('device-roles//delete/', views.DeviceRoleDeleteView.as_view(), name='devicerole_delete'), path('device-roles//changelog/', ObjectChangeLogView.as_view(), name='devicerole_changelog', kwargs={'model': DeviceRole}), @@ -189,6 +192,7 @@ urlpatterns = [ path('platforms/import/', views.PlatformBulkImportView.as_view(), name='platform_import'), path('platforms/edit/', views.PlatformBulkEditView.as_view(), name='platform_bulk_edit'), path('platforms/delete/', views.PlatformBulkDeleteView.as_view(), name='platform_bulk_delete'), + path('platforms//', views.PlatformView.as_view(), name='platform'), path('platforms//edit/', views.PlatformEditView.as_view(), name='platform_edit'), path('platforms//delete/', views.PlatformDeleteView.as_view(), name='platform_delete'), path('platforms//changelog/', ObjectChangeLogView.as_view(), name='platform_changelog', kwargs={'model': Platform}), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index e624b6926..e1f24df25 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -20,6 +20,7 @@ from secrets.models import Secret from utilities.forms import ConfirmationForm from utilities.paginator import EnhancedPaginator, get_paginate_count from utilities.permissions import get_permission_for_model +from utilities.tables import paginate_table from utilities.utils import csv_format, count_related from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin from virtualization.models import VirtualMachine @@ -341,6 +342,23 @@ class RackRoleListView(generic.ObjectListView): table = tables.RackRoleTable +class RackRoleView(generic.ObjectView): + queryset = RackRole.objects.all() + + def get_extra_context(self, request, instance): + racks = Rack.objects.restrict(request.user, 'view').filter( + role=instance + ) + + racks_table = tables.RackTable(racks) + racks_table.columns.hide('role') + paginate_table(racks_table, request) + + return { + 'racks_table': racks_table, + } + + class RackRoleEditView(generic.ObjectEditView): queryset = RackRole.objects.all() model_form = forms.RackRoleForm @@ -567,6 +585,23 @@ class ManufacturerListView(generic.ObjectListView): table = tables.ManufacturerTable +class ManufacturerView(generic.ObjectView): + queryset = Manufacturer.objects.all() + + def get_extra_context(self, request, instance): + devicetypes = DeviceType.objects.restrict(request.user, 'view').filter( + manufacturer=instance + ) + + devicetypes_table = tables.DeviceTypeTable(devicetypes) + devicetypes_table.columns.hide('manufacturer') + paginate_table(devicetypes_table, request) + + return { + 'devicetypes_table': devicetypes_table, + } + + class ManufacturerEditView(generic.ObjectEditView): queryset = Manufacturer.objects.all() model_form = forms.ManufacturerForm @@ -1017,6 +1052,23 @@ class DeviceRoleListView(generic.ObjectListView): table = tables.DeviceRoleTable +class DeviceRoleView(generic.ObjectView): + queryset = DeviceRole.objects.all() + + def get_extra_context(self, request, instance): + devices = Device.objects.restrict(request.user, 'view').filter( + device_role=instance + ) + + devices_table = tables.DeviceTable(devices) + devices_table.columns.hide('device_role') + paginate_table(devices_table, request) + + return { + 'devices_table': devices_table, + } + + class DeviceRoleEditView(generic.ObjectEditView): queryset = DeviceRole.objects.all() model_form = forms.DeviceRoleForm @@ -1056,6 +1108,23 @@ class PlatformListView(generic.ObjectListView): table = tables.PlatformTable +class PlatformView(generic.ObjectView): + queryset = Platform.objects.all() + + def get_extra_context(self, request, instance): + devices = Device.objects.restrict(request.user, 'view').filter( + platform=instance + ) + + devices_table = tables.DeviceTable(devices) + devices_table.columns.hide('platform') + paginate_table(devices_table, request) + + return { + 'devices_table': devices_table, + } + + class PlatformEditView(generic.ObjectEditView): queryset = Platform.objects.all() model_form = forms.PlatformForm diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index 9867f6069..b11a88d54 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -66,7 +66,7 @@ class RIR(OrganizationalModel): return self.name def get_absolute_url(self): - return "{}?rir={}".format(reverse('ipam:aggregate_list'), self.slug) + return reverse('ipam:rir', args=[self.pk]) def to_csv(self): return ( @@ -216,6 +216,9 @@ class Role(OrganizationalModel): def __str__(self): return self.name + def get_absolute_url(self): + return reverse('ipam:role', args=[self.pk]) + def to_csv(self): return ( self.name, diff --git a/netbox/ipam/models/vlans.py b/netbox/ipam/models/vlans.py index 26cb5299f..0c184adff 100644 --- a/netbox/ipam/models/vlans.py +++ b/netbox/ipam/models/vlans.py @@ -70,7 +70,7 @@ class VLANGroup(OrganizationalModel): return self.name def get_absolute_url(self): - return reverse('ipam:vlangroup_vlans', args=[self.pk]) + return reverse('ipam:vlangroup', args=[self.pk]) def clean(self): super().clean() diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index e25bf5351..f41c7cfff 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -224,6 +224,9 @@ class AggregateDetailTable(AggregateTable): class RoleTable(BaseTable): pk = ToggleColumn() + name = tables.Column( + linkify=True + ) prefix_count = LinkedCountColumn( viewname='ipam:prefix_list', url_params={'role': 'slug'}, @@ -450,9 +453,8 @@ class VLANTable(BaseTable): site = tables.Column( linkify=True ) - group = tables.LinkColumn( - viewname='ipam:vlangroup_vlans', - args=[Accessor('group__pk')] + group = tables.Column( + linkify=True ) tenant = TenantColumn() status = ChoiceFieldColumn( diff --git a/netbox/ipam/urls.py b/netbox/ipam/urls.py index 4b576d21f..262537b8b 100644 --- a/netbox/ipam/urls.py +++ b/netbox/ipam/urls.py @@ -37,6 +37,7 @@ urlpatterns = [ path('rirs/import/', views.RIRBulkImportView.as_view(), name='rir_import'), path('rirs/edit/', views.RIRBulkEditView.as_view(), name='rir_bulk_edit'), path('rirs/delete/', views.RIRBulkDeleteView.as_view(), name='rir_bulk_delete'), + path('rirs//', views.RIRView.as_view(), name='rir'), path('rirs//edit/', views.RIREditView.as_view(), name='rir_edit'), path('rirs//delete/', views.RIRDeleteView.as_view(), name='rir_delete'), path('rirs//changelog/', ObjectChangeLogView.as_view(), name='rir_changelog', kwargs={'model': RIR}), @@ -59,6 +60,7 @@ urlpatterns = [ path('roles/import/', views.RoleBulkImportView.as_view(), name='role_import'), path('roles/edit/', views.RoleBulkEditView.as_view(), name='role_bulk_edit'), path('roles/delete/', views.RoleBulkDeleteView.as_view(), name='role_bulk_delete'), + path('roles//', views.RoleView.as_view(), name='role'), path('roles//edit/', views.RoleEditView.as_view(), name='role_edit'), path('roles//delete/', views.RoleDeleteView.as_view(), name='role_delete'), path('roles//changelog/', ObjectChangeLogView.as_view(), name='role_changelog', kwargs={'model': Role}), @@ -97,9 +99,9 @@ urlpatterns = [ path('vlan-groups/import/', views.VLANGroupBulkImportView.as_view(), name='vlangroup_import'), path('vlan-groups/edit/', views.VLANGroupBulkEditView.as_view(), name='vlangroup_bulk_edit'), path('vlan-groups/delete/', views.VLANGroupBulkDeleteView.as_view(), name='vlangroup_bulk_delete'), + path('vlan-groups//', views.VLANGroupView.as_view(), name='vlangroup'), path('vlan-groups//edit/', views.VLANGroupEditView.as_view(), name='vlangroup_edit'), path('vlan-groups//delete/', views.VLANGroupDeleteView.as_view(), name='vlangroup_delete'), - path('vlan-groups//vlans/', views.VLANGroupVLANsView.as_view(), name='vlangroup_vlans'), path('vlan-groups//changelog/', ObjectChangeLogView.as_view(), name='vlangroup_changelog', kwargs={'model': VLANGroup}), # VLANs diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 47eccead1..b0da44b6d 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -148,6 +148,23 @@ class RIRListView(generic.ObjectListView): template_name = 'ipam/rir_list.html' +class RIRView(generic.ObjectView): + queryset = RIR.objects.all() + + def get_extra_context(self, request, instance): + aggregates = Aggregate.objects.restrict(request.user, 'view').filter( + rir=instance + ) + + aggregates_table = tables.AggregateTable(aggregates) + aggregates_table.columns.hide('rir') + paginate_table(aggregates_table, request) + + return { + 'aggregates_table': aggregates_table, + } + + class RIREditView(generic.ObjectEditView): queryset = RIR.objects.all() model_form = forms.RIRForm @@ -286,6 +303,23 @@ class RoleListView(generic.ObjectListView): table = tables.RoleTable +class RoleView(generic.ObjectView): + queryset = Role.objects.all() + + def get_extra_context(self, request, instance): + prefixes = Prefix.objects.restrict(request.user, 'view').filter( + role=instance + ) + + prefixes_table = tables.PrefixTable(prefixes) + prefixes_table.columns.hide('role') + paginate_table(prefixes_table, request) + + return { + 'prefixes_table': prefixes_table, + } + + class RoleEditView(generic.ObjectEditView): queryset = Role.objects.all() model_form = forms.RoleForm @@ -633,6 +667,29 @@ class VLANGroupListView(generic.ObjectListView): table = tables.VLANGroupTable +class VLANGroupView(generic.ObjectView): + queryset = VLANGroup.objects.all() + + def get_extra_context(self, request, instance): + vlans = VLAN.objects.restrict(request.user, 'view').filter(group=instance).prefetch_related( + Prefetch('prefixes', queryset=Prefix.objects.restrict(request.user)) + ) + vlans_count = vlans.count() + vlans = add_available_vlans(instance, vlans) + + vlans_table = tables.VLANDetailTable(vlans) + if request.user.has_perm('ipam.change_vlan') or request.user.has_perm('ipam.delete_vlan'): + vlans_table.columns.show('pk') + vlans_table.columns.hide('site') + vlans_table.columns.hide('group') + paginate_table(vlans_table, request) + + return { + 'vlans_count': vlans_count, + 'vlans_table': vlans_table, + } + + class VLANGroupEditView(generic.ObjectEditView): queryset = VLANGroup.objects.all() model_form = forms.VLANGroupForm @@ -666,38 +723,6 @@ class VLANGroupBulkDeleteView(generic.BulkDeleteView): table = tables.VLANGroupTable -class VLANGroupVLANsView(generic.ObjectView): - queryset = VLANGroup.objects.all() - template_name = 'ipam/vlangroup_vlans.html' - - def get_extra_context(self, request, instance): - vlans = VLAN.objects.restrict(request.user, 'view').filter(group=instance).prefetch_related( - Prefetch('prefixes', queryset=Prefix.objects.restrict(request.user)) - ) - vlans = add_available_vlans(instance, vlans) - - vlan_table = tables.VLANDetailTable(vlans) - if request.user.has_perm('ipam.change_vlan') or request.user.has_perm('ipam.delete_vlan'): - vlan_table.columns.show('pk') - vlan_table.columns.hide('site') - vlan_table.columns.hide('group') - paginate_table(vlan_table, request) - - # Compile permissions list for rendering the object table - permissions = { - 'add': request.user.has_perm('ipam.add_vlan'), - 'change': request.user.has_perm('ipam.change_vlan'), - 'delete': request.user.has_perm('ipam.delete_vlan'), - } - - return { - 'first_available_vlan': instance.get_next_available_vid(), - 'bulk_querystring': f'group_id={instance.pk}', - 'vlan_table': vlan_table, - 'permissions': permissions, - } - - # # VLANs # diff --git a/netbox/secrets/models.py b/netbox/secrets/models.py index 932242060..dc3a65747 100644 --- a/netbox/secrets/models.py +++ b/netbox/secrets/models.py @@ -263,7 +263,7 @@ class SecretRole(OrganizationalModel): return self.name def get_absolute_url(self): - return "{}?role={}".format(reverse('secrets:secret_list'), self.slug) + return reverse('secrets:secretrole', args=[self.pk]) def to_csv(self): return ( diff --git a/netbox/secrets/urls.py b/netbox/secrets/urls.py index 7c72b848c..a47070152 100644 --- a/netbox/secrets/urls.py +++ b/netbox/secrets/urls.py @@ -13,6 +13,7 @@ urlpatterns = [ path('secret-roles/import/', views.SecretRoleBulkImportView.as_view(), name='secretrole_import'), path('secret-roles/edit/', views.SecretRoleBulkEditView.as_view(), name='secretrole_bulk_edit'), path('secret-roles/delete/', views.SecretRoleBulkDeleteView.as_view(), name='secretrole_bulk_delete'), + path('secret-roles//', views.SecretRoleView.as_view(), name='secretrole'), path('secret-roles//edit/', views.SecretRoleEditView.as_view(), name='secretrole_edit'), path('secret-roles//delete/', views.SecretRoleDeleteView.as_view(), name='secretrole_delete'), path('secret-roles//changelog/', ObjectChangeLogView.as_view(), name='secretrole_changelog', kwargs={'model': SecretRole}), diff --git a/netbox/secrets/views.py b/netbox/secrets/views.py index 0ae2f6231..57d64f064 100644 --- a/netbox/secrets/views.py +++ b/netbox/secrets/views.py @@ -7,6 +7,7 @@ from django.utils.html import escape from django.utils.safestring import mark_safe from netbox.views import generic +from utilities.tables import paginate_table from utilities.utils import count_related from . import filters, forms, tables from .models import SecretRole, Secret, SessionKey, UserKey @@ -33,6 +34,23 @@ class SecretRoleListView(generic.ObjectListView): table = tables.SecretRoleTable +class SecretRoleView(generic.ObjectView): + queryset = SecretRole.objects.all() + + def get_extra_context(self, request, instance): + secrets = Secret.objects.restrict(request.user, 'view').filter( + role=instance + ) + + secrets_table = tables.SecretTable(secrets) + secrets_table.columns.hide('role') + paginate_table(secrets_table, request) + + return { + 'secrets_table': secrets_table, + } + + class SecretRoleEditView(generic.ObjectEditView): queryset = SecretRole.objects.all() model_form = forms.SecretRoleForm diff --git a/netbox/templates/circuits/circuittype.html b/netbox/templates/circuits/circuittype.html new file mode 100644 index 000000000..aee7bf944 --- /dev/null +++ b/netbox/templates/circuits/circuittype.html @@ -0,0 +1,60 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Circuit Types
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Circuit Type +
    + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Circuits + {{ circuits_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Circuits +
    + {% include 'inc/table.html' with table=circuits_table %} + {% if perms.circuits.add_circuit %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=circuits_table.paginator page=circuits_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/dcim/devicerole.html b/netbox/templates/dcim/devicerole.html new file mode 100644 index 000000000..c6cbf4952 --- /dev/null +++ b/netbox/templates/dcim/devicerole.html @@ -0,0 +1,76 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Device Roles
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Device Role +
    + + + + + + + + + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Color +   +
    VM Role + {% if object.vm_role %} + + {% else %} + + {% endif %} +
    Devices + {{ devices_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Devices +
    + {% include 'inc/table.html' with table=devices_table %} + {% if perms.dcim.add_device %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=devices_table.paginator page=devices_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/dcim/manufacturer.html b/netbox/templates/dcim/manufacturer.html new file mode 100644 index 000000000..b2ecacbb1 --- /dev/null +++ b/netbox/templates/dcim/manufacturer.html @@ -0,0 +1,60 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Manufacturers
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Manufacturer +
    + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Device types + {{ devicetypes_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Device Types +
    + {% include 'inc/table.html' with table=devicetypes_table %} + {% if perms.dcim.add_devicetype %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=devicetypes_table.paginator page=devicetypes_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/dcim/platform.html b/netbox/templates/dcim/platform.html new file mode 100644 index 000000000..8a3ff5b6e --- /dev/null +++ b/netbox/templates/dcim/platform.html @@ -0,0 +1,68 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Platforms
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Platform +
    + + + + + + + + + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    NAPALM Driver{{ object.napalm_driver }}
    NAPALM Arguments
    {{ object.napalm_args }}
    Devices + {{ devices_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Devices +
    + {% include 'inc/table.html' with table=devices_table %} + {% if perms.dcim.add_device %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=devices_table.paginator page=devices_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/dcim/rackrole.html b/netbox/templates/dcim/rackrole.html new file mode 100644 index 000000000..89306b481 --- /dev/null +++ b/netbox/templates/dcim/rackrole.html @@ -0,0 +1,66 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Rack Roles
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Rack Role +
    + + + + + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Color +   +
    Racks + {{ racks_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Racks +
    + {% include 'inc/table.html' with table=racks_table %} + {% if perms.dcim.add_rack %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=racks_table.paginator page=racks_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/ipam/rir.html b/netbox/templates/ipam/rir.html new file mode 100644 index 000000000..332c90da7 --- /dev/null +++ b/netbox/templates/ipam/rir.html @@ -0,0 +1,70 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • RIRs
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + RIR +
    + + + + + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Private + {% if object.is_private %} + + {% else %} + + {% endif %} +
    Aggregates + {{ aggregates_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Aggregates +
    + {% include 'inc/table.html' with table=aggregates_table %} + {% if perms.ipam.add_aggregate %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=aggregates_table.paginator page=aggregates_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/ipam/role.html b/netbox/templates/ipam/role.html new file mode 100644 index 000000000..307e6e21a --- /dev/null +++ b/netbox/templates/ipam/role.html @@ -0,0 +1,64 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Roles
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Role +
    + + + + + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Weight{{ object.weight }}
    Prefixes + {{ prefixes_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Prefixes +
    + {% include 'inc/table.html' with table=prefixes_table %} + {% if perms.ipam.add_prefix %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=prefixes_table.paginator page=prefixes_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/ipam/vlangroup.html b/netbox/templates/ipam/vlangroup.html new file mode 100644 index 000000000..e265aa608 --- /dev/null +++ b/netbox/templates/ipam/vlangroup.html @@ -0,0 +1,72 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • VLAN Groups
  • + {% if object.scope %} +
  • {{ object.scope }}
  • + {% endif %} +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + VLAN Group +
    + + + + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Scope + {% if object.scope %} + {{ object.scope }} + {% else %} + + {% endif %} +
    VLANs + {{ vlans_count }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + VLANs +
    + {% include 'inc/table.html' with table=vlans_table %} + {% if perms.ipam.add_vlan %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=vlans_table.paginator page=vlans_table.page %} +
    +
    +{% endblock %} + diff --git a/netbox/templates/ipam/vlangroup_vlans.html b/netbox/templates/ipam/vlangroup_vlans.html deleted file mode 100644 index ffb75d3b8..000000000 --- a/netbox/templates/ipam/vlangroup_vlans.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}{{ object }} - VLANs{% endblock %} - -{% block content %} -
    -
    - -
    -
    - {% include 'ipam/inc/vlangroup_header.html' %} -
    -
    - {% include 'utilities/obj_table.html' with table=vlan_table table_template='panel_table.html' heading='VLANs' bulk_edit_url='ipam:vlan_bulk_edit' bulk_delete_url='ipam:vlan_bulk_delete' %} -
    -
    -{% endblock %} - diff --git a/netbox/templates/secrets/secretrole.html b/netbox/templates/secrets/secretrole.html new file mode 100644 index 000000000..0e284fb75 --- /dev/null +++ b/netbox/templates/secrets/secretrole.html @@ -0,0 +1,60 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Secret Roles
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Secret Role +
    + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Secrets + {{ secrets_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Secrets +
    + {% include 'inc/table.html' with table=secrets_table %} + {% if perms.secrets.add_secret %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=secrets_table.paginator page=secrets_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/virtualization/clustergroup.html b/netbox/templates/virtualization/clustergroup.html new file mode 100644 index 000000000..9b2085189 --- /dev/null +++ b/netbox/templates/virtualization/clustergroup.html @@ -0,0 +1,60 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Cluster Groups
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Cluster Group +
    + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Clusters + {{ clusters_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Clusters +
    + {% include 'inc/table.html' with table=clusters_table %} + {% if perms.virtualization.add_cluster %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=clusters_table.paginator page=clusters_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/virtualization/clustertype.html b/netbox/templates/virtualization/clustertype.html new file mode 100644 index 000000000..d36934a74 --- /dev/null +++ b/netbox/templates/virtualization/clustertype.html @@ -0,0 +1,60 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Cluster Types
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Cluster Type +
    + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Clusters + {{ clusters_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Clusters +
    + {% include 'inc/table.html' with table=clusters_table %} + {% if perms.virtualization.add_cluster %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=clusters_table.paginator page=clusters_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/virtualization/models.py b/netbox/virtualization/models.py index e26fc8fc9..a34d09662 100644 --- a/netbox/virtualization/models.py +++ b/netbox/virtualization/models.py @@ -59,7 +59,7 @@ class ClusterType(OrganizationalModel): return self.name def get_absolute_url(self): - return "{}?type={}".format(reverse('virtualization:cluster_list'), self.slug) + return reverse('virtualization:clustertype', args=[self.pk]) def to_csv(self): return ( @@ -102,7 +102,7 @@ class ClusterGroup(OrganizationalModel): return self.name def get_absolute_url(self): - return "{}?group={}".format(reverse('virtualization:cluster_list'), self.slug) + return reverse('virtualization:clustergroup', args=[self.pk]) def to_csv(self): return ( diff --git a/netbox/virtualization/urls.py b/netbox/virtualization/urls.py index a036ab0bd..17750e806 100644 --- a/netbox/virtualization/urls.py +++ b/netbox/virtualization/urls.py @@ -14,6 +14,7 @@ urlpatterns = [ path('cluster-types/import/', views.ClusterTypeBulkImportView.as_view(), name='clustertype_import'), path('cluster-types/edit/', views.ClusterTypeBulkEditView.as_view(), name='clustertype_bulk_edit'), path('cluster-types/delete/', views.ClusterTypeBulkDeleteView.as_view(), name='clustertype_bulk_delete'), + path('cluster-types//', views.ClusterTypeView.as_view(), name='clustertype'), path('cluster-types//edit/', views.ClusterTypeEditView.as_view(), name='clustertype_edit'), path('cluster-types//delete/', views.ClusterTypeDeleteView.as_view(), name='clustertype_delete'), path('cluster-types//changelog/', ObjectChangeLogView.as_view(), name='clustertype_changelog', kwargs={'model': ClusterType}), @@ -24,6 +25,7 @@ urlpatterns = [ path('cluster-groups/import/', views.ClusterGroupBulkImportView.as_view(), name='clustergroup_import'), path('cluster-groups/edit/', views.ClusterGroupBulkEditView.as_view(), name='clustergroup_bulk_edit'), path('cluster-groups/delete/', views.ClusterGroupBulkDeleteView.as_view(), name='clustergroup_bulk_delete'), + path('cluster-groups//', views.ClusterGroupView.as_view(), name='clustergroup'), path('cluster-groups//edit/', views.ClusterGroupEditView.as_view(), name='clustergroup_edit'), path('cluster-groups//delete/', views.ClusterGroupDeleteView.as_view(), name='clustergroup_delete'), path('cluster-groups//changelog/', ObjectChangeLogView.as_view(), name='clustergroup_changelog', kwargs={'model': ClusterGroup}), diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index d0bb0cf76..a874d2a92 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -11,6 +11,7 @@ from ipam.models import IPAddress, Service from ipam.tables import InterfaceIPAddressTable, InterfaceVLANTable from netbox.views import generic from secrets.models import Secret +from utilities.tables import paginate_table from utilities.utils import count_related from . import filters, forms, tables from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface @@ -27,6 +28,23 @@ class ClusterTypeListView(generic.ObjectListView): table = tables.ClusterTypeTable +class ClusterTypeView(generic.ObjectView): + queryset = ClusterType.objects.all() + + def get_extra_context(self, request, instance): + clusters = Cluster.objects.restrict(request.user, 'view').filter( + type=instance + ) + + clusters_table = tables.ClusterTable(clusters) + clusters_table.columns.hide('type') + paginate_table(clusters_table, request) + + return { + 'clusters_table': clusters_table, + } + + class ClusterTypeEditView(generic.ObjectEditView): queryset = ClusterType.objects.all() model_form = forms.ClusterTypeForm @@ -69,6 +87,23 @@ class ClusterGroupListView(generic.ObjectListView): table = tables.ClusterGroupTable +class ClusterGroupView(generic.ObjectView): + queryset = ClusterGroup.objects.all() + + def get_extra_context(self, request, instance): + clusters = Cluster.objects.restrict(request.user, 'view').filter( + group=instance + ) + + clusters_table = tables.ClusterTable(clusters) + clusters_table.columns.hide('group') + paginate_table(clusters_table, request) + + return { + 'clusters_table': clusters_table, + } + + class ClusterGroupEditView(generic.ObjectEditView): queryset = ClusterGroup.objects.all() model_form = forms.ClusterGroupForm