mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 04:02:52 -06:00
commit
d3e2241ff7
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -14,7 +14,7 @@ body:
|
||||
attributes:
|
||||
label: NetBox version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v3.1.4
|
||||
placeholder: v3.1.5
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@ -14,7 +14,7 @@ body:
|
||||
attributes:
|
||||
label: NetBox version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v3.1.4
|
||||
placeholder: v3.1.5
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
@ -1,5 +1,23 @@
|
||||
# NetBox v3.1
|
||||
|
||||
## v3.1.5 (2022-01-06)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#8231](https://github.com/netbox-community/netbox/issues/8231) - Use in-page dialogs for confirming object deletion
|
||||
* [#8244](https://github.com/netbox-community/netbox/issues/8244) - Add length & length unit fields to cable filter form
|
||||
* [#8252](https://github.com/netbox-community/netbox/issues/8252) - Linkify type and group columns in clusters table
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#8213](https://github.com/netbox-community/netbox/issues/8213) - Fix ValueError exception under prefix IP addresses view
|
||||
* [#8224](https://github.com/netbox-community/netbox/issues/8224) - Fix KeyError exception when creating FHRP group with IP address and protocol "other"
|
||||
* [#8226](https://github.com/netbox-community/netbox/issues/8226) - Honor return URL after populating a device bay
|
||||
* [#8228](https://github.com/netbox-community/netbox/issues/8228) - Optional ChoiceVar fields should not force a selection
|
||||
* [#8255](https://github.com/netbox-community/netbox/issues/8255) - Fix bulk editing of authentication parameters for wireless LANs and links
|
||||
|
||||
---
|
||||
|
||||
## v3.1.4 (2022-01-03)
|
||||
|
||||
### Enhancements
|
||||
|
@ -578,7 +578,7 @@ class CableFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['site_id', 'rack_id', 'device_id'],
|
||||
['type', 'status', 'color'],
|
||||
['type', 'status', 'color', 'length', 'length_unit'],
|
||||
['tenant_group_id', 'tenant_id'],
|
||||
]
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
@ -603,6 +603,16 @@ class CableFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
'site_id': '$site_id'
|
||||
}
|
||||
)
|
||||
device_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'site_id': '$site_id',
|
||||
'tenant_id': '$tenant_id',
|
||||
'rack_id': '$rack_id',
|
||||
},
|
||||
label=_('Device')
|
||||
)
|
||||
type = forms.MultipleChoiceField(
|
||||
choices=add_blank_choice(CableTypeChoices),
|
||||
required=False,
|
||||
@ -616,15 +626,12 @@ class CableFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
color = ColorField(
|
||||
required=False
|
||||
)
|
||||
device_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'site_id': '$site_id',
|
||||
'tenant_id': '$tenant_id',
|
||||
'rack_id': '$rack_id',
|
||||
},
|
||||
label=_('Device')
|
||||
length = forms.IntegerField(
|
||||
required=False
|
||||
)
|
||||
length_unit = forms.ChoiceField(
|
||||
choices=add_blank_choice(CableLengthUnitChoices),
|
||||
required=False
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
@ -2035,8 +2035,9 @@ class DeviceBayPopulateView(generic.ObjectEditView):
|
||||
device_bay.installed_device = form.cleaned_data['installed_device']
|
||||
device_bay.save()
|
||||
messages.success(request, "Added {} to {}.".format(device_bay.installed_device, device_bay))
|
||||
return_url = self.get_return_url(request)
|
||||
|
||||
return redirect('dcim:device', pk=device_bay.device.pk)
|
||||
return redirect(return_url)
|
||||
|
||||
return render(request, 'dcim/devicebay_populate.html', {
|
||||
'device_bay': device_bay,
|
||||
|
@ -7,8 +7,8 @@ from extras.models import *
|
||||
from extras.utils import FeatureQuery
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.forms import (
|
||||
add_blank_choice, BootstrapMixin, CommentField, ContentTypeChoiceField,
|
||||
ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField, JSONField, SlugField, StaticSelect,
|
||||
add_blank_choice, BootstrapMixin, CommentField, ContentTypeChoiceField, ContentTypeMultipleChoiceField,
|
||||
DynamicModelMultipleChoiceField, JSONField, SlugField, StaticSelect,
|
||||
)
|
||||
from virtualization.models import Cluster, ClusterGroup
|
||||
|
||||
@ -41,6 +41,10 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
|
||||
('Values', ('default', 'choices')),
|
||||
('Validation', ('validation_minimum', 'validation_maximum', 'validation_regex')),
|
||||
)
|
||||
widgets = {
|
||||
'type': StaticSelect(),
|
||||
'filter_logic': StaticSelect(),
|
||||
}
|
||||
|
||||
|
||||
class CustomLinkForm(BootstrapMixin, forms.ModelForm):
|
||||
@ -57,6 +61,7 @@ class CustomLinkForm(BootstrapMixin, forms.ModelForm):
|
||||
('Templates', ('link_text', 'link_url')),
|
||||
)
|
||||
widgets = {
|
||||
'button_class': StaticSelect(),
|
||||
'link_text': forms.Textarea(attrs={'class': 'font-monospace'}),
|
||||
'link_url': forms.Textarea(attrs={'class': 'font-monospace'}),
|
||||
}
|
||||
@ -96,8 +101,7 @@ class WebhookForm(BootstrapMixin, forms.ModelForm):
|
||||
model = Webhook
|
||||
fields = '__all__'
|
||||
fieldsets = (
|
||||
('Webhook', ('name', 'enabled')),
|
||||
('Assigned Models', ('content_types',)),
|
||||
('Webhook', ('name', 'content_types', 'enabled')),
|
||||
('Events', ('type_create', 'type_update', 'type_delete')),
|
||||
('HTTP Request', (
|
||||
'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret',
|
||||
@ -105,7 +109,13 @@ class WebhookForm(BootstrapMixin, forms.ModelForm):
|
||||
('Conditions', ('conditions',)),
|
||||
('SSL', ('ssl_verification', 'ca_file_path')),
|
||||
)
|
||||
labels = {
|
||||
'type_create': 'Creations',
|
||||
'type_update': 'Updates',
|
||||
'type_delete': 'Deletions',
|
||||
}
|
||||
widgets = {
|
||||
'http_method': StaticSelect(),
|
||||
'additional_headers': forms.Textarea(attrs={'class': 'font-monospace'}),
|
||||
'body_template': forms.Textarea(attrs={'class': 'font-monospace'}),
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ from extras.models import JobResult
|
||||
from ipam.formfields import IPAddressFormField, IPNetworkFormField
|
||||
from ipam.validators import MaxPrefixLengthValidator, MinPrefixLengthValidator, prefix_validator
|
||||
from utilities.exceptions import AbortTransaction
|
||||
from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||
from utilities.forms import add_blank_choice, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||
from .context_managers import change_logging
|
||||
from .forms import ScriptForm
|
||||
|
||||
@ -164,16 +164,22 @@ class ChoiceVar(ScriptVariable):
|
||||
def __init__(self, choices, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Set field choices
|
||||
self.field_attrs['choices'] = choices
|
||||
# Set field choices, adding a blank choice to avoid forced selections
|
||||
self.field_attrs['choices'] = add_blank_choice(choices)
|
||||
|
||||
|
||||
class MultiChoiceVar(ChoiceVar):
|
||||
class MultiChoiceVar(ScriptVariable):
|
||||
"""
|
||||
Like ChoiceVar, but allows for the selection of multiple choices.
|
||||
"""
|
||||
form_field = forms.MultipleChoiceField
|
||||
|
||||
def __init__(self, choices, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Set field choices
|
||||
self.field_attrs['choices'] = choices
|
||||
|
||||
|
||||
class ObjectVar(ScriptVariable):
|
||||
"""
|
||||
|
@ -65,6 +65,7 @@ FHRP_PROTOCOL_ROLE_MAPPINGS = {
|
||||
FHRPGroupProtocolChoices.PROTOCOL_HSRP: IPAddressRoleChoices.ROLE_HSRP,
|
||||
FHRPGroupProtocolChoices.PROTOCOL_GLBP: IPAddressRoleChoices.ROLE_GLBP,
|
||||
FHRPGroupProtocolChoices.PROTOCOL_CARP: IPAddressRoleChoices.ROLE_CARP,
|
||||
FHRPGroupProtocolChoices.PROTOCOL_OTHER: IPAddressRoleChoices.ROLE_VIP,
|
||||
}
|
||||
|
||||
|
||||
|
@ -580,7 +580,7 @@ class FHRPGroupForm(CustomFieldModelForm):
|
||||
vrf=self.cleaned_data['ip_vrf'],
|
||||
address=self.cleaned_data['ip_address'],
|
||||
status=self.cleaned_data['ip_status'],
|
||||
role=FHRP_PROTOCOL_ROLE_MAPPINGS[self.cleaned_data['protocol']],
|
||||
role=FHRP_PROTOCOL_ROLE_MAPPINGS.get(self.cleaned_data['protocol'], IPAddressRoleChoices.ROLE_VIP),
|
||||
assigned_object=instance
|
||||
)
|
||||
ipaddress.save()
|
||||
@ -628,8 +628,7 @@ class FHRPGroupAssignmentForm(BootstrapMixin, forms.ModelForm):
|
||||
class VLANGroupForm(CustomFieldModelForm):
|
||||
scope_type = ContentTypeChoiceField(
|
||||
queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
|
||||
required=False,
|
||||
widget=StaticSelect
|
||||
required=False
|
||||
)
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
|
@ -1,5 +1,7 @@
|
||||
import datetime
|
||||
|
||||
from django.test import override_settings
|
||||
from django.urls import reverse
|
||||
from netaddr import IPNetwork
|
||||
|
||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
||||
@ -222,6 +224,21 @@ class AggregateTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_aggregate_prefixes(self):
|
||||
rir = RIR.objects.first()
|
||||
aggregate = Aggregate.objects.create(prefix=IPNetwork('192.168.0.0/16'), rir=rir)
|
||||
prefixes = (
|
||||
Prefix(prefix=IPNetwork('192.168.1.0/24')),
|
||||
Prefix(prefix=IPNetwork('192.168.2.0/24')),
|
||||
Prefix(prefix=IPNetwork('192.168.3.0/24')),
|
||||
)
|
||||
Prefix.objects.bulk_create(prefixes)
|
||||
self.assertEqual(aggregate.get_child_prefixes().count(), 3)
|
||||
|
||||
url = reverse('ipam:aggregate_prefixes', kwargs={'pk': aggregate.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
|
||||
class RoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
||||
model = Role
|
||||
@ -319,6 +336,48 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_prefix_prefixes(self):
|
||||
prefixes = (
|
||||
Prefix(prefix=IPNetwork('192.168.0.0/16')),
|
||||
Prefix(prefix=IPNetwork('192.168.1.0/24')),
|
||||
Prefix(prefix=IPNetwork('192.168.2.0/24')),
|
||||
Prefix(prefix=IPNetwork('192.168.3.0/24')),
|
||||
)
|
||||
Prefix.objects.bulk_create(prefixes)
|
||||
self.assertEqual(prefixes[0].get_child_prefixes().count(), 3)
|
||||
|
||||
url = reverse('ipam:prefix_prefixes', kwargs={'pk': prefixes[0].pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_prefix_ipranges(self):
|
||||
prefix = Prefix.objects.create(prefix=IPNetwork('192.168.0.0/16'))
|
||||
ip_ranges = (
|
||||
IPRange(start_address='192.168.0.1/24', end_address='192.168.0.100/24', size=99),
|
||||
IPRange(start_address='192.168.1.1/24', end_address='192.168.1.100/24', size=99),
|
||||
IPRange(start_address='192.168.2.1/24', end_address='192.168.2.100/24', size=99),
|
||||
)
|
||||
IPRange.objects.bulk_create(ip_ranges)
|
||||
self.assertEqual(prefix.get_child_ranges().count(), 3)
|
||||
|
||||
url = reverse('ipam:prefix_ipranges', kwargs={'pk': prefix.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_prefix_ipaddresses(self):
|
||||
prefix = Prefix.objects.create(prefix=IPNetwork('192.168.0.0/16'))
|
||||
ip_addresses = (
|
||||
IPAddress(address=IPNetwork('192.168.0.1/16')),
|
||||
IPAddress(address=IPNetwork('192.168.0.2/16')),
|
||||
IPAddress(address=IPNetwork('192.168.0.3/16')),
|
||||
)
|
||||
IPAddress.objects.bulk_create(ip_addresses)
|
||||
self.assertEqual(prefix.get_child_ips().count(), 3)
|
||||
|
||||
url = reverse('ipam:prefix_ipaddresses', kwargs={'pk': prefix.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
|
||||
class IPRangeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
model = IPRange
|
||||
@ -377,6 +436,24 @@ class IPRangeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_iprange_ipaddresses(self):
|
||||
iprange = IPRange.objects.create(
|
||||
start_address=IPNetwork('192.168.0.1/24'),
|
||||
end_address=IPNetwork('192.168.0.100/24'),
|
||||
size=99
|
||||
)
|
||||
ip_addresses = (
|
||||
IPAddress(address=IPNetwork('192.168.0.1/24')),
|
||||
IPAddress(address=IPNetwork('192.168.0.2/24')),
|
||||
IPAddress(address=IPNetwork('192.168.0.3/24')),
|
||||
)
|
||||
IPAddress.objects.bulk_create(ip_addresses)
|
||||
self.assertEqual(iprange.get_child_ips().count(), 3)
|
||||
|
||||
url = reverse('ipam:iprange_ipaddresses', kwargs={'pk': iprange.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
|
||||
class IPAddressTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
model = IPAddress
|
||||
|
@ -505,9 +505,7 @@ class PrefixIPAddressesView(generic.ObjectChildrenView):
|
||||
template_name = 'ipam/prefix/ip_addresses.html'
|
||||
|
||||
def get_children(self, request, parent):
|
||||
return parent.get_child_ips().restrict(request.user, 'view').prefetch_related(
|
||||
'vrf', 'role', 'tenant',
|
||||
)
|
||||
return parent.get_child_ips().restrict(request.user, 'view').prefetch_related('vrf', 'tenant')
|
||||
|
||||
def prep_table_data(self, request, queryset, parent):
|
||||
show_available = bool(request.GET.get('show_available', 'true') == 'true')
|
||||
@ -531,7 +529,6 @@ class PrefixEditView(generic.ObjectEditView):
|
||||
|
||||
class PrefixDeleteView(generic.ObjectDeleteView):
|
||||
queryset = Prefix.objects.all()
|
||||
template_name = 'ipam/prefix_delete.html'
|
||||
|
||||
|
||||
class PrefixBulkImportView(generic.BulkImportView):
|
||||
|
@ -19,7 +19,7 @@ from netbox.config import PARAMS
|
||||
# Environment setup
|
||||
#
|
||||
|
||||
VERSION = '3.1.4'
|
||||
VERSION = '3.1.5'
|
||||
|
||||
# Hostname
|
||||
HOSTNAME = platform.node()
|
||||
|
@ -10,6 +10,7 @@ from django.db.models import ManyToManyField, ProtectedError
|
||||
from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput, Textarea
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.html import escape
|
||||
from django.utils.http import is_safe_url
|
||||
from django.utils.safestring import mark_safe
|
||||
@ -430,10 +431,21 @@ class ObjectDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
||||
obj = self.get_object(kwargs)
|
||||
form = ConfirmationForm(initial=request.GET)
|
||||
|
||||
return render(request, self.template_name, {
|
||||
'obj': obj,
|
||||
# If this is an HTMX request, return only the rendered deletion form as modal content
|
||||
if is_htmx(request):
|
||||
viewname = f'{self.queryset.model._meta.app_label}:{self.queryset.model._meta.model_name}_delete'
|
||||
form_url = reverse(viewname, kwargs={'pk': obj.pk})
|
||||
return render(request, 'htmx/delete_form.html', {
|
||||
'object': obj,
|
||||
'object_type': self.queryset.model._meta.verbose_name,
|
||||
'form': form,
|
||||
'form_url': form_url,
|
||||
})
|
||||
|
||||
return render(request, self.template_name, {
|
||||
'object': obj,
|
||||
'object_type': self.queryset.model._meta.verbose_name,
|
||||
'form': form,
|
||||
'obj_type': self.queryset.model._meta.verbose_name,
|
||||
'return_url': self.get_return_url(request, obj),
|
||||
})
|
||||
|
||||
@ -466,9 +478,9 @@ class ObjectDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
||||
logger.debug("Form validation failed")
|
||||
|
||||
return render(request, self.template_name, {
|
||||
'obj': obj,
|
||||
'object': obj,
|
||||
'object_type': self.queryset.model._meta.verbose_name,
|
||||
'form': form,
|
||||
'obj_type': self.queryset.model._meta.verbose_name,
|
||||
'return_url': self.get_return_url(request, obj),
|
||||
})
|
||||
|
||||
|
BIN
netbox/project-static/dist/netbox-dark.css
vendored
BIN
netbox/project-static/dist/netbox-dark.css
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox-light.css
vendored
BIN
netbox/project-static/dist/netbox-light.css
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox-print.css
vendored
BIN
netbox/project-static/dist/netbox-print.css
vendored
Binary file not shown.
@ -358,7 +358,7 @@ nav.search {
|
||||
// Don't overtake dropdowns
|
||||
z-index: 999;
|
||||
justify-content: center;
|
||||
background-color: var(--nbx-body-bg);
|
||||
background-color: $navbar-light-color;
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
@ -452,8 +452,8 @@ main.login-container {
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: $tab-content-bg;
|
||||
padding: 0;
|
||||
|
||||
.nav-link {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
@ -517,6 +517,10 @@ h6.accordion-item-title {
|
||||
}
|
||||
}
|
||||
|
||||
.navbar {
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
@ -554,6 +558,7 @@ div.content-container {
|
||||
}
|
||||
|
||||
div.content {
|
||||
background-color: $tab-content-bg;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@ -898,6 +903,7 @@ div.card-overlay {
|
||||
|
||||
// Tabbed content
|
||||
.nav-tabs {
|
||||
background-color: $body-bg;
|
||||
.nav-link {
|
||||
&:hover {
|
||||
// Don't show a bottom-border on a hovered nav link because it overlaps with the .nav-tab border.
|
||||
@ -919,14 +925,6 @@ div.card-overlay {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: $spacer;
|
||||
background-color: $tab-content-bg;
|
||||
border-bottom: 1px solid $nav-tabs-border-color;
|
||||
|
||||
// Remove background and border when printing.
|
||||
@media print {
|
||||
background-color: var(--nbx-body-bg) !important;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Override masonry-layout styles when printing.
|
||||
|
@ -33,95 +33,6 @@ $darkest: #171b1d;
|
||||
|
||||
@import '../node_modules/bootstrap/scss/variables';
|
||||
|
||||
// Make color palette colors available as theme colors.
|
||||
// For example, you could use `.bg-red-100`, if needed.
|
||||
$theme-color-addons: (
|
||||
'darker': $darker,
|
||||
'darkest': $darkest,
|
||||
'gray': $gray-400,
|
||||
'gray-100': $gray-100,
|
||||
'gray-200': $gray-200,
|
||||
'gray-300': $gray-300,
|
||||
'gray-400': $gray-400,
|
||||
'gray-500': $gray-500,
|
||||
'gray-600': $gray-600,
|
||||
'gray-700': $gray-700,
|
||||
'gray-800': $gray-800,
|
||||
'gray-900': $gray-900,
|
||||
'red-100': $red-100,
|
||||
'red-200': $red-200,
|
||||
'red-300': $red-300,
|
||||
'red-400': $red-400,
|
||||
'red-500': $red-500,
|
||||
'red-600': $red-600,
|
||||
'red-700': $red-700,
|
||||
'red-800': $red-800,
|
||||
'red-900': $red-900,
|
||||
'yellow-100': $yellow-100,
|
||||
'yellow-200': $yellow-200,
|
||||
'yellow-300': $yellow-300,
|
||||
'yellow-400': $yellow-400,
|
||||
'yellow-500': $yellow-500,
|
||||
'yellow-600': $yellow-600,
|
||||
'yellow-700': $yellow-700,
|
||||
'yellow-800': $yellow-800,
|
||||
'yellow-900': $yellow-900,
|
||||
'green-100': $green-100,
|
||||
'green-200': $green-200,
|
||||
'green-300': $green-300,
|
||||
'green-400': $green-400,
|
||||
'green-500': $green-500,
|
||||
'green-600': $green-600,
|
||||
'green-700': $green-700,
|
||||
'green-800': $green-800,
|
||||
'green-900': $green-900,
|
||||
'blue-100': $blue-100,
|
||||
'blue-200': $blue-200,
|
||||
'blue-300': $blue-300,
|
||||
'blue-400': $blue-400,
|
||||
'blue-500': $blue-500,
|
||||
'blue-600': $blue-600,
|
||||
'blue-700': $blue-700,
|
||||
'blue-800': $blue-800,
|
||||
'blue-900': $blue-900,
|
||||
'cyan-100': $cyan-100,
|
||||
'cyan-200': $cyan-200,
|
||||
'cyan-300': $cyan-300,
|
||||
'cyan-400': $cyan-400,
|
||||
'cyan-500': $cyan-500,
|
||||
'cyan-600': $cyan-600,
|
||||
'cyan-700': $cyan-700,
|
||||
'cyan-800': $cyan-800,
|
||||
'cyan-900': $cyan-900,
|
||||
'indigo-100': $indigo-100,
|
||||
'indigo-200': $indigo-200,
|
||||
'indigo-300': $indigo-300,
|
||||
'indigo-400': $indigo-400,
|
||||
'indigo-500': $indigo-500,
|
||||
'indigo-600': $indigo-600,
|
||||
'indigo-700': $indigo-700,
|
||||
'indigo-800': $indigo-800,
|
||||
'indigo-900': $indigo-900,
|
||||
'purple-100': $purple-100,
|
||||
'purple-200': $purple-200,
|
||||
'purple-300': $purple-300,
|
||||
'purple-400': $purple-400,
|
||||
'purple-500': $purple-500,
|
||||
'purple-600': $purple-600,
|
||||
'purple-700': $purple-700,
|
||||
'purple-800': $purple-800,
|
||||
'purple-900': $purple-900,
|
||||
'pink-100': $pink-100,
|
||||
'pink-200': $pink-200,
|
||||
'pink-300': $pink-300,
|
||||
'pink-400': $pink-400,
|
||||
'pink-500': $pink-500,
|
||||
'pink-600': $pink-600,
|
||||
'pink-700': $pink-700,
|
||||
'pink-800': $pink-800,
|
||||
'pink-900': $pink-900,
|
||||
);
|
||||
|
||||
// This is the same value as the default from Bootstrap, but it needs to be in scope prior to
|
||||
// importing _variables.scss from Bootstrap.
|
||||
$btn-close-width: 1em;
|
||||
|
@ -3,6 +3,7 @@
|
||||
@use 'sass:map';
|
||||
@import './theme-base';
|
||||
|
||||
// Theme colors (BS5 classes)
|
||||
$primary: $blue-300;
|
||||
$secondary: $gray-500;
|
||||
$success: $green-300;
|
||||
@ -13,6 +14,7 @@ $light: $gray-300;
|
||||
$dark: $gray-500;
|
||||
|
||||
$theme-colors: (
|
||||
// BS5 theme colors
|
||||
'primary': $primary,
|
||||
'secondary': $secondary,
|
||||
'success': $success,
|
||||
@ -21,18 +23,23 @@ $theme-colors: (
|
||||
'danger': $danger,
|
||||
'light': $light,
|
||||
'dark': $dark,
|
||||
'red': $red-300,
|
||||
'yellow': $yellow-300,
|
||||
'green': $green-300,
|
||||
|
||||
// General-purpose palette
|
||||
'blue': $blue-300,
|
||||
'cyan': $cyan-300,
|
||||
'indigo': $indigo-300,
|
||||
'purple': $purple-300,
|
||||
'pink': $pink-300,
|
||||
'red': $red-300,
|
||||
'orange': $orange-300,
|
||||
'yellow': $yellow-300,
|
||||
'green': $green-300,
|
||||
'teal': $teal-300,
|
||||
'cyan': $cyan-300,
|
||||
'gray': $gray-300,
|
||||
'black': $black,
|
||||
'white': $white,
|
||||
);
|
||||
|
||||
$theme-colors: map-merge($theme-colors, $theme-color-addons);
|
||||
|
||||
// Gradient
|
||||
$gradient: linear-gradient(180deg, rgba($white, 0.15), rgba($white, 0));
|
||||
|
||||
@ -139,7 +146,7 @@ $nav-tabs-link-active-border-color: $gray-800 $gray-800 $nav-tabs-link-active-bg
|
||||
$nav-pills-link-active-color: $component-active-color;
|
||||
$nav-pills-link-active-bg: $component-active-bg;
|
||||
|
||||
$navbar-light-color: $gray-500;
|
||||
$navbar-light-color: $darkest;
|
||||
$navbar-light-toggler-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path stroke='#{$navbar-light-color}' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>");
|
||||
$navbar-light-toggler-border-color: $gray-700;
|
||||
|
||||
|
@ -2,28 +2,47 @@
|
||||
|
||||
@import './theme-base.scss';
|
||||
|
||||
$input-border-color: $gray-200;
|
||||
// Theme colors (BS5 classes)
|
||||
$primary: #337ab7;
|
||||
$secondary: $gray-600;
|
||||
$success: $green-500;
|
||||
$info: #54d6f0;
|
||||
$warning: $yellow-500;
|
||||
$danger: $red-500;
|
||||
$light: $gray-200;
|
||||
$dark: $gray-800;
|
||||
|
||||
$theme-colors: map-merge(
|
||||
$theme-colors,
|
||||
(
|
||||
'primary': #337ab7,
|
||||
'info': #54d6f0,
|
||||
'red': $red-500,
|
||||
'yellow': $yellow-500,
|
||||
'green': $green-500,
|
||||
$theme-colors: (
|
||||
// BS5 theme colors
|
||||
'primary': $primary,
|
||||
'secondary': $secondary,
|
||||
'success': $success,
|
||||
'info': $info,
|
||||
'warning': $warning,
|
||||
'danger': $danger,
|
||||
'light': $light,
|
||||
'dark': $dark,
|
||||
|
||||
// General-purpose palette
|
||||
'blue': $blue-500,
|
||||
'cyan': $cyan-500,
|
||||
'indigo': $indigo-500,
|
||||
'purple': $purple-500,
|
||||
'pink': $pink-500,
|
||||
)
|
||||
'red': $red-500,
|
||||
'orange': $orange-500,
|
||||
'yellow': $yellow-500,
|
||||
'green': $green-500,
|
||||
'teal': $teal-500,
|
||||
'cyan': $cyan-500,
|
||||
'gray': $gray-500,
|
||||
'black': $black,
|
||||
'white': $white,
|
||||
);
|
||||
|
||||
$theme-colors: map-merge($theme-colors, $theme-color-addons);
|
||||
|
||||
$light: $gray-200;
|
||||
|
||||
$navbar-light-color: $gray-100;
|
||||
|
||||
$card-cap-color: $gray-800;
|
||||
|
||||
$accordion-bg: transparent;
|
||||
|
@ -20,7 +20,7 @@
|
||||
</div>
|
||||
|
||||
{# Top bar #}
|
||||
<nav class="navbar navbar-light sticky-top flex-md-nowrap p-1 mb-3 search container-fluid border-bottom noprint">
|
||||
<nav class="navbar navbar-light sticky-top flex-md-nowrap p-1 mb-3 search container-fluid noprint">
|
||||
|
||||
{# Mobile Navigation #}
|
||||
<div class="nav-mobile">
|
||||
@ -103,6 +103,9 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# BS5 pop-up modals #}
|
||||
{% block modals %}{% endblock %}
|
||||
|
||||
{# Page footer #}
|
||||
<footer class="footer container-fluid">
|
||||
<div class="row align-items-center justify-content-between mx-0">
|
||||
|
@ -42,5 +42,8 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock modals %}
|
||||
|
@ -42,5 +42,8 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock modals %}
|
||||
|
@ -39,5 +39,8 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock modals %}
|
||||
|
@ -42,5 +42,8 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock modals %}
|
||||
|
@ -77,5 +77,8 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock modals %}
|
||||
|
@ -39,5 +39,8 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock modals %}
|
||||
|
@ -42,5 +42,8 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock modals %}
|
||||
|
@ -42,5 +42,8 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock modals %}
|
||||
|
@ -42,5 +42,8 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock modals %}
|
||||
|
@ -4,7 +4,7 @@
|
||||
{% render_errors form %}
|
||||
|
||||
{% block content %}
|
||||
<form action="." method="post">
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6 offset-md-3">
|
||||
|
@ -73,3 +73,5 @@
|
||||
|
||||
</div>
|
||||
{% endblock content-wrapper %}
|
||||
|
||||
{% block modals %}{% endblock %}
|
||||
|
@ -10,7 +10,7 @@
|
||||
{% block breadcrumbs %}
|
||||
<li class="breadcrumb-item"><a href="{% url 'extras:report_list' %}">Reports</a></li>
|
||||
<li class="breadcrumb-item"><a href="{% url 'extras:report_list' %}#module.{{ report.module }}">{{ report.module|bettertitle }}</a></li>
|
||||
{% endblock %}
|
||||
{% endblock breadcrumbs %}
|
||||
|
||||
{% block subtitle %}
|
||||
{% if report.description %}
|
||||
@ -18,14 +18,22 @@
|
||||
<div class="text-muted">{{ report.description|render_markdown }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock subtitle %}
|
||||
|
||||
{% block controls %}{% endblock %}
|
||||
{% block tabs %}{% endblock %}
|
||||
|
||||
{% block content-wrapper %}
|
||||
{% block tabs %}
|
||||
<ul class="nav nav-tabs px-3">
|
||||
<li class="nav-item" role="presentation">
|
||||
<a href="#report" role="tab" data-bs-toggle="tab" class="nav-link active">Report</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endblock tabs %}
|
||||
|
||||
{% block content %}
|
||||
<div role="tabpanel" class="tab-pane active" id="report">
|
||||
{% if perms.extras.run_report %}
|
||||
<div class="px-3 float-end noprint">
|
||||
<div class="float-end noprint">
|
||||
<form action="{% url 'extras:report' module=report.module name=report.class_name %}" method="post">
|
||||
{% csrf_token %}
|
||||
<button type="submit" name="_run" class="btn btn-primary">
|
||||
@ -38,7 +46,7 @@
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row px-3">
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
{% if report.result %}
|
||||
Last run: <a href="{% url 'extras:report_result' job_result_pk=report.result.pk %}">
|
||||
@ -47,4 +55,5 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% extends 'extras/report.html' %}
|
||||
|
||||
{% block content-wrapper %}
|
||||
<div class="row px-3">
|
||||
<div class="row p-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>
|
||||
|
@ -7,18 +7,18 @@
|
||||
|
||||
{% block object_identifier %}
|
||||
{{ script.full_name }}
|
||||
{% endblock %}
|
||||
{% endblock object_identifier %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}">Scripts</a></li>
|
||||
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}#module.{{ module }}">{{ module|bettertitle }}</a></li>
|
||||
{% endblock %}
|
||||
{% endblock breadcrumbs %}
|
||||
|
||||
{% block subtitle %}
|
||||
<div class="object-subtitle">
|
||||
<div class="text-muted">{{ script.Meta.description|render_markdown }}</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock subtitle %}
|
||||
|
||||
{% block controls %}{% endblock %}
|
||||
|
||||
@ -31,10 +31,9 @@
|
||||
<a href="#source" role="tab" data-bs-toggle="tab" class="nav-link">Source</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
{% endblock tabs %}
|
||||
|
||||
{% block content-wrapper %}
|
||||
<div class="tab-content">
|
||||
{% block content %}
|
||||
<div role="tabpanel" class="tab-pane active" id="run">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
@ -71,5 +70,4 @@
|
||||
<code class="h6 my-3 d-block">{{ script.filename }}</code>
|
||||
<pre class="block">{{ script.source }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content-wrapper %}
|
||||
{% endblock content %}
|
||||
|
@ -100,4 +100,8 @@
|
||||
<div class="tab-content">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock content-wrapper %}
|
||||
|
||||
{% block modals %}
|
||||
{% include 'inc/htmx_modal.html' %}
|
||||
{% endblock modals %}
|
||||
|
@ -1,9 +1,16 @@
|
||||
{% extends 'generic/confirmation_form.html' %}
|
||||
{% extends 'base/layout.html' %}
|
||||
{% load form_helpers %}
|
||||
|
||||
{% block title %}Delete {{ obj_type }}?{% endblock %}
|
||||
{% block title %}Delete {{ object_type }}?{% endblock %}
|
||||
|
||||
{% block message %}
|
||||
<p>Are you sure you want to <strong class="text-danger">delete</strong> {{ obj_type }} <strong>{{ obj }}</strong>?</p>
|
||||
{% block message_extra %}{% endblock %}
|
||||
{% endblock message %}
|
||||
{% block header %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="modal" tabindex="-1" style="display: block; position: static">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content" >
|
||||
{% include 'htmx/delete_form.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -133,6 +133,8 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Table config form #}
|
||||
{% table_config_form table table_name="ObjectTable" %}
|
||||
{% endblock content-wrapper %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table table_name="ObjectTable" %}
|
||||
{% endblock modals %}
|
||||
|
20
netbox/templates/htmx/delete_form.html
Normal file
20
netbox/templates/htmx/delete_form.html
Normal file
@ -0,0 +1,20 @@
|
||||
{% load form_helpers %}
|
||||
|
||||
<form action="{{ form_url }}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Confirm Deletion</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to <strong class="text-danger">delete</strong> {{ object_type }} <strong>{{ object }}</strong>?</p>
|
||||
{% render_form form %}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
{% if return_url %}
|
||||
<a href="{{ return_url }}" class="btn btn-outline-secondary">Cancel</a>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
{% endif %}
|
||||
<button type="submit" class="btn btn-danger">Delete</button>
|
||||
</div>
|
||||
</form>
|
7
netbox/templates/inc/htmx_modal.html
Normal file
7
netbox/templates/inc/htmx_modal.html
Normal file
@ -0,0 +1,7 @@
|
||||
<div class="modal fade" id="htmx-modal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content" id="htmx-modal-content">
|
||||
{# Dynamic content goes here #}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -37,5 +37,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock modals %}
|
||||
|
@ -35,5 +35,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock modals %}
|
||||
|
@ -35,5 +35,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock modals %}
|
||||
|
@ -35,5 +35,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock modals %}
|
||||
|
@ -37,5 +37,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock modals %}
|
||||
|
@ -1,5 +0,0 @@
|
||||
{% extends 'generic/object_delete.html' %}
|
||||
|
||||
{% block message_extra %}
|
||||
<p>Note: This will <strong>not</strong> delete any child prefixes or IP addresses.</p>
|
||||
{% endblock %}
|
@ -37,9 +37,3 @@
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
{% block content-wrapper %}
|
||||
<div class="tab-content">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -5,13 +5,14 @@
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% include 'inc/table_controls_htmx.html' with table_modal="VLANDevicesTable_config" %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body" id="object_list">
|
||||
{% include 'htmx/table.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
{% endblock content %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
{% endblock modals %}
|
||||
|
@ -5,13 +5,14 @@
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% include 'inc/table_controls_htmx.html' with table_modal="VLANVirtualMachinesTable_config" %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body" id="object_list">
|
||||
{% include 'htmx/table.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
{% endblock content %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
{% endblock modals %}
|
||||
|
@ -6,13 +6,11 @@
|
||||
<form action="{% url 'virtualization:cluster_remove_devices' pk=object.pk %}" method="post">
|
||||
{% csrf_token %}
|
||||
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceTable_config" %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body" id="object_list">
|
||||
{% include 'htmx/table.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="noprint bulk-buttons">
|
||||
<div class="bulk-button-group">
|
||||
{% if perms.virtualization.change_cluster %}
|
||||
@ -23,5 +21,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
{% endblock modals %}
|
||||
|
@ -6,13 +6,11 @@
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% include 'inc/table_controls_htmx.html' with table_modal="VirtualMachineTable_config" %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body" id="object_list">
|
||||
{% include 'htmx/table.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="noprint bulk-buttons">
|
||||
<div class="bulk-button-group">
|
||||
{% if perms.virtualization.change_virtualmachine %}
|
||||
@ -28,5 +26,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
{% endblock modals %}
|
||||
|
@ -37,5 +37,8 @@
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
|
||||
{% block modals %}
|
||||
{% table_config_form table %}
|
||||
{% endblock %}
|
||||
{% endblock modals %}
|
||||
|
@ -168,11 +168,11 @@ class ContentTypeChoiceMixin:
|
||||
|
||||
|
||||
class ContentTypeChoiceField(ContentTypeChoiceMixin, forms.ModelChoiceField):
|
||||
pass
|
||||
widget = widgets.StaticSelect
|
||||
|
||||
|
||||
class ContentTypeMultipleChoiceField(ContentTypeChoiceMixin, forms.ModelMultipleChoiceField):
|
||||
pass
|
||||
widget = widgets.StaticSelectMultiple
|
||||
|
||||
|
||||
#
|
||||
|
@ -14,7 +14,6 @@ __all__ = (
|
||||
'BulkEditNullBooleanSelect',
|
||||
'ClearableFileInput',
|
||||
'ColorSelect',
|
||||
'ContentTypeSelect',
|
||||
'DatePicker',
|
||||
'DateTimePicker',
|
||||
'NumericArrayField',
|
||||
@ -110,15 +109,6 @@ class SelectWithPK(StaticSelect):
|
||||
option_template_name = 'widgets/select_option_with_pk.html'
|
||||
|
||||
|
||||
class ContentTypeSelect(StaticSelect):
|
||||
"""
|
||||
Appends an `api-value` attribute equal to the slugified model name for each ContentType. For example:
|
||||
<option value="37" api-value="console-server-port">console server port</option>
|
||||
This attribute can be used to reference the relevant API endpoint for a particular ContentType.
|
||||
"""
|
||||
option_template_name = 'widgets/select_contenttype.html'
|
||||
|
||||
|
||||
class SelectSpeedWidget(forms.NumberInput):
|
||||
"""
|
||||
Speed field with dropdown selections for convenience.
|
||||
|
@ -1,3 +1,9 @@
|
||||
<a href="{{ url }}" class="btn btn-sm btn-danger" role="button">
|
||||
<a href="#"
|
||||
hx-get="{{ url }}"
|
||||
hx-target="#htmx-modal-content"
|
||||
class="btn btn-sm btn-danger"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#htmx-modal"
|
||||
>
|
||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
||||
</a>
|
||||
|
@ -1 +0,0 @@
|
||||
<option value="{{ widget.value }}"{% include "django/forms/widgets/attrs.html" %}{% if widget.value %} api-value="{{ widget.label|slugify }}"{% endif %}>{{ widget.label.label|default:widget.label|capfirst }}</option>
|
@ -80,6 +80,12 @@ class ClusterTable(BaseTable):
|
||||
name = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
type = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
group = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
tenant = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
|
@ -3,7 +3,7 @@ from django import forms
|
||||
from dcim.choices import LinkStatusChoices
|
||||
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
|
||||
from ipam.models import VLAN
|
||||
from utilities.forms import DynamicModelChoiceField
|
||||
from utilities.forms import add_blank_choice, DynamicModelChoiceField
|
||||
from wireless.choices import *
|
||||
from wireless.constants import SSID_MAX_LENGTH
|
||||
from wireless.models import *
|
||||
@ -45,24 +45,27 @@ class WirelessLANBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
vlan = DynamicModelChoiceField(
|
||||
queryset=VLAN.objects.all(),
|
||||
required=False,
|
||||
label='VLAN'
|
||||
)
|
||||
ssid = forms.CharField(
|
||||
max_length=SSID_MAX_LENGTH,
|
||||
required=False
|
||||
required=False,
|
||||
label='SSID'
|
||||
)
|
||||
description = forms.CharField(
|
||||
required=False
|
||||
)
|
||||
auth_type = forms.ChoiceField(
|
||||
choices=WirelessAuthTypeChoices,
|
||||
choices=add_blank_choice(WirelessAuthTypeChoices),
|
||||
required=False
|
||||
)
|
||||
auth_cipher = forms.ChoiceField(
|
||||
choices=WirelessAuthCipherChoices,
|
||||
choices=add_blank_choice(WirelessAuthCipherChoices),
|
||||
required=False
|
||||
)
|
||||
auth_psk = forms.CharField(
|
||||
required=False
|
||||
required=False,
|
||||
label='Pre-shared key'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@ -76,25 +79,27 @@ class WirelessLinkBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
)
|
||||
ssid = forms.CharField(
|
||||
max_length=SSID_MAX_LENGTH,
|
||||
required=False
|
||||
required=False,
|
||||
label='SSID'
|
||||
)
|
||||
status = forms.ChoiceField(
|
||||
choices=LinkStatusChoices,
|
||||
choices=add_blank_choice(LinkStatusChoices),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
required=False
|
||||
)
|
||||
auth_type = forms.ChoiceField(
|
||||
choices=WirelessAuthTypeChoices,
|
||||
choices=add_blank_choice(WirelessAuthTypeChoices),
|
||||
required=False
|
||||
)
|
||||
auth_cipher = forms.ChoiceField(
|
||||
choices=WirelessAuthCipherChoices,
|
||||
choices=add_blank_choice(WirelessAuthCipherChoices),
|
||||
required=False
|
||||
)
|
||||
auth_psk = forms.CharField(
|
||||
required=False
|
||||
required=False,
|
||||
label='Pre-shared key'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -1,4 +1,4 @@
|
||||
Django==3.2.10
|
||||
Django==3.2.11
|
||||
django-cors-headers==3.10.1
|
||||
django-debug-toolbar==3.2.4
|
||||
django-filter==21.1
|
||||
@ -18,7 +18,7 @@ gunicorn==20.1.0
|
||||
Jinja2==3.0.3
|
||||
Markdown==3.3.6
|
||||
markdown-include==0.6.0
|
||||
mkdocs-material==8.1.3
|
||||
mkdocs-material==8.1.4
|
||||
netaddr==0.8.0
|
||||
Pillow==8.4.0
|
||||
psycopg2-binary==2.9.3
|
||||
|
Loading…
Reference in New Issue
Block a user