1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Merge branch 'develop' into feature

This commit is contained in:
jeremystretch
2021-12-21 11:28:16 -05:00
26 changed files with 324 additions and 536 deletions

View File

@ -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)

View File

@ -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',
)

View File

@ -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):

View File

@ -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):

View File

@ -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

View File

@ -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,

View File

@ -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}",

View File

@ -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',

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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;

View File

@ -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);
}

View File

@ -102,82 +102,83 @@
</a>
</li>
{% 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 %}
<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>
{% 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 %}
<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>
{% 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 %}
<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>
{% 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 %}
<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>
{% 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 %}
<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>
{% 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 %}
<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>
{% 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 %}
<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>
{% 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 %}
<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>
{% 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 %}
<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>
{% 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 %}
<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>
{% endif %}
{% endwith %}

View File

@ -17,22 +17,22 @@
<div class="noprint bulk-buttons">
<div class="bulk-button-group">
{% 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
</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
</button>
{% endif %}
{% 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
</button>
{% endif %}
</div>
{% if perms.dcim.add_devicebay %}
<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
</a>
</div>

View File

@ -17,22 +17,22 @@
<div class="noprint bulk-buttons">
<div class="bulk-button-group">
{% 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
</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
</button>
{% endif %}
{% 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
</button>
{% endif %}
</div>
{% if perms.dcim.add_modulebay %}
<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
</a>
</div>

View File

@ -18,31 +18,31 @@
</button>
<ul class="dropdown-menu">
{% 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 %}
{% 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 %}
{% 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 %}
{% 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 %}
{% 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 %}
{% 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 %}
{% 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 %}
{% 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 %}
{% 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 %}
</ul>
</div>
@ -56,74 +56,74 @@
</a>
</li>
{% 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 %}
<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>
{% 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 %}
<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>
{% 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 %}
<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>
{% 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 %}
<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>
{% 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 %}
<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>
{% 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 %}
<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>
{% 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 %}
<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>
{% 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 %}
<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>
{% 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 %}
<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>
{% endif %}
{% endwith %}

View File

@ -13,18 +13,18 @@
</div>
<div class="card-footer noprint">
{% 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
</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
</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
</button>
{% endif %}
<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>
Add {{ title }}
</a>

View File

@ -18,25 +18,25 @@
</button>
<ul class="dropdown-menu">
{% 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 %}
{% 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 %}
{% 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 %}
{% 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 %}
{% 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 %}
{% 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 %}
{% 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 %}
</ul>
</div>

View File

@ -13,18 +13,18 @@
</div>
<div class="card-footer noprint">
{% 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
</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
</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
</button>
{% endif %}
<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>
Add {{ title }}
</a>

View 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">&mdash;</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 %}

View 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 %}

View 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>

View File

@ -1,99 +1,9 @@
{% 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 %}
<div class="row px-3">
<div class="col col-md-12">
<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 %}
<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">&mdash;</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 class="row px-3">
<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 %}>
{% include 'extras/htmx/report_result.html' %}
</div>
</div>
{% endblock %}
{% block data %}
<span data-job-url="{% url 'extras-api:jobresult-detail' pk=result.pk %}"></span>
<span data-job-complete="{{ result.completed }}"></span>
</div>
{% endblock %}

View File

@ -1,117 +1,48 @@
{% extends 'base/layout.html' %}
{% 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 subtitle %}
{{ script.Meta.description|render_markdown }}
<span id="pending-result-label">{% include 'extras/inc/job_label.html' with result=result %}</span>
{% endblock %}
{% block header %}
<div class="row noprint">
<div class="col col-md-12">
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}">Scripts</a></li>
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}#module.{{ script.module }}">{{ script.module|bettertitle }}</a></li>
<li class="breadcrumb-item"><a href="{% url 'extras:script' module=script.module name=class_name %}">{{ script }}</a></li>
<li class="breadcrumb-item">{{ result.created|annotated_date }}</li>
</ol>
</nav>
</div>
<div class="col col-md-12">
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}">Scripts</a></li>
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}#module.{{ script.module }}">{{ script.module|bettertitle }}</a></li>
<li class="breadcrumb-item"><a href="{% url 'extras:script' module=script.module name=class_name %}">{{ script }}</a></li>
<li class="breadcrumb-item">{{ result.created|annotated_date }}</li>
</ol>
</nav>
</div>
</div>
{{ block.super }}
{% endblock header %}
{% block content-wrapper %}
<ul class="nav nav-tabs px-3" role="tablist">
<li class="nav-item" role="presentation">
<a href="#log" role="tab" data-bs-toggle="tab" class="nav-link active">Log</a>
</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">
<a href="#source" role="tab" data-bs-toggle="tab" class="nav-link">Source</a>
</li>
<li class="nav-item" role="presentation">
<a href="#log" role="tab" data-bs-toggle="tab" class="nav-link active">Log</a>
</li>
<li class="nav-item" role="presentation">
<a href="#source" role="tab" data-bs-toggle="tab" class="nav-link">Source</a>
</li>
</ul>
<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">
{% if result.completed %}
<div class="row">
<div class="col col-md-12">
<div class="card">
<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>
{% 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">
<p><code>{{ script.filename }}</code></p>
<pre class="block">{{ script.source }}</pre>
<div role="tabpanel" class="tab-pane active" id="log">
<div class="row">
<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 %}>
{% include 'extras/htmx/script_result.html' %}
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="source">
<p><code>{{ script.filename }}</code></p>
<pre class="block">{{ script.source }}</pre>
</div>
</div>
{% 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 %}

View File

@ -24,8 +24,6 @@
</div>
</div>
<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">
{% render_field form.device %}
</div>

View File

@ -208,7 +208,6 @@ class ButtonsColumn(tables.TemplateColumn):
:param model: Model class to use for calculating URL view names
: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')
attrs = {'td': {'class': 'text-end text-nowrap noprint'}}
@ -220,18 +219,18 @@ class ButtonsColumn(tables.TemplateColumn):
</a>
{{% endif %}}
{{% 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>
</a>
{{% endif %}}
{{% 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>
</a>
{{% 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:
prepend_template = prepend_template.replace('{', '{{')
prepend_template = prepend_template.replace('}', '}}')
@ -251,7 +250,6 @@ class ButtonsColumn(tables.TemplateColumn):
self.extra_context.update({
'buttons': buttons or self.buttons,
'return_url_extra': return_url_extra,
})
def header(self):