diff --git a/docs/release-notes/version-3.1.md b/docs/release-notes/version-3.1.md index b117c4b1e..a09b43400 100644 --- a/docs/release-notes/version-3.1.md +++ b/docs/release-notes/version-3.1.md @@ -2,6 +2,11 @@ ## v3.1.3 (FUTURE) +### Bug Fixes + +* [#7962](https://github.com/netbox-community/netbox/issues/7962) - Fix user menu under report/script result view +* [#8131](https://github.com/netbox-community/netbox/issues/8131) - Restore annotation of available IPs under prefix IPs view + --- ## v3.1.2 (2021-12-20) diff --git a/netbox/dcim/models/__init__.py b/netbox/dcim/models/__init__.py index 8d4b1dce6..d74f34828 100644 --- a/netbox/dcim/models/__init__.py +++ b/netbox/dcim/models/__init__.py @@ -5,46 +5,3 @@ from .devices import * from .power import * from .racks import * from .sites import * - -__all__ = ( - 'BaseInterface', - 'Cable', - 'CablePath', - 'LinkTermination', - 'ConsolePort', - 'ConsolePortTemplate', - 'ConsoleServerPort', - 'ConsoleServerPortTemplate', - 'Device', - 'DeviceBay', - 'DeviceBayTemplate', - 'DeviceRole', - 'DeviceType', - 'FrontPort', - 'FrontPortTemplate', - 'Interface', - 'InterfaceTemplate', - 'InventoryItem', - 'Location', - 'Manufacturer', - 'Module', - 'ModuleBay', - 'ModuleBayTemplate', - 'ModuleType', - 'Platform', - 'PowerFeed', - 'PowerOutlet', - 'PowerOutletTemplate', - 'PowerPanel', - 'PowerPort', - 'PowerPortTemplate', - 'Rack', - 'RackReservation', - 'RackRole', - 'RearPort', - 'RearPortTemplate', - 'Region', - 'Site', - 'SiteGroup', - 'VirtualChassis', -) diff --git a/netbox/dcim/tables/devicetypes.py b/netbox/dcim/tables/devicetypes.py index 38a33c652..ad4c4d844 100644 --- a/netbox/dcim/tables/devicetypes.py +++ b/netbox/dcim/tables/devicetypes.py @@ -112,8 +112,7 @@ class ComponentTemplateTable(BaseTable): class ConsolePortTemplateTable(ComponentTemplateTable): actions = ButtonsColumn( model=ConsolePortTemplate, - buttons=('edit', 'delete'), - return_url_extra='%23tab_consoleports' + buttons=('edit', 'delete') ) class Meta(ComponentTemplateTable.Meta): @@ -125,8 +124,7 @@ class ConsolePortTemplateTable(ComponentTemplateTable): class ConsoleServerPortTemplateTable(ComponentTemplateTable): actions = ButtonsColumn( model=ConsoleServerPortTemplate, - buttons=('edit', 'delete'), - return_url_extra='%23tab_consoleserverports' + buttons=('edit', 'delete') ) class Meta(ComponentTemplateTable.Meta): @@ -138,8 +136,7 @@ class ConsoleServerPortTemplateTable(ComponentTemplateTable): class PowerPortTemplateTable(ComponentTemplateTable): actions = ButtonsColumn( model=PowerPortTemplate, - buttons=('edit', 'delete'), - return_url_extra='%23tab_powerports' + buttons=('edit', 'delete') ) class Meta(ComponentTemplateTable.Meta): @@ -151,8 +148,7 @@ class PowerPortTemplateTable(ComponentTemplateTable): class PowerOutletTemplateTable(ComponentTemplateTable): actions = ButtonsColumn( model=PowerOutletTemplate, - buttons=('edit', 'delete'), - return_url_extra='%23tab_poweroutlets' + buttons=('edit', 'delete') ) class Meta(ComponentTemplateTable.Meta): @@ -167,8 +163,7 @@ class InterfaceTemplateTable(ComponentTemplateTable): ) actions = ButtonsColumn( model=InterfaceTemplate, - buttons=('edit', 'delete'), - return_url_extra='%23tab_interfaces' + buttons=('edit', 'delete') ) class Meta(ComponentTemplateTable.Meta): @@ -184,8 +179,7 @@ class FrontPortTemplateTable(ComponentTemplateTable): color = ColorColumn() actions = ButtonsColumn( model=FrontPortTemplate, - buttons=('edit', 'delete'), - return_url_extra='%23tab_frontports' + buttons=('edit', 'delete') ) class Meta(ComponentTemplateTable.Meta): @@ -198,8 +192,7 @@ class RearPortTemplateTable(ComponentTemplateTable): color = ColorColumn() actions = ButtonsColumn( model=RearPortTemplate, - buttons=('edit', 'delete'), - return_url_extra='%23tab_rearports' + buttons=('edit', 'delete') ) class Meta(ComponentTemplateTable.Meta): @@ -211,8 +204,7 @@ class RearPortTemplateTable(ComponentTemplateTable): class ModuleBayTemplateTable(ComponentTemplateTable): actions = ButtonsColumn( model=ModuleBayTemplate, - buttons=('edit', 'delete'), - return_url_extra='%23tab_modulebays' + buttons=('edit', 'delete') ) class Meta(ComponentTemplateTable.Meta): @@ -224,8 +216,7 @@ class ModuleBayTemplateTable(ComponentTemplateTable): class DeviceBayTemplateTable(ComponentTemplateTable): actions = ButtonsColumn( model=DeviceBayTemplate, - buttons=('edit', 'delete'), - return_url_extra='%23tab_devicebays' + buttons=('edit', 'delete') ) class Meta(ComponentTemplateTable.Meta): diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index a7f1e2bca..5aff57a4e 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -27,13 +27,7 @@ from virtualization.models import VirtualMachine from . import filtersets, forms, tables from .choices import DeviceFaceChoices from .constants import NONCONNECTABLE_IFACE_TYPES -from .models import ( - Cable, CablePath, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, - DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, - InventoryItem, Manufacturer, Module, ModuleBay, ModuleBayTemplate, ModuleType, PathEndpoint, Platform, PowerFeed, - PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort, PowerPortTemplate, Rack, Location, RackReservation, - RackRole, RearPort, RearPortTemplate, Region, Site, SiteGroup, VirtualChassis, -) +from .models import * class DeviceComponentsView(generic.ObjectChildrenView): @@ -51,10 +45,21 @@ class DeviceComponentsView(generic.ObjectChildrenView): class DeviceTypeComponentsView(DeviceComponentsView): queryset = DeviceType.objects.all() template_name = 'dcim/devicetype/component_templates.html' + viewname = None # Used for return_url resolution def get_children(self, request, parent): return self.child_model.objects.restrict(request.user, 'view').filter(device_type=parent) + def get_extra_context(self, request, instance): + if self.viewname: + return_url = reverse(self.viewname, kwargs={'pk': instance.pk}) + else: + return_url = instance.get_absolute_url() + return { + 'active_tab': f"{self.child_model._meta.verbose_name_plural.replace(' ', '-')}", + 'return_url': return_url, + } + class ModuleTypeComponentsView(DeviceComponentsView): queryset = ModuleType.objects.all() @@ -806,42 +811,49 @@ class DeviceTypeConsolePortsView(DeviceTypeComponentsView): child_model = ConsolePortTemplate table = tables.ConsolePortTemplateTable filterset = filtersets.ConsolePortTemplateFilterSet + viewname = 'dcim:devicetype_consoleports' class DeviceTypeConsoleServerPortsView(DeviceTypeComponentsView): child_model = ConsoleServerPortTemplate table = tables.ConsoleServerPortTemplateTable filterset = filtersets.ConsoleServerPortTemplateFilterSet + viewname = 'dcim:devicetype_consoleserverports' class DeviceTypePowerPortsView(DeviceTypeComponentsView): child_model = PowerPortTemplate table = tables.PowerPortTemplateTable filterset = filtersets.PowerPortTemplateFilterSet + viewname = 'dcim:devicetype_powerports' class DeviceTypePowerOutletsView(DeviceTypeComponentsView): child_model = PowerOutletTemplate table = tables.PowerOutletTemplateTable filterset = filtersets.PowerOutletTemplateFilterSet + viewname = 'dcim:devicetype_poweroutlets' class DeviceTypeInterfacesView(DeviceTypeComponentsView): child_model = InterfaceTemplate table = tables.InterfaceTemplateTable filterset = filtersets.InterfaceTemplateFilterSet + viewname = 'dcim:devicetype_interfaces' class DeviceTypeFrontPortsView(DeviceTypeComponentsView): child_model = FrontPortTemplate table = tables.FrontPortTemplateTable filterset = filtersets.FrontPortTemplateFilterSet + viewname = 'dcim:devicetype_frontports' class DeviceTypeRearPortsView(DeviceTypeComponentsView): child_model = RearPortTemplate table = tables.RearPortTemplateTable filterset = filtersets.RearPortTemplateFilterSet + viewname = 'dcim:devicetype_rearports' class DeviceTypeModuleBaysView(DeviceTypeComponentsView): @@ -854,6 +866,7 @@ class DeviceTypeDeviceBaysView(DeviceTypeComponentsView): child_model = DeviceBayTemplate table = tables.DeviceBayTemplateTable filterset = filtersets.DeviceBayTemplateFilterSet + viewname = 'dcim:devicetype_devicebays' class DeviceTypeEditView(generic.ObjectEditView): diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index 1be187596..9e4665cc2 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -5,10 +5,10 @@ from drf_yasg.utils import swagger_serializer_method from rest_framework import serializers from dcim.api.nested_serializers import ( - NestedDeviceSerializer, NestedDeviceRoleSerializer, NestedDeviceTypeSerializer, NestedPlatformSerializer, - NestedRackSerializer, NestedRegionSerializer, NestedSiteSerializer, NestedSiteGroupSerializer, + NestedDeviceRoleSerializer, NestedDeviceTypeSerializer, NestedPlatformSerializer, NestedRegionSerializer, + NestedSiteSerializer, NestedSiteGroupSerializer, ) -from dcim.models import Device, DeviceRole, DeviceType, Platform, Rack, Region, Site, SiteGroup +from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup from extras.choices import * from extras.models import * from extras.utils import FeatureQuery diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 4382d1fbb..a2bc92f88 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -10,6 +10,7 @@ from rq import Worker from netbox.views import generic from utilities.forms import ConfirmationForm +from utilities.htmx import is_htmx from utilities.tables import paginate_table from utilities.utils import copy_safe_request, count_related, normalize_querydict, shallow_compare_dict from utilities.views import ContentTypePermissionRequiredMixin @@ -693,16 +694,26 @@ class ReportResultView(ContentTypePermissionRequiredMixin, View): def get(self, request, job_result_pk): report_content_type = ContentType.objects.get(app_label='extras', model='report') - jobresult = get_object_or_404(JobResult.objects.all(), pk=job_result_pk, obj_type=report_content_type) + result = get_object_or_404(JobResult.objects.all(), pk=job_result_pk, obj_type=report_content_type) # Retrieve the Report and attach the JobResult to it - module, report_name = jobresult.name.split('.') + module, report_name = result.name.split('.') report = get_report(module, report_name) - report.result = jobresult + report.result = result + + # If this is an HTMX request, return only the result HTML + if is_htmx(request): + response = render(request, 'extras/htmx/report_result.html', { + 'report': report, + 'result': result, + }) + if result.completed: + response.status_code = 286 + return response return render(request, 'extras/report_result.html', { 'report': report, - 'result': jobresult, + 'result': result, }) @@ -820,6 +831,16 @@ class ScriptResultView(ContentTypePermissionRequiredMixin, GetScriptMixin, View) script = self._get_script(result.name) + # If this is an HTMX request, return only the result HTML + if is_htmx(request): + response = render(request, 'extras/htmx/script_result.html', { + 'script': script, + 'result': result, + }) + if result.completed: + response.status_code = 286 + return response + return render(request, 'extras/script_result.html', { 'script': script, 'result': result, diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index ab1cdec5b..4c93ee982 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -5,18 +5,18 @@ from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from dcim.filtersets import InterfaceFilterSet -from dcim.models import Device, Interface, Site +from dcim.models import Interface, Site from dcim.tables import SiteTable from netbox.views import generic from utilities.tables import paginate_table from utilities.utils import count_related from virtualization.filtersets import VMInterfaceFilterSet -from virtualization.models import VirtualMachine, VMInterface +from virtualization.models import VMInterface from . import filtersets, forms, tables from .constants import * from .models import * from .models import ASN -from .utils import add_requested_prefixes, add_available_vlans +from .utils import add_requested_prefixes, add_available_ipaddresses, add_available_vlans # @@ -502,6 +502,13 @@ class PrefixIPAddressesView(generic.ObjectChildrenView): def get_children(self, request, parent): return parent.get_child_ips().restrict(request.user, 'view') + def prep_table_data(self, request, queryset, parent): + show_available = bool(request.GET.get('show_available', 'true') == 'true') + if show_available: + return add_available_ipaddresses(parent.prefix, queryset, parent.is_pool) + + return queryset + def get_extra_context(self, request, instance): return { 'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&parent={instance.prefix}", diff --git a/netbox/project-static/bundle.js b/netbox/project-static/bundle.js index 100b70ac8..76a1581ad 100644 --- a/netbox/project-static/bundle.js +++ b/netbox/project-static/bundle.js @@ -40,7 +40,6 @@ async function bundleGraphIQL() { async function bundleNetBox() { const entryPoints = { netbox: 'src/index.ts', - jobs: 'src/jobs.ts', lldp: 'src/device/lldp.ts', config: 'src/device/config.ts', status: 'src/device/status.ts', diff --git a/netbox/project-static/dist/jobs.js b/netbox/project-static/dist/jobs.js deleted file mode 100644 index 2aedf1219..000000000 Binary files a/netbox/project-static/dist/jobs.js and /dev/null differ diff --git a/netbox/project-static/dist/jobs.js.map b/netbox/project-static/dist/jobs.js.map deleted file mode 100644 index d7c1dbcbf..000000000 Binary files a/netbox/project-static/dist/jobs.js.map and /dev/null differ diff --git a/netbox/project-static/src/global.d.ts b/netbox/project-static/src/global.d.ts index bad12c795..89c106e9c 100644 --- a/netbox/project-static/src/global.d.ts +++ b/netbox/project-static/src/global.d.ts @@ -98,38 +98,6 @@ type APISecret = { url: string; }; -type JobResultLog = { - message: string; - status: 'success' | 'warning' | 'danger' | 'info'; -}; - -type JobStatus = { - label: string; - value: 'completed' | 'failed' | 'errored' | 'running'; -}; - -type APIJobResult = { - completed: string; - created: string; - data: { - log: JobResultLog[]; - output: string; - }; - display: string; - id: number; - job_id: string; - name: string; - obj_type: string; - status: JobStatus; - url: string; - user: { - display: string; - username: string; - id: number; - url: string; - }; -}; - type APIUserConfig = { tables: { [k: string]: { columns: string[]; available_columns: string[] } }; [k: string]: unknown; diff --git a/netbox/project-static/src/jobs.ts b/netbox/project-static/src/jobs.ts deleted file mode 100644 index dedf0706d..000000000 --- a/netbox/project-static/src/jobs.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { createToast } from './bs'; -import { apiGetBase, hasError, getNetboxData } from './util'; - -let timeout: number = 1000; - -interface JobInfo { - url: Nullable; - complete: boolean; -} - -/** - * Mimic the behavior of setTimeout() in an async function. - */ -function asyncTimeout(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -/** - * Job ID & Completion state are only from Django context, which can only be used from the HTML - * template. Hidden elements are present in the template to provide access to these values from - * JavaScript. - */ -function getJobInfo(): JobInfo { - let complete = false; - - // Determine the API URL for the job status - const url = getNetboxData('data-job-url'); - - // Determine the job completion status, if present. If the job is not complete, the value will be - // "None". Otherwise, it will be a stringified date. - const jobComplete = getNetboxData('data-job-complete'); - if (typeof jobComplete === 'string' && jobComplete.toLowerCase() !== 'none') { - complete = true; - } - return { url, complete }; -} - -/** - * Update the job status label element based on the API response. - */ -function updateLabel(status: JobStatus) { - const element = document.querySelector('#pending-result-label > span.badge'); - if (element !== null) { - let labelClass = 'secondary'; - switch (status.value) { - case 'failed' || 'errored': - labelClass = 'danger'; - break; - case 'running': - labelClass = 'warning'; - break; - case 'completed': - labelClass = 'success'; - break; - } - element.setAttribute('class', `badge bg-${labelClass}`); - element.innerText = status.label; - } -} - -/** - * Recursively check the job's status. - * @param url API URL for job result - */ -async function checkJobStatus(url: string) { - const res = await apiGetBase(url); - if (hasError(res)) { - // If the response is an API error, display an error message and stop checking for job status. - const toast = createToast('danger', 'Error', res.error); - toast.show(); - return; - } else { - // Update the job status label. - updateLabel(res.status); - - // If the job is complete, reload the page. - if (['completed', 'failed', 'errored'].includes(res.status.value)) { - location.reload(); - return; - } else { - // Otherwise, keep checking the job's status, backing off 1 second each time, until a 10 - // second interval is reached. - if (timeout < 10000) { - timeout += 1000; - } - await Promise.all([checkJobStatus(url), asyncTimeout(timeout)]); - } - } -} - -function initJobs() { - const { url, complete } = getJobInfo(); - - if (url !== null && !complete) { - // If there is a job ID and it is not completed, check for the job's status. - Promise.resolve(checkJobStatus(url)); - } -} - -if (document.readyState !== 'loading') { - initJobs(); -} else { - document.addEventListener('DOMContentLoaded', initJobs); -} diff --git a/netbox/templates/dcim/device/base.html b/netbox/templates/dcim/device/base.html index d9ff0657c..4d24717cd 100644 --- a/netbox/templates/dcim/device/base.html +++ b/netbox/templates/dcim/device/base.html @@ -102,82 +102,83 @@ - {% with devicebay_count=object.devicebays.count %} - {% if devicebay_count %} + {% with tab_name='device-bays' devicebay_count=object.devicebays.count %} + {% if active_tab == tab_name or devicebay_count %} {% endif %} {% endwith %} - {% with modulebay_count=object.modulebays.count %} - {% if modulebay_count %} + {% with tab_name='module-bays' modulebay_count=object.modulebays.count %} + {% if active_tab == tab_name or modulebay_count %} {% endif %} {% endwith %} - {% with interface_count=object.interfaces_count %} - {% if interface_count %} + {% with tab_name='interfaces' interface_count=object.interfaces_count %} + {% if active_tab == tab_name or interface_count %} {% endif %} {% endwith %} - {% with frontport_count=object.frontports.count %} - {% if frontport_count %} + {% with tab_name='front-ports' frontport_count=object.frontports.count %} + {% if active_tab == tab_name or frontport_count %} {% endif %} {% endwith %} - {% with rearport_count=object.rearports.count %} - {% if rearport_count %} + {% with tab_name='rear-ports' rearport_count=object.rearports.count %} + {% if active_tab == tab_name or rearport_count %} {% endif %} {% endwith %} - {% with consoleport_count=object.consoleports.count %} - {% if consoleport_count %} + {% with tab_name='console-ports' consoleport_count=object.consoleports.count %} + {% if active_tab == tab_name or consoleport_count %} {% endif %} {% endwith %} - {% with consoleserverport_count=object.consoleserverports.count %} - {% if consoleserverport_count %} + {% with tab_name='console-server-ports' consoleserverport_count=object.consoleserverports.count %} + {% if active_tab == tab_name or consoleserverport_count %} {% endif %} {% endwith %} - {% with powerport_count=object.powerports.count %} - {% if powerport_count %} + {% with tab_name='power-ports' powerport_count=object.powerports.count %} + {% if active_tab == tab_name or powerport_count %} {% endif %} {% endwith %} - {% with poweroutlet_count=object.poweroutlets.count %} - {% if poweroutlet_count %} + {% with tab_name='power-outlets' poweroutlet_count=object.poweroutlets.count %} + {% if active_tab == tab_name or poweroutlet_count %} {% endif %} {% endwith %} - {% with inventoryitem_count=object.inventoryitems.count %} - {% if inventoryitem_count %} + + {% with tab_name='inventory-items' inventoryitem_count=object.inventoryitems.count %} + {% if active_tab == tab_name or inventoryitem_count %} {% endif %} {% endwith %} diff --git a/netbox/templates/dcim/device/devicebays.html b/netbox/templates/dcim/device/devicebays.html index 2d66e860d..6ab387513 100644 --- a/netbox/templates/dcim/device/devicebays.html +++ b/netbox/templates/dcim/device/devicebays.html @@ -17,22 +17,22 @@
{% if perms.dcim.change_devicebay %} - - {% endif %} {% if perms.dcim.delete_devicebay %} - {% endif %}
{% if perms.dcim.add_devicebay %} diff --git a/netbox/templates/dcim/device/modulebays.html b/netbox/templates/dcim/device/modulebays.html index b386aae04..e9c672b57 100644 --- a/netbox/templates/dcim/device/modulebays.html +++ b/netbox/templates/dcim/device/modulebays.html @@ -17,22 +17,22 @@
{% if perms.dcim.change_modulebay %} - - {% endif %} {% if perms.dcim.delete_modulebay %} - {% endif %}
{% if perms.dcim.add_modulebay %} diff --git a/netbox/templates/dcim/devicetype/base.html b/netbox/templates/dcim/devicetype/base.html index 2c1e8a537..9c0b08c19 100644 --- a/netbox/templates/dcim/devicetype/base.html +++ b/netbox/templates/dcim/devicetype/base.html @@ -18,31 +18,31 @@
@@ -56,74 +56,74 @@ - {% with devicebay_count=object.devicebaytemplates.count %} - {% if devicebay_count %} + {% with tab_name='device-bay-templates' devicebay_count=object.devicebaytemplates.count %} + {% if active_tab == tab_name or devicebay_count %} {% endif %} {% endwith %} - {% with modulebay_count=object.modulebaytemplates.count %} - {% if modulebay_count %} + {% with tab_name='module-bay-templates' modulebay_count=object.modulebaytemplates.count %} + {% if active_tab == tab_name or modulebay_count %} {% endif %} {% endwith %} - {% with interface_count=object.interfacetemplates.count %} - {% if interface_count %} + {% with tab_name='interface-templates' interface_count=object.interfacetemplates.count %} + {% if active_tab == tab_name or interface_count %} {% endif %} {% endwith %} - {% with frontport_count=object.frontporttemplates.count %} - {% if frontport_count %} + {% with tab_name='front-port-templates' frontport_count=object.frontporttemplates.count %} + {% if active_tab == tab_name or frontport_count %} {% endif %} {% endwith %} - {% with rearport_count=object.rearporttemplates.count %} - {% if rearport_count %} + {% with tab_name='rear-port-templates' rearport_count=object.rearporttemplates.count %} + {% if active_tab == tab_name or rearport_count %} {% endif %} {% endwith %} - {% with consoleport_count=object.consoleporttemplates.count %} - {% if consoleport_count %} + {% with tab_name='console-port-templates' consoleport_count=object.consoleporttemplates.count %} + {% if active_tab == tab_name or consoleport_count %} {% endif %} {% endwith %} - {% with consoleserverport_count=object.consoleserverporttemplates.count %} - {% if consoleserverport_count %} + {% with tab_name='console-server-port-templates' consoleserverport_count=object.consoleserverporttemplates.count %} + {% if active_tab == tab_name or consoleserverport_count %} {% endif %} {% endwith %} - {% with powerport_count=object.powerporttemplates.count %} - {% if powerport_count %} + {% with tab_name='power-port-templates' powerport_count=object.powerporttemplates.count %} + {% if active_tab == tab_name or powerport_count %} {% endif %} {% endwith %} - {% with poweroutlet_count=object.poweroutlettemplates.count %} - {% if poweroutlet_count %} + {% with tab_name='power-outlet-templates' poweroutlet_count=object.poweroutlettemplates.count %} + {% if active_tab == tab_name or poweroutlet_count %} {% endif %} {% endwith %} diff --git a/netbox/templates/dcim/devicetype/component_templates.html b/netbox/templates/dcim/devicetype/component_templates.html index b1e0daf78..002a2044b 100644 --- a/netbox/templates/dcim/devicetype/component_templates.html +++ b/netbox/templates/dcim/devicetype/component_templates.html @@ -13,18 +13,18 @@