mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-28 08:07:45 -06:00
Compare commits
34 Commits
7604-filte
...
v4.3.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
290e4afaa0 | ||
|
|
ca95050b7d | ||
|
|
34e4ccb212 | ||
|
|
fcb49f9881 | ||
|
|
7e40f40248 | ||
|
|
8e08524fed | ||
|
|
8bb47dad0f | ||
|
|
5d7c8318aa | ||
|
|
2d495d4f32 | ||
|
|
cea83f31b8 | ||
|
|
6c0dc8b630 | ||
|
|
1c86f81298 | ||
|
|
630d7aa4c2 | ||
|
|
043275df19 | ||
|
|
122f612750 | ||
|
|
65b36fd594 | ||
|
|
e828ca5cb4 | ||
|
|
fce10c73b7 | ||
|
|
0cf76bc5c7 | ||
|
|
11f228cae9 | ||
|
|
a86cd9dfc6 | ||
|
|
15541c6440 | ||
|
|
6ce3012f93 | ||
|
|
fec6cf705f | ||
|
|
9c6d0d1ddc | ||
|
|
47359d9284 | ||
|
|
669df62cde | ||
|
|
9df0bdcfaf | ||
|
|
d222913716 | ||
|
|
2c09973e01 | ||
|
|
4506c809d8 | ||
|
|
5d194214aa | ||
|
|
0827198cad | ||
|
|
bb83187505 |
@@ -15,7 +15,7 @@ body:
|
||||
attributes:
|
||||
label: NetBox version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v4.3.5
|
||||
placeholder: v4.3.6
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/02-bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/02-bug_report.yaml
vendored
@@ -27,7 +27,7 @@ body:
|
||||
attributes:
|
||||
label: NetBox Version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v4.3.5
|
||||
placeholder: v4.3.6
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
"iec-60320-c8",
|
||||
"iec-60320-c14",
|
||||
"iec-60320-c16",
|
||||
"iec-60320-c18",
|
||||
"iec-60320-c20",
|
||||
"iec-60320-c22",
|
||||
"iec-60309-p-n-e-4h",
|
||||
@@ -209,6 +210,7 @@
|
||||
"iec-60320-c7",
|
||||
"iec-60320-c13",
|
||||
"iec-60320-c15",
|
||||
"iec-60320-c17",
|
||||
"iec-60320-c19",
|
||||
"iec-60320-c21",
|
||||
"iec-60309-p-n-e-4h",
|
||||
@@ -474,6 +476,13 @@
|
||||
"passive-48v-2pair",
|
||||
"passive-48v-4pair"
|
||||
]
|
||||
},
|
||||
"rf_role": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ap",
|
||||
"station"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# NetBox v4.3
|
||||
|
||||
## v4.3.6 (2025-08-12)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#17222](https://github.com/netbox-community/netbox/issues/17222) - Made unread notifications more visible with improved styling and positioning
|
||||
* [#18843](https://github.com/netbox-community/netbox/issues/18843) - Include color name when exporting cables
|
||||
* [#18873](https://github.com/netbox-community/netbox/issues/18873) - Add a request timeout parameter to the RSS feed dashboard widget
|
||||
* [#19622](https://github.com/netbox-community/netbox/issues/19622) - Allow sharing GraphQL queries as links
|
||||
* [#19728](https://github.com/netbox-community/netbox/issues/19728) - Added C18 power port type for audio devices
|
||||
* [#19968](https://github.com/netbox-community/netbox/issues/19968) - Improve object type selection form field when editing permissions
|
||||
* [#19977](https://github.com/netbox-community/netbox/issues/19977) - Improve performance when filtering device components by site, location, or rack
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#19321](https://github.com/netbox-community/netbox/issues/19321) - Reduce redundant database queries when bulk importing devices
|
||||
* [#19379](https://github.com/netbox-community/netbox/issues/19379) - Support singular VLAN IDs in list when editing a VLAN group
|
||||
* [#19812](https://github.com/netbox-community/netbox/issues/19812) - Implement `contains` GraphQL filter for IPAM prefixes and IP ranges
|
||||
* [#19917](https://github.com/netbox-community/netbox/issues/19917) - Ensure deterministic ordering of duplicate MAC addresses
|
||||
* [#19996](https://github.com/netbox-community/netbox/issues/19996) - Correct dynamic query parameters for IP Address field in Add/Edit Service form
|
||||
* [#19998](https://github.com/netbox-community/netbox/issues/19998) - Fix missing changelog records for deleted tags
|
||||
* [#19999](https://github.com/netbox-community/netbox/issues/19999) - Corrected excessive whitespace in script list dashboard widget
|
||||
* [#20001](https://github.com/netbox-community/netbox/issues/20001) - `is_api_request()` should not evaluate a request's content type
|
||||
* [#20009](https://github.com/netbox-community/netbox/issues/20009) - Ensure search parameter is escaped for export links under object list views
|
||||
* [#20017](https://github.com/netbox-community/netbox/issues/20017) - Fix highlighting of changed lines in changelog data
|
||||
* [#20023](https://github.com/netbox-community/netbox/issues/20023) - Add GiST index on prefixes table to vastly improve bulk deletion time
|
||||
* [#20030](https://github.com/netbox-community/netbox/issues/20030) - Fix height of object list action buttons & others
|
||||
* [#20033](https://github.com/netbox-community/netbox/issues/20033) - Fix `TypeError` exception when bulk deleting bookmarks
|
||||
* [#20056](https://github.com/netbox-community/netbox/issues/20056) - Fixed missing RF role options in device type schema validation
|
||||
|
||||
## v4.3.5 (2025-07-29)
|
||||
|
||||
### Enhancements
|
||||
@@ -16,6 +45,9 @@
|
||||
* [#19934](https://github.com/netbox-community/netbox/issues/19934) - Added missing description field to tenant bulk edit form
|
||||
* [#19956](https://github.com/netbox-community/netbox/issues/19956) - Prevent duplicate deletion records in changelog from cascading deletions
|
||||
|
||||
!!! note "Plugin Developer Advisory"
|
||||
The fix for bug [#18900](https://github.com/netbox-community/netbox/issues/18900) now raises explicit exceptions when API endpoints attempt to paginate unordered querysets. Plugin maintainers should review their API viewsets to ensure proper queryset ordering is applied before pagination, either by using `.order_by()` on querysets or by setting `ordering` in model Meta classes. Previously silent pagination issues in plugin code will now raise `QuerySetNotOrdered` exceptions and may require updates to maintain compatibility.
|
||||
|
||||
## v4.3.4 (2025-07-15)
|
||||
|
||||
### Enhancements
|
||||
|
||||
@@ -13,6 +13,7 @@ from django_prometheus.models import model_deletes, model_inserts, model_updates
|
||||
from core.choices import JobStatusChoices, ObjectChangeActionChoices
|
||||
from core.events import *
|
||||
from extras.events import enqueue_event
|
||||
from extras.models import Tag
|
||||
from extras.utils import run_validators
|
||||
from netbox.config import get_config
|
||||
from netbox.context import current_request, events_queue
|
||||
@@ -72,6 +73,17 @@ def handle_changed_object(sender, instance, **kwargs):
|
||||
# m2m_changed with objects added or removed
|
||||
m2m_changed = True
|
||||
event_type = OBJECT_UPDATED
|
||||
elif kwargs.get('action') == 'post_clear':
|
||||
# Handle clearing of an M2M field
|
||||
if kwargs.get('model') == Tag and getattr(instance, '_prechange_snapshot', {}).get('tags'):
|
||||
# Handle generation of M2M changes for Tags which have a previous value (ignoring changes where the
|
||||
# prechange snapshot is empty)
|
||||
m2m_changed = True
|
||||
event_type = OBJECT_UPDATED
|
||||
else:
|
||||
# Other endpoints are unimpacted as they send post_add and post_remove
|
||||
# This will impact changes that utilize clear() however so we may want to give consideration for this branch
|
||||
return
|
||||
else:
|
||||
return
|
||||
|
||||
|
||||
@@ -344,6 +344,7 @@ class PowerPortTypeChoices(ChoiceSet):
|
||||
TYPE_IEC_C8 = 'iec-60320-c8'
|
||||
TYPE_IEC_C14 = 'iec-60320-c14'
|
||||
TYPE_IEC_C16 = 'iec-60320-c16'
|
||||
TYPE_IEC_C18 = 'iec-60320-c18'
|
||||
TYPE_IEC_C20 = 'iec-60320-c20'
|
||||
TYPE_IEC_C22 = 'iec-60320-c22'
|
||||
# IEC 60309
|
||||
@@ -462,6 +463,7 @@ class PowerPortTypeChoices(ChoiceSet):
|
||||
(TYPE_IEC_C8, 'C8'),
|
||||
(TYPE_IEC_C14, 'C14'),
|
||||
(TYPE_IEC_C16, 'C16'),
|
||||
(TYPE_IEC_C18, 'C18'),
|
||||
(TYPE_IEC_C20, 'C20'),
|
||||
(TYPE_IEC_C22, 'C22'),
|
||||
)),
|
||||
@@ -599,6 +601,7 @@ class PowerOutletTypeChoices(ChoiceSet):
|
||||
TYPE_IEC_C7 = 'iec-60320-c7'
|
||||
TYPE_IEC_C13 = 'iec-60320-c13'
|
||||
TYPE_IEC_C15 = 'iec-60320-c15'
|
||||
TYPE_IEC_C17 = 'iec-60320-c17'
|
||||
TYPE_IEC_C19 = 'iec-60320-c19'
|
||||
TYPE_IEC_C21 = 'iec-60320-c21'
|
||||
# IEC 60309
|
||||
@@ -711,6 +714,7 @@ class PowerOutletTypeChoices(ChoiceSet):
|
||||
(TYPE_IEC_C7, 'C7'),
|
||||
(TYPE_IEC_C13, 'C13'),
|
||||
(TYPE_IEC_C15, 'C15'),
|
||||
(TYPE_IEC_C17, 'C17'),
|
||||
(TYPE_IEC_C19, 'C19'),
|
||||
(TYPE_IEC_C21, 'C21'),
|
||||
)),
|
||||
|
||||
@@ -7,6 +7,7 @@ from jinja2 import FileSystemLoader, Environment
|
||||
|
||||
from dcim.choices import *
|
||||
from netbox.choices import WeightUnitChoices
|
||||
from wireless.choices import WirelessRoleChoices
|
||||
|
||||
TEMPLATE_FILENAME = 'devicetype_schema.jinja2'
|
||||
OUTPUT_FILENAME = 'contrib/generated_schema.json'
|
||||
@@ -23,6 +24,7 @@ CHOICES_MAP = {
|
||||
'interface_type_choices': InterfaceTypeChoices,
|
||||
'interface_poe_mode_choices': InterfacePoEModeChoices,
|
||||
'interface_poe_type_choices': InterfacePoETypeChoices,
|
||||
'interface_rf_role_choices': WirelessRoleChoices,
|
||||
'front_port_type_choices': PortTypeChoices,
|
||||
'rear_port_type_choices': PortTypeChoices,
|
||||
}
|
||||
|
||||
19
netbox/dcim/migrations/0210_macaddress_ordering.py
Normal file
19
netbox/dcim/migrations/0210_macaddress_ordering.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0209_device_component_denorm_site_location'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='macaddress',
|
||||
options={
|
||||
'ordering': ('mac_address', 'pk'),
|
||||
'verbose_name': 'MAC address',
|
||||
'verbose_name_plural': 'MAC addresses'
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1276,7 +1276,7 @@ class MACAddress(PrimaryModel):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('mac_address',)
|
||||
ordering = ('mac_address', 'pk',)
|
||||
verbose_name = _('MAC address')
|
||||
verbose_name_plural = _('MAC addresses')
|
||||
|
||||
|
||||
@@ -309,6 +309,7 @@ class RSSFeedWidget(DashboardWidget):
|
||||
default_config = {
|
||||
'max_entries': 10,
|
||||
'cache_timeout': 3600, # seconds
|
||||
'request_timeout': 3, # seconds
|
||||
'requires_internet': True,
|
||||
}
|
||||
description = _('Embed an RSS feed from an external website.')
|
||||
@@ -335,6 +336,12 @@ class RSSFeedWidget(DashboardWidget):
|
||||
max_value=86400, # 24 hours
|
||||
help_text=_('How long to stored the cached content (in seconds)')
|
||||
)
|
||||
request_timeout = forms.IntegerField(
|
||||
min_value=1,
|
||||
max_value=60,
|
||||
required=False,
|
||||
help_text=_('Timeout value for fetching the feed (in seconds)')
|
||||
)
|
||||
|
||||
def render(self, request):
|
||||
return render_to_string(self.template_name, {
|
||||
@@ -366,7 +373,7 @@ class RSSFeedWidget(DashboardWidget):
|
||||
url=self.config['feed_url'],
|
||||
headers={'User-Agent': f'NetBox/{settings.RELEASE.version}'},
|
||||
proxies=resolve_proxies(url=self.config['feed_url'], context={'client': self}),
|
||||
timeout=3
|
||||
timeout=self.config.get('request_timeout', 3),
|
||||
)
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.RequestException as e:
|
||||
|
||||
@@ -849,6 +849,9 @@ class Bookmark(models.Model):
|
||||
return str(self.object)
|
||||
return super().__str__()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('account:bookmarks')
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
|
||||
|
||||
@@ -317,11 +317,12 @@ class TableConfigTable(NetBoxTable):
|
||||
|
||||
class BookmarkTable(NetBoxTable):
|
||||
object_type = columns.ContentTypeColumn(
|
||||
verbose_name=_('Object Types'),
|
||||
verbose_name=_('Object Type'),
|
||||
)
|
||||
object = tables.Column(
|
||||
verbose_name=_('Object'),
|
||||
linkify=True
|
||||
linkify=True,
|
||||
orderable=False
|
||||
)
|
||||
actions = columns.ActionsColumn(
|
||||
actions=('delete',)
|
||||
|
||||
@@ -807,3 +807,21 @@ class NotificationTestCase(
|
||||
|
||||
def test_list_objects_with_constrained_permission(self):
|
||||
return
|
||||
|
||||
|
||||
class ScriptListViewTest(TestCase):
|
||||
user_permissions = ['extras.view_script']
|
||||
|
||||
def test_script_list_embedded_parameter(self):
|
||||
"""Test that ScriptListView accepts embedded parameter without error"""
|
||||
url = reverse('extras:script_list')
|
||||
|
||||
# Test normal request
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'extras/script_list.html')
|
||||
|
||||
# Test embedded request
|
||||
response = self.client.get(url, {'embedded': 'true'})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'extras/inc/script_list_content.html')
|
||||
|
||||
@@ -1282,11 +1282,18 @@ class ScriptListView(ContentTypePermissionRequiredMixin, View):
|
||||
script_modules = ScriptModule.objects.restrict(request.user).prefetch_related(
|
||||
'data_source', 'data_file', 'jobs'
|
||||
)
|
||||
|
||||
return render(request, 'extras/script_list.html', {
|
||||
context = {
|
||||
'model': ScriptModule,
|
||||
'script_modules': script_modules,
|
||||
})
|
||||
}
|
||||
|
||||
# Use partial template for dashboard widgets
|
||||
template_name = 'extras/script_list.html'
|
||||
if request.GET.get('embedded'):
|
||||
template_name = 'extras/inc/script_list_content.html'
|
||||
context['embedded'] = True
|
||||
|
||||
return render(request, template_name, context)
|
||||
|
||||
|
||||
class BaseScriptView(generic.ObjectView):
|
||||
|
||||
@@ -21,7 +21,7 @@ from utilities.forms.rendering import FieldSet, InlineFields, ObjectAttribute, T
|
||||
from utilities.forms.utils import get_field_value
|
||||
from utilities.forms.widgets import DatePicker, HTMXSelect
|
||||
from utilities.templatetags.builtins.filters import bettertitle
|
||||
from virtualization.models import VMInterface
|
||||
from virtualization.models import VMInterface, VirtualMachine
|
||||
|
||||
__all__ = (
|
||||
'AggregateForm',
|
||||
@@ -783,10 +783,6 @@ class ServiceForm(NetBoxModelForm):
|
||||
queryset=IPAddress.objects.all(),
|
||||
required=False,
|
||||
label=_('IP Addresses'),
|
||||
query_params={
|
||||
'device_id': '$device',
|
||||
'virtual_machine_id': '$virtual_machine',
|
||||
}
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
@@ -815,10 +811,22 @@ class ServiceForm(NetBoxModelForm):
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if (parent_object_type_id := get_field_value(self, 'parent_object_type')):
|
||||
if parent_object_type_id := get_field_value(self, 'parent_object_type'):
|
||||
try:
|
||||
parent_type = ContentType.objects.get(pk=parent_object_type_id)
|
||||
model = parent_type.model_class()
|
||||
if model == Device:
|
||||
self.fields['ipaddresses'].widget.add_query_params({
|
||||
'device_id': '$parent',
|
||||
})
|
||||
elif model == VirtualMachine:
|
||||
self.fields['ipaddresses'].widget.add_query_params({
|
||||
'virtual_machine_id': '$parent',
|
||||
})
|
||||
elif model == FHRPGroup:
|
||||
self.fields['ipaddresses'].widget.add_query_params({
|
||||
'fhrpgroup_id': '$parent',
|
||||
})
|
||||
self.fields['parent'].queryset = model.objects.all()
|
||||
self.fields['parent'].widget.attrs['selector'] = model._meta.label_lower
|
||||
self.fields['parent'].disabled = False
|
||||
|
||||
@@ -222,6 +222,19 @@ class IPRangeFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilterMi
|
||||
return Q()
|
||||
return q
|
||||
|
||||
@strawberry_django.filter_field()
|
||||
def contains(self, value: list[str], prefix) -> Q:
|
||||
if not value:
|
||||
return Q()
|
||||
q = Q()
|
||||
for subnet in value:
|
||||
net = netaddr.IPNetwork(subnet.strip())
|
||||
q |= Q(
|
||||
start_address__host__inet__lte=str(netaddr.IPAddress(net.first)),
|
||||
end_address__host__inet__gte=str(netaddr.IPAddress(net.last)),
|
||||
)
|
||||
return q
|
||||
|
||||
|
||||
@strawberry_django.filter_type(models.Prefix, lookups=True)
|
||||
class PrefixFilter(ContactFilterMixin, ScopedFilterMixin, TenancyFilterMixin, PrimaryModelFilterMixin):
|
||||
@@ -238,6 +251,16 @@ class PrefixFilter(ContactFilterMixin, ScopedFilterMixin, TenancyFilterMixin, Pr
|
||||
is_pool: FilterLookup[bool] | None = strawberry_django.filter_field()
|
||||
mark_utilized: FilterLookup[bool] | None = strawberry_django.filter_field()
|
||||
|
||||
@strawberry_django.filter_field()
|
||||
def contains(self, value: list[str], prefix) -> Q:
|
||||
if not value:
|
||||
return Q()
|
||||
q = Q()
|
||||
for subnet in value:
|
||||
query = str(netaddr.IPNetwork(subnet.strip()).cidr)
|
||||
q |= Q(prefix__net_contains=query)
|
||||
return q
|
||||
|
||||
|
||||
@strawberry_django.filter_type(models.RIR, lookups=True)
|
||||
class RIRFilter(OrganizationalModelFilterMixin):
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
from django.contrib.postgres.indexes import GistIndex
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('dcim', '0210_macaddress_ordering'),
|
||||
('extras', '0129_fix_script_paths'),
|
||||
('ipam', '0081_remove_service_device_virtual_machine_add_parent_gfk_index'),
|
||||
('tenancy', '0020_remove_contactgroupmembership'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name='prefix',
|
||||
index=GistIndex(fields=['prefix'], name='ipam_prefix_gist_idx', opclasses=['inet_ops']),
|
||||
),
|
||||
]
|
||||
@@ -1,5 +1,6 @@
|
||||
import netaddr
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.postgres.indexes import GistIndex
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.db.models import F
|
||||
@@ -281,6 +282,13 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
|
||||
ordering = (F('vrf').asc(nulls_first=True), 'prefix', 'pk') # (vrf, prefix) may be non-unique
|
||||
verbose_name = _('prefix')
|
||||
verbose_name_plural = _('prefixes')
|
||||
indexes = [
|
||||
GistIndex(
|
||||
fields=['prefix'],
|
||||
name='ipam_prefix_gist_idx',
|
||||
opclasses=['inet_ops'],
|
||||
),
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -237,7 +237,11 @@ class ActionsColumn(tables.Column):
|
||||
:param split_actions: When True, converts the actions dropdown menu into a split button with first action as the
|
||||
direct button link and icon (default: True)
|
||||
"""
|
||||
attrs = {'td': {'class': 'text-end text-nowrap noprint'}}
|
||||
attrs = {
|
||||
'td': {
|
||||
'class': 'text-end text-nowrap noprint p-1'
|
||||
}
|
||||
}
|
||||
empty_values = ()
|
||||
actions = {
|
||||
'edit': ActionsItem('Edit', 'pencil', 'change', 'warning'),
|
||||
|
||||
2
netbox/project-static/dist/netbox.css
vendored
2
netbox/project-static/dist/netbox.css
vendored
File diff suppressed because one or more lines are too long
2
netbox/project-static/dist/netbox.js
vendored
2
netbox/project-static/dist/netbox.js
vendored
File diff suppressed because one or more lines are too long
6
netbox/project-static/dist/netbox.js.map
vendored
6
netbox/project-static/dist/netbox.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -38,7 +38,9 @@ function handleQuickSearchParams(event: Event): void {
|
||||
|
||||
if (quickSearchParameters != null) {
|
||||
const link = document.getElementById('export_current_view') as HTMLLinkElement;
|
||||
const search_parameter = `q=${quickSearchParameters.value}`;
|
||||
const params = new URLSearchParams();
|
||||
params.set('q', quickSearchParameters.value);
|
||||
const search_parameter = params.toString();
|
||||
const linkUpdated = link?.href + '&' + search_parameter;
|
||||
link.setAttribute('href', linkUpdated);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ pre.change-data {
|
||||
display: block;
|
||||
padding-right: $spacer;
|
||||
padding-left: $spacer;
|
||||
width: 100%;
|
||||
min-width: fit-content;
|
||||
|
||||
&.added {
|
||||
background-color: $green;
|
||||
|
||||
@@ -845,78 +845,78 @@
|
||||
"@types/estree" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^8.37.0":
|
||||
version "8.38.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz#6e5220d16f2691ab6d983c1737dd5b36e17641b7"
|
||||
integrity sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==
|
||||
version "8.39.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.1.tgz#28dffcb5272d20afe250bfeec3173263db5528a0"
|
||||
integrity sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==
|
||||
dependencies:
|
||||
"@eslint-community/regexpp" "^4.10.0"
|
||||
"@typescript-eslint/scope-manager" "8.38.0"
|
||||
"@typescript-eslint/type-utils" "8.38.0"
|
||||
"@typescript-eslint/utils" "8.38.0"
|
||||
"@typescript-eslint/visitor-keys" "8.38.0"
|
||||
"@typescript-eslint/scope-manager" "8.39.1"
|
||||
"@typescript-eslint/type-utils" "8.39.1"
|
||||
"@typescript-eslint/utils" "8.39.1"
|
||||
"@typescript-eslint/visitor-keys" "8.39.1"
|
||||
graphemer "^1.4.0"
|
||||
ignore "^7.0.0"
|
||||
natural-compare "^1.4.0"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/parser@^8.37.0":
|
||||
version "8.38.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.38.0.tgz#6723a5ea881e1777956b1045cba30be5ea838293"
|
||||
integrity sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==
|
||||
version "8.39.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.39.1.tgz#7f8f9ecfc7e172d67e42c366fa198e42324e5d50"
|
||||
integrity sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "8.38.0"
|
||||
"@typescript-eslint/types" "8.38.0"
|
||||
"@typescript-eslint/typescript-estree" "8.38.0"
|
||||
"@typescript-eslint/visitor-keys" "8.38.0"
|
||||
"@typescript-eslint/scope-manager" "8.39.1"
|
||||
"@typescript-eslint/types" "8.39.1"
|
||||
"@typescript-eslint/typescript-estree" "8.39.1"
|
||||
"@typescript-eslint/visitor-keys" "8.39.1"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/project-service@8.38.0":
|
||||
version "8.38.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.38.0.tgz#4900771f943163027fd7d2020a062892056b5e2f"
|
||||
integrity sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==
|
||||
"@typescript-eslint/project-service@8.39.1":
|
||||
version "8.39.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.39.1.tgz#63525878d488ebf27c485f295e83434a1398f52d"
|
||||
integrity sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils" "^8.38.0"
|
||||
"@typescript-eslint/types" "^8.38.0"
|
||||
"@typescript-eslint/tsconfig-utils" "^8.39.1"
|
||||
"@typescript-eslint/types" "^8.39.1"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/scope-manager@8.38.0":
|
||||
version "8.38.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz#5a0efcb5c9cf6e4121b58f87972f567c69529226"
|
||||
integrity sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==
|
||||
"@typescript-eslint/scope-manager@8.39.1":
|
||||
version "8.39.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.39.1.tgz#1253fe3e1f2f33f08a3e438a05b5dd7faf9fbca6"
|
||||
integrity sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.38.0"
|
||||
"@typescript-eslint/visitor-keys" "8.38.0"
|
||||
"@typescript-eslint/types" "8.39.1"
|
||||
"@typescript-eslint/visitor-keys" "8.39.1"
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@8.38.0", "@typescript-eslint/tsconfig-utils@^8.38.0":
|
||||
version "8.38.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz#6de4ce224a779601a8df667db56527255c42c4d0"
|
||||
integrity sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==
|
||||
"@typescript-eslint/tsconfig-utils@8.39.1", "@typescript-eslint/tsconfig-utils@^8.39.1":
|
||||
version "8.39.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.1.tgz#17f13b4ad481e7bec7c249ee1854078645b34b12"
|
||||
integrity sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==
|
||||
|
||||
"@typescript-eslint/type-utils@8.38.0":
|
||||
version "8.38.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz#a56cd84765fa6ec135fe252b5db61e304403a85b"
|
||||
integrity sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==
|
||||
"@typescript-eslint/type-utils@8.39.1":
|
||||
version "8.39.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.39.1.tgz#642f9fb96173649e2928fea0375b1d74d31906c2"
|
||||
integrity sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.38.0"
|
||||
"@typescript-eslint/typescript-estree" "8.38.0"
|
||||
"@typescript-eslint/utils" "8.38.0"
|
||||
"@typescript-eslint/types" "8.39.1"
|
||||
"@typescript-eslint/typescript-estree" "8.39.1"
|
||||
"@typescript-eslint/utils" "8.39.1"
|
||||
debug "^4.3.4"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/types@8.38.0", "@typescript-eslint/types@^8.38.0":
|
||||
version "8.38.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.38.0.tgz#297351c994976b93c82ac0f0e206c8143aa82529"
|
||||
integrity sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==
|
||||
"@typescript-eslint/types@8.39.1", "@typescript-eslint/types@^8.39.1":
|
||||
version "8.39.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.39.1.tgz#f0ab996c8ab2c3b046bbf86bb1990b03529869a1"
|
||||
integrity sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==
|
||||
|
||||
"@typescript-eslint/typescript-estree@8.38.0":
|
||||
version "8.38.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz#82262199eb6778bba28a319e25ad05b1158957df"
|
||||
integrity sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==
|
||||
"@typescript-eslint/typescript-estree@8.39.1":
|
||||
version "8.39.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.1.tgz#8825d3ea7ea2144c577859ae489eec24ef7318a5"
|
||||
integrity sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service" "8.38.0"
|
||||
"@typescript-eslint/tsconfig-utils" "8.38.0"
|
||||
"@typescript-eslint/types" "8.38.0"
|
||||
"@typescript-eslint/visitor-keys" "8.38.0"
|
||||
"@typescript-eslint/project-service" "8.39.1"
|
||||
"@typescript-eslint/tsconfig-utils" "8.39.1"
|
||||
"@typescript-eslint/types" "8.39.1"
|
||||
"@typescript-eslint/visitor-keys" "8.39.1"
|
||||
debug "^4.3.4"
|
||||
fast-glob "^3.3.2"
|
||||
is-glob "^4.0.3"
|
||||
@@ -924,22 +924,22 @@
|
||||
semver "^7.6.0"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/utils@8.38.0":
|
||||
version "8.38.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.38.0.tgz#5f10159899d30eb92ba70e642ca6f754bddbf15a"
|
||||
integrity sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==
|
||||
"@typescript-eslint/utils@8.39.1":
|
||||
version "8.39.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.39.1.tgz#58a834f89f93b786ada2cd14d77fa63c3c8f408b"
|
||||
integrity sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.7.0"
|
||||
"@typescript-eslint/scope-manager" "8.38.0"
|
||||
"@typescript-eslint/types" "8.38.0"
|
||||
"@typescript-eslint/typescript-estree" "8.38.0"
|
||||
"@typescript-eslint/scope-manager" "8.39.1"
|
||||
"@typescript-eslint/types" "8.39.1"
|
||||
"@typescript-eslint/typescript-estree" "8.39.1"
|
||||
|
||||
"@typescript-eslint/visitor-keys@8.38.0":
|
||||
version "8.38.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz#a9765a527b082cb8fc60fd8a16e47c7ad5b60ea5"
|
||||
integrity sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==
|
||||
"@typescript-eslint/visitor-keys@8.39.1":
|
||||
version "8.39.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.1.tgz#a467742a98f2fa3c03d7bed4979dc0db3850a77a"
|
||||
integrity sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.38.0"
|
||||
"@typescript-eslint/types" "8.39.1"
|
||||
eslint-visitor-keys "^4.2.1"
|
||||
|
||||
"@ungap/structured-clone@^1.2.0":
|
||||
@@ -1742,9 +1742,9 @@ eslint-plugin-import@^2.32.0:
|
||||
tsconfig-paths "^3.15.0"
|
||||
|
||||
eslint-plugin-prettier@^5.5.1:
|
||||
version "5.5.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.3.tgz#1f88e9220a72ac8be171eec5f9d4e4d529b5f4a0"
|
||||
integrity sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==
|
||||
version "5.5.4"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz#9d61c4ea11de5af704d4edf108c82ccfa7f2e61c"
|
||||
integrity sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==
|
||||
dependencies:
|
||||
prettier-linter-helpers "^1.0.0"
|
||||
synckit "^0.11.7"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version: "4.3.5"
|
||||
version: "4.3.6"
|
||||
edition: "Community"
|
||||
published: "2025-07-29"
|
||||
published: "2025-08-12"
|
||||
|
||||
@@ -4,22 +4,22 @@
|
||||
<div class="card">
|
||||
<h2 class="card-header d-flex justify-content-between">
|
||||
{% blocktrans %}Termination{% endblocktrans %} {{ side }}
|
||||
<div>
|
||||
<div class="card-actions">
|
||||
{% if not termination and perms.circuits.add_circuittermination %}
|
||||
<a href="{% url 'circuits:circuittermination_add' %}?circuit={{ object.pk }}&term_side={{ side }}&return_url={{ object.get_absolute_url }}" class="btn btn-success lh-1">
|
||||
<a href="{% url 'circuits:circuittermination_add' %}?circuit={{ object.pk }}&term_side={{ side }}&return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-ghost-primary">
|
||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if termination and perms.circuits.change_circuittermination %}
|
||||
<a href="{% url 'circuits:circuittermination_edit' pk=termination.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-warning lh-1">
|
||||
<a href="{% url 'circuits:circuittermination_edit' pk=termination.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-ghost-warning">
|
||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> {% trans "Edit" %}
|
||||
</a>
|
||||
<a href="{% url 'circuits:circuit_terminations_swap' pk=object.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-primary lh-1">
|
||||
<a href="{% url 'circuits:circuit_terminations_swap' pk=object.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-ghost-primary">
|
||||
<span class="mdi mdi-swap-vertical" aria-hidden="true"></span> {% trans "Swap" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if termination and perms.circuits.delete_circuittermination %}
|
||||
<a href="{% url 'circuits:circuittermination_delete' pk=termination.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-danger lh-1">
|
||||
<a href="{% url 'circuits:circuittermination_delete' pk=termination.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-ghost-danger">
|
||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> {% trans "Delete" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
@@ -29,16 +29,16 @@
|
||||
{{ peer|linkify }}{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
<div class="mt-1">
|
||||
<a href="{% url 'circuits:circuittermination_trace' pk=termination.pk %}" class="btn btn-primary lh-1" title="{% trans "Trace" %}">
|
||||
<a href="{% url 'circuits:circuittermination_trace' pk=termination.pk %}" class="btn btn-sm btn-primary" title="{% trans "Trace" %}">
|
||||
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i> {% trans "Trace" %}
|
||||
</a>
|
||||
{% if perms.dcim.change_cable %}
|
||||
<a href="{% url 'dcim:cable_edit' pk=termination.cable.pk %}?return_url={{ termination.circuit.get_absolute_url }}" title="{% trans "Edit cable" %}" class="btn btn-warning lh-1">
|
||||
<a href="{% url 'dcim:cable_edit' pk=termination.cable.pk %}?return_url={{ termination.circuit.get_absolute_url }}" title="{% trans "Edit cable" %}" class="btn btn-sm btn-warning">
|
||||
<i class="mdi mdi-ethernet-cable" aria-hidden="true"></i> {% trans "Edit" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.dcim.delete_cable %}
|
||||
<a href="{% url 'dcim:cable_delete' pk=termination.cable.pk %}?return_url={{ termination.circuit.get_absolute_url }}" title="{% trans "Remove cable" %}" class="btn btn-danger lh-1">
|
||||
<a href="{% url 'dcim:cable_delete' pk=termination.cable.pk %}?return_url={{ termination.circuit.get_absolute_url }}" title="{% trans "Remove cable" %}" class="btn btn-sm btn-danger">
|
||||
<i class="mdi mdi-ethernet-cable-off" aria-hidden="true"></i> {% trans "Disconnect" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
<th scope="row">{% trans "Cable" %}</th>
|
||||
<td>
|
||||
{{ object.cable|linkify }}
|
||||
<a href="{% url 'dcim:frontport_trace' pk=object.pk %}" class="btn btn-primary lh-1" title="{% trans "Trace" %}">
|
||||
<a href="{% url 'dcim:frontport_trace' pk=object.pk %}" class="btn btn-sm btn-primary" title="{% trans "Trace" %}">
|
||||
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<i class="mdi mdi-chevron-right" aria-hidden="true"></i>
|
||||
{{ term|linkify }}
|
||||
{% with trace_url=term|viewname:"trace" %}
|
||||
<a href="{% url trace_url pk=term.pk %}" class="btn btn-primary lh-1" title="{% trans "Trace" %}">
|
||||
<a href="{% url trace_url pk=term.pk %}" class="btn btn-sm btn-primary" title="{% trans "Trace" %}">
|
||||
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endwith %}
|
||||
@@ -48,7 +48,7 @@
|
||||
{% for term in terminations %}
|
||||
{{ term|linkify }}
|
||||
{% with trace_url=term|viewname:"trace" %}
|
||||
<a href="{% url trace_url pk=term.pk %}" class="btn btn-primary lh-1" title="{% trans "Trace" %}">
|
||||
<a href="{% url trace_url pk=term.pk %}" class="btn btn-sm btn-primary" title="{% trans "Trace" %}">
|
||||
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endwith %}
|
||||
@@ -68,7 +68,7 @@
|
||||
{% for term in terminations %}
|
||||
{{ term.circuit|linkify }} ({{ term }})
|
||||
{% with trace_url=term|viewname:"trace" %}
|
||||
<a href="{% url trace_url pk=term.pk %}" class="btn btn-primary lh-1" title="{% trans "Trace" %}">
|
||||
<a href="{% url trace_url pk=term.pk %}" class="btn btn-sm btn-primary" title="{% trans "Trace" %}">
|
||||
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endwith %}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<th scope="row">{% trans "Cable" %}</th>
|
||||
<td>
|
||||
{{ object.cable|linkify }}
|
||||
<a href="{% url trace_url pk=object.pk %}" class="btn btn-primary lh-1" title="{% trans "Trace" %}">
|
||||
<a href="{% url trace_url pk=object.pk %}" class="btn btn-sm btn-primary" title="{% trans "Trace" %}">
|
||||
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@@ -29,12 +29,12 @@
|
||||
<td>{{ item.role|linkify|placeholder }}</td>
|
||||
<td class="text-end d-print-none">
|
||||
{% if perms.dcim.change_inventoryitem %}
|
||||
<a href="{% url 'dcim:inventoryitem_edit' pk=item.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-warning lh-1" title="{% trans "Edit" %}">
|
||||
<a href="{% url 'dcim:inventoryitem_edit' pk=item.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-warning" title="{% trans "Edit" %}">
|
||||
<i class="mdi mdi-pencil" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.ipam.delete_inventoryitem %}
|
||||
<a href="{% url 'dcim:inventoryitem_delete' pk=item.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-danger lh-1" title="{% trans "Delete" %}">
|
||||
<a href="{% url 'dcim:inventoryitem_delete' pk=item.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-danger" title="{% trans "Delete" %}">
|
||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
@@ -208,7 +208,7 @@
|
||||
<th scope="row">{% trans "Wireless Link" %}</th>
|
||||
<td>
|
||||
{{ object.wireless_link|linkify }}
|
||||
<a href="{% url 'dcim:interface_trace' pk=object.pk %}" class="btn btn-primary lh-1" title="{% trans "Trace" %}">
|
||||
<a href="{% url 'dcim:interface_trace' pk=object.pk %}" class="btn btn-sm btn-primary" title="{% trans "Trace" %}">
|
||||
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
<th scope="row">{% trans "Cable" %}</th>
|
||||
<td>
|
||||
{{ object.cable|linkify }}
|
||||
<a href="{% url 'dcim:rearport_trace' pk=object.pk %}" class="btn btn-primary lh-1" title="{% trans "Trace" %}">
|
||||
<a href="{% url 'dcim:rearport_trace' pk=object.pk %}" class="btn btn-sm btn-primary" title="{% trans "Trace" %}">
|
||||
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
{% trans "Output" %}
|
||||
{% if job.completed %}
|
||||
<div>
|
||||
<a href="?export=output" class="btn btn-primary lh-1" role="button">
|
||||
<a href="?export=output" class="btn btn-sm btn-primary" role="button">
|
||||
<i class="mdi mdi-download" aria-hidden="true"></i> {% trans "Download" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
139
netbox/templates/extras/inc/script_list_content.html
Normal file
139
netbox/templates/extras/inc/script_list_content.html
Normal file
@@ -0,0 +1,139 @@
|
||||
{% load buttons %}
|
||||
{% load helpers %}
|
||||
{% load perms %}
|
||||
{% load i18n %}
|
||||
|
||||
{# Core script list content - used by both full page and embedded views #}
|
||||
{% for module in script_modules %}
|
||||
{% include 'inc/sync_warning.html' with object=module %}
|
||||
<div class="card{% if embedded %} mb-3{% endif %}">
|
||||
{% if not embedded %}
|
||||
<h2 class="card-header" id="module{{ module.pk }}">
|
||||
<i class="mdi mdi-file-document-outline"></i> {{ module }}
|
||||
<div class="card-actions">
|
||||
{% if perms.extras.edit_scriptmodule %}
|
||||
<a href="{% url 'extras:scriptmodule_edit' pk=module.pk %}" class="btn btn-ghost-warning btn-sm">
|
||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> {% trans "Edit" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.extras.delete_scriptmodule %}
|
||||
<a href="{% url 'extras:scriptmodule_delete' pk=module.pk %}" class="btn btn-ghost-danger btn-sm">
|
||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> {% trans "Delete" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</h2>
|
||||
{% endif %}
|
||||
{% with scripts=module.ordered_scripts %}
|
||||
{% if scripts %}
|
||||
<table class="table table-hover scripts{% if embedded %} object-list table-sm{% endif %}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Description" %}</th>
|
||||
<th>{% trans "Last Run" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for script in scripts %}
|
||||
{% with last_job=script.get_latest_jobs|first %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if script.is_executable %}
|
||||
<a href="{% url 'extras:script' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.python_class.name }}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'extras:script_jobs' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.python_class.name }}</a>
|
||||
<span class="text-danger">
|
||||
<i class="mdi mdi-alert" title="{% trans "Script is no longer present in the source file" %}"></i>
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ script.python_class.description|markdown|placeholder }}</td>
|
||||
{% if last_job %}
|
||||
<td>
|
||||
<a href="{% url 'extras:script_result' job_pk=last_job.pk %}">{{ last_job.created|isodatetime }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% badge last_job.get_status_display last_job.get_status_color %}
|
||||
</td>
|
||||
{% else %}
|
||||
<td class="text-muted">{% trans "Never" %}</td>
|
||||
<td>{{ ''|placeholder }}</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
{% if request.user|can_run:script and script.is_executable %}
|
||||
<div class="float-end d-print-none">
|
||||
<form action="{% url 'extras:script' script.pk %}" method="post">
|
||||
{% if script.python_class.commit_default %}
|
||||
<input type="checkbox" name="_commit" hidden checked>
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
<button type="submit" name="_run" class="btn btn-primary{% if embedded %} btn-sm{% endif %}">
|
||||
{% if last_job %}
|
||||
<i class="mdi mdi-replay"></i> {% if not embedded %}{% trans "Run Again" %}{% endif %}
|
||||
{% else %}
|
||||
<i class="mdi mdi-play"></i> {% if not embedded %}{% trans "Run Script" %}{% endif %}
|
||||
{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if last_job and not embedded %}
|
||||
{% for test_name, data in last_job.data.tests.items %}
|
||||
<tr>
|
||||
<td colspan="4" class="method">
|
||||
<span class="ps-3">{{ test_name }}</span>
|
||||
</td>
|
||||
<td class="text-end text-nowrap script-stats">
|
||||
<span class="badge text-bg-success">{{ data.success }}</span>
|
||||
<span class="badge text-bg-info">{{ data.info }}</span>
|
||||
<span class="badge text-bg-warning">{{ data.warning }}</span>
|
||||
<span class="badge text-bg-danger">{{ data.failure }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% elif last_job and not last_job.data.log and not embedded %}
|
||||
{# legacy #}
|
||||
{% for method, stats in last_job.data.items %}
|
||||
<tr>
|
||||
<td colspan="4" class="method">
|
||||
<span class="ps-3">{{ method }}</span>
|
||||
</td>
|
||||
<td class="text-end text-nowrap report-stats">
|
||||
<span class="badge bg-success">{{ stats.success }}</span>
|
||||
<span class="badge bg-info">{{ stats.info }}</span>
|
||||
<span class="badge bg-warning">{{ stats.warning }}</span>
|
||||
<span class="badge bg-danger">{{ stats.failure }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="card-body">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<i class="mdi mdi-alert"></i>
|
||||
{% blocktrans with module=module.name %}Could not load scripts from module {{ module }}{% endblocktrans %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="alert alert-info" role="alert">
|
||||
<h4 class="alert-heading">{% trans "No Scripts Found" %}</h4>
|
||||
{% if perms.extras.add_scriptmodule and not embedded %}
|
||||
{% url 'extras:scriptmodule_add' as create_script_url %}
|
||||
{% blocktrans trimmed %}
|
||||
Get started by <a href="{{ create_script_url }}">creating a script</a> from an uploaded file or data source.
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -54,11 +54,11 @@
|
||||
<div class="card">
|
||||
<h2 class="card-header d-flex justify-content-between">
|
||||
{% trans "Rendered Config" %}
|
||||
<div>
|
||||
{% copy_content "rendered_config" %}
|
||||
<a href="?export=True" class="btn btn-primary lh-1" role="button">
|
||||
<div class="card-actions">
|
||||
<a href="?export=True" class="btn btn-sm btn-ghost-primary" role="button">
|
||||
<i class="mdi mdi-download" aria-hidden="true"></i> {% trans "Download" %}
|
||||
</a>
|
||||
{% copy_content "rendered_config" %}
|
||||
</div>
|
||||
</h2>
|
||||
<pre class="card-body" id="rendered_config">{{ rendered_config }}</pre>
|
||||
|
||||
@@ -70,6 +70,10 @@
|
||||
"poe_type": {
|
||||
"type": "string",
|
||||
"enum": {{ interface_poe_type_choices }}
|
||||
},
|
||||
"rf_role": {
|
||||
"type": "string",
|
||||
"enum": {{ interface_rf_role_choices }}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -19,135 +19,5 @@
|
||||
{% endblock controls %}
|
||||
|
||||
{% block content %}
|
||||
{% for module in script_modules %}
|
||||
{% include 'inc/sync_warning.html' with object=module %}
|
||||
<div class="card">
|
||||
<h2 class="card-header" id="module{{ module.pk }}">
|
||||
<i class="mdi mdi-file-document-outline"></i> {{ module }}
|
||||
<div class="card-actions">
|
||||
{% if perms.extras.edit_scriptmodule %}
|
||||
<a href="{% url 'extras:scriptmodule_edit' pk=module.pk %}" class="btn btn-ghost-warning btn-sm">
|
||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> {% trans "Edit" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.extras.delete_scriptmodule %}
|
||||
<a href="{% url 'extras:scriptmodule_delete' pk=module.pk %}" class="btn btn-ghost-danger btn-sm">
|
||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> {% trans "Delete" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</h2>
|
||||
{% with scripts=module.ordered_scripts %}
|
||||
{% if scripts %}
|
||||
<table class="table table-hover scripts">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Description" %}</th>
|
||||
<th>{% trans "Last Run" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for script in scripts %}
|
||||
{% with last_job=script.get_latest_jobs|first %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if script.is_executable %}
|
||||
<a href="{% url 'extras:script' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.python_class.name }}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'extras:script_jobs' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.python_class.name }}</a>
|
||||
<span class="text-danger">
|
||||
<i class="mdi mdi-alert" title="{% trans "Script is no longer present in the source file" %}"></i>
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ script.python_class.description|markdown|placeholder }}</td>
|
||||
{% if last_job %}
|
||||
<td>
|
||||
<a href="{% url 'extras:script_result' job_pk=last_job.pk %}">{{ last_job.created|isodatetime }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% badge last_job.get_status_display last_job.get_status_color %}
|
||||
</td>
|
||||
{% else %}
|
||||
<td class="text-muted">{% trans "Never" %}</td>
|
||||
<td>{{ ''|placeholder }}</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
{% if request.user|can_run:script and script.is_executable %}
|
||||
<div class="float-end d-print-none">
|
||||
<form action="{% url 'extras:script' script.pk %}" method="post">
|
||||
{% if script.python_class.commit_default %}
|
||||
<input type="checkbox" name="_commit" hidden checked>
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
<button type="submit" name="_run" class="btn btn-primary btn-sm">
|
||||
{% if last_job %}
|
||||
<i class="mdi mdi-replay"></i> {% trans "Run Again" %}
|
||||
{% else %}
|
||||
<i class="mdi mdi-play"></i> {% trans "Run Script" %}
|
||||
{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if last_job %}
|
||||
{% for test_name, data in last_job.data.tests.items %}
|
||||
<tr>
|
||||
<td colspan="4" class="method">
|
||||
<span class="ps-3">{{ test_name }}</span>
|
||||
</td>
|
||||
<td class="text-end text-nowrap script-stats">
|
||||
<span class="badge text-bg-success">{{ data.success }}</span>
|
||||
<span class="badge text-bg-info">{{ data.info }}</span>
|
||||
<span class="badge text-bg-warning">{{ data.warning }}</span>
|
||||
<span class="badge text-bg-danger">{{ data.failure }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% elif not last_job.data.log %}
|
||||
{# legacy #}
|
||||
{% for method, stats in last_job.data.items %}
|
||||
<tr>
|
||||
<td colspan="4" class="method">
|
||||
<span class="ps-3">{{ method }}</span>
|
||||
</td>
|
||||
<td class="text-end text-nowrap report-stats">
|
||||
<span class="badge bg-success">{{ stats.success }}</span>
|
||||
<span class="badge bg-info">{{ stats.info }}</span>
|
||||
<span class="badge bg-warning">{{ stats.warning }}</span>
|
||||
<span class="badge bg-danger">{{ stats.failure }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="card-body">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<i class="mdi mdi-alert"></i>
|
||||
{% blocktrans with module=module.name %}Could not load scripts from module {{ module }}{% endblocktrans %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="alert alert-info" role="alert">
|
||||
<h4 class="alert-heading">{% trans "No Scripts Found" %}</h4>
|
||||
{% if perms.extras.add_scriptmodule %}
|
||||
{% url 'extras:scriptmodule_add' as create_script_url %}
|
||||
{% blocktrans trimmed %}
|
||||
Get started by <a href="{{ create_script_url }}">creating a script</a> from an uploaded file or data source.
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% include 'extras/inc/script_list_content.html' with embedded=False %}
|
||||
{% endblock content %}
|
||||
|
||||
@@ -88,6 +88,21 @@
|
||||
#
|
||||
`;
|
||||
|
||||
let sharedQuery;
|
||||
const hashArgs = new URLSearchParams(window.location.hash.substring(1));
|
||||
if (hashArgs.has('query')) {
|
||||
sharedQuery = hashArgs.get('query');
|
||||
// reset url to not motivate copying of stale URL
|
||||
hashArgs.delete('query');
|
||||
let remainingHash = "";
|
||||
if (hashArgs.size !== 0) {
|
||||
remainingHash = `#${hashArgs.toString()}`;
|
||||
}
|
||||
history.pushState("", document.title,
|
||||
window.location.pathname + window.location.search + remainingHash
|
||||
);
|
||||
}
|
||||
|
||||
const fetchURL = window.location.href;
|
||||
|
||||
function httpUrlToWebSockeUrl(url) {
|
||||
@@ -123,6 +138,8 @@
|
||||
defaultEditorToolsVisibility: true,
|
||||
plugins: [explorerPlugin],
|
||||
inputValueDeprecation: true,
|
||||
defaultQuery: EXAMPLE_QUERY,
|
||||
query: sharedQuery,
|
||||
}),
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
<div class="d-block text-secondary fs-5">{{ notification.event }} {{ notification.created|timesince }} {% trans "ago" %}</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a href="#" hx-get="{% url 'extras:notification_dismiss' pk=notification.pk %}" hx-target="closest .notifications" class="list-group-item-actions text-secondary" title="{% trans "Dismiss" %}">
|
||||
<a href="#" hx-get="{% url 'extras:notification_dismiss' pk=notification.pk %}" hx-target="closest .notifications" class="list-group-item-actions text-red" title="{% trans "Dismiss" %}">
|
||||
<i class="mdi mdi-close"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="dropdown-item text-muted">
|
||||
<div class="dropdown-item disabled">
|
||||
{% trans "No unread notifications" %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="d-flex ms-2">
|
||||
<button class="btn color-mode-toggle hide-theme-dark" title="{% trans "Enable dark mode" %}" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<button class="nav-link color-mode-toggle hide-theme-dark fs-2 p-0 text-secondary" title="{% trans "Enable dark mode" %}" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<i class="mdi mdi-lightbulb"></i>
|
||||
</button>
|
||||
<button class="btn color-mode-toggle hide-theme-light" title="{% trans "Enable light mode" %}" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<button class="nav-link color-mode-toggle hide-theme-light fs-2 p-0 text-secondary" title="{% trans "Enable light mode" %}" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<i class="mdi mdi-lightbulb-on"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% if notifications %}
|
||||
<span class="text-primary" id="notifications-alert" hx-swap-oob="true">
|
||||
<i class="mdi mdi-bell-badge"></i>
|
||||
<i class="mdi mdi-bell-ring"></i>
|
||||
<span class="badge bg-red"></span>
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-muted" id="notifications-alert" hx-swap-oob="true">
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
{# Notifications #}
|
||||
{% with notifications=request.user.notifications.unread.exists %}
|
||||
<div class="dropdown">
|
||||
<a href="#" class="nav-link px-1" data-bs-toggle="dropdown" hx-get="{% url 'extras:notifications' %}" hx-target="next .notifications" aria-label="{% trans "Notifications" %}">
|
||||
<button class="nav-link fs-2 p-0" data-bs-toggle="dropdown" hx-get="{% url 'extras:notifications' %}" hx-target="next .notifications" aria-label="{% trans "Notifications" %}">
|
||||
{% include 'inc/notification_bell.html' %}
|
||||
</a>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow notifications"></div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
|
||||
@@ -40,12 +40,12 @@
|
||||
<td>{{ assignment.priority }}</td>
|
||||
<td class="text-end d-print-none">
|
||||
{% if perms.ipam.change_fhrpgroupassignment %}
|
||||
<a href="{% url 'ipam:fhrpgroupassignment_edit' pk=assignment.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-warning lh-1" title="{% trans "Edit" %}">
|
||||
<a href="{% url 'ipam:fhrpgroupassignment_edit' pk=assignment.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-warning" title="{% trans "Edit" %}">
|
||||
<i class="mdi mdi-pencil" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.ipam.delete_fhrpgroupassignment %}
|
||||
<a href="{% url 'ipam:fhrpgroupassignment_delete' pk=assignment.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-danger lh-1" title="{% trans "Delete" %}">
|
||||
<a href="{% url 'ipam:fhrpgroupassignment_delete' pk=assignment.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-danger" title="{% trans "Delete" %}">
|
||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -160,9 +160,17 @@ def string_to_ranges(value):
|
||||
return None
|
||||
value.replace(' ', '') # Remove whitespace
|
||||
values = []
|
||||
for dash_range in value.split(','):
|
||||
if '-' not in dash_range:
|
||||
for data in value.split(','):
|
||||
dash_range = data.strip().split('-')
|
||||
if len(dash_range) == 1 and str(dash_range[0]).isdigit():
|
||||
# Single integer value; expand to a range
|
||||
lower = dash_range[0]
|
||||
upper = dash_range[0]
|
||||
elif len(dash_range) == 2 and str(dash_range[0]).isdigit() and str(dash_range[1]).isdigit():
|
||||
# The range has two values and both are valid integers
|
||||
lower = dash_range[0]
|
||||
upper = dash_range[1]
|
||||
else:
|
||||
return None
|
||||
lower, upper = dash_range.split('-')
|
||||
values.append(NumericRange(int(lower), int(upper), bounds='[]'))
|
||||
return values
|
||||
|
||||
@@ -32,12 +32,14 @@ class NumericArrayField(SimpleArrayField):
|
||||
class NumericRangeArrayField(forms.CharField):
|
||||
"""
|
||||
A field which allows for array of numeric ranges:
|
||||
Example: 1-5,7-20,30-50
|
||||
Example: 1-5,10,20-30
|
||||
"""
|
||||
def __init__(self, *args, help_text='', **kwargs):
|
||||
if not help_text:
|
||||
help_text = mark_safe(
|
||||
_("Specify one or more numeric ranges separated by commas. Example: " + "<code>1-5,20-30</code>")
|
||||
_(
|
||||
"Specify one or more individual numbers or numeric ranges separated by commas. Example: {example}"
|
||||
).format(example="<code>1-5,10,20-30</code>")
|
||||
)
|
||||
super().__init__(*args, help_text=help_text, **kwargs)
|
||||
|
||||
|
||||
@@ -66,3 +66,17 @@ class RangeFunctionsTestCase(TestCase):
|
||||
NumericRange(100, 199, bounds='[]'), # 100-199
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
string_to_ranges('1-2, 5, 10-12'),
|
||||
[
|
||||
NumericRange(1, 2, bounds='[]'), # 1-2
|
||||
NumericRange(5, 5, bounds='[]'), # 5-5
|
||||
NumericRange(10, 12, bounds='[]'), # 10-12
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
string_to_ranges('2-10, a-b'),
|
||||
None # Fails to convert
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[project]
|
||||
name = "netbox"
|
||||
version = "4.3.5"
|
||||
version = "4.3.6"
|
||||
requires-python = ">=3.10"
|
||||
authors = [
|
||||
{ name = "NetBox Community" }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Django==5.2.4
|
||||
Django==5.2.5
|
||||
django-cors-headers==4.7.0
|
||||
django-debug-toolbar==5.2.0
|
||||
django-filter==25.1
|
||||
@@ -9,14 +9,14 @@ django-pglocks==1.0.4
|
||||
django-prometheus==2.4.1
|
||||
django-redis==6.0.0
|
||||
django-rich==2.0.0
|
||||
django-rq==3.0.1
|
||||
django-rq==3.1
|
||||
django-storages==1.14.6
|
||||
django-tables2==2.7.5
|
||||
django-taggit==6.1.0
|
||||
django-timezone-field==7.1
|
||||
djangorestframework==3.16.0
|
||||
djangorestframework==3.16.1
|
||||
drf-spectacular==0.28.0
|
||||
drf-spectacular-sidecar==2025.7.1
|
||||
drf-spectacular-sidecar==2025.8.1
|
||||
feedparser==6.0.11
|
||||
gunicorn==23.0.0
|
||||
Jinja2==3.1.6
|
||||
@@ -33,7 +33,7 @@ requests==2.32.4
|
||||
rq==2.4.1
|
||||
social-auth-app-django==5.5.1
|
||||
social-auth-core==4.7.0
|
||||
strawberry-graphql==0.278.0
|
||||
strawberry-graphql==0.278.1
|
||||
strawberry-graphql-django==0.65.1
|
||||
svgwrite==1.4.3
|
||||
tablib==3.8.0
|
||||
|
||||
Reference in New Issue
Block a user