mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-19 09:53:34 -06:00
Merge branch 'develop' into feature
This commit is contained in:
commit
5e32c69e0e
@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
## v3.1.3 (FUTURE)
|
## 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)
|
## v3.1.2 (2021-12-20)
|
||||||
|
@ -5,46 +5,3 @@ from .devices import *
|
|||||||
from .power import *
|
from .power import *
|
||||||
from .racks import *
|
from .racks import *
|
||||||
from .sites 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',
|
|
||||||
)
|
|
||||||
|
@ -112,8 +112,7 @@ class ComponentTemplateTable(BaseTable):
|
|||||||
class ConsolePortTemplateTable(ComponentTemplateTable):
|
class ConsolePortTemplateTable(ComponentTemplateTable):
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
model=ConsolePortTemplate,
|
model=ConsolePortTemplate,
|
||||||
buttons=('edit', 'delete'),
|
buttons=('edit', 'delete')
|
||||||
return_url_extra='%23tab_consoleports'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
@ -125,8 +124,7 @@ class ConsolePortTemplateTable(ComponentTemplateTable):
|
|||||||
class ConsoleServerPortTemplateTable(ComponentTemplateTable):
|
class ConsoleServerPortTemplateTable(ComponentTemplateTable):
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
model=ConsoleServerPortTemplate,
|
model=ConsoleServerPortTemplate,
|
||||||
buttons=('edit', 'delete'),
|
buttons=('edit', 'delete')
|
||||||
return_url_extra='%23tab_consoleserverports'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
@ -138,8 +136,7 @@ class ConsoleServerPortTemplateTable(ComponentTemplateTable):
|
|||||||
class PowerPortTemplateTable(ComponentTemplateTable):
|
class PowerPortTemplateTable(ComponentTemplateTable):
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
model=PowerPortTemplate,
|
model=PowerPortTemplate,
|
||||||
buttons=('edit', 'delete'),
|
buttons=('edit', 'delete')
|
||||||
return_url_extra='%23tab_powerports'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
@ -151,8 +148,7 @@ class PowerPortTemplateTable(ComponentTemplateTable):
|
|||||||
class PowerOutletTemplateTable(ComponentTemplateTable):
|
class PowerOutletTemplateTable(ComponentTemplateTable):
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
model=PowerOutletTemplate,
|
model=PowerOutletTemplate,
|
||||||
buttons=('edit', 'delete'),
|
buttons=('edit', 'delete')
|
||||||
return_url_extra='%23tab_poweroutlets'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
@ -167,8 +163,7 @@ class InterfaceTemplateTable(ComponentTemplateTable):
|
|||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
model=InterfaceTemplate,
|
model=InterfaceTemplate,
|
||||||
buttons=('edit', 'delete'),
|
buttons=('edit', 'delete')
|
||||||
return_url_extra='%23tab_interfaces'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
@ -184,8 +179,7 @@ class FrontPortTemplateTable(ComponentTemplateTable):
|
|||||||
color = ColorColumn()
|
color = ColorColumn()
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
model=FrontPortTemplate,
|
model=FrontPortTemplate,
|
||||||
buttons=('edit', 'delete'),
|
buttons=('edit', 'delete')
|
||||||
return_url_extra='%23tab_frontports'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
@ -198,8 +192,7 @@ class RearPortTemplateTable(ComponentTemplateTable):
|
|||||||
color = ColorColumn()
|
color = ColorColumn()
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
model=RearPortTemplate,
|
model=RearPortTemplate,
|
||||||
buttons=('edit', 'delete'),
|
buttons=('edit', 'delete')
|
||||||
return_url_extra='%23tab_rearports'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
@ -211,8 +204,7 @@ class RearPortTemplateTable(ComponentTemplateTable):
|
|||||||
class ModuleBayTemplateTable(ComponentTemplateTable):
|
class ModuleBayTemplateTable(ComponentTemplateTable):
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
model=ModuleBayTemplate,
|
model=ModuleBayTemplate,
|
||||||
buttons=('edit', 'delete'),
|
buttons=('edit', 'delete')
|
||||||
return_url_extra='%23tab_modulebays'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
@ -224,8 +216,7 @@ class ModuleBayTemplateTable(ComponentTemplateTable):
|
|||||||
class DeviceBayTemplateTable(ComponentTemplateTable):
|
class DeviceBayTemplateTable(ComponentTemplateTable):
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
model=DeviceBayTemplate,
|
model=DeviceBayTemplate,
|
||||||
buttons=('edit', 'delete'),
|
buttons=('edit', 'delete')
|
||||||
return_url_extra='%23tab_devicebays'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
|
@ -27,13 +27,7 @@ from virtualization.models import VirtualMachine
|
|||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .choices import DeviceFaceChoices
|
from .choices import DeviceFaceChoices
|
||||||
from .constants import NONCONNECTABLE_IFACE_TYPES
|
from .constants import NONCONNECTABLE_IFACE_TYPES
|
||||||
from .models import (
|
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceComponentsView(generic.ObjectChildrenView):
|
class DeviceComponentsView(generic.ObjectChildrenView):
|
||||||
@ -51,10 +45,21 @@ class DeviceComponentsView(generic.ObjectChildrenView):
|
|||||||
class DeviceTypeComponentsView(DeviceComponentsView):
|
class DeviceTypeComponentsView(DeviceComponentsView):
|
||||||
queryset = DeviceType.objects.all()
|
queryset = DeviceType.objects.all()
|
||||||
template_name = 'dcim/devicetype/component_templates.html'
|
template_name = 'dcim/devicetype/component_templates.html'
|
||||||
|
viewname = None # Used for return_url resolution
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
return self.child_model.objects.restrict(request.user, 'view').filter(device_type=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):
|
class ModuleTypeComponentsView(DeviceComponentsView):
|
||||||
queryset = ModuleType.objects.all()
|
queryset = ModuleType.objects.all()
|
||||||
@ -806,42 +811,49 @@ class DeviceTypeConsolePortsView(DeviceTypeComponentsView):
|
|||||||
child_model = ConsolePortTemplate
|
child_model = ConsolePortTemplate
|
||||||
table = tables.ConsolePortTemplateTable
|
table = tables.ConsolePortTemplateTable
|
||||||
filterset = filtersets.ConsolePortTemplateFilterSet
|
filterset = filtersets.ConsolePortTemplateFilterSet
|
||||||
|
viewname = 'dcim:devicetype_consoleports'
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeConsoleServerPortsView(DeviceTypeComponentsView):
|
class DeviceTypeConsoleServerPortsView(DeviceTypeComponentsView):
|
||||||
child_model = ConsoleServerPortTemplate
|
child_model = ConsoleServerPortTemplate
|
||||||
table = tables.ConsoleServerPortTemplateTable
|
table = tables.ConsoleServerPortTemplateTable
|
||||||
filterset = filtersets.ConsoleServerPortTemplateFilterSet
|
filterset = filtersets.ConsoleServerPortTemplateFilterSet
|
||||||
|
viewname = 'dcim:devicetype_consoleserverports'
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypePowerPortsView(DeviceTypeComponentsView):
|
class DeviceTypePowerPortsView(DeviceTypeComponentsView):
|
||||||
child_model = PowerPortTemplate
|
child_model = PowerPortTemplate
|
||||||
table = tables.PowerPortTemplateTable
|
table = tables.PowerPortTemplateTable
|
||||||
filterset = filtersets.PowerPortTemplateFilterSet
|
filterset = filtersets.PowerPortTemplateFilterSet
|
||||||
|
viewname = 'dcim:devicetype_powerports'
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypePowerOutletsView(DeviceTypeComponentsView):
|
class DeviceTypePowerOutletsView(DeviceTypeComponentsView):
|
||||||
child_model = PowerOutletTemplate
|
child_model = PowerOutletTemplate
|
||||||
table = tables.PowerOutletTemplateTable
|
table = tables.PowerOutletTemplateTable
|
||||||
filterset = filtersets.PowerOutletTemplateFilterSet
|
filterset = filtersets.PowerOutletTemplateFilterSet
|
||||||
|
viewname = 'dcim:devicetype_poweroutlets'
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeInterfacesView(DeviceTypeComponentsView):
|
class DeviceTypeInterfacesView(DeviceTypeComponentsView):
|
||||||
child_model = InterfaceTemplate
|
child_model = InterfaceTemplate
|
||||||
table = tables.InterfaceTemplateTable
|
table = tables.InterfaceTemplateTable
|
||||||
filterset = filtersets.InterfaceTemplateFilterSet
|
filterset = filtersets.InterfaceTemplateFilterSet
|
||||||
|
viewname = 'dcim:devicetype_interfaces'
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeFrontPortsView(DeviceTypeComponentsView):
|
class DeviceTypeFrontPortsView(DeviceTypeComponentsView):
|
||||||
child_model = FrontPortTemplate
|
child_model = FrontPortTemplate
|
||||||
table = tables.FrontPortTemplateTable
|
table = tables.FrontPortTemplateTable
|
||||||
filterset = filtersets.FrontPortTemplateFilterSet
|
filterset = filtersets.FrontPortTemplateFilterSet
|
||||||
|
viewname = 'dcim:devicetype_frontports'
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeRearPortsView(DeviceTypeComponentsView):
|
class DeviceTypeRearPortsView(DeviceTypeComponentsView):
|
||||||
child_model = RearPortTemplate
|
child_model = RearPortTemplate
|
||||||
table = tables.RearPortTemplateTable
|
table = tables.RearPortTemplateTable
|
||||||
filterset = filtersets.RearPortTemplateFilterSet
|
filterset = filtersets.RearPortTemplateFilterSet
|
||||||
|
viewname = 'dcim:devicetype_rearports'
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeModuleBaysView(DeviceTypeComponentsView):
|
class DeviceTypeModuleBaysView(DeviceTypeComponentsView):
|
||||||
@ -854,6 +866,7 @@ class DeviceTypeDeviceBaysView(DeviceTypeComponentsView):
|
|||||||
child_model = DeviceBayTemplate
|
child_model = DeviceBayTemplate
|
||||||
table = tables.DeviceBayTemplateTable
|
table = tables.DeviceBayTemplateTable
|
||||||
filterset = filtersets.DeviceBayTemplateFilterSet
|
filterset = filtersets.DeviceBayTemplateFilterSet
|
||||||
|
viewname = 'dcim:devicetype_devicebays'
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeEditView(generic.ObjectEditView):
|
class DeviceTypeEditView(generic.ObjectEditView):
|
||||||
|
@ -5,10 +5,10 @@ from drf_yasg.utils import swagger_serializer_method
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from dcim.api.nested_serializers import (
|
from dcim.api.nested_serializers import (
|
||||||
NestedDeviceSerializer, NestedDeviceRoleSerializer, NestedDeviceTypeSerializer, NestedPlatformSerializer,
|
NestedDeviceRoleSerializer, NestedDeviceTypeSerializer, NestedPlatformSerializer, NestedRegionSerializer,
|
||||||
NestedRackSerializer, NestedRegionSerializer, NestedSiteSerializer, NestedSiteGroupSerializer,
|
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.choices import *
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
from extras.utils import FeatureQuery
|
from extras.utils import FeatureQuery
|
||||||
|
@ -10,6 +10,7 @@ from rq import Worker
|
|||||||
|
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
|
from utilities.htmx import is_htmx
|
||||||
from utilities.tables import paginate_table
|
from utilities.tables import paginate_table
|
||||||
from utilities.utils import copy_safe_request, count_related, normalize_querydict, shallow_compare_dict
|
from utilities.utils import copy_safe_request, count_related, normalize_querydict, shallow_compare_dict
|
||||||
from utilities.views import ContentTypePermissionRequiredMixin
|
from utilities.views import ContentTypePermissionRequiredMixin
|
||||||
@ -693,16 +694,26 @@ class ReportResultView(ContentTypePermissionRequiredMixin, View):
|
|||||||
|
|
||||||
def get(self, request, job_result_pk):
|
def get(self, request, job_result_pk):
|
||||||
report_content_type = ContentType.objects.get(app_label='extras', model='report')
|
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
|
# 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 = 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', {
|
return render(request, 'extras/report_result.html', {
|
||||||
'report': report,
|
'report': report,
|
||||||
'result': jobresult,
|
'result': result,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -820,6 +831,16 @@ class ScriptResultView(ContentTypePermissionRequiredMixin, GetScriptMixin, View)
|
|||||||
|
|
||||||
script = self._get_script(result.name)
|
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', {
|
return render(request, 'extras/script_result.html', {
|
||||||
'script': script,
|
'script': script,
|
||||||
'result': result,
|
'result': result,
|
||||||
|
@ -5,18 +5,18 @@ 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.filtersets import InterfaceFilterSet
|
||||||
from dcim.models import Device, Interface, Site
|
from dcim.models import 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.filtersets import VMInterfaceFilterSet
|
||||||
from virtualization.models import VirtualMachine, VMInterface
|
from virtualization.models import 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_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):
|
def get_children(self, request, parent):
|
||||||
return parent.get_child_ips().restrict(request.user, 'view')
|
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):
|
def get_extra_context(self, request, instance):
|
||||||
return {
|
return {
|
||||||
'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&parent={instance.prefix}",
|
'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&parent={instance.prefix}",
|
||||||
|
@ -40,7 +40,6 @@ async function bundleGraphIQL() {
|
|||||||
async function bundleNetBox() {
|
async function bundleNetBox() {
|
||||||
const entryPoints = {
|
const entryPoints = {
|
||||||
netbox: 'src/index.ts',
|
netbox: 'src/index.ts',
|
||||||
jobs: 'src/jobs.ts',
|
|
||||||
lldp: 'src/device/lldp.ts',
|
lldp: 'src/device/lldp.ts',
|
||||||
config: 'src/device/config.ts',
|
config: 'src/device/config.ts',
|
||||||
status: 'src/device/status.ts',
|
status: 'src/device/status.ts',
|
||||||
|
BIN
netbox/project-static/dist/jobs.js
vendored
BIN
netbox/project-static/dist/jobs.js
vendored
Binary file not shown.
BIN
netbox/project-static/dist/jobs.js.map
vendored
BIN
netbox/project-static/dist/jobs.js.map
vendored
Binary file not shown.
32
netbox/project-static/src/global.d.ts
vendored
32
netbox/project-static/src/global.d.ts
vendored
@ -98,38 +98,6 @@ type APISecret = {
|
|||||||
url: string;
|
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 = {
|
type APIUserConfig = {
|
||||||
tables: { [k: string]: { columns: string[]; available_columns: string[] } };
|
tables: { [k: string]: { columns: string[]; available_columns: string[] } };
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
|
@ -1,104 +0,0 @@
|
|||||||
import { createToast } from './bs';
|
|
||||||
import { apiGetBase, hasError, getNetboxData } from './util';
|
|
||||||
|
|
||||||
let timeout: number = 1000;
|
|
||||||
|
|
||||||
interface JobInfo {
|
|
||||||
url: Nullable<string>;
|
|
||||||
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<HTMLSpanElement>('#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<APIJobResult>(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);
|
|
||||||
}
|
|
@ -102,82 +102,83 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{% with devicebay_count=object.devicebays.count %}
|
{% with tab_name='device-bays' devicebay_count=object.devicebays.count %}
|
||||||
{% if devicebay_count %}
|
{% if active_tab == tab_name or devicebay_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'device-bays' %} active{% endif %}" href="{% url 'dcim:device_devicebays' pk=object.pk %}">Device Bays {% badge devicebay_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_devicebays' pk=object.pk %}">Device Bays {% badge devicebay_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% with modulebay_count=object.modulebays.count %}
|
{% with tab_name='module-bays' modulebay_count=object.modulebays.count %}
|
||||||
{% if modulebay_count %}
|
{% if active_tab == tab_name or modulebay_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'module-bays' %} active{% endif %}" href="{% url 'dcim:device_modulebays' pk=object.pk %}">Module Bays {% badge modulebay_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_modulebays' pk=object.pk %}">Module Bays {% badge modulebay_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% with interface_count=object.interfaces_count %}
|
{% with tab_name='interfaces' interface_count=object.interfaces_count %}
|
||||||
{% if interface_count %}
|
{% if active_tab == tab_name or interface_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'interfaces' %} active{% endif %}" href="{% url 'dcim:device_interfaces' pk=object.pk %}">Interfaces {% badge interface_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_interfaces' pk=object.pk %}">Interfaces {% badge interface_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% with frontport_count=object.frontports.count %}
|
{% with tab_name='front-ports' frontport_count=object.frontports.count %}
|
||||||
{% if frontport_count %}
|
{% if active_tab == tab_name or frontport_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'front-ports' %} active{% endif %}" href="{% url 'dcim:device_frontports' pk=object.pk %}">Front Ports {% badge frontport_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_frontports' pk=object.pk %}">Front Ports {% badge frontport_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% with rearport_count=object.rearports.count %}
|
{% with tab_name='rear-ports' rearport_count=object.rearports.count %}
|
||||||
{% if rearport_count %}
|
{% if active_tab == tab_name or rearport_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'rear-ports' %} active{% endif %}" href="{% url 'dcim:device_rearports' pk=object.pk %}">Rear Ports {% badge rearport_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_rearports' pk=object.pk %}">Rear Ports {% badge rearport_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% with consoleport_count=object.consoleports.count %}
|
{% with tab_name='console-ports' consoleport_count=object.consoleports.count %}
|
||||||
{% if consoleport_count %}
|
{% if active_tab == tab_name or consoleport_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'console-ports' %} active{% endif %}" href="{% url 'dcim:device_consoleports' pk=object.pk %}">Console Ports {% badge consoleport_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_consoleports' pk=object.pk %}">Console Ports {% badge consoleport_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% with consoleserverport_count=object.consoleserverports.count %}
|
{% with tab_name='console-server-ports' consoleserverport_count=object.consoleserverports.count %}
|
||||||
{% if consoleserverport_count %}
|
{% if active_tab == tab_name or consoleserverport_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'console-server-ports' %} active{% endif %}" href="{% url 'dcim:device_consoleserverports' pk=object.pk %}">Console Server Ports {% badge consoleserverport_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_consoleserverports' pk=object.pk %}">Console Server Ports {% badge consoleserverport_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% with powerport_count=object.powerports.count %}
|
{% with tab_name='power-ports' powerport_count=object.powerports.count %}
|
||||||
{% if powerport_count %}
|
{% if active_tab == tab_name or powerport_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'power-ports' %} active{% endif %}" href="{% url 'dcim:device_powerports' pk=object.pk %}">Power Ports {% badge powerport_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_powerports' pk=object.pk %}">Power Ports {% badge powerport_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% with poweroutlet_count=object.poweroutlets.count %}
|
{% with tab_name='power-outlets' poweroutlet_count=object.poweroutlets.count %}
|
||||||
{% if poweroutlet_count %}
|
{% if active_tab == tab_name or poweroutlet_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'power-outlets' %} active{% endif %}" href="{% url 'dcim:device_poweroutlets' pk=object.pk %}">Power Outlets {% badge poweroutlet_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_poweroutlets' pk=object.pk %}">Power Outlets {% badge poweroutlet_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% 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 %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'inventory' %} active{% endif %}" href="{% url 'dcim:device_inventory' pk=object.pk %}">Inventory {% badge inventoryitem_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_inventory' pk=object.pk %}">Inventory {% badge inventoryitem_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
@ -17,22 +17,22 @@
|
|||||||
<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 %}
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={{ object.get_absolute_url }}%23tab_devicebays" class="btn btn-outline-warning btn-sm">
|
<button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:devicebay_bulk_edit' %}?device={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_devicebays" class="btn btn-warning btn-sm">
|
<button type="submit" name="_edit" formaction="{% url 'dcim:devicebay_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-warning btn-sm">
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_devicebay %}
|
{% if perms.dcim.delete_devicebay %}
|
||||||
<button type="submit" formaction="{% url 'dcim:devicebay_bulk_delete' %}?return_url={{ object.get_absolute_url }}%23tab_devicebays" class="btn btn-outline-danger btn-sm">
|
<button type="submit" formaction="{% url 'dcim:devicebay_bulk_delete' %}?return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete selected
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete selected
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if perms.dcim.add_devicebay %}
|
{% if perms.dcim.add_devicebay %}
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
<a href="{% url 'dcim:devicebay_add' %}?device={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_devicebays" class="btn btn-primary btn-sm">
|
<a href="{% url 'dcim:devicebay_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-primary btn-sm">
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Device Bays
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Device Bays
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,22 +17,22 @@
|
|||||||
<div class="noprint bulk-buttons">
|
<div class="noprint bulk-buttons">
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
{% if perms.dcim.change_modulebay %}
|
{% if perms.dcim.change_modulebay %}
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:modulebay_bulk_rename' %}?return_url={{ object.get_absolute_url }}%23tab_modulebays" class="btn btn-outline-warning btn-sm">
|
<button type="submit" name="_rename" formaction="{% url 'dcim:modulebay_bulk_rename' %}?return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:modulebay_bulk_edit' %}?device={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_modulebays" class="btn btn-warning btn-sm">
|
<button type="submit" name="_edit" formaction="{% url 'dcim:modulebay_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-warning btn-sm">
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_modulebay %}
|
{% if perms.dcim.delete_modulebay %}
|
||||||
<button type="submit" formaction="{% url 'dcim:modulebay_bulk_delete' %}?return_url={{ object.get_absolute_url }}%23tab_modulebays" class="btn btn-outline-danger btn-sm">
|
<button type="submit" formaction="{% url 'dcim:modulebay_bulk_delete' %}?return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete selected
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete selected
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if perms.dcim.add_modulebay %}
|
{% if perms.dcim.add_modulebay %}
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
<a href="{% url 'dcim:modulebay_add' %}?device={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_modulebays" class="btn btn-primary btn-sm">
|
<a href="{% url 'dcim:modulebay_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-primary btn-sm">
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Module Bays
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Module Bays
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,31 +18,31 @@
|
|||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
{% if perms.dcim.add_consoleporttemplate %}
|
{% if perms.dcim.add_consoleporttemplate %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:consoleporttemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_consoleports">Console Ports</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:consoleporttemplate_add' %}?device_type={{ object.pk }}&return_url={% url 'dcim:devicetype_consoleports' pk=object.pk %}">Console Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_consoleserverporttemplate %}
|
{% if perms.dcim.add_consoleserverporttemplate %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:consoleserverporttemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_consoleserverports">Console Server Ports</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:consoleserverporttemplate_add' %}?device_type={{ object.pk }}&return_url={% url 'dcim:devicetype_consoleserverports' pk=object.pk %}">Console Server Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_powerporttemplate %}
|
{% if perms.dcim.add_powerporttemplate %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:powerporttemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_powerports">Power Ports</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:powerporttemplate_add' %}?device_type={{ object.pk }}&return_url={% url 'dcim:devicetype_powerports' pk=object.pk %}">Power Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_poweroutlettemplate %}
|
{% if perms.dcim.add_poweroutlettemplate %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:poweroutlettemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_poweroutlets">Power Outlets</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:poweroutlettemplate_add' %}?device_type={{ object.pk }}&return_url={% url 'dcim:devicetype_poweroutlets' pk=object.pk %}">Power Outlets</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_interfacetemplate %}
|
{% if perms.dcim.add_interfacetemplate %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:interfacetemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_interfaces">Interfaces</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:interfacetemplate_add' %}?device_type={{ object.pk }}&return_url={% url 'dcim:devicetype_interfaces' pk=object.pk %}">Interfaces</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_frontporttemplate %}
|
{% if perms.dcim.add_frontporttemplate %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:frontporttemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_frontports">Front Ports</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:frontporttemplate_add' %}?device_type={{ object.pk }}&return_url={% url 'dcim:devicetype_frontports' pk=object.pk %}">Front Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_rearporttemplate %}
|
{% if perms.dcim.add_rearporttemplate %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:rearporttemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_rearports">Rear Ports</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:rearporttemplate_add' %}?device_type={{ object.pk }}&return_url={% url 'dcim:devicetype_rearports' pk=object.pk %}">Rear Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_modulebaytemplate %}
|
{% if perms.dcim.add_modulebaytemplate %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:modulebaytemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_modulebays">Module Bays</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:modulebaytemplate_add' %}?device_type={{ object.pk }}&return_url={% url 'dcim:devicetype_modulebays' pk=object.pk %}">Module Bays</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_devicebaytemplate %}
|
{% if perms.dcim.add_devicebaytemplate %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:devicebaytemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_devicebays">Device Bays</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:devicebaytemplate_add' %}?device_type={{ object.pk }}&return_url={% url 'dcim:devicetype_devicebays' pk=object.pk %}">Device Bays</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -56,74 +56,74 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{% with devicebay_count=object.devicebaytemplates.count %}
|
{% with tab_name='device-bay-templates' devicebay_count=object.devicebaytemplates.count %}
|
||||||
{% if devicebay_count %}
|
{% if active_tab == tab_name or devicebay_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'device-bay-templates' %} active{% endif %}" href="{% url 'dcim:devicetype_devicebays' pk=object.pk %}">Device Bays {% badge devicebay_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_devicebays' pk=object.pk %}">Device Bays {% badge devicebay_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% with modulebay_count=object.modulebaytemplates.count %}
|
{% with tab_name='module-bay-templates' modulebay_count=object.modulebaytemplates.count %}
|
||||||
{% if modulebay_count %}
|
{% if active_tab == tab_name or modulebay_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'module-bay-templates' %} active{% endif %}" href="{% url 'dcim:devicetype_modulebays' pk=object.pk %}">Module Bays {% badge modulebay_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_modulebays' pk=object.pk %}">Module Bays {% badge modulebay_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% with interface_count=object.interfacetemplates.count %}
|
{% with tab_name='interface-templates' interface_count=object.interfacetemplates.count %}
|
||||||
{% if interface_count %}
|
{% if active_tab == tab_name or interface_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'interface-templates' %} active{% endif %}" href="{% url 'dcim:devicetype_interfaces' pk=object.pk %}">Interfaces {% badge interface_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_interfaces' pk=object.pk %}">Interfaces {% badge interface_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% with frontport_count=object.frontporttemplates.count %}
|
{% with tab_name='front-port-templates' frontport_count=object.frontporttemplates.count %}
|
||||||
{% if frontport_count %}
|
{% if active_tab == tab_name or frontport_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'front-port-templates' %} active{% endif %}" href="{% url 'dcim:devicetype_frontports' pk=object.pk %}">Front Ports {% badge frontport_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_frontports' pk=object.pk %}">Front Ports {% badge frontport_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% with rearport_count=object.rearporttemplates.count %}
|
{% with tab_name='rear-port-templates' rearport_count=object.rearporttemplates.count %}
|
||||||
{% if rearport_count %}
|
{% if active_tab == tab_name or rearport_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'rear-port-templates' %} active{% endif %}" href="{% url 'dcim:devicetype_rearports' pk=object.pk %}">Rear Ports {% badge rearport_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_rearports' pk=object.pk %}">Rear Ports {% badge rearport_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% with consoleport_count=object.consoleporttemplates.count %}
|
{% with tab_name='console-port-templates' consoleport_count=object.consoleporttemplates.count %}
|
||||||
{% if consoleport_count %}
|
{% if active_tab == tab_name or consoleport_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'console-port-templates' %} active{% endif %}" href="{% url 'dcim:devicetype_consoleports' pk=object.pk %}">Console Ports {% badge consoleport_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_consoleports' pk=object.pk %}">Console Ports {% badge consoleport_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% with consoleserverport_count=object.consoleserverporttemplates.count %}
|
{% with tab_name='console-server-port-templates' consoleserverport_count=object.consoleserverporttemplates.count %}
|
||||||
{% if consoleserverport_count %}
|
{% if active_tab == tab_name or consoleserverport_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'console-server-port-templates' %} active{% endif %}" href="{% url 'dcim:devicetype_consoleserverports' pk=object.pk %}">Console Server Ports {% badge consoleserverport_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_consoleserverports' pk=object.pk %}">Console Server Ports {% badge consoleserverport_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% with powerport_count=object.powerporttemplates.count %}
|
{% with tab_name='power-port-templates' powerport_count=object.powerporttemplates.count %}
|
||||||
{% if powerport_count %}
|
{% if active_tab == tab_name or powerport_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'power-port-templates' %} active{% endif %}" href="{% url 'dcim:devicetype_powerports' pk=object.pk %}">Power Ports {% badge powerport_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_powerports' pk=object.pk %}">Power Ports {% badge powerport_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% with poweroutlet_count=object.poweroutlettemplates.count %}
|
{% with tab_name='power-outlet-templates' poweroutlet_count=object.poweroutlettemplates.count %}
|
||||||
{% if poweroutlet_count %}
|
{% if active_tab == tab_name or poweroutlet_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link {% if active_tab == 'power-outlet-templates' %} active{% endif %}" href="{% url 'dcim:devicetype_poweroutlets' pk=object.pk %}">Power Outlets {% badge poweroutlet_count %}</a>
|
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_poweroutlets' pk=object.pk %}">Power Outlets {% badge poweroutlet_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
@ -13,18 +13,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-footer noprint">
|
<div class="card-footer noprint">
|
||||||
{% if table.rows %}
|
{% if table.rows %}
|
||||||
<button type="submit" name="_edit" formaction="{% url table.Meta.model|viewname:"bulk_rename" %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-warning">
|
<button type="submit" name="_edit" formaction="{% url table.Meta.model|viewname:"bulk_rename" %}?return_url={{ return_url }}" class="btn btn-sm btn-warning">
|
||||||
<span class="mdi mdi-pencil-outline" aria-hidden="true"></span> Rename
|
<span class="mdi mdi-pencil-outline" aria-hidden="true"></span> Rename
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_edit" formaction="{% url table.Meta.model|viewname:"bulk_edit" %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-warning">
|
<button type="submit" name="_edit" formaction="{% url table.Meta.model|viewname:"bulk_edit" %}?return_url={{ return_url }}" class="btn btn-sm btn-warning">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_delete" formaction="{% url table.Meta.model|viewname:"bulk_delete" %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-danger">
|
<button type="submit" name="_delete" formaction="{% url table.Meta.model|viewname:"bulk_delete" %}?return_url={{ return_url }}" class="btn btn-sm btn-danger">
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="float-end">
|
<div class="float-end">
|
||||||
<a href="{% url table.Meta.model|viewname:"add" %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_{{ tab }}" class="btn btn-primary btn-sm">
|
<a href="{% url table.Meta.model|viewname:"add" %}?device_type={{ object.pk }}&return_url={{ return_url }}" class="btn btn-primary btn-sm">
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i>
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i>
|
||||||
Add {{ title }}
|
Add {{ title }}
|
||||||
</a>
|
</a>
|
||||||
|
@ -18,25 +18,25 @@
|
|||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
{% if perms.dcim.add_consoleporttemplate %}
|
{% if perms.dcim.add_consoleporttemplate %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:consoleporttemplate_add' %}?module_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_consoleports">Console Ports</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:consoleporttemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_consoleports' pk=object.pk %}">Console Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_consoleserverporttemplate %}
|
{% if perms.dcim.add_consoleserverporttemplate %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:consoleserverporttemplate_add' %}?module_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_consoleserverports">Console Server Ports</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:consoleserverporttemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_consoleserverports' pk=object.pk %}">Console Server Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_powerporttemplate %}
|
{% if perms.dcim.add_powerporttemplate %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:powerporttemplate_add' %}?module_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_powerports">Power Ports</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:powerporttemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_powerports' pk=object.pk %}">Power Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_poweroutlettemplate %}
|
{% if perms.dcim.add_poweroutlettemplate %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:poweroutlettemplate_add' %}?module_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_poweroutlets">Power Outlets</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:poweroutlettemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_poweroutlets' pk=object.pk %}">Power Outlets</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_interfacetemplate %}
|
{% if perms.dcim.add_interfacetemplate %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:interfacetemplate_add' %}?module_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_interfaces">Interfaces</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:interfacetemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_interfaces' pk=object.pk %}">Interfaces</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_frontporttemplate %}
|
{% if perms.dcim.add_frontporttemplate %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:frontporttemplate_add' %}?module_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_frontports">Front Ports</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:frontporttemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_frontports' pk=object.pk %}">Front Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_rearporttemplate %}
|
{% if perms.dcim.add_rearporttemplate %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:rearporttemplate_add' %}?module_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_rearports">Rear Ports</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:rearporttemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_rearports' pk=object.pk %}">Rear Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,18 +13,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-footer noprint">
|
<div class="card-footer noprint">
|
||||||
{% if table.rows %}
|
{% if table.rows %}
|
||||||
<button type="submit" name="_edit" formaction="{% url table.Meta.model|viewname:"bulk_rename" %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-warning">
|
<button type="submit" name="_edit" formaction="{% url table.Meta.model|viewname:"bulk_rename" %}?return_url={{ return_url }}" class="btn btn-sm btn-warning">
|
||||||
<span class="mdi mdi-pencil-outline" aria-hidden="true"></span> Rename
|
<span class="mdi mdi-pencil-outline" aria-hidden="true"></span> Rename
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_edit" formaction="{% url table.Meta.model|viewname:"bulk_edit" %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-warning">
|
<button type="submit" name="_edit" formaction="{% url table.Meta.model|viewname:"bulk_edit" %}?return_url={{ return_url }}" class="btn btn-sm btn-warning">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_delete" formaction="{% url table.Meta.model|viewname:"bulk_delete" %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-danger">
|
<button type="submit" name="_delete" formaction="{% url table.Meta.model|viewname:"bulk_delete" %}?return_url={{ return_url }}" class="btn btn-sm btn-danger">
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="float-end">
|
<div class="float-end">
|
||||||
<a href="{% url table.Meta.model|viewname:"add" %}?module_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_{{ tab }}" class="btn btn-primary btn-sm">
|
<a href="{% url table.Meta.model|viewname:"add" %}?module_type={{ object.pk }}&return_url={{ return_url }}" class="btn btn-primary btn-sm">
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i>
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i>
|
||||||
Add {{ title }}
|
Add {{ title }}
|
||||||
</a>
|
</a>
|
||||||
|
73
netbox/templates/extras/htmx/report_result.html
Normal file
73
netbox/templates/extras/htmx/report_result.html
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
{% load helpers %}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Initiated: <strong>{{ result.created|annotated_date }}</strong>
|
||||||
|
{% if result.completed %}
|
||||||
|
Duration: <strong>{{ result.duration }}</strong>
|
||||||
|
{% endif %}
|
||||||
|
<span id="pending-result-label">{% include 'extras/inc/job_label.html' %}</span>
|
||||||
|
</p>
|
||||||
|
{% if result.completed %}
|
||||||
|
<div class="card">
|
||||||
|
<h5 class="card-header">Report Methods</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-hover">
|
||||||
|
{% for method, data in result.data.items %}
|
||||||
|
<tr>
|
||||||
|
<td class="font-monospace"><a href="#{{ method }}">{{ method }}</a></td>
|
||||||
|
<td class="text-end report-stats">
|
||||||
|
<span class="badge bg-success">{{ data.success }}</span>
|
||||||
|
<span class="badge bg-info">{{ data.info }}</span>
|
||||||
|
<span class="badge bg-warning">{{ data.warning }}</span>
|
||||||
|
<span class="badge bg-danger">{{ data.failure }}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h5 class="card-header">Report Results</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-hover report">
|
||||||
|
<thead>
|
||||||
|
<tr class="table-headings">
|
||||||
|
<th>Time</th>
|
||||||
|
<th>Level</th>
|
||||||
|
<th>Object</th>
|
||||||
|
<th>Message</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for method, data in result.data.items %}
|
||||||
|
<tr>
|
||||||
|
<th colspan="4" style="font-family: monospace">
|
||||||
|
<a name="{{ method }}"></a>{{ method }}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
{% for time, level, obj, url, message in data.log %}
|
||||||
|
<tr class="{% if level == 'failure' %}danger{% elif level %}{{ level }}{% endif %}">
|
||||||
|
<td>{{ time }}</td>
|
||||||
|
<td>
|
||||||
|
<label class="badge bg-{% if level == 'failure' %}danger{% else %}{{ level }}{% endif %}">{{ level|title }}</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if obj and url %}
|
||||||
|
<a href="{{ url }}">{{ obj }}</a>
|
||||||
|
{% elif obj %}
|
||||||
|
{{ obj }}
|
||||||
|
{% else %}
|
||||||
|
<span class="muted">—</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="rendered-markdown">{{ message|render_markdown }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% include 'extras/inc/result_pending.html' %}
|
||||||
|
{% endif %}
|
50
netbox/templates/extras/htmx/script_result.html
Normal file
50
netbox/templates/extras/htmx/script_result.html
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{% load helpers %}
|
||||||
|
{% load log_levels %}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Initiated: <strong>{{ result.created|annotated_date }}</strong>
|
||||||
|
{% if result.completed %}
|
||||||
|
Duration: <strong>{{ result.duration }}</strong>
|
||||||
|
{% endif %}
|
||||||
|
<span id="pending-result-label">{% include 'extras/inc/job_label.html' %}</span>
|
||||||
|
</p>
|
||||||
|
{% if result.completed %}
|
||||||
|
<div class="card mb-3">
|
||||||
|
<h5 class="card-header">Script Log</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-hover panel-body">
|
||||||
|
<tr>
|
||||||
|
<th>Line</th>
|
||||||
|
<th>Level</th>
|
||||||
|
<th>Message</th>
|
||||||
|
</tr>
|
||||||
|
{% for log in result.data.log %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ forloop.counter }}</td>
|
||||||
|
<td>{% log_level log.status %}</td>
|
||||||
|
<td class="rendered-markdown">{{ log.message|render_markdown }}</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="text-center text-muted">
|
||||||
|
No log output
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% if execution_time %}
|
||||||
|
<div class="card-footer text-end text-muted">
|
||||||
|
<small>Exec Time: {{ execution_time|floatformat:3 }}s</small>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<h4>Output</h4>
|
||||||
|
{% if result.data.output %}
|
||||||
|
<pre class="block">{{ result.data.output }}</pre>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-muted">None</p>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{% include 'extras/inc/result_pending.html' %}
|
||||||
|
{% endif %}
|
6
netbox/templates/extras/inc/result_pending.html
Normal file
6
netbox/templates/extras/inc/result_pending.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{# Indicates that a job result is still pending; used for HTMX requests #}
|
||||||
|
<div class="spinner-border float-start me-1" id="spinner" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<h3>Results pending...</h3>
|
||||||
|
<small class="text-muted">Last updated {% now "H:i:s" %}</small>
|
@ -1,99 +1,9 @@
|
|||||||
{% extends 'extras/report.html' %}
|
{% extends 'extras/report.html' %}
|
||||||
{% load helpers %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block head %}
|
|
||||||
<script src="{% static 'jobs.js' %}?v{{ settings.VERSION }}"
|
|
||||||
onerror="window.location='{% url 'media_failure' %}?filename=jobs.js'"></script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content-wrapper %}
|
{% block content-wrapper %}
|
||||||
<div class="row px-3">
|
<div class="row px-3">
|
||||||
<div class="col col-md-12">
|
<div class="col col-md-12"{% if not result.completed %} hx-get="{% url 'extras:report_result' job_result_pk=result.pk %}" hx-trigger="every 3s"{% endif %}>
|
||||||
<p>
|
{% include 'extras/htmx/report_result.html' %}
|
||||||
Run: <strong>{{ result.created|annotated_date }}</strong>
|
|
||||||
{% if result.completed %}
|
|
||||||
Duration: <strong>{{ result.duration }}</strong>
|
|
||||||
{% else %}
|
|
||||||
<div class="spinner-border" role="status">
|
|
||||||
<span class="visually-hidden">Loading...</span>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<span id="pending-result-label">{% include 'extras/inc/job_label.html' with result=result %}</span>
|
|
||||||
</p>
|
|
||||||
{% if result.completed %}
|
|
||||||
<div class="card">
|
|
||||||
<h5 class="card-header">
|
|
||||||
Report Methods
|
|
||||||
</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
<table class="table table-hover">
|
|
||||||
{% for method, data in result.data.items %}
|
|
||||||
<tr>
|
|
||||||
<td class="font-monospace"><a href="#{{ method }}">{{ method }}</a></td>
|
|
||||||
<td class="text-end report-stats">
|
|
||||||
<span class="badge bg-success">{{ data.success }}</span>
|
|
||||||
<span class="badge bg-info">{{ data.info }}</span>
|
|
||||||
<span class="badge bg-warning">{{ data.warning }}</span>
|
|
||||||
<span class="badge bg-danger">{{ data.failure }}</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<h5 class="card-header">
|
|
||||||
Report Results
|
|
||||||
</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
<table class="table table-hover report">
|
|
||||||
<thead>
|
|
||||||
<tr class="table-headings">
|
|
||||||
<th>Time</th>
|
|
||||||
<th>Level</th>
|
|
||||||
<th>Object</th>
|
|
||||||
<th>Message</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for method, data in result.data.items %}
|
|
||||||
<tr>
|
|
||||||
<th colspan="4" style="font-family: monospace">
|
|
||||||
<a name="{{ method }}"></a>{{ method }}
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
{% for time, level, obj, url, message in data.log %}
|
|
||||||
<tr class="{% if level == 'failure' %}danger{% elif level %}{{ level }}{% endif %}">
|
|
||||||
<td>{{ time }}</td>
|
|
||||||
<td>
|
|
||||||
<label class="badge bg-{% if level == 'failure' %}danger{% else %}{{ level }}{% endif %}">{{ level|title }}</label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% if obj and url %}
|
|
||||||
<a href="{{ url }}">{{ obj }}</a>
|
|
||||||
{% elif obj %}
|
|
||||||
{{ obj }}
|
|
||||||
{% else %}
|
|
||||||
<span class="muted">—</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="rendered-markdown">{{ message|render_markdown }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="well">Pending results</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block data %}
|
|
||||||
<span data-job-url="{% url 'extras-api:jobresult-detail' pk=result.pk %}"></span>
|
|
||||||
<span data-job-complete="{{ result.completed }}"></span>
|
|
||||||
{% endblock %}
|
|
||||||
|
@ -1,19 +1,10 @@
|
|||||||
{% extends 'base/layout.html' %}
|
{% extends 'base/layout.html' %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load form_helpers %}
|
|
||||||
{% load log_levels %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block head %}
|
|
||||||
<script src="{% static 'jobs.js' %}?v{{ settings.VERSION }}"
|
|
||||||
onerror="window.location='{% url 'media_failure' %}?filename=jobs.js'"></script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block title %}{{ script }}{% endblock %}
|
{% block title %}{{ script }}{% endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{% block subtitle %}
|
||||||
{{ script.Meta.description|render_markdown }}
|
{{ script.Meta.description|render_markdown }}
|
||||||
<span id="pending-result-label">{% include 'extras/inc/job_label.html' with result=result %}</span>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
@ -37,81 +28,21 @@
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a href="#log" role="tab" data-bs-toggle="tab" class="nav-link active">Log</a>
|
<a href="#log" role="tab" data-bs-toggle="tab" class="nav-link active">Log</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<a href="#output" role="tab" data-bs-toggle="tab" class="nav-link">Output</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a href="#source" role="tab" data-bs-toggle="tab" class="nav-link">Source</a>
|
<a href="#source" role="tab" data-bs-toggle="tab" class="nav-link">Source</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content mb-3">
|
<div class="tab-content mb-3">
|
||||||
<p>
|
|
||||||
Run: <strong>{{ result.created|annotated_date }}</strong>
|
|
||||||
{% if result.completed %}
|
|
||||||
Duration: <strong>{{ result.duration }}</strong>
|
|
||||||
{% else %}
|
|
||||||
<div class="spinner-border" role="status">
|
|
||||||
<span class="visually-hidden">Loading...</span>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
<div role="tabpanel" class="tab-pane active" id="log">
|
<div role="tabpanel" class="tab-pane active" id="log">
|
||||||
{% if result.completed %}
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-12">
|
<div class="col col-md-12"{% if not result.completed %} hx-get="{% url 'extras:script_result' job_result_pk=result.pk %}" hx-trigger="every 3s"{% endif %}>
|
||||||
<div class="card">
|
{% include 'extras/htmx/script_result.html' %}
|
||||||
<h5 class="card-header">
|
|
||||||
Script Log
|
|
||||||
</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
<table class="table table-hover panel-body">
|
|
||||||
<tr>
|
|
||||||
<th>Line</th>
|
|
||||||
<th>Level</th>
|
|
||||||
<th>Message</th>
|
|
||||||
</tr>
|
|
||||||
{% for log in result.data.log %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ forloop.counter }}</td>
|
|
||||||
<td>{% log_level log.status %}</td>
|
|
||||||
<td class="rendered-markdown">{{ log.message|render_markdown }}</td>
|
|
||||||
</tr>
|
|
||||||
{% empty %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="3" class="text-center text-muted">
|
|
||||||
No log output
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% if execution_time %}
|
|
||||||
<div class="card-footer text-end text-muted">
|
|
||||||
<small>Exec Time: {{ execution_time|floatformat:3 }}s</small>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col col-md-12">
|
|
||||||
<div class="well">Pending Results</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div role="tabpanel" class="tab-pane" id="output">
|
|
||||||
<pre class="block">{{ result.data.output }}</pre>
|
|
||||||
</div>
|
|
||||||
<div role="tabpanel" class="tab-pane" id="source">
|
<div role="tabpanel" class="tab-pane" id="source">
|
||||||
<p><code>{{ script.filename }}</code></p>
|
<p><code>{{ script.filename }}</code></p>
|
||||||
<pre class="block">{{ script.source }}</pre>
|
<pre class="block">{{ script.source }}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content-wrapper %}
|
{% endblock content-wrapper %}
|
||||||
|
|
||||||
{% block data %}
|
|
||||||
<span data-job-url="{% url 'extras-api:jobresult-detail' pk=result.pk %}"></span>
|
|
||||||
<span data-job-complete="{{ result.completed }}"></span>
|
|
||||||
{% endblock %}
|
|
||||||
|
@ -24,8 +24,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content p-0 border-0">
|
<div class="tab-content p-0 border-0">
|
||||||
{{ form.initial.device }}
|
|
||||||
{{ form.initial.virtual_machine }}
|
|
||||||
<div class="tab-pane {% if not form.initial.virtual_machine %}active{% endif %}" id="device" role="tabpanel" aria-labeled-by="device_tab">
|
<div class="tab-pane {% if not form.initial.virtual_machine %}active{% endif %}" id="device" role="tabpanel" aria-labeled-by="device_tab">
|
||||||
{% render_field form.device %}
|
{% render_field form.device %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -208,7 +208,6 @@ class ButtonsColumn(tables.TemplateColumn):
|
|||||||
|
|
||||||
:param model: Model class to use for calculating URL view names
|
:param model: Model class to use for calculating URL view names
|
||||||
:param prepend_content: Additional template content to render in the column (optional)
|
:param prepend_content: Additional template content to render in the column (optional)
|
||||||
:param return_url_extra: String to append to the return URL (e.g. for specifying a tab) (optional)
|
|
||||||
"""
|
"""
|
||||||
buttons = ('changelog', 'edit', 'delete')
|
buttons = ('changelog', 'edit', 'delete')
|
||||||
attrs = {'td': {'class': 'text-end text-nowrap noprint'}}
|
attrs = {'td': {'class': 'text-end text-nowrap noprint'}}
|
||||||
@ -220,18 +219,18 @@ class ButtonsColumn(tables.TemplateColumn):
|
|||||||
</a>
|
</a>
|
||||||
{{% endif %}}
|
{{% endif %}}
|
||||||
{{% if "edit" in buttons and perms.{app_label}.change_{model_name} %}}
|
{{% if "edit" in buttons and perms.{app_label}.change_{model_name} %}}
|
||||||
<a href="{{% url '{app_label}:{model_name}_edit' pk=record.pk %}}?return_url={{{{ request.path }}}}{{{{ return_url_extra }}}}" class="btn btn-sm btn-warning" title="Edit">
|
<a href="{{% url '{app_label}:{model_name}_edit' pk=record.pk %}}?return_url={{{{ request.path }}}}" class="btn btn-sm btn-warning" title="Edit">
|
||||||
<i class="mdi mdi-pencil"></i>
|
<i class="mdi mdi-pencil"></i>
|
||||||
</a>
|
</a>
|
||||||
{{% endif %}}
|
{{% endif %}}
|
||||||
{{% if "delete" in buttons and perms.{app_label}.delete_{model_name} %}}
|
{{% if "delete" in buttons and perms.{app_label}.delete_{model_name} %}}
|
||||||
<a href="{{% url '{app_label}:{model_name}_delete' pk=record.pk %}}?return_url={{{{ request.path }}}}{{{{ return_url_extra }}}}" class="btn btn-sm btn-danger" title="Delete">
|
<a href="{{% url '{app_label}:{model_name}_delete' pk=record.pk %}}?return_url={{{{ request.path }}}}" class="btn btn-sm btn-danger" title="Delete">
|
||||||
<i class="mdi mdi-trash-can-outline"></i>
|
<i class="mdi mdi-trash-can-outline"></i>
|
||||||
</a>
|
</a>
|
||||||
{{% endif %}}
|
{{% endif %}}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, model, *args, buttons=None, prepend_template=None, return_url_extra='', **kwargs):
|
def __init__(self, model, *args, buttons=None, prepend_template=None, **kwargs):
|
||||||
if prepend_template:
|
if prepend_template:
|
||||||
prepend_template = prepend_template.replace('{', '{{')
|
prepend_template = prepend_template.replace('{', '{{')
|
||||||
prepend_template = prepend_template.replace('}', '}}')
|
prepend_template = prepend_template.replace('}', '}}')
|
||||||
@ -251,7 +250,6 @@ class ButtonsColumn(tables.TemplateColumn):
|
|||||||
|
|
||||||
self.extra_context.update({
|
self.extra_context.update({
|
||||||
'buttons': buttons or self.buttons,
|
'buttons': buttons or self.buttons,
|
||||||
'return_url_extra': return_url_extra,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
def header(self):
|
def header(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user