Compare commits

...

60 Commits

Author SHA1 Message Date
Jeremy Stretch
63c6687a87 Merge pull request #10026 from netbox-community/develop
Release v3.2.9
2022-08-16 12:29:59 -04:00
jeremystretch
e01b7951f2 Release v3.2.9 2022-08-16 11:50:14 -04:00
jeremystretch
8c220cc04f Fixes #9491: Remove button for adding inventory item templates to module type components 2022-08-16 11:39:51 -04:00
jeremystretch
9e9e90f88b Closes #9933: Add DOCSIS interface type 2022-08-16 10:11:40 -04:00
jeremystretch
6d328a82e9 Cleanup for #9505 2022-08-16 10:04:47 -04:00
jeremystretch
dedee0f9d9 #9979: Fix fallback to default value 2022-08-16 09:53:13 -04:00
jeremystretch
0ef1bc8490 Clean up bulk edit buttons 2022-08-16 09:49:51 -04:00
jeremystretch
30ab1e5a5e Changelog for #8723, #9505, #9979 2022-08-16 09:14:19 -04:00
Jeremy Stretch
14821eed44 Merge pull request #9639 from cpund/8723-branch
PR for #8723
2022-08-16 09:10:24 -04:00
Jeremy Stretch
c8ecee9682 Merge pull request #9712 from renatoalmeidaoliveira/develop
Include Network information in Prefix Template Issue:#9505
2022-08-16 09:06:56 -04:00
Jeremy Stretch
a8dd809f8e Merge pull request #9981 from chcon/develop
re-enable markdown in custom columns
2022-08-16 08:57:37 -04:00
Christoph Schneider
15f4b1fd5d add newline 2022-08-13 14:02:26 +02:00
Christoph Schneider
36491b13d8 remove class definition 2022-08-13 14:01:07 +02:00
Christoph Schneider
ac540b6183 remove import 2022-08-13 13:59:19 +02:00
Christoph Schneider
6f09d94e2a remove commented line 2022-08-13 13:56:51 +02:00
Christoph Schneider
f942216f3f re-enable markup in longtext custom columns 2022-08-13 13:54:38 +02:00
jeremystretch
ca0b21bef5 Closes #9980: Use standard table controls template for device interfaces list 2022-08-12 11:25:03 -04:00
jeremystretch
e4fa8af47f Changelog for #8595 2022-08-12 10:48:16 -04:00
Jeremy Stretch
6cf898fa13 Merge pull request #9982 from DorianXGH/pon_if_types
Closes #8595: Added new PON interface types
2022-08-12 10:43:15 -04:00
jeremystretch
41ad9b242c Fixes #9986: Workaround for upstream timezone data bug 2022-08-12 10:12:01 -04:00
Dorian Bourgeoisat
693ad700e8 Swapping NG-PON2 as main name instead of TWDM-PON 2022-08-12 00:49:13 +02:00
Craig Pund
5873ad95dc handle objects without names 2022-08-11 15:16:42 -04:00
Craig Pund
6a687a9ed1 not necessary to prefetch 2022-08-11 15:16:01 -04:00
jeremystretch
e2d5313940 Changelog for #9857 2022-08-11 13:02:37 -04:00
Jeremy Stretch
a59169fa96 Merge pull request #9964 from jsenecal/feat9857
Add a "clear" button for quick search
2022-08-11 12:20:33 -04:00
Jonathan Senecal
f74b7aa7ac Add a "clear" button for quick search
Fixes #9857
2022-08-11 08:26:25 -04:00
Christoph Schneider
9a80a491c9 re-enable markdown in custom columns 2022-08-11 14:11:41 +02:00
jeremystretch
aabe8f7c5b Changelog for #9625 2022-08-10 16:18:30 -04:00
Jeremy Stretch
10af44c12a Merge pull request #9970 from barnebyte-timewarp/develop
Closes #9625: Add Contact Phone/Email to quick view panes to save time
2022-08-10 16:16:04 -04:00
jeremystretch
a9aaa8939c Closes #9161: Pretty print JSON custom field data when editing 2022-08-10 16:12:04 -04:00
jeremystretch
8f1e70f01d Fixes #9961: Correct typo 2022-08-10 15:24:45 -04:00
Dorian Bourgeoisat
1c7ef73d1f Closes #8595: Added new PON interface types 2022-08-10 15:33:33 +02:00
Barnabas Lovas
c24f1f14ec Closes #9625: Add Contact Phone/Email to quick view panes to save time 2022-08-10 13:22:58 +02:00
Jeremy Stretch
b318b79027 Merge pull request #9958 from threadedstream/fix_typo_virt_filtersets
fix typo in virtualization/forms/filtersets.py
2022-08-09 14:29:58 -04:00
gildarov
c7faca9480 fix typo in virtualization/forms/filtersets.py 2022-08-09 11:56:19 +03:00
jeremystretch
064d7f3bd0 PRVB 2022-08-08 15:34:13 -04:00
Jeremy Stretch
f1877c0c5f Merge pull request #9955 from netbox-community/develop
Release v3.2.8
2022-08-08 15:32:38 -04:00
jeremystretch
ce7fb8ab17 Release v3.2.8 2022-08-08 15:17:36 -04:00
jeremystretch
caca074161 Fixes #9950: Prevent redirection to arbitrary URLs via 'next' parameter on login URL 2022-08-08 14:21:42 -04:00
jeremystretch
8721ad987c Fixes #9952: Prevent InvalidMove when attempting to assign a nested child object as parent 2022-08-08 12:22:22 -04:00
jeremystretch
876251c1cf Fixes #9948: Fix TypeError exception when requesting API tokens list as non-authenticated user 2022-08-08 12:22:01 -04:00
jeremystretch
36ac83a319 Fixes #9949: Fix KeyError exception resulting from invalid API token provisioning request 2022-08-08 11:43:27 -04:00
jeremystretch
90317adae7 Clean up usages of mark_safe() 2022-08-08 10:47:07 -04:00
jeremystretch
135543683d Changelog for #9919 2022-08-08 10:24:49 -04:00
Jeremy Stretch
38350a1023 Merge pull request #9940 from osamu-kj/develop
Fixes #9919: XSS Bypass in custom fields displayed in tables
2022-08-08 10:10:11 -04:00
jeremystretch
0e1947bc4b PEP8 fix 2022-08-08 09:58:58 -04:00
Osamu-kj
7141fc8eb0 Custom fields - removed the debug lines 2022-08-06 17:17:43 +02:00
Osamu-kj
db38ed4f19 Fixed the XSS protection code inside custom fields 2022-08-06 15:10:31 +02:00
Osamu-kj
f874e9932d Added HTML Sanitization to the custom fields 2022-08-04 18:52:25 +02:00
jeremystretch
a2e84dd279 Changelog for #9827, #9906 2022-08-03 15:22:51 -04:00
Jeremy Stretch
a397ce234a Merge pull request #9850 from sleepinggenius2/issue_9827
Adds patterned_fields support for bulk component creation
2022-08-03 15:22:16 -04:00
Jeremy Stretch
3694e5e846 Merge pull request #9911 from oasys/9906-support-color-on-frontrearport-import-export
Fixes #9906 import/export front/rearport color field for module- and device-types
2022-08-03 14:58:02 -04:00
Jason Lavoie
c6e25f068d import/export color field on front- and rear-ports for module-types and device-types
Closes: #9906

- Adds `color` field to front and rearport template import forms
- Adds `color` field to `to_yaml` export for front and rearport
  templates
2022-08-03 09:22:06 -04:00
sleepinggenius2
bbf4b906e4 Adds patterned_fields support for bulk components 2022-07-26 17:16:03 -04:00
Renato Almeida de Oliveira
7d6882bec2 Change display to Modal 2022-07-23 20:24:33 +00:00
Renato Almeida de Oliveira
e135f8e74d Include Network information in Prefix Template Issue:#9505 2022-07-13 02:49:14 +00:00
Craig Pund
76e634330f draft for error handling on device with no name 2022-06-30 16:00:03 -04:00
Craig Pund
ef03a2f383 fix return url to account 4 filtered device lists 2022-06-30 14:13:56 -04:00
Craig Pund
5dff7433e8 add bulk device rename button to device_list 2022-06-30 01:38:53 -04:00
Craig Pund
fa014fcbf0 add device bulk rename view and url 2022-06-30 01:38:38 -04:00
52 changed files with 640 additions and 394 deletions

View File

@@ -14,7 +14,7 @@ body:
attributes: attributes:
label: NetBox version label: NetBox version
description: What version of NetBox are you currently running? description: What version of NetBox are you currently running?
placeholder: v3.2.7 placeholder: v3.2.9
validations: validations:
required: true required: true
- type: dropdown - type: dropdown

View File

@@ -14,7 +14,7 @@ body:
attributes: attributes:
label: NetBox version label: NetBox version
description: What version of NetBox are you currently running? description: What version of NetBox are you currently running?
placeholder: v3.2.7 placeholder: v3.2.9
validations: validations:
required: true required: true
- type: dropdown - type: dropdown

View File

@@ -4,7 +4,7 @@ bleach
# The Python web framework on which NetBox is built # The Python web framework on which NetBox is built
# https://github.com/django/django # https://github.com/django/django
Django Django<4.1
# Django middleware which permits cross-domain API requests # Django middleware which permits cross-domain API requests
# https://github.com/OttoYiu/django-cors-headers # https://github.com/OttoYiu/django-cors-headers

View File

@@ -1,3 +1,3 @@
## Front Ports ## Front Ports
Front ports are pass-through ports used to represent physical cable connections that comprise part of a longer path. For example, the ports on the front face of a UTP patch panel would be modeled in NetBox as front ports. Each port is assigned a physical type, and must be mapped to a specific rear port on the same device. A single rear port may be mapped to multiple rear ports, using numeric positions to annotate the specific alignment of each. Front ports are pass-through ports used to represent physical cable connections that comprise part of a longer path. For example, the ports on the front face of a UTP patch panel would be modeled in NetBox as front ports. Each port is assigned a physical type, and must be mapped to a specific rear port on the same device. A single rear port may be mapped to multiple front ports, using numeric positions to annotate the specific alignment of each.

View File

@@ -1,6 +1,26 @@
# NetBox v3.2 # NetBox v3.2
## v3.2.8 (FUTURE) ## v3.2.9 (2022-08-16)
### Enhancements
* [#8595](https://github.com/netbox-community/netbox/issues/8595) - Add PON interface types
* [#8723](https://github.com/netbox-community/netbox/issues/8723) - Enable bulk renaming of devices
* [#9161](https://github.com/netbox-community/netbox/issues/9161) - Pretty print JSON custom field data when editing
* [#9505](https://github.com/netbox-community/netbox/issues/9505) - Display extra addressing details for IPv4 prefixes
* [#9625](https://github.com/netbox-community/netbox/issues/9625) - Add phone & email details to contacts panel
* [#9857](https://github.com/netbox-community/netbox/issues/9857) - Add clear button to quick search fields
* [#9933](https://github.com/netbox-community/netbox/issues/9933) - Add DOCSIS interface type
### Bug Fixes
* [#9491](https://github.com/netbox-community/netbox/issues/9491) - Remove button for adding inventory item templates to module type components
* [#9979](https://github.com/netbox-community/netbox/issues/9979) - Fix Markdown rendering for custom fields in table columns
* [#9986](https://github.com/netbox-community/netbox/issues/9986) - Workaround for upstream timezone data bug
---
## v3.2.8 (2022-08-08)
### Enhancements ### Enhancements
@@ -11,13 +31,20 @@
* [#9881](https://github.com/netbox-community/netbox/issues/9881) - Increase granularity in utilization graph values * [#9881](https://github.com/netbox-community/netbox/issues/9881) - Increase granularity in utilization graph values
* [#9882](https://github.com/netbox-community/netbox/issues/9882) - Add manufacturer column to modules table * [#9882](https://github.com/netbox-community/netbox/issues/9882) - Add manufacturer column to modules table
* [#9883](https://github.com/netbox-community/netbox/issues/9883) - Linkify location column in power panels table * [#9883](https://github.com/netbox-community/netbox/issues/9883) - Linkify location column in power panels table
* [#9906](https://github.com/netbox-community/netbox/issues/9906) - Include `color` attribute in front & rear port YAML import/export
### Bug Fixes ### Bug Fixes
* [#9827](https://github.com/netbox-community/netbox/issues/9827) - Fix assignment of module bay position during bulk creation
* [#9871](https://github.com/netbox-community/netbox/issues/9871) - Fix utilization graph value alignments * [#9871](https://github.com/netbox-community/netbox/issues/9871) - Fix utilization graph value alignments
* [#9884](https://github.com/netbox-community/netbox/issues/9884) - Prevent querying assigned VRF on prefix object init * [#9884](https://github.com/netbox-community/netbox/issues/9884) - Prevent querying assigned VRF on prefix object init
* [#9885](https://github.com/netbox-community/netbox/issues/9885) - Fix child prefix counts when editing/deleting aggregates in bulk * [#9885](https://github.com/netbox-community/netbox/issues/9885) - Fix child prefix counts when editing/deleting aggregates in bulk
* [#9891](https://github.com/netbox-community/netbox/issues/9891) - Ensure consistent ordering for tags during object serialization * [#9891](https://github.com/netbox-community/netbox/issues/9891) - Ensure consistent ordering for tags during object serialization
* [#9919](https://github.com/netbox-community/netbox/issues/9919) - Fix potential XSS avenue via linked objects in tables
* [#9948](https://github.com/netbox-community/netbox/issues/9948) - Fix TypeError exception when requesting API tokens list as non-authenticated user
* [#9949](https://github.com/netbox-community/netbox/issues/9949) - Fix KeyError exception resulting from invalid API token provisioning request
* [#9950](https://github.com/netbox-community/netbox/issues/9950) - Prevent redirection to arbitrary URLs via `next` parameter on login URL
* [#9952](https://github.com/netbox-community/netbox/issues/9952) - Prevent InvalidMove when attempting to assign a nested child object as parent
--- ---

View File

@@ -814,6 +814,17 @@ class InterfaceTypeChoices(ChoiceSet):
# ATM/DSL # ATM/DSL
TYPE_XDSL = 'xdsl' TYPE_XDSL = 'xdsl'
# Coaxial
TYPE_DOCSIS = 'docsis'
# PON
TYPE_GPON = 'gpon'
TYPE_XG_PON = 'xg-pon'
TYPE_XGS_PON = 'xgs-pon'
TYPE_NG_PON2 = 'ng-pon2'
TYPE_EPON = 'epon'
TYPE_10G_EPON = '10g-epon'
# Stacking # Stacking
TYPE_STACKWISE = 'cisco-stackwise' TYPE_STACKWISE = 'cisco-stackwise'
TYPE_STACKWISE_PLUS = 'cisco-stackwise-plus' TYPE_STACKWISE_PLUS = 'cisco-stackwise-plus'
@@ -950,6 +961,23 @@ class InterfaceTypeChoices(ChoiceSet):
(TYPE_XDSL, 'xDSL'), (TYPE_XDSL, 'xDSL'),
) )
), ),
(
'Coaxial',
(
(TYPE_DOCSIS, 'DOCSIS'),
)
),
(
'PON',
(
(TYPE_GPON, 'GPON (2.5 Gbps / 1.25 Gps)'),
(TYPE_XG_PON, 'XG-PON (10 Gbps / 2.5 Gbps)'),
(TYPE_XGS_PON, 'XGS-PON (10 Gbps)'),
(TYPE_NG_PON2, 'NG-PON2 (TWDM-PON) (4x10 Gbps)'),
(TYPE_EPON, 'EPON (1 Gbps)'),
(TYPE_10G_EPON, '10G-EPON (10 Gbps)'),
)
),
( (
'Stacking', 'Stacking',
( (

View File

@@ -146,7 +146,7 @@ class FrontPortTemplateImportForm(ComponentTemplateImportForm):
class Meta: class Meta:
model = FrontPortTemplate model = FrontPortTemplate
fields = [ fields = [
'device_type', 'module_type', 'name', 'type', 'rear_port', 'rear_port_position', 'label', 'description', 'device_type', 'module_type', 'name', 'type', 'color', 'rear_port', 'rear_port_position', 'label', 'description',
] ]
@@ -158,7 +158,7 @@ class RearPortTemplateImportForm(ComponentTemplateImportForm):
class Meta: class Meta:
model = RearPortTemplate model = RearPortTemplate
fields = [ fields = [
'device_type', 'module_type', 'name', 'type', 'positions', 'label', 'description', 'device_type', 'module_type', 'name', 'type', 'color', 'positions', 'label', 'description',
] ]

View File

@@ -462,6 +462,7 @@ class FrontPortTemplate(ModularComponentTemplateModel):
return { return {
'name': self.name, 'name': self.name,
'type': self.type, 'type': self.type,
'color': self.color,
'rear_port': self.rear_port.name, 'rear_port': self.rear_port.name,
'rear_port_position': self.rear_port_position, 'rear_port_position': self.rear_port_position,
'label': self.label, 'label': self.label,
@@ -511,6 +512,7 @@ class RearPortTemplate(ModularComponentTemplateModel):
return { return {
'name': self.name, 'name': self.name,
'type': self.type, 'type': self.type,
'color': self.color,
'positions': self.positions, 'positions': self.positions,
'label': self.label, 'label': self.label,
'description': self.description, 'description': self.description,

View File

@@ -100,7 +100,7 @@ LOCATION_BUTTONS = """
MODULAR_COMPONENT_TEMPLATE_BUTTONS = """ MODULAR_COMPONENT_TEMPLATE_BUTTONS = """
{% load helpers %} {% load helpers %}
{% if perms.dcim.add_inventoryitemtemplate %} {% if perms.dcim.add_inventoryitemtemplate and record.device_type_id %}
<a href="{% url 'dcim:inventoryitemtemplate_add' %}?device_type={{ record.device_type_id }}&component_type={{ record|content_type_id }}&component_id={{ record.pk }}&return_url={{ request.path }}" title="Add inventory item" class="btn btn-primary btn-sm"> <a href="{% url 'dcim:inventoryitemtemplate_add' %}?device_type={{ record.device_type_id }}&component_type={{ record|content_type_id }}&component_id={{ record.pk }}&return_url={{ request.path }}" title="Add inventory item" class="btn btn-primary btn-sm">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> <i class="mdi mdi-plus-thick" aria-hidden="true"></i>
</a> </a>

View File

@@ -248,6 +248,7 @@ urlpatterns = [
path('devices/import/', views.DeviceBulkImportView.as_view(), name='device_import'), path('devices/import/', views.DeviceBulkImportView.as_view(), name='device_import'),
path('devices/import/child-devices/', views.ChildDeviceBulkImportView.as_view(), name='device_import_child'), path('devices/import/child-devices/', views.ChildDeviceBulkImportView.as_view(), name='device_import_child'),
path('devices/edit/', views.DeviceBulkEditView.as_view(), name='device_bulk_edit'), path('devices/edit/', views.DeviceBulkEditView.as_view(), name='device_bulk_edit'),
path('devices/rename/', views.DeviceBulkRenameView.as_view(), name='device_bulk_rename'),
path('devices/delete/', views.DeviceBulkDeleteView.as_view(), name='device_bulk_delete'), path('devices/delete/', views.DeviceBulkDeleteView.as_view(), name='device_bulk_delete'),
path('devices/<int:pk>/', views.DeviceView.as_view(), name='device'), path('devices/<int:pk>/', views.DeviceView.as_view(), name='device'),
path('devices/<int:pk>/edit/', views.DeviceEditView.as_view(), name='device_edit'), path('devices/<int:pk>/edit/', views.DeviceEditView.as_view(), name='device_edit'),

View File

@@ -1784,6 +1784,12 @@ class DeviceBulkDeleteView(generic.BulkDeleteView):
table = tables.DeviceTable table = tables.DeviceTable
class DeviceBulkRenameView(generic.BulkRenameView):
queryset = Device.objects.all()
filterset = filtersets.DeviceFilterSet
table = tables.DeviceTable
# #
# Devices # Devices
# #
@@ -2707,6 +2713,7 @@ class DeviceBulkAddModuleBayView(generic.BulkComponentCreateView):
filterset = filtersets.DeviceFilterSet filterset = filtersets.DeviceFilterSet
table = tables.DeviceTable table = tables.DeviceTable
default_return_url = 'dcim:device_list' default_return_url = 'dcim:device_list'
patterned_fields = ('name', 'label', 'position')
class DeviceBulkAddDeviceBayView(generic.BulkComponentCreateView): class DeviceBulkAddDeviceBayView(generic.BulkComponentCreateView):
@@ -3082,7 +3089,7 @@ class VirtualChassisAddMemberView(ObjectPermissionRequiredMixin, GetReturnURLMix
if membership_form.is_valid(): if membership_form.is_valid():
membership_form.save() membership_form.save()
msg = 'Added member <a href="{}">{}</a>'.format(device.get_absolute_url(), escape(device)) msg = f'Added member <a href="{device.get_absolute_url()}">{escape(device)}</a>'
messages.success(request, mark_safe(msg)) messages.success(request, mark_safe(msg))
if '_addanother' in request.POST: if '_addanother' in request.POST:
@@ -3127,8 +3134,7 @@ class VirtualChassisRemoveMemberView(ObjectPermissionRequiredMixin, GetReturnURL
# Protect master device from being removed # Protect master device from being removed
virtual_chassis = VirtualChassis.objects.filter(master=device).first() virtual_chassis = VirtualChassis.objects.filter(master=device).first()
if virtual_chassis is not None: if virtual_chassis is not None:
msg = 'Unable to remove master device {} from the virtual chassis.'.format(escape(device)) messages.error(request, f'Unable to remove master device {device} from the virtual chassis.')
messages.error(request, mark_safe(msg))
return redirect(device.get_absolute_url()) return redirect(device.get_absolute_url())
if form.is_valid(): if form.is_valid():

View File

@@ -18,7 +18,7 @@ from netbox.models.features import ExportTemplatesMixin, WebhooksMixin
from utilities import filters from utilities import filters
from utilities.forms import ( from utilities.forms import (
CSVChoiceField, CSVMultipleChoiceField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, CSVChoiceField, CSVMultipleChoiceField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
LaxURLField, StaticSelectMultiple, StaticSelect, add_blank_choice, JSONField, LaxURLField, StaticSelectMultiple, StaticSelect, add_blank_choice,
) )
from utilities.querysets import RestrictedQuerySet from utilities.querysets import RestrictedQuerySet
from utilities.validators import validate_regex from utilities.validators import validate_regex
@@ -343,7 +343,7 @@ class CustomField(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
# JSON # JSON
elif self.type == CustomFieldTypeChoices.TYPE_JSON: elif self.type == CustomFieldTypeChoices.TYPE_JSON:
field = forms.JSONField(required=required, initial=initial) field = JSONField(required=required, initial=initial)
# Object # Object
elif self.type == CustomFieldTypeChoices.TYPE_OBJECT: elif self.type == CustomFieldTypeChoices.TYPE_OBJECT:

View File

@@ -89,9 +89,9 @@ class NestedGroupModel(NetBoxFeatureSet, MPTTModel):
super().clean() super().clean()
# An MPTT model cannot be its own parent # An MPTT model cannot be its own parent
if self.pk and self.parent_id == self.pk: if self.pk and self.parent and self.parent in self.get_descendants(include_self=True):
raise ValidationError({ raise ValidationError({
"parent": "Cannot assign self as parent." "parent": f"Cannot assign self or child {self._meta.verbose_name} as parent."
}) })

View File

@@ -29,7 +29,7 @@ django.utils.encoding.force_text = force_str
# Environment setup # Environment setup
# #
VERSION = '3.2.8-dev' VERSION = '3.2.9'
# Hostname # Hostname
HOSTNAME = platform.node() HOSTNAME = platform.node()

View File

@@ -7,12 +7,14 @@ from django.contrib.auth.models import AnonymousUser
from django.db.models import DateField, DateTimeField from django.db.models import DateField, DateTimeField
from django.template import Context, Template from django.template import Context, Template
from django.urls import reverse from django.urls import reverse
from django.utils.html import escape
from django.utils.formats import date_format from django.utils.formats import date_format
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django_tables2.columns import library from django_tables2.columns import library
from django_tables2.utils import Accessor from django_tables2.utils import Accessor
from extras.choices import CustomFieldTypeChoices from extras.choices import CustomFieldTypeChoices
from utilities.templatetags.builtins.filters import render_markdown
from utilities.utils import content_type_identifier, content_type_name, get_viewname from utilities.utils import content_type_identifier, content_type_name, get_viewname
__all__ = ( __all__ = (
@@ -426,10 +428,10 @@ class CustomFieldColumn(tables.Column):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@staticmethod @staticmethod
def _likify_item(item): def _linkify_item(item):
if hasattr(item, 'get_absolute_url'): if hasattr(item, 'get_absolute_url'):
return f'<a href="{item.get_absolute_url()}">{item}</a>' return f'<a href="{item.get_absolute_url()}">{escape(item)}</a>'
return item return escape(item)
def render(self, value): def render(self, value):
if self.customfield.type == CustomFieldTypeChoices.TYPE_BOOLEAN and value is True: if self.customfield.type == CustomFieldTypeChoices.TYPE_BOOLEAN and value is True:
@@ -437,16 +439,18 @@ class CustomFieldColumn(tables.Column):
if self.customfield.type == CustomFieldTypeChoices.TYPE_BOOLEAN and value is False: if self.customfield.type == CustomFieldTypeChoices.TYPE_BOOLEAN and value is False:
return mark_safe('<i class="mdi mdi-close-thick text-danger"></i>') return mark_safe('<i class="mdi mdi-close-thick text-danger"></i>')
if self.customfield.type == CustomFieldTypeChoices.TYPE_URL: if self.customfield.type == CustomFieldTypeChoices.TYPE_URL:
return mark_safe(f'<a href="{value}">{value}</a>') return mark_safe(f'<a href="{escape(value)}">{escape(value)}</a>')
if self.customfield.type == CustomFieldTypeChoices.TYPE_MULTISELECT: if self.customfield.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
return ', '.join(v for v in value) return ', '.join(v for v in value)
if self.customfield.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT: if self.customfield.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT:
return mark_safe(', '.join([ return mark_safe(', '.join(
self._likify_item(obj) for obj in self.customfield.deserialize(value) self._linkify_item(obj) for obj in self.customfield.deserialize(value)
])) ))
if self.customfield.type == CustomFieldTypeChoices.TYPE_LONGTEXT and value:
return render_markdown(value)
if value is not None: if value is not None:
obj = self.customfield.deserialize(value) obj = self.customfield.deserialize(value)
return mark_safe(self._likify_item(obj)) return mark_safe(self._linkify_item(obj))
return self.default return self.default
def value(self, value): def value(self, value):

View File

@@ -633,7 +633,7 @@ class BulkRenameView(GetReturnURLMixin, BaseMultiObjectView):
replace = form.cleaned_data['replace'] replace = form.cleaned_data['replace']
if form.cleaned_data['use_regex']: if form.cleaned_data['use_regex']:
try: try:
obj.new_name = re.sub(find, replace, obj.name) obj.new_name = re.sub(find, replace, obj.name or '')
# Catch regex group reference errors # Catch regex group reference errors
except re.error: except re.error:
obj.new_name = obj.name obj.new_name = obj.name
@@ -795,6 +795,7 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
model_form = None model_form = None
filterset = None filterset = None
table = None table = None
patterned_fields = ('name', 'label')
def get_required_permission(self): def get_required_permission(self):
return f'dcim.add_{self.queryset.model._meta.model_name}' return f'dcim.add_{self.queryset.model._meta.model_name}'
@@ -830,16 +831,16 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
for obj in data['pk']: for obj in data['pk']:
names = data['name_pattern'] pattern_count = len(data[f'{self.patterned_fields[0]}_pattern'])
labels = data['label_pattern'] if 'label_pattern' in data else None for i in range(pattern_count):
for i, name in enumerate(names):
label = labels[i] if labels else None
component_data = { component_data = {
self.parent_field: obj.pk, self.parent_field: obj.pk
'name': name,
'label': label
} }
for field_name in self.patterned_fields:
if data.get(f'{field_name}_pattern'):
component_data[field_name] = data[f'{field_name}_pattern'][i]
component_data.update(data) component_data.update(data)
component_form = self.model_form(component_data) component_form = self.model_form(component_data)
if component_form.is_valid(): if component_form.is_valid():

View File

@@ -386,10 +386,10 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView):
) )
logger.info(f"{msg} {obj} (PK: {obj.pk})") logger.info(f"{msg} {obj} (PK: {obj.pk})")
if hasattr(obj, 'get_absolute_url'): if hasattr(obj, 'get_absolute_url'):
msg = '{} <a href="{}">{}</a>'.format(msg, obj.get_absolute_url(), escape(obj)) msg = mark_safe(f'{msg} <a href="{obj.get_absolute_url()}">{escape(obj)}</a>')
else: else:
msg = '{} {}'.format(msg, escape(obj)) msg = f'{msg} {obj}'
messages.success(request, mark_safe(msg)) messages.success(request, msg)
if '_addanother' in request.POST: if '_addanother' in request.POST:
redirect_url = request.path redirect_url = request.path

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -27,6 +27,23 @@ function handleSearchDropdownClick(event: Event, button: HTMLButtonElement): voi
} }
} }
/**
* Show/hide quicksearch clear button.
*
* @param event "keyup" or "search" event for the quicksearch input
*/
function quickSearchEventHandler(event: Event): void {
const quicksearch = event.currentTarget as HTMLInputElement;
const inputgroup = quicksearch.parentElement as HTMLDivElement;
if (isTruthy(inputgroup)) {
if (quicksearch.value === "") {
inputgroup.classList.add("hide-last-child");
} else {
inputgroup.classList.remove("hide-last-child");
}
}
}
/** /**
* Initialize Search Bar Elements. * Initialize Search Bar Elements.
*/ */
@@ -40,8 +57,35 @@ function initSearchBar(): void {
} }
} }
/**
* Initialize Quicksearch Event listener/handlers.
*/
function initQuickSearch(): void {
const quicksearch = document.getElementById("quicksearch") as HTMLInputElement;
const clearbtn = document.getElementById("quicksearch_clear") as HTMLButtonElement;
if (isTruthy(quicksearch)) {
quicksearch.addEventListener("keyup", quickSearchEventHandler, {
passive: true
})
quicksearch.addEventListener("search", quickSearchEventHandler, {
passive: true
})
if (isTruthy(clearbtn)) {
clearbtn.addEventListener("click", async () => {
const search = new Event('search');
quicksearch.value = '';
await new Promise(f => setTimeout(f, 100));
quicksearch.dispatchEvent(search);
}, {
passive: true
})
}
}
}
export function initSearch(): void { export function initSearch(): void {
for (const func of [initSearchBar]) { for (const func of [initSearchBar]) {
func(); func();
} }
initQuickSearch();
} }

View File

@@ -416,6 +416,27 @@ nav.search {
} }
} }
// Styles for the quicksearch and its clear button;
// Overrides input-group styles and adds transition effects
.quicksearch {
input[type="search"] {
border-radius: $border-radius !important;
}
button {
margin-left: -32px !important;
z-index: 100 !important;
outline: none !important;
border-radius: $border-radius !important;
transition: visibility 0s, opacity 0.2s linear;
}
button :hover {
opacity: 50%;
transition: visibility 0s, opacity 0.1s linear;
}
}
main.layout { main.layout {
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
@@ -714,11 +735,8 @@ textarea.form-control[rows='10'] {
height: 18rem; height: 18rem;
} }
textarea#id_local_context_data,
textarea.markdown, textarea.markdown,
textarea#id_public_key, textarea.form-control[name='csv'] {
textarea.form-control[name='csv'],
textarea.form-control[name='data'] {
font-family: $font-family-monospace; font-family: $font-family-monospace;
} }

View File

@@ -34,3 +34,11 @@ a[type='button'] {
.badge { .badge {
font-size: $font-size-xs; font-size: $font-size-xs;
} }
/* clears the 'X' in search inputs from webkit browsers */
input[type='search']::-webkit-search-decoration,
input[type='search']::-webkit-search-cancel-button,
input[type='search']::-webkit-search-results-button,
input[type='search']::-webkit-search-results-decoration {
-webkit-appearance: none !important;
}

View File

@@ -92,6 +92,10 @@ $input-focus-color: $input-color;
$input-placeholder-color: $gray-700; $input-placeholder-color: $gray-700;
$input-plaintext-color: $body-color; $input-plaintext-color: $body-color;
input {
color-scheme: dark;
}
$form-check-input-active-filter: brightness(90%); $form-check-input-active-filter: brightness(90%);
$form-check-input-bg: $input-bg; $form-check-input-bg: $input-bg;
$form-check-input-border: 1px solid rgba(255, 255, 255, 0.25); $form-check-input-border: 1px solid rgba(255, 255, 255, 0.25);

View File

@@ -22,7 +22,6 @@ $theme-colors: (
'danger': $danger, 'danger': $danger,
'light': $light, 'light': $light,
'dark': $dark, 'dark': $dark,
// General-purpose palette // General-purpose palette
'blue': $blue-500, 'blue': $blue-500,
'indigo': $indigo-500, 'indigo': $indigo-500,
@@ -36,7 +35,7 @@ $theme-colors: (
'cyan': $cyan-500, 'cyan': $cyan-500,
'gray': $gray-500, 'gray': $gray-500,
'black': $black, 'black': $black,
'white': $white, 'white': $white
); );
$light: $gray-200; $light: $gray-200;

View File

@@ -42,3 +42,9 @@ table td {
visibility: visible !important; visibility: visible !important;
} }
} }
// Hides the last child of an element
.hide-last-child :last-child {
visibility: hidden;
opacity: 0;
}

View File

@@ -18,21 +18,27 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_consoleport %} {% if perms.dcim.change_consoleport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleport_bulk_rename' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <div class="btn-group" role="group">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:consoleport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-warning btn-sm"> <button type="submit" name="_edit" formaction="{% url 'dcim:consoleport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleport_bulk_disconnect' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-outline-danger btn-sm"> <button type="submit" name="_rename" formaction="{% url 'dcim:consoleport_bulk_rename' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button> </button>
</div>
{% endif %} {% endif %}
<div class="btn-group" role="group">
{% if perms.dcim.delete_consoleport %} {% if perms.dcim.delete_consoleport %}
<button type="submit" name="_delete" formaction="{% url 'dcim:consoleport_bulk_delete' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" name="_delete" formaction="{% url 'dcim:consoleport_bulk_delete' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>
{% endif %} {% endif %}
{% if perms.dcim.change_consoleport %}
<button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleport_bulk_disconnect' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
</div>
</div> </div>
{% if perms.dcim.add_consoleport %} {% if perms.dcim.add_consoleport %}
<div class="bulk-button-group"> <div class="bulk-button-group">

View File

@@ -18,21 +18,27 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_consoleserverport %} {% if perms.dcim.change_consoleserverport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleserverport_bulk_rename' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <div class="btn-group" role="group">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:consoleserverport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-warning btn-sm"> <button type="submit" name="_edit" formaction="{% url 'dcim:consoleserverport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleserverport_bulk_disconnect' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-outline-danger btn-sm"> <button type="submit" name="_rename" formaction="{% url 'dcim:consoleserverport_bulk_rename' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button> </button>
</div>
{% endif %} {% endif %}
<div class="btn-group" role="group">
{% if perms.dcim.delete_consoleserverport %} {% if perms.dcim.delete_consoleserverport %}
<button type="submit" formaction="{% url 'dcim:consoleserverport_bulk_delete' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" formaction="{% url 'dcim:consoleserverport_bulk_delete' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>
{% endif %} {% endif %}
{% if perms.dcim.change_consoleserverport %}
<button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleserverport_bulk_disconnect' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
</div>
</div> </div>
{% if perms.dcim.add_consoleserverport %} {% if perms.dcim.add_consoleserverport %}
<div class="bulk-button-group"> <div class="bulk-button-group">

View File

@@ -18,16 +18,18 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_devicebay %} {% if perms.dcim.change_devicebay %}
<button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <div class="btn-group" role="group">
<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={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-warning btn-sm"> <button type="submit" name="_edit" formaction="{% url 'dcim:devicebay_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
<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>
</div>
{% endif %} {% endif %}
{% if perms.dcim.delete_devicebay %} {% if perms.dcim.delete_devicebay %}
<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"> <button type="submit" name="_delete" formaction="{% url 'dcim:devicebay_bulk_delete' %}?return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete selected <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>
{% endif %} {% endif %}
</div> </div>

View File

@@ -18,21 +18,27 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_frontport %} {% if perms.dcim.change_frontport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:frontport_bulk_rename' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <div class="btn-group" role="group">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:frontport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-warning btn-sm"> <button type="submit" name="_edit" formaction="{% url 'dcim:frontport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:frontport_bulk_disconnect' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-outline-danger btn-sm"> <button type="submit" name="_rename" formaction="{% url 'dcim:frontport_bulk_rename' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button> </button>
</div>
{% endif %} {% endif %}
<div class="btn-group" role="group">
{% if perms.dcim.delete_frontport %} {% if perms.dcim.delete_frontport %}
<button type="submit" formaction="{% url 'dcim:frontport_bulk_delete' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" formaction="{% url 'dcim:frontport_bulk_delete' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>
{% endif %} {% endif %}
{% if perms.dcim.change_frontport %}
<button type="submit" name="_disconnect" formaction="{% url 'dcim:frontport_bulk_disconnect' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
</div>
</div> </div>
{% if perms.dcim.add_frontport %} {% if perms.dcim.add_frontport %}
<div class="bulk-button-group"> <div class="bulk-button-group">

View File

@@ -0,0 +1,11 @@
{% extends 'inc/table_controls_htmx.html' %}
{% block extra_table_controls %}
<button class="btn btn-sm btn-outline-dark dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="mdi mdi-eye"></i>
</button>
<ul class="dropdown-menu">
<button type="button" class="dropdown-item toggle-enabled" data-state="show">Hide Enabled</button>
<button type="button" class="dropdown-item toggle-disabled" data-state="show">Hide Disabled</button>
</ul>
{% endblock extra_table_controls %}

View File

@@ -4,47 +4,11 @@
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="row mb-3 justify-content-between"> {% include 'dcim/device/inc/interface_table_controls.html' with table_modal="DeviceInterfaceTable_config" %}
<div class="col col-12 col-lg-4 my-3 my-lg-0 d-flex noprint table-controls">
<div class="input-group input-group-sm">
<input
type="text"
name="q"
class="form-control"
placeholder="Quick search"
hx-get="{{ request.full_path }}"
hx-target="#object_list"
hx-trigger="keyup changed delay:500ms"
/>
</div>
</div>
<div class="col col-md-3 mb-0 d-flex noprint table-controls">
<div class="input-group input-group-sm justify-content-end">
{% if request.user.is_authenticated %}
<button
type="button"
class="btn btn-sm btn-outline-dark"
data-bs-toggle="modal"
data-bs-target="#DeviceInterfaceTable_config"
title="Configure Table">
<i class="mdi mdi-cog"></i> Configure Table
</button>
{% endif %}
<button class="btn btn-sm btn-outline-dark dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="mdi mdi-eye"></i>
</button>
<ul class="dropdown-menu">
<button type="button" class="dropdown-item toggle-enabled" data-state="show">Hide Enabled</button>
<button type="button" class="dropdown-item toggle-disabled" data-state="show">Hide Disabled</button>
</ul>
</div>
</div>
</div>
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
<div class="card"> <div class="card">
<div class="card-body" id="object_list"> <div class="card-body" id="object_list">
{% include 'htmx/table.html' %} {% include 'htmx/table.html' %}
@@ -54,25 +18,40 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_interface %} {% if perms.dcim.change_interface %}
<button type="submit" name="_rename" formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <div class="btn-group" role="group">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename <button type="submit" name="_edit"
</button> formaction="{% url 'dcim:interface_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
<button type="submit" name="_edit" formaction="{% url 'dcim:interface_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-warning btn-sm"> class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:interface_bulk_disconnect' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-outline-danger btn-sm"> <button type="submit" name="_rename"
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
class="btn btn-outline-warning btn-sm">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button> </button>
</div>
{% endif %} {% endif %}
<div class="btn-group" role="group">
{% if perms.dcim.delete_interface %} {% if perms.dcim.delete_interface %}
<button type="submit" name="_delete" formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" name="_delete"
formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>
{% endif %} {% endif %}
{% if perms.dcim.change_interface %}
<button type="submit" name="_disconnect"
formaction="{% url 'dcim:interface_bulk_disconnect' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
class="btn btn-outline-danger btn-sm">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
</div>
</div> </div>
{% if perms.dcim.add_interface %} {% if perms.dcim.add_interface %}
<div class="bulk-button-group"> <div class="bulk-button-group">
<a href="{% url 'dcim:interface_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-primary btn-sm"> <a href="{% url 'dcim:interface_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
class="btn btn-primary btn-sm">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Interfaces <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Interfaces
</a> </a>
</div> </div>

View File

@@ -18,12 +18,14 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_inventoryitem %} {% if perms.dcim.change_inventoryitem %}
<button type="submit" name="_rename" formaction="{% url 'dcim:inventoryitem_bulk_rename' %}?return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-warning btn-sm"> <div class="btn-group" role="group">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:inventoryitem_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-warning btn-sm"> <button type="submit" name="_edit" formaction="{% url 'dcim:inventoryitem_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
<button type="submit" name="_rename" formaction="{% url 'dcim:inventoryitem_bulk_rename' %}?return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button>
</div>
{% endif %} {% endif %}
{% if perms.dcim.delete_inventoryitem %} {% if perms.dcim.delete_inventoryitem %}
<button type="submit" name="_delete" formaction="{% url 'dcim:inventoryitem_bulk_delete' %}?return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" name="_delete" formaction="{% url 'dcim:inventoryitem_bulk_delete' %}?return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-danger btn-sm">

View File

@@ -18,16 +18,18 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_modulebay %} {% if perms.dcim.change_modulebay %}
<button type="submit" name="_rename" formaction="{% url 'dcim:modulebay_bulk_rename' %}?return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <div class="btn-group" role="group">
<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={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-warning btn-sm"> <button type="submit" name="_edit" formaction="{% url 'dcim:modulebay_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
<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>
</div>
{% endif %} {% endif %}
{% if perms.dcim.delete_modulebay %} {% if perms.dcim.delete_modulebay %}
<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"> <button type="submit" formaction="{% url 'dcim:modulebay_bulk_delete' %}?return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete selected <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>
{% endif %} {% endif %}
</div> </div>

View File

@@ -18,21 +18,27 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_powerport %} {% if perms.dcim.change_powerport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:poweroutlet_bulk_rename' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <div class="btn-group" role="group">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:poweroutlet_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-warning btn-sm"> <button type="submit" name="_edit" formaction="{% url 'dcim:poweroutlet_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:poweroutlet_bulk_disconnect' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-outline-danger btn-sm"> <button type="submit" name="_rename" formaction="{% url 'dcim:poweroutlet_bulk_rename' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button> </button>
</div>
{% endif %} {% endif %}
<div class="btn-group" role="group">
{% if perms.dcim.delete_poweroutlet %} {% if perms.dcim.delete_poweroutlet %}
<button type="submit" formaction="{% url 'dcim:poweroutlet_bulk_delete' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" formaction="{% url 'dcim:poweroutlet_bulk_delete' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>
{% endif %} {% endif %}
{% if perms.dcim.change_powerport %}
<button type="submit" name="_disconnect" formaction="{% url 'dcim:poweroutlet_bulk_disconnect' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
</div>
</div> </div>
{% if perms.dcim.add_poweroutlet %} {% if perms.dcim.add_poweroutlet %}
<div class="bulk-button-group"> <div class="bulk-button-group">

View File

@@ -18,21 +18,27 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_powerport %} {% if perms.dcim.change_powerport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:powerport_bulk_rename' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <div class="btn-group" role="group">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:powerport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-warning btn-sm"> <button type="submit" name="_edit" formaction="{% url 'dcim:powerport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:powerport_bulk_disconnect' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-outline-danger btn-sm"> <button type="submit" name="_rename" formaction="{% url 'dcim:powerport_bulk_rename' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button> </button>
</div>
{% endif %} {% endif %}
<div class="btn-group" role="group">
{% if perms.dcim.delete_powerport %} {% if perms.dcim.delete_powerport %}
<button type="submit" name="_delete" formaction="{% url 'dcim:powerport_bulk_delete' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" name="_delete" formaction="{% url 'dcim:powerport_bulk_delete' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>
{% endif %} {% endif %}
{% if perms.dcim.change_powerport %}
<button type="submit" name="_disconnect" formaction="{% url 'dcim:powerport_bulk_disconnect' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
</div>
</div> </div>
{% if perms.dcim.add_powerport %} {% if perms.dcim.add_powerport %}
<div class="bulk-button-group"> <div class="bulk-button-group">

View File

@@ -18,21 +18,27 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_rearport %} {% if perms.dcim.change_rearport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:rearport_bulk_rename' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <div class="btn-group" role="group">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:rearport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-warning btn-sm"> <button type="submit" name="_edit" formaction="{% url 'dcim:rearport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:rearport_bulk_disconnect' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-outline-danger btn-sm"> <button type="submit" name="_rename" formaction="{% url 'dcim:rearport_bulk_rename' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button> </button>
</div>
{% endif %} {% endif %}
<div class="btn-group" role="group">
{% if perms.dcim.delete_rearport %} {% if perms.dcim.delete_rearport %}
<button type="submit" formaction="{% url 'dcim:rearport_bulk_delete' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" formaction="{% url 'dcim:rearport_bulk_delete' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>
{% endif %} {% endif %}
{% if perms.dcim.change_rearport %}
<button type="submit" name="_disconnect" formaction="{% url 'dcim:rearport_bulk_disconnect' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
</div>
</div> </div>
{% if perms.dcim.add_rearport %} {% if perms.dcim.add_rearport %}
<div class="bulk-button-group"> <div class="bulk-button-group">

View File

@@ -1,4 +1,5 @@
{% extends 'generic/object_list.html' %} {% extends 'generic/object_list.html' %}
{% load buttons %}
{% block bulk_buttons %} {% block bulk_buttons %}
{% if perms.dcim.change_device %} {% if perms.dcim.change_device %}
@@ -73,5 +74,15 @@
</ul> </ul>
</div> </div>
{% endif %} {% endif %}
{{ block.super }} {% if 'bulk_edit' in actions %}
<div class="btn-group" role="group">
{% bulk_edit_button model query_params=request.GET %}
<button type="submit" name="_rename" formaction="{% url 'dcim:device_bulk_rename' %}?return_url={% url 'dcim:device_list' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-outline-warning btn-sm">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button>
</div>
{% endif %}
{% if 'bulk_delete' in actions %}
{% bulk_delete_button model query_params=request.GET %}
{% endif %}
{% endblock %} {% endblock %}

View File

@@ -10,6 +10,8 @@
<th>Name</th> <th>Name</th>
<th>Role</th> <th>Role</th>
<th>Priority</th> <th>Priority</th>
<th>Phone</th>
<th>Email</th>
<th></th> <th></th>
</tr> </tr>
{% for contact in contacts %} {% for contact in contacts %}
@@ -17,6 +19,20 @@
<td>{{ contact.contact|linkify }}</td> <td>{{ contact.contact|linkify }}</td>
<td>{{ contact.role|placeholder }}</td> <td>{{ contact.role|placeholder }}</td>
<td>{{ contact.get_priority_display|placeholder }}</td> <td>{{ contact.get_priority_display|placeholder }}</td>
<td>
{% if contact.contact.phone %}
<a href="tel:{{ contact.contact.phone }}">{{ contact.contact.phone }}</a>
{% else %}
{{ ''|placeholder }}
{% endif %}
</td>
<td>
{% if contact.contact.email %}
<a href="mailto:{{ contact.contact.email }}">{{ contact.contact.email }}</a>
{% else %}
{{ ''|placeholder }}
{% endif %}
</td>
<td class="text-end noprint"> <td class="text-end noprint">
{% if perms.tenancy.change_contactassignment %} {% if perms.tenancy.change_contactassignment %}
<a href="{% url 'tenancy:contactassignment_edit' pk=contact.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-warning btn-sm lh-1" title="Edit"> <a href="{% url 'tenancy:contactassignment_edit' pk=contact.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-warning btn-sm lh-1" title="Edit">

View File

@@ -1,29 +1,19 @@
{% load helpers %} {% load helpers %}
<div class="row mb-3 justify-content-between"> <div class="row mb-3">
<div class="table-controls noprint col col-12 col-md-8 col-lg-4"> <div class="col-auto table-controls noprint">
<div class="input-group input-group-sm"> <div class="input-group input-group-sm me-2 quicksearch hide-last-child">
<input <input type="search" results=5 name="q" id="quicksearch" class="form-control" placeholder="Quick search"
type="text" hx-get="{{ request.full_path }}" hx-target="#object_list" hx-trigger="keyup changed delay:500ms, search" />
name="q" <button class="btn bg-transparent" type="button" id="quicksearch_clear"><i class="mdi mdi-close-circle"></i></button>
class="form-control"
placeholder="Quick search"
hx-get="{{ request.full_path }}"
hx-target="#object_list"
hx-trigger="keyup changed delay:500ms"
/>
</div> </div>
{% block extra_table_controls %}{% endblock %}
</div> </div>
<div class="table-controls noprint col col-md-3 mb-0"> <div class="col-auto ms-auto table-controls noprint">
{% if request.user.is_authenticated and table_modal %} {% if request.user.is_authenticated and table_modal %}
<div class="table-configure input-group input-group-sm"> <div class="table-configure input-group input-group-sm">
<button <button type="button" data-bs-toggle="modal" title="Configure Table" data-bs-target="#{{ table_modal }}"
type="button" class="btn btn-sm btn-outline-dark">
data-bs-toggle="modal"
title="Configure Table"
data-bs-target="#{{ table_modal }}"
class="btn btn-sm btn-outline-dark"
>
<i class="mdi mdi-cog"></i> Configure Table <i class="mdi mdi-cog"></i> Configure Table
</button> </button>
</div> </div>

View File

@@ -144,6 +144,13 @@
</td> </td>
</tr> </tr>
</table> </table>
{% if object.prefix.version == 4 %}
<div class="float-end">
<a class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#prefix-modal">
<i class="mdi mdi-information-outline" aria-hidden="true"></i> Addressing Details
</a>
</div>
{% endif %}
</div> </div>
</div> </div>
{% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/custom_fields.html' %}
@@ -161,3 +168,39 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block modals %}
{{ block.super }}
{% if object.prefix.version == 4 %}
<div class="modal fade" id="prefix-modal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Prefix Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<table class="table table-hover attr-table">
<tr>
<td>Network Address</td>
<td>{{ object.prefix.network }}</td>
</tr>
<tr>
<td>Network Mask</td>
<td>{{ object.prefix.netmask }}</td>
</tr>
<tr>
<td>Wildcard Mask</td>
<td>{{ object.prefix.hostmask }}</td>
</tr>
<tr>
<td>Broadcast Address</td>
<td>{{ object.prefix.broadcast }}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
{% endif %}
{% endblock %}

View File

@@ -16,12 +16,14 @@
<div class="noprint"> <div class="noprint">
{% if perms.virtualization.change_vminterface %} {% if perms.virtualization.change_vminterface %}
<button type="submit" name="_rename" formaction="{% url 'virtualization:vminterface_bulk_rename' %}?return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-warning btn-sm"> <div class="btn-group" role="group">
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'virtualization:vminterface_bulk_edit' %}?virtual_machine={{ object.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-warning btn-sm"> <button type="submit" name="_edit" formaction="{% url 'virtualization:vminterface_bulk_edit' %}?virtual_machine={{ object.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-warning btn-sm">
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit <span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
</button> </button>
<button type="submit" name="_rename" formaction="{% url 'virtualization:vminterface_bulk_rename' %}?return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
</button>
</div>
{% endif %} {% endif %}
{% if perms.virtualization.delete_vminterface %} {% if perms.virtualization.delete_vminterface %}
<button type="submit" name="_delete" formaction="{% url 'virtualization:vminterface_bulk_delete' %}?return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" name="_delete" formaction="{% url 'virtualization:vminterface_bulk_delete' %}?return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-danger btn-sm">
@@ -35,7 +37,6 @@
</a> </a>
</div> </div>
{% endif %} {% endif %}
<div class="clearfix"></div>
</div> </div>
</form> </form>
{% endblock content %} {% endblock content %}

View File

@@ -58,6 +58,8 @@ class TokenViewSet(NetBoxModelViewSet):
# Workaround for schema generation (drf_yasg) # Workaround for schema generation (drf_yasg)
if getattr(self, 'swagger_fake_view', False): if getattr(self, 'swagger_fake_view', False):
return queryset.none() return queryset.none()
if not self.request.user.is_authenticated:
return queryset.none()
if self.request.user.is_superuser: if self.request.user.is_superuser:
return queryset return queryset
return queryset.filter(user=self.request.user) return queryset.filter(user=self.request.user)
@@ -74,11 +76,11 @@ class TokenProvisionView(APIView):
serializer.is_valid() serializer.is_valid()
# Authenticate the user account based on the provided credentials # Authenticate the user account based on the provided credentials
user = authenticate( username = serializer.data.get('username')
request=request, password = serializer.data.get('password')
username=serializer.data['username'], if not username or not password:
password=serializer.data['password'] raise AuthenticationFailed("Username and password must be provided to provision a token.")
) user = authenticate(request=request, username=username, password=password)
if user is None: if user is None:
raise AuthenticationFailed("Invalid username/password") raise AuthenticationFailed("Invalid username/password")

View File

@@ -10,6 +10,7 @@ from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.http import url_has_allowed_host_and_scheme
from django.views.decorators.debug import sensitive_post_parameters from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import View from django.views.generic import View
from social_core.backends.utils import load_backends from social_core.backends.utils import load_backends
@@ -91,7 +92,7 @@ class LoginView(View):
data = request.POST if request.method == "POST" else request.GET data = request.POST if request.method == "POST" else request.GET
redirect_url = data.get('next', settings.LOGIN_REDIRECT_URL) redirect_url = data.get('next', settings.LOGIN_REDIRECT_URL)
if redirect_url and redirect_url.startswith('/'): if redirect_url and url_has_allowed_host_and_scheme(redirect_url, allowed_hosts=None):
logger.debug(f"Redirecting user to {redirect_url}") logger.debug(f"Redirecting user to {redirect_url}")
else: else:
if redirect_url: if redirect_url:

View File

@@ -99,6 +99,7 @@ class JSONField(_JSONField):
if not self.help_text: if not self.help_text:
self.help_text = 'Enter context data in <a href="https://json.org/">JSON</a> format.' self.help_text = 'Enter context data in <a href="https://json.org/">JSON</a> format.'
self.widget.attrs['placeholder'] = '' self.widget.attrs['placeholder'] = ''
self.widget.attrs['class'] = 'font-monospace'
def prepare_value(self, value): def prepare_value(self, value):
if isinstance(value, InvalidJSONInput): if isinstance(value, InvalidJSONInput):

View File

@@ -136,7 +136,7 @@ class ImportForm(BootstrapMixin, forms.Form):
Generic form for creating an object from JSON/YAML data Generic form for creating an object from JSON/YAML data
""" """
data = forms.CharField( data = forms.CharField(
widget=forms.Textarea, widget=forms.Textarea(attrs={'class': 'font-monospace'}),
help_text="Enter object data in JSON or YAML format. Note: Only a single object/document is supported." help_text="Enter object data in JSON or YAML format. Note: Only a single object/document is supported."
) )
format = forms.ChoiceField( format = forms.ChoiceField(

View File

@@ -86,8 +86,8 @@ def placeholder(value):
""" """
if value not in ('', None): if value not in ('', None):
return value return value
placeholder = '<span class="text-muted">&mdash;</span>'
return mark_safe(placeholder) return mark_safe('<span class="text-muted">&mdash;</span>')
@register.filter() @register.filter()

View File

@@ -109,9 +109,7 @@ def annotated_date(date_value):
long_ts = date(date_value, 'DATETIME_FORMAT') long_ts = date(date_value, 'DATETIME_FORMAT')
short_ts = date(date_value, 'SHORT_DATETIME_FORMAT') short_ts = date(date_value, 'SHORT_DATETIME_FORMAT')
span = f'<span title="{long_ts}">{short_ts}</span>' return mark_safe(f'<span title="{long_ts}">{short_ts}</span>')
return mark_safe(span)
@register.simple_tag @register.simple_tag

View File

@@ -89,7 +89,7 @@ class VirtualMachineFilterForm(
(None, ('q', 'tag')), (None, ('q', 'tag')),
('Cluster', ('cluster_group_id', 'cluster_type_id', 'cluster_id')), ('Cluster', ('cluster_group_id', 'cluster_type_id', 'cluster_id')),
('Location', ('region_id', 'site_group_id', 'site_id')), ('Location', ('region_id', 'site_group_id', 'site_id')),
('Attriubtes', ('status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data')), ('Attributes', ('status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data')),
('Tenant', ('tenant_group_id', 'tenant_id')), ('Tenant', ('tenant_group_id', 'tenant_id')),
('Contacts', ('contact', 'contact_role', 'contact_group')), ('Contacts', ('contact', 'contact_role', 'contact_group')),
) )

View File

@@ -1,5 +1,5 @@
bleach==5.0.1 bleach==5.0.1
Django==4.0.6 Django==4.0.7
django-cors-headers==3.13.0 django-cors-headers==3.13.0
django-debug-toolbar==3.5.0 django-debug-toolbar==3.5.0
django-filter==22.1 django-filter==22.1
@@ -13,24 +13,27 @@ django-tables2==2.4.1
django-taggit==2.1.0 django-taggit==2.1.0
django-timezone-field==5.0 django-timezone-field==5.0
djangorestframework==3.13.1 djangorestframework==3.13.1
drf-yasg[validation]==1.20.0 drf-yasg[validation]==1.21.3
graphene-django==2.15.0 graphene-django==2.15.0
gunicorn==20.1.0 gunicorn==20.1.0
Jinja2==3.1.2 Jinja2==3.1.2
Markdown==3.3.7 Markdown==3.4.1
markdown-include==0.6.0 markdown-include==0.7.0
mkdocs-material==8.3.9 mkdocs-material==8.4.0
mkdocstrings[python-legacy]==0.19.0 mkdocstrings[python-legacy]==0.19.0
netaddr==0.8.0 netaddr==0.8.0
Pillow==9.2.0 Pillow==9.2.0
psycopg2-binary==2.9.3 psycopg2-binary==2.9.3
PyYAML==6.0 PyYAML==6.0
sentry-sdk==1.7.0 sentry-sdk==1.9.5
social-auth-app-django==5.0.0 social-auth-app-django==5.0.0
social-auth-core==4.3.0 social-auth-core==4.3.0
svgwrite==1.4.2 svgwrite==1.4.3
tablib==3.2.1 tablib==3.2.1
tzdata==2022.1 tzdata==2022.1
# Workaround for #7401 # Workaround for #7401
jsonschema==3.2.0 jsonschema==3.2.0
# Workaround for #9986
pytz==2022.1