Compare commits

...

11 Commits

Author SHA1 Message Date
Martin Hauser
3d76eb887b fix(ui): Suppress unauthorized embedded object tables
Add a `should_render()` hook to the `Panel` base class and override it
in `ObjectsTablePanel` to check the requesting user's view permission
for the panel's model. This prevents object detail pages from issuing
HTMX requests for related tables (e.g. locations, devices, image
attachments) that return 403 and disrupt the page.

Fixes #21893

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2026-04-15 23:37:38 +02:00
Jeremy Stretch
1af320e0a9 Fixes #21538: Fix annotated count for contacts assigned to multiple contact groups (#21919) 2026-04-15 16:01:19 -05:00
Martin Hauser
c28736e1d6 Fixes #21913: Restore plugin template extension support on declarative-layout detail views (#21928) 2026-04-15 14:29:17 -05:00
Jeremy Stretch
f0fc93d827 Fixes #21683: Fix support for importing port mappings on device/module types (#21921) 2026-04-15 19:45:26 +02:00
Jeremy Stretch
bf9de4721e Closes #20881: get_filterset_for_model() should reference application registry (#21922) 2026-04-15 19:36:33 +02:00
Jeremy Stretch
bce667300a Fixes #21737: Check that uploaded custom scripts are valid Python modules before saving (#21920) 2026-04-15 10:16:58 -07:00
Sergio López
660ca42149 Closes #21875: Allow subclasses of dict for API_TOKEN_PEPPERS 2026-04-14 16:59:49 -04:00
Jeremy Stretch
75e1b86613 Release v4.5.8 (#21903)
* Release v4.5.8
* Limit django-tables2 to <v2.9
2026-04-14 08:39:16 -04:00
github-actions
e12334c01b Update source translation strings 2026-04-14 05:39:35 +00:00
Jeremy Stretch
5aeb045fb5 Closes #21783: Fix support for bulk import of cables connected to power feeds (#21873) 2026-04-13 12:03:46 -05:00
Martin Hauser
6c12d8b402 Fixes #21869: Remove redundant ScriptModule class synchronization on save (#21899) 2026-04-13 10:53:00 -05:00
62 changed files with 8870 additions and 8513 deletions

View File

@@ -15,7 +15,7 @@ body:
attributes:
label: NetBox version
description: What version of NetBox are you currently running?
placeholder: v4.5.7
placeholder: v4.5.8
validations:
required: true
- type: dropdown

View File

@@ -27,7 +27,7 @@ body:
attributes:
label: NetBox Version
description: What version of NetBox are you currently running?
placeholder: v4.5.7
placeholder: v4.5.8
validations:
required: true
- type: dropdown

View File

@@ -8,7 +8,7 @@ body:
attributes:
label: NetBox Version
description: What version of NetBox are you currently running?
placeholder: v4.5.7
placeholder: v4.5.8
validations:
required: true
- type: dropdown

View File

@@ -55,7 +55,8 @@ django-storages
# Abstraction models for rendering and paginating HTML tables
# https://github.com/jieter/django-tables2/blob/master/CHANGELOG.md
django-tables2
# See #21902 for upgrading to django-tables2 v2.9+
django-tables2<2.9
# User-defined tags for objects
# https://github.com/jazzband/django-taggit/blob/master/CHANGELOG.rst

View File

@@ -2,7 +2,7 @@
"openapi": "3.0.3",
"info": {
"title": "NetBox REST API",
"version": "4.5.7",
"version": "4.5.8",
"license": {
"name": "Apache v2 License"
}
@@ -57744,7 +57744,7 @@
"type": "array",
"items": {
"type": "integer",
"format": "int32"
"format": "int64"
}
},
"explode": true,
@@ -57757,7 +57757,7 @@
"type": "array",
"items": {
"type": "integer",
"format": "int32"
"format": "int64"
}
},
"explode": true,
@@ -57770,7 +57770,7 @@
"type": "array",
"items": {
"type": "integer",
"format": "int32"
"format": "int64"
}
},
"explode": true,
@@ -57783,7 +57783,7 @@
"type": "array",
"items": {
"type": "integer",
"format": "int32"
"format": "int64"
}
},
"explode": true,
@@ -57796,7 +57796,7 @@
"type": "array",
"items": {
"type": "integer",
"format": "int32"
"format": "int64"
}
},
"explode": true,
@@ -57809,7 +57809,7 @@
"type": "array",
"items": {
"type": "integer",
"format": "int32"
"format": "int64"
}
},
"explode": true,
@@ -57822,7 +57822,7 @@
"type": "array",
"items": {
"type": "integer",
"format": "int32"
"format": "int64"
}
},
"explode": true,
@@ -240094,8 +240094,9 @@
},
"speed": {
"type": "integer",
"maximum": 2147483647,
"maximum": 9223372036854775807,
"minimum": 0,
"format": "int64",
"nullable": true,
"title": "Speed (Kbps)"
},
@@ -241179,8 +241180,9 @@
},
"speed": {
"type": "integer",
"maximum": 2147483647,
"maximum": 9223372036854775807,
"minimum": 0,
"format": "int64",
"nullable": true,
"title": "Speed (Kbps)"
},
@@ -258026,8 +258028,9 @@
},
"speed": {
"type": "integer",
"maximum": 2147483647,
"maximum": 9223372036854775807,
"minimum": 0,
"format": "int64",
"nullable": true,
"title": "Speed (Kbps)"
},
@@ -280622,8 +280625,9 @@
},
"speed": {
"type": "integer",
"maximum": 2147483647,
"maximum": 9223372036854775807,
"minimum": 0,
"format": "int64",
"nullable": true,
"title": "Speed (Kbps)"
},

View File

@@ -1,5 +1,27 @@
# NetBox v4.5
## v4.5.8 (2026-04-14)
### Enhancements
* [#21430](https://github.com/netbox-community/netbox/issues/21430) - Display the device role's color in the device view
* [#21795](https://github.com/netbox-community/netbox/issues/21795) - Update `humanize_speed` template filter to support decimal Gbps/Tbps values
### Bug Fixes
* [#21529](https://github.com/netbox-community/netbox/issues/21529) - Exclude non-existent custom fields from object changelog data returned via the REST API
* [#21542](https://github.com/netbox-community/netbox/issues/21542) - Expand interface speed field to 64-bit integer to prevent overflow for LAG interfaces exceeding ~2.1 Tbps
* [#21704](https://github.com/netbox-community/netbox/issues/21704) - Fix missing port mappings in device type YAML export
* [#21783](https://github.com/netbox-community/netbox/issues/21783) - Fix support for bulk import of cables connected to power feeds
* [#21801](https://github.com/netbox-community/netbox/issues/21801) - Prevent duplicate filename collision when uploading files using S3 storage
* [#21814](https://github.com/netbox-community/netbox/issues/21814) - Fix custom script "last run" time to reflect job start time rather than creation time
* [#21835](https://github.com/netbox-community/netbox/issues/21835) - Correct help text for color selection form fields
* [#21841](https://github.com/netbox-community/netbox/issues/21841) - Restore visibility of the edit button for script modules to non-superusers
* [#21845](https://github.com/netbox-community/netbox/issues/21845) - Fix CSV export of connection columns rendering template whitespace instead of a formatted value
* [#21869](https://github.com/netbox-community/netbox/issues/21869) - Remove redundant `ScriptModule` class synchronization triggered on save
---
## v4.5.7 (2026-04-03)
### Enhancements

View File

@@ -18,7 +18,7 @@ class JobTableTest(TableTestCases.StandardTableTestCase):
class ObjectChangeTableTest(TableTestCases.StandardTableTestCase):
table = ObjectChangeTable
queryset_sources = [
('ObjectChangeListView', ObjectChange.objects.valid_models()),
('ObjectChangeListView', ObjectChange.objects.all()),
]

View File

@@ -192,6 +192,12 @@ class DataFileView(generic.ObjectView):
layout.Column(
panels.DataFilePanel(),
panels.DataFileContentPanel(),
PluginContentPanel('left_page'),
),
),
layout.Row(
layout.Column(
PluginContentPanel('full_width_page'),
),
),
)
@@ -253,6 +259,12 @@ class JobLogView(generic.ObjectView):
layout.Row(
layout.Column(
ContextTablePanel('table', title=_('Log Entries')),
PluginContentPanel('left_page'),
),
),
layout.Row(
layout.Column(
PluginContentPanel('full_width_page'),
),
),
)
@@ -393,6 +405,12 @@ class ConfigRevisionView(generic.ObjectView):
layout.Column(
TemplatePanel('core/panels/configrevision_data.html'),
TemplatePanel('core/panels/configrevision_comment.html'),
PluginContentPanel('left_page'),
),
),
layout.Row(
layout.Column(
PluginContentPanel('full_width_page'),
),
),
)

View File

@@ -1409,8 +1409,16 @@ class CableImportForm(PrimaryModelImportForm):
side_a_device = CSVModelChoiceField(
label=_('Side A device'),
queryset=Device.objects.all(),
required=False,
to_field_name='name',
help_text=_('Device name')
help_text=_('Device name (for device component terminations)')
)
side_a_power_panel = CSVModelChoiceField(
label=_('Side A power panel'),
queryset=PowerPanel.objects.all(),
required=False,
to_field_name='name',
help_text=_('Power panel name (for power feed terminations)')
)
side_a_type = CSVContentTypeField(
label=_('Side A type'),
@@ -1434,8 +1442,16 @@ class CableImportForm(PrimaryModelImportForm):
side_b_device = CSVModelChoiceField(
label=_('Side B device'),
queryset=Device.objects.all(),
required=False,
to_field_name='name',
help_text=_('Device name')
help_text=_('Device name (for device component terminations)')
)
side_b_power_panel = CSVModelChoiceField(
label=_('Side B power panel'),
queryset=PowerPanel.objects.all(),
required=False,
to_field_name='name',
help_text=_('Power panel name (for power feed terminations)')
)
side_b_type = CSVContentTypeField(
label=_('Side B type'),
@@ -1490,8 +1506,9 @@ class CableImportForm(PrimaryModelImportForm):
class Meta:
model = Cable
fields = [
'side_a_site', 'side_a_device', 'side_a_type', 'side_a_name', 'side_b_site', 'side_b_device', 'side_b_type',
'side_b_name', 'type', 'status', 'profile', 'tenant', 'label', 'color', 'length', 'length_unit',
'side_a_site', 'side_a_device', 'side_a_power_panel', 'side_a_type', 'side_a_name',
'side_b_site', 'side_b_device', 'side_b_power_panel', 'side_b_type', 'side_b_name',
'type', 'status', 'profile', 'tenant', 'label', 'color', 'length', 'length_unit',
'description', 'owner', 'comments', 'tags',
]
@@ -1501,16 +1518,22 @@ class CableImportForm(PrimaryModelImportForm):
if data:
# Limit choices for side_a_device to the assigned side_a_site
if side_a_site := data.get('side_a_site'):
side_a_device_params = {f'site__{self.fields["side_a_site"].to_field_name}': side_a_site}
side_a_parent_params = {f'site__{self.fields['side_a_site'].to_field_name}': side_a_site}
self.fields['side_a_device'].queryset = self.fields['side_a_device'].queryset.filter(
**side_a_device_params
**side_a_parent_params
)
self.fields['side_a_power_panel'].queryset = self.fields['side_a_power_panel'].queryset.filter(
**side_a_parent_params
)
# Limit choices for side_b_device to the assigned side_b_site
if side_b_site := data.get('side_b_site'):
side_b_device_params = {f'site__{self.fields["side_b_site"].to_field_name}': side_b_site}
side_b_parent_params = {f'site__{self.fields['side_b_site'].to_field_name}': side_b_site}
self.fields['side_b_device'].queryset = self.fields['side_b_device'].queryset.filter(
**side_b_device_params
**side_b_parent_params
)
self.fields['side_b_power_panel'].queryset = self.fields['side_b_power_panel'].queryset.filter(
**side_b_parent_params
)
def _clean_side(self, side):
@@ -1522,33 +1545,57 @@ class CableImportForm(PrimaryModelImportForm):
assert side in 'ab', f"Invalid side designation: {side}"
device = self.cleaned_data.get(f'side_{side}_device')
power_panel = self.cleaned_data.get(f'side_{side}_power_panel')
content_type = self.cleaned_data.get(f'side_{side}_type')
name = self.cleaned_data.get(f'side_{side}_name')
if not device or not content_type or not name:
if not content_type or not name:
return None
model = content_type.model_class()
try:
if (
device.virtual_chassis and
device.virtual_chassis.master == device and
not model.objects.filter(device=device, name=name).exists()
):
termination_object = model.objects.get(device__in=device.virtual_chassis.members.all(), name=name)
else:
termination_object = model.objects.get(device=device, name=name)
if termination_object.cable is not None and termination_object.cable != self.instance:
# PowerFeed terminations reference a PowerPanel, not a Device
if content_type.model == 'powerfeed':
if not power_panel:
return None
try:
termination_object = model.objects.get(power_panel=power_panel, name=name)
if termination_object.cable is not None and termination_object.cable != self.instance:
raise forms.ValidationError(
_("Side {side_upper}: {power_panel} {termination_object} is already connected").format(
side_upper=side.upper(), power_panel=power_panel, termination_object=termination_object
)
)
except ObjectDoesNotExist:
raise forms.ValidationError(
_("Side {side_upper}: {device} {termination_object} is already connected").format(
side_upper=side.upper(), device=device, termination_object=termination_object
_("{side_upper} side termination not found: {power_panel} {name}").format(
side_upper=side.upper(), power_panel=power_panel, name=name
)
)
except ObjectDoesNotExist:
raise forms.ValidationError(
_("{side_upper} side termination not found: {device} {name}").format(
side_upper=side.upper(), device=device, name=name
else:
if not device:
return None
try:
if (
device.virtual_chassis and
device.virtual_chassis.master == device and
not model.objects.filter(device=device, name=name).exists()
):
termination_object = model.objects.get(device__in=device.virtual_chassis.members.all(), name=name)
else:
termination_object = model.objects.get(device=device, name=name)
if termination_object.cable is not None and termination_object.cable != self.instance:
raise forms.ValidationError(
_("Side {side_upper}: {device} {termination_object} is already connected").format(
side_upper=side.upper(), device=device, termination_object=termination_object
)
)
except ObjectDoesNotExist:
raise forms.ValidationError(
_("{side_upper} side termination not found: {device} {name}").format(
side_upper=side.upper(), device=device, name=name
)
)
)
setattr(self.instance, f'{side}_terminations', [termination_object])
return termination_object

View File

@@ -150,9 +150,25 @@ class PortTemplateMappingImportForm(forms.ModelForm):
class Meta:
model = PortTemplateMapping
fields = [
'front_port', 'front_port_position', 'rear_port', 'rear_port_position',
'device_type', 'module_type', 'front_port', 'front_port_position', 'rear_port', 'rear_port_position',
]
def clean_device_type(self):
if device_type := self.cleaned_data['device_type']:
front_port = self.fields['front_port']
rear_port = self.fields['rear_port']
front_port.queryset = front_port.queryset.filter(device_type=device_type)
rear_port.queryset = rear_port.queryset.filter(device_type=device_type)
return device_type
def clean_module_type(self):
if module_type := self.cleaned_data['module_type']:
front_port = self.fields['front_port']
rear_port = self.fields['rear_port']
front_port.queryset = front_port.queryset.filter(module_type=module_type)
rear_port.queryset = rear_port.queryset.filter(module_type=module_type)
return module_type
class ModuleBayTemplateImportForm(forms.ModelForm):

View File

@@ -194,6 +194,28 @@ class SiteTestCase(ViewTestCases.PrimaryObjectViewTestCase):
'description': 'New description',
}
def test_get_object_with_only_site_view_permission_hides_unauthorized_embedded_panels(self):
site = self._get_queryset().first()
obj_perm = ObjectPermission(
name='Test permission',
actions=['view'],
)
obj_perm.save()
obj_perm.users.add(self.user)
obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
response = self.client.get(site.get_absolute_url())
self.assertHttpStatus(response, 200)
for panel, url in (
('locations', reverse('dcim:location_list')),
('devices', reverse('dcim:device_list')),
('image attachments', reverse('extras:imageattachment_list')),
):
with self.subTest(panel=panel):
self.assertNotContains(response, url)
class LocationTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
model = Location
@@ -3603,6 +3625,21 @@ class CableTestCase(
cable3 = Cable(a_terminations=[interfaces[2]], b_terminations=[interfaces[5]], type=CableTypeChoices.TYPE_CAT6)
cable3.save()
# Power panel, power feeds, and power ports for powerfeed-to-powerport cable import tests
power_panel = PowerPanel.objects.create(site=sites[0], name='Power Panel 1')
power_feeds = (
PowerFeed(name='Power Feed 1', power_panel=power_panel),
PowerFeed(name='Power Feed 2', power_panel=power_panel),
PowerFeed(name='Power Feed 3', power_panel=power_panel),
)
PowerFeed.objects.bulk_create(power_feeds)
power_ports = (
PowerPort(device=devices[3], name='Power Port 1'),
PowerPort(device=devices[3], name='Power Port 2'),
PowerPort(device=devices[3], name='Power Port 3'),
)
PowerPort.objects.bulk_create(power_ports)
tags = create_tags('Alpha', 'Bravo', 'Charlie')
cls.form_data = {
@@ -3640,7 +3677,14 @@ class CableTestCase(
"Site 1,Device 3,dcim.interface,Interface 3,Site 2,Device 1,dcim.interface,Interface 3",
"Site 1,Device 1,dcim.interface,Device 2 Interface,Site 2,Device 1,dcim.interface,Interface 4",
"Site 1,Device 1,dcim.interface,Device 3 Interface,Site 2,Device 1,dcim.interface,Interface 5",
)
),
'powerfeed-to-powerport': (
# Ensure that powerfeed-to-powerport cables can be imported via CSV using side_a_power_panel
"side_a_power_panel,side_a_type,side_a_name,side_b_device,side_b_type,side_b_name",
"Power Panel 1,dcim.powerfeed,Power Feed 1,Device 4,dcim.powerport,Power Port 1",
"Power Panel 1,dcim.powerfeed,Power Feed 2,Device 4,dcim.powerport,Power Port 2",
"Power Panel 1,dcim.powerfeed,Power Feed 3,Device 4,dcim.powerport,Power Port 3",
),
}
cls.csv_update_data = (

View File

@@ -9,6 +9,7 @@ from rest_framework import serializers
from core.api.serializers_.jobs import JobSerializer
from core.choices import ManagedFileRootPathChoices
from extras.models import Script, ScriptModule
from extras.utils import validate_script_content
from netbox.api.serializers import ValidatedModelSerializer
from utilities.datetime import local_now
@@ -39,6 +40,15 @@ class ScriptModuleSerializer(ValidatedModelSerializer):
data = super().validate(data)
data.pop('file_root', None)
if file is not None:
# Validate that the uploaded script can be loaded as a Python module
content = file.read()
file.seek(0)
try:
validate_script_content(content, file.name)
except Exception as e:
raise serializers.ValidationError(
_("Error loading script: {error}").format(error=e)
)
data['file'] = file
return data

View File

@@ -4,6 +4,7 @@ from django.utils.translation import gettext_lazy as _
from core.choices import JobIntervalChoices
from core.forms import ManagedFileForm
from extras.utils import validate_script_content
from utilities.datetime import local_now
from utilities.forms.widgets import DateTimePicker, NumberWithOptions
@@ -64,6 +65,22 @@ class ScriptFileForm(ManagedFileForm):
"""
ManagedFileForm with a custom save method to use django-storages.
"""
def clean(self):
super().clean()
if upload_file := self.cleaned_data.get('upload_file'):
# Validate that the uploaded script can be loaded as a Python module
content = upload_file.read()
upload_file.seek(0)
try:
validate_script_content(content, upload_file.name)
except Exception as e:
raise forms.ValidationError(
_("Error loading script: {error}").format(error=e)
)
return self.cleaned_data
def save(self, *args, **kwargs):
# If a file was uploaded, save it to disk
if self.cleaned_data['upload_file']:

View File

@@ -5,8 +5,6 @@ from functools import cached_property
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
from django.db.models import Q
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
@@ -188,9 +186,7 @@ class ScriptModule(PythonModuleMixin, JobsMixin, ManagedFile):
def save(self, *args, **kwargs):
self.file_root = ManagedFileRootPathChoices.SCRIPTS
super().save(*args, **kwargs)
# Sync script classes after the module has been saved. This is the
# single intended synchronization path for ScriptModule saves.
self.sync_classes()
@receiver(post_save, sender=ScriptModule)
def script_module_post_save_handler(instance, created, **kwargs):
instance.sync_classes()

View File

@@ -1450,6 +1450,21 @@ class ScriptModuleTest(APITestCase):
self.assertTrue(ScriptModule.objects.filter(file_path='test_upload.py').exists())
self.assertTrue(Script.objects.filter(module__file_path='test_upload.py', name='TestScript').exists())
def test_upload_faulty_script_module(self):
"""Uploading a script with an import error should return 400 and not create a DB record."""
self.add_permissions('extras.add_scriptmodule', 'core.add_managedfile')
# 'extras.script' is invalid; the correct module is 'extras.scripts'
script_content = b"from extras.script import Script\nclass TestScript(Script):\n pass\n"
upload_file = SimpleUploadedFile('test_faulty.py', script_content, content_type='text/plain')
response = self.client.post(
self.url,
{'file': upload_file},
format='multipart',
**self.header,
)
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(ScriptModule.objects.filter(file_path='test_faulty.py').exists())
def test_upload_script_module_without_file_fails(self):
self.add_permissions('extras.add_scriptmodule', 'core.add_managedfile')
response = self.client.post(self.url, {}, format='json', **self.header)

View File

@@ -1,4 +1,5 @@
import importlib
import types
from pathlib import Path
from django.core.exceptions import ImproperlyConfigured, SuspiciousFileOperation
@@ -21,6 +22,7 @@ __all__ = (
'is_script',
'is_taggable',
'run_validators',
'validate_script_content',
)
@@ -134,6 +136,17 @@ def is_script(obj):
return False
def validate_script_content(content, filename):
"""
Validate that the given content can be loaded as a Python module by compiling
and executing it. Raises an exception if the script cannot be loaded.
"""
code = compile(content, filename, 'exec')
module_name = Path(filename).stem
module = types.ModuleType(module_name)
exec(code, module.__dict__)
def is_report(obj):
"""
Returns True if the given object is a Report.

View File

@@ -16,6 +16,7 @@ from netbox.ui.panels import (
CommentsPanel,
ContextTablePanel,
ObjectsTablePanel,
PluginContentPanel,
RelatedObjectsPanel,
TemplatePanel,
)
@@ -55,11 +56,13 @@ class VRFView(GetRelatedModelsMixin, generic.ObjectView):
layout.Column(
panels.VRFPanel(),
TagsPanel(),
PluginContentPanel('left_page'),
),
layout.Column(
RelatedObjectsPanel(),
CustomFieldsPanel(),
CommentsPanel(),
PluginContentPanel('right_page'),
),
),
layout.Row(
@@ -70,6 +73,11 @@ class VRFView(GetRelatedModelsMixin, generic.ObjectView):
ContextTablePanel('export_targets_table', title=_('Export route targets')),
),
),
layout.Row(
layout.Column(
PluginContentPanel('full_width_page'),
),
),
)
def get_extra_context(self, request, instance):
@@ -169,10 +177,12 @@ class RouteTargetView(generic.ObjectView):
layout.Column(
panels.RouteTargetPanel(),
TagsPanel(),
PluginContentPanel('left_page'),
),
layout.Column(
CustomFieldsPanel(),
CommentsPanel(),
PluginContentPanel('right_page'),
),
),
layout.Row(
@@ -207,6 +217,11 @@ class RouteTargetView(generic.ObjectView):
),
),
),
layout.Row(
layout.Column(
PluginContentPanel('full_width_page'),
),
),
)

View File

@@ -45,6 +45,7 @@ from netbox.graphql.types import (
PrimaryObjectType,
)
from netbox.models import NestedGroupModel, NetBoxModel, OrganizationalModel, PrimaryModel
from netbox.registry import registry
from netbox.tables import (
NestedGroupModelTable,
NetBoxTable,
@@ -174,11 +175,10 @@ class FilterSetClassesTestCase(TestCase):
@staticmethod
def get_filterset_for_model(model):
"""
Import and return the filterset class for a given model.
Return the filterset class for a given model from the application registry.
"""
app_label = model._meta.app_label
model_name = model.__name__
return import_string(f'{app_label}.filtersets.{model_name}FilterSet')
label = f'{model._meta.app_label}.{model._meta.model_name}'
return registry['filtersets'].get(label)
@staticmethod
def get_model_filterset_base_class(model):
@@ -204,6 +204,7 @@ class FilterSetClassesTestCase(TestCase):
for model in apps.get_models():
if base_class := self.get_model_filterset_base_class(model):
filterset = self.get_filterset_for_model(model)
self.assertIsNotNone(filterset, f"No registered filterset found for model {model}")
self.assertTrue(
issubclass(filterset, base_class),
f"{filterset} does not inherit from {base_class}",

View File

@@ -1,4 +1,4 @@
from django.test import TestCase
from django.test import RequestFactory, TestCase
from circuits.choices import CircuitStatusChoices, VirtualCircuitTerminationRoleChoices
from circuits.models import (
@@ -8,9 +8,12 @@ from circuits.models import (
VirtualCircuitTermination,
VirtualCircuitType,
)
from core.models import ObjectType
from dcim.choices import InterfaceTypeChoices
from dcim.models import Interface
from dcim.models import Interface, Site
from netbox.ui import attrs
from netbox.ui.panels import ObjectsTablePanel
from users.models import ObjectPermission, User
from utilities.testing import create_test_device
from vpn.choices import (
AuthenticationAlgorithmChoices,
@@ -213,3 +216,55 @@ class RelatedObjectListAttrTest(TestCase):
self.assertInHTML('<li>IKE Proposal 2</li>', rendered)
self.assertNotIn('IKE Proposal 3', rendered)
self.assertIn('', rendered)
class ObjectsTablePanelTest(TestCase):
"""
Verify that ObjectsTablePanel.should_render() hides the panel when
the requesting user lacks view permission for the panel's model.
"""
@classmethod
def setUpTestData(cls):
cls.user = User.objects.create_user(username='test_user', password='test_password')
# Grant view permission only for Site
obj_perm = ObjectPermission.objects.create(
name='View sites only',
actions=['view'],
)
obj_perm.object_types.add(ObjectType.objects.get_for_model(Site))
obj_perm.users.add(cls.user)
def setUp(self):
self.factory = RequestFactory()
self.panel = ObjectsTablePanel(model='dcim.site')
self.panel_no_perm = ObjectsTablePanel(model='dcim.location')
def _make_context(self, user=None):
if user is None:
return {}
request = self.factory.get('/')
request.user = user
return {'request': request}
def test_should_render_without_request(self):
"""
Panel should render when no request is present in context.
"""
context = self.panel.get_context({})
self.assertTrue(self.panel.should_render(context))
def test_should_render_with_permission(self):
"""
Panel should render when the user has view permission for the panel's model.
"""
context = self.panel.get_context(self._make_context(self.user))
self.assertTrue(self.panel.should_render(context))
def test_should_not_render_without_permission(self):
"""
Panel should be hidden when the user lacks view permission for the panel's model.
"""
context = self.panel_no_perm.get_context(self._make_context(self.user))
self.assertFalse(self.panel_no_perm.should_render(context))

View File

@@ -5,6 +5,7 @@ from django.utils.translation import gettext_lazy as _
from netbox.ui import attrs
from netbox.ui.actions import CopyContent
from utilities.data import resolve_attr_path
from utilities.permissions import get_permission_for_model
from utilities.querydict import dict_to_querydict
from utilities.string import title
from utilities.templatetags.plugins import _get_registered_content
@@ -74,6 +75,15 @@ class Panel:
'panel_class': self.__class__.__name__,
}
def should_render(self, context):
"""
Determines whether the panel should render on the page. (Default: True)
Parameters:
context (dict): The panel's prepared context (the return value of get_context())
"""
return True
def render(self, context):
"""
Render the panel as HTML.
@@ -81,7 +91,10 @@ class Panel:
Parameters:
context (dict): The template context
"""
return render_to_string(self.template_name, self.get_context(context))
ctx = self.get_context(context)
if not self.should_render(ctx):
return ''
return render_to_string(self.template_name, ctx, request=ctx.get('request'))
#
@@ -314,6 +327,16 @@ class ObjectsTablePanel(Panel):
'url_params': dict_to_querydict(url_params),
}
def should_render(self, context):
"""
Hide the panel if the user does not have view permission for the panel's model.
"""
request = context.get('request')
if request is None:
return True
return request.user.has_perm(get_permission_for_model(self.model, 'view'))
class TemplatePanel(Panel):
"""

View File

@@ -1 +0,0 @@
Build local for local documentation

View File

@@ -31,29 +31,29 @@
"gridstack": "12.4.2",
"htmx.org": "2.0.8",
"query-string": "9.3.1",
"sass": "1.98.0",
"sass": "1.99.0",
"tom-select": "2.5.2",
"typeface-inter": "3.18.1",
"typeface-roboto-mono": "1.1.13"
},
"devDependencies": {
"@eslint/compat": "^2.0.3",
"@eslint/compat": "^2.0.5",
"@eslint/eslintrc": "^3.3.5",
"@eslint/js": "^9.39.2",
"@types/bootstrap": "5.2.10",
"@types/cookie": "^1.0.0",
"@types/node": "^24.10.1",
"@typescript-eslint/eslint-plugin": "^8.57.0",
"@typescript-eslint/parser": "^8.57.0",
"esbuild": "^0.27.4",
"@typescript-eslint/eslint-plugin": "^8.58.2",
"@typescript-eslint/parser": "^8.58.2",
"esbuild": "^0.28.0",
"esbuild-sass-plugin": "^3.7.0",
"eslint": "^9.39.2",
"eslint": "^10.2.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-prettier": "^5.5.5",
"globals": "^17.4.0",
"prettier": "^3.8.1",
"globals": "^17.5.0",
"prettier": "^3.8.2",
"typescript": "^5.9.3"
},
"resolutions": {

View File

@@ -24,135 +24,135 @@
dependencies:
tslib "^2.4.0"
"@esbuild/aix-ppc64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz#4c585002f7ad694d38fe0e8cbf5cfd939ccff327"
integrity sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==
"@esbuild/aix-ppc64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz#7a289c158e29cbf59ea0afc83cc80f06d1c89402"
integrity sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==
"@esbuild/android-arm64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz#7625d0952c3b402d3ede203a16c9f2b78f8a4827"
integrity sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==
"@esbuild/android-arm64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz#b8828d9edfa3a92660644eb8de6e4f3c203d7b17"
integrity sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==
"@esbuild/android-arm@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.4.tgz#9a0cf1d12997ec46dddfb32ce67e9bca842381ac"
integrity sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==
"@esbuild/android-arm@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.28.0.tgz#5ec1847605e05b5dbe5df90db9ff7e3e4c58dca7"
integrity sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==
"@esbuild/android-x64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.4.tgz#06e1fdc6283fccd6bc6aadd6754afce6cf96f42e"
integrity sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==
"@esbuild/android-x64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.28.0.tgz#390642175b88ef82bad4cce03f8ab13fe9b1912e"
integrity sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==
"@esbuild/darwin-arm64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz#6c550ee6c0273bcb0fac244478ff727c26755d80"
integrity sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==
"@esbuild/darwin-arm64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz#ae45325960d5950cd6951e4f97396f4e1ff7d8d3"
integrity sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==
"@esbuild/darwin-x64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz#ed7a125e9f25ce0091b9aff783ee943f6ba6cb86"
integrity sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==
"@esbuild/darwin-x64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz#c079247d589b6b99449659d94f06951b84bff2e4"
integrity sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==
"@esbuild/freebsd-arm64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz#597dc8e7161dba71db4c1656131c1f1e9d7660c6"
integrity sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==
"@esbuild/freebsd-arm64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz#45c456215a486593c94900297202dc11c880a37a"
integrity sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==
"@esbuild/freebsd-x64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz#ea171f9f4f00efaa8e9d3fe8baa1b75d757d1b36"
integrity sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==
"@esbuild/freebsd-x64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz#0399494c1c85e4388e9b7040bd60d48f2a5b0d2c"
integrity sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==
"@esbuild/linux-arm64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz#e52d57f202369386e6dbcb3370a17a0491ab1464"
integrity sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==
"@esbuild/linux-arm64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz#d6d9f09ef0de54116bf459a4d53cac7e0952fe39"
integrity sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==
"@esbuild/linux-arm@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz#5e0c0b634908adbce0a02cebeba8b3acac263fb6"
integrity sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==
"@esbuild/linux-arm@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz#7b42ffa84c288ae94fdc431c1b28a89e3c3b9278"
integrity sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==
"@esbuild/linux-ia32@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz#5f90f01f131652473ec06b038a14c49683e14ec7"
integrity sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==
"@esbuild/linux-ia32@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz#deb15d112ed8dd605346b6b953d23a21ff81253f"
integrity sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==
"@esbuild/linux-loong64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz#63bacffdb99574c9318f9afbd0dd4fff76a837e3"
integrity sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==
"@esbuild/linux-loong64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz#81fb89d07eecc79b157dea61033757726fce0ca4"
integrity sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==
"@esbuild/linux-mips64el@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz#c4b6952eca6a8efff67fee3671a3536c8e67b7eb"
integrity sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==
"@esbuild/linux-mips64el@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz#d0e42691b3ff7af9fb2217b70fc01f343bdb62bb"
integrity sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==
"@esbuild/linux-ppc64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz#6dea67d3d98c6986f1b7769e4f1848e5ae47ad58"
integrity sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==
"@esbuild/linux-ppc64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz#389f3e5e98f17d477c467cc87136e1a076eead87"
integrity sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==
"@esbuild/linux-riscv64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz#9ad2b4c3c0502c6bada9c81997bb56c597853489"
integrity sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==
"@esbuild/linux-riscv64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz#763bd60d59b242be12da1e67d5729f3024c605fa"
integrity sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==
"@esbuild/linux-s390x@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz#c43d3cfd073042ca6f5c52bb9bc313ed2066ce28"
integrity sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==
"@esbuild/linux-s390x@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz#aac6061634872e4677de693bce8030d73b1fd055"
integrity sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==
"@esbuild/linux-x64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz#45fa173e0591ac74d80d3cf76704713e14e2a4a6"
integrity sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==
"@esbuild/linux-x64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz#4f2917747188fe77632bcec65b2d84b422419779"
integrity sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==
"@esbuild/netbsd-arm64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz#366b0ef40cdb986fc751cbdad16e8c25fe1ba879"
integrity sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==
"@esbuild/netbsd-arm64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz#814df0ae57a0c386814491b8397eeba82094a947"
integrity sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==
"@esbuild/netbsd-x64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz#e985d49a3668fd2044343071d52e1ae815112b3e"
integrity sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==
"@esbuild/netbsd-x64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz#e01bdf7e60fa1a08e46d46d960b0d9bb8ac210af"
integrity sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==
"@esbuild/openbsd-arm64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz#6fb4ab7b73f7e5572ce5ec9cf91c13ff6dd44842"
integrity sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==
"@esbuild/openbsd-arm64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz#4a15c36aacca68d2d5a4c90b710c06759f4c1ffa"
integrity sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==
"@esbuild/openbsd-x64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz#641f052040a0d79843d68898f5791638a026d983"
integrity sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==
"@esbuild/openbsd-x64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz#475e6101498a8ecce3008d7c388111d7a27c17bd"
integrity sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==
"@esbuild/openharmony-arm64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz#fc1d33eac9d81ae0a433b3ed1dd6171a20d4e317"
integrity sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==
"@esbuild/openharmony-arm64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz#cfdc3957f0b7a69f1bde129aad17fcc2f6fa033e"
integrity sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==
"@esbuild/sunos-x64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz#af2cd5ca842d6d057121f66a192d4f797de28f53"
integrity sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==
"@esbuild/sunos-x64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz#a013c856fecacd1c3aec985c8afe1d1cb017497d"
integrity sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==
"@esbuild/win32-arm64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz#78ec7e59bb06404583d4c9511e621db31c760de3"
integrity sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==
"@esbuild/win32-arm64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz#eae05e0f35271cad3898b43168d3e9a3bbaf47e5"
integrity sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==
"@esbuild/win32-ia32@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz#0e616aa488b7ee5d2592ab070ff9ec06a9fddf11"
integrity sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==
"@esbuild/win32-ia32@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz#06161ebc5bf75c08d69feb3c6b22560515913998"
integrity sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==
"@esbuild/win32-x64@0.27.4":
version "0.27.4"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz#1f7ba71a3d6155d44a6faa8dbe249c62ab3e408c"
integrity sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==
"@esbuild/win32-x64@0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz#04d90d5752b4ce65d2b6ac25eba08ff7624fe07c"
integrity sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==
"@eslint-community/eslint-utils@^4.8.0":
version "4.9.0"
@@ -168,63 +168,41 @@
dependencies:
eslint-visitor-keys "^3.4.3"
"@eslint-community/regexpp@^4.12.1", "@eslint-community/regexpp@^4.12.2":
"@eslint-community/regexpp@^4.12.2":
version "4.12.2"
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b"
integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==
"@eslint/compat@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@eslint/compat/-/compat-2.0.3.tgz#860bdd23d0df1c71a8d751f0aa1430e05bc056dd"
integrity sha512-SjIJhGigp8hmd1YGIBwh7Ovri7Kisl42GYFjrOyHhtfYGGoLW6teYi/5p8W50KSsawUPpuLOSmsq1bD0NGQLBw==
"@eslint/compat@^2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@eslint/compat/-/compat-2.0.5.tgz#65421b3f6e5a864e0255ab31884fb26fdc4d0210"
integrity sha512-IbHDbHJfkVNv6xjlET8AIVo/K1NQt7YT4Rp6ok/clyBGcpRx1l6gv0Rq3vBvYfPJIZt6ODf66Zq08FJNDpnzgg==
dependencies:
"@eslint/core" "^1.1.1"
"@eslint/core" "^1.2.1"
"@eslint/config-array@^0.21.1":
version "0.21.1"
resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.1.tgz#7d1b0060fea407f8301e932492ba8c18aff29713"
integrity sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==
"@eslint/config-array@^0.23.4":
version "0.23.5"
resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.23.5.tgz#56e86d243049195d8acc0c06a1b3dfdc3fa3de95"
integrity sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==
dependencies:
"@eslint/object-schema" "^2.1.7"
"@eslint/object-schema" "^3.0.5"
debug "^4.3.1"
minimatch "^3.1.2"
minimatch "^10.2.4"
"@eslint/config-helpers@^0.4.2":
version "0.4.2"
resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz#1bd006ceeb7e2e55b2b773ab318d300e1a66aeda"
integrity sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==
"@eslint/config-helpers@^0.5.4":
version "0.5.5"
resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.5.5.tgz#ae16134e4792ac5fbdc533548a24ac1ea9f7f3ae"
integrity sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==
dependencies:
"@eslint/core" "^0.17.0"
"@eslint/core" "^1.2.1"
"@eslint/core@^0.17.0":
version "0.17.0"
resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.17.0.tgz#77225820413d9617509da9342190a2019e78761c"
integrity sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==
"@eslint/core@^1.2.0", "@eslint/core@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@eslint/core/-/core-1.2.1.tgz#c1da7cd1b82fa8787f98b5629fb811848a1b63ce"
integrity sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==
dependencies:
"@types/json-schema" "^7.0.15"
"@eslint/core@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@eslint/core/-/core-1.1.1.tgz#450f3d2be2d463ccd51119544092256b4e88df32"
integrity sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==
dependencies:
"@types/json-schema" "^7.0.15"
"@eslint/eslintrc@^3.3.1":
version "3.3.3"
resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz"
integrity sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
espree "^10.0.1"
globals "^14.0.0"
ignore "^5.2.0"
import-fresh "^3.2.1"
js-yaml "^4.1.1"
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/eslintrc@^3.3.5":
version "3.3.5"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.5.tgz#c131793cfc1a7b96f24a83e0a8bbd4b881558c60"
@@ -240,22 +218,22 @@
minimatch "^3.1.5"
strip-json-comments "^3.1.1"
"@eslint/js@9.39.2", "@eslint/js@^9.39.2":
"@eslint/js@^9.39.2":
version "9.39.2"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.2.tgz#2d4b8ec4c3ea13c1b3748e0c97ecd766bdd80599"
integrity sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==
"@eslint/object-schema@^2.1.7":
version "2.1.7"
resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.7.tgz#6e2126a1347e86a4dedf8706ec67ff8e107ebbad"
integrity sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==
"@eslint/object-schema@^3.0.5":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-3.0.5.tgz#88e9bf4d11d2b19c082e78ebe7ce88724a5eb091"
integrity sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==
"@eslint/plugin-kit@^0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz#9779e3fd9b7ee33571a57435cf4335a1794a6cb2"
integrity sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==
"@eslint/plugin-kit@^0.7.0":
version "0.7.1"
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz#c4125fd015eceeb09b793109fdbcd4dd0a02d346"
integrity sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==
dependencies:
"@eslint/core" "^0.17.0"
"@eslint/core" "^1.2.1"
levn "^0.4.1"
"@floating-ui/core@^1.7.3":
@@ -921,7 +899,12 @@
dependencies:
cookie "*"
"@types/estree@*", "@types/estree@^1.0.6":
"@types/esrecurse@^4.3.1":
version "4.3.1"
resolved "https://registry.yarnpkg.com/@types/esrecurse/-/esrecurse-4.3.1.tgz#6f636af962fbe6191b830bd676ba5986926bccec"
integrity sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==
"@types/estree@*", "@types/estree@^1.0.6", "@types/estree@^1.0.8":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
@@ -950,100 +933,100 @@
dependencies:
"@types/estree" "*"
"@typescript-eslint/eslint-plugin@^8.57.0":
version "8.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.0.tgz#6e4085604ab63f55b3dcc61ce2c16965b2c36374"
integrity sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==
"@typescript-eslint/eslint-plugin@^8.58.2":
version "8.58.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz#a6882a6a328e1259cff259fdb03184245ef06191"
integrity sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==
dependencies:
"@eslint-community/regexpp" "^4.12.2"
"@typescript-eslint/scope-manager" "8.57.0"
"@typescript-eslint/type-utils" "8.57.0"
"@typescript-eslint/utils" "8.57.0"
"@typescript-eslint/visitor-keys" "8.57.0"
"@typescript-eslint/scope-manager" "8.58.2"
"@typescript-eslint/type-utils" "8.58.2"
"@typescript-eslint/utils" "8.58.2"
"@typescript-eslint/visitor-keys" "8.58.2"
ignore "^7.0.5"
natural-compare "^1.4.0"
ts-api-utils "^2.4.0"
ts-api-utils "^2.5.0"
"@typescript-eslint/parser@^8.57.0":
version "8.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.57.0.tgz#444c57a943e8b04f255cda18a94c8e023b46b08c"
integrity sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==
"@typescript-eslint/parser@^8.58.2":
version "8.58.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.58.2.tgz#b267545e4bd515d896fe1f3a5b6f334fa6aa0026"
integrity sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==
dependencies:
"@typescript-eslint/scope-manager" "8.57.0"
"@typescript-eslint/types" "8.57.0"
"@typescript-eslint/typescript-estree" "8.57.0"
"@typescript-eslint/visitor-keys" "8.57.0"
"@typescript-eslint/scope-manager" "8.58.2"
"@typescript-eslint/types" "8.58.2"
"@typescript-eslint/typescript-estree" "8.58.2"
"@typescript-eslint/visitor-keys" "8.58.2"
debug "^4.4.3"
"@typescript-eslint/project-service@8.57.0":
version "8.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.57.0.tgz#2014ed527bcd0eff8aecb7e44879ae3150604ab3"
integrity sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==
"@typescript-eslint/project-service@8.58.2":
version "8.58.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.58.2.tgz#8c980249100e21b87baba0ca10880fdf893e0a8e"
integrity sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.57.0"
"@typescript-eslint/types" "^8.57.0"
"@typescript-eslint/tsconfig-utils" "^8.58.2"
"@typescript-eslint/types" "^8.58.2"
debug "^4.4.3"
"@typescript-eslint/scope-manager@8.57.0":
version "8.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.57.0.tgz#7d2a2aeaaef2ae70891b21939fadb4cb0b19f840"
integrity sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==
"@typescript-eslint/scope-manager@8.58.2":
version "8.58.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.58.2.tgz#aa73784d78f117940e83f71705af07ba695cd60c"
integrity sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==
dependencies:
"@typescript-eslint/types" "8.57.0"
"@typescript-eslint/visitor-keys" "8.57.0"
"@typescript-eslint/types" "8.58.2"
"@typescript-eslint/visitor-keys" "8.58.2"
"@typescript-eslint/tsconfig-utils@8.57.0", "@typescript-eslint/tsconfig-utils@^8.57.0":
version "8.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.0.tgz#cf2f2822af3887d25dd325b6bea6c3f60a83a0b4"
integrity sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==
"@typescript-eslint/tsconfig-utils@8.58.2", "@typescript-eslint/tsconfig-utils@^8.58.2":
version "8.58.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.2.tgz#fa13f96432c9348bf87f6f44826def585fad7bca"
integrity sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==
"@typescript-eslint/type-utils@8.57.0":
version "8.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.57.0.tgz#2877af4c2e8f0998b93a07dad1c34ce1bb669448"
integrity sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==
"@typescript-eslint/type-utils@8.58.2":
version "8.58.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.58.2.tgz#024eb1dd597f8a34cb22d8d9ab32da857bc9a817"
integrity sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==
dependencies:
"@typescript-eslint/types" "8.57.0"
"@typescript-eslint/typescript-estree" "8.57.0"
"@typescript-eslint/utils" "8.57.0"
"@typescript-eslint/types" "8.58.2"
"@typescript-eslint/typescript-estree" "8.58.2"
"@typescript-eslint/utils" "8.58.2"
debug "^4.4.3"
ts-api-utils "^2.4.0"
ts-api-utils "^2.5.0"
"@typescript-eslint/types@8.57.0", "@typescript-eslint/types@^8.57.0":
version "8.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.57.0.tgz#4fa5385ffd1cd161fa5b9dce93e0493d491b8dc6"
integrity sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==
"@typescript-eslint/types@8.58.2", "@typescript-eslint/types@^8.58.2":
version "8.58.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.58.2.tgz#3ab8051de0f19a46ddefb0749d0f7d82974bd57c"
integrity sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==
"@typescript-eslint/typescript-estree@8.57.0":
version "8.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.0.tgz#e0e4a89bfebb207de314826df876e2dabc7dea04"
integrity sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==
"@typescript-eslint/typescript-estree@8.58.2":
version "8.58.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.2.tgz#b1beb1f959385b341cc76f0aebbf028e23dfdb8b"
integrity sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==
dependencies:
"@typescript-eslint/project-service" "8.57.0"
"@typescript-eslint/tsconfig-utils" "8.57.0"
"@typescript-eslint/types" "8.57.0"
"@typescript-eslint/visitor-keys" "8.57.0"
"@typescript-eslint/project-service" "8.58.2"
"@typescript-eslint/tsconfig-utils" "8.58.2"
"@typescript-eslint/types" "8.58.2"
"@typescript-eslint/visitor-keys" "8.58.2"
debug "^4.4.3"
minimatch "^10.2.2"
semver "^7.7.3"
tinyglobby "^0.2.15"
ts-api-utils "^2.4.0"
ts-api-utils "^2.5.0"
"@typescript-eslint/utils@8.57.0":
version "8.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.57.0.tgz#c7193385b44529b788210d20c94c11de79ad3498"
integrity sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==
"@typescript-eslint/utils@8.58.2":
version "8.58.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.58.2.tgz#27165554a02d1ff57d98262fa92060498dabc8b3"
integrity sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==
dependencies:
"@eslint-community/eslint-utils" "^4.9.1"
"@typescript-eslint/scope-manager" "8.57.0"
"@typescript-eslint/types" "8.57.0"
"@typescript-eslint/typescript-estree" "8.57.0"
"@typescript-eslint/scope-manager" "8.58.2"
"@typescript-eslint/types" "8.58.2"
"@typescript-eslint/typescript-estree" "8.58.2"
"@typescript-eslint/visitor-keys@8.57.0":
version "8.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.0.tgz#23aea662279bb66209700854453807a119350f85"
integrity sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==
"@typescript-eslint/visitor-keys@8.58.2":
version "8.58.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.2.tgz#9ed699eaa9b5720b6b6b6f9c16e6c7d4cd32b276"
integrity sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==
dependencies:
"@typescript-eslint/types" "8.57.0"
"@typescript-eslint/types" "8.58.2"
eslint-visitor-keys "^5.0.0"
"@unrs/resolver-binding-android-arm-eabi@1.11.1":
@@ -1153,15 +1136,10 @@ acorn@^8.15.0:
resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz"
integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
ajv@^6.12.4:
version "6.12.6"
resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
acorn@^8.16.0:
version "8.16.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a"
integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==
ajv@^6.14.0:
version "6.14.0"
@@ -1173,13 +1151,6 @@ ajv@^6.14.0:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
color-convert "^2.0.1"
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
@@ -1378,14 +1349,6 @@ callsites@^3.0.0:
resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
chalk@^4.0.0:
version "4.1.2"
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chokidar@^4.0.0:
version "4.0.1"
resolved "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz"
@@ -1425,18 +1388,6 @@ codemirror@^5.65.3:
resolved "https://registry.npmjs.org/codemirror/-/codemirror-5.65.20.tgz"
integrity sha512-i5dLDDxwkFCbhjvL2pNjShsojoL3XHyDwsGv1jqETUoW+lzpBKKqNTUWgQwVAOa0tUm4BwekT455ujafi8payA==
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
@@ -1802,37 +1753,37 @@ esbuild-sass-plugin@^3.7.0:
resolve "^1.22.11"
sass "^1.97.3"
esbuild@^0.27.4:
version "0.27.4"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.4.tgz#b9591dd7e0ab803a11c9c3b602850403bef22f00"
integrity sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==
esbuild@^0.28.0:
version "0.28.0"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.28.0.tgz#5dee347ffb3e3874212a35a69836b077b1ce6d96"
integrity sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==
optionalDependencies:
"@esbuild/aix-ppc64" "0.27.4"
"@esbuild/android-arm" "0.27.4"
"@esbuild/android-arm64" "0.27.4"
"@esbuild/android-x64" "0.27.4"
"@esbuild/darwin-arm64" "0.27.4"
"@esbuild/darwin-x64" "0.27.4"
"@esbuild/freebsd-arm64" "0.27.4"
"@esbuild/freebsd-x64" "0.27.4"
"@esbuild/linux-arm" "0.27.4"
"@esbuild/linux-arm64" "0.27.4"
"@esbuild/linux-ia32" "0.27.4"
"@esbuild/linux-loong64" "0.27.4"
"@esbuild/linux-mips64el" "0.27.4"
"@esbuild/linux-ppc64" "0.27.4"
"@esbuild/linux-riscv64" "0.27.4"
"@esbuild/linux-s390x" "0.27.4"
"@esbuild/linux-x64" "0.27.4"
"@esbuild/netbsd-arm64" "0.27.4"
"@esbuild/netbsd-x64" "0.27.4"
"@esbuild/openbsd-arm64" "0.27.4"
"@esbuild/openbsd-x64" "0.27.4"
"@esbuild/openharmony-arm64" "0.27.4"
"@esbuild/sunos-x64" "0.27.4"
"@esbuild/win32-arm64" "0.27.4"
"@esbuild/win32-ia32" "0.27.4"
"@esbuild/win32-x64" "0.27.4"
"@esbuild/aix-ppc64" "0.28.0"
"@esbuild/android-arm" "0.28.0"
"@esbuild/android-arm64" "0.28.0"
"@esbuild/android-x64" "0.28.0"
"@esbuild/darwin-arm64" "0.28.0"
"@esbuild/darwin-x64" "0.28.0"
"@esbuild/freebsd-arm64" "0.28.0"
"@esbuild/freebsd-x64" "0.28.0"
"@esbuild/linux-arm" "0.28.0"
"@esbuild/linux-arm64" "0.28.0"
"@esbuild/linux-ia32" "0.28.0"
"@esbuild/linux-loong64" "0.28.0"
"@esbuild/linux-mips64el" "0.28.0"
"@esbuild/linux-ppc64" "0.28.0"
"@esbuild/linux-riscv64" "0.28.0"
"@esbuild/linux-s390x" "0.28.0"
"@esbuild/linux-x64" "0.28.0"
"@esbuild/netbsd-arm64" "0.28.0"
"@esbuild/netbsd-x64" "0.28.0"
"@esbuild/openbsd-arm64" "0.28.0"
"@esbuild/openbsd-x64" "0.28.0"
"@esbuild/openharmony-arm64" "0.28.0"
"@esbuild/sunos-x64" "0.28.0"
"@esbuild/win32-arm64" "0.28.0"
"@esbuild/win32-ia32" "0.28.0"
"@esbuild/win32-x64" "0.28.0"
escape-string-regexp@^4.0.0:
version "4.0.0"
@@ -1914,11 +1865,13 @@ eslint-plugin-prettier@^5.5.5:
prettier-linter-helpers "^1.0.1"
synckit "^0.11.12"
eslint-scope@^8.4.0:
version "8.4.0"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82"
integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==
eslint-scope@^9.1.2:
version "9.1.2"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-9.1.2.tgz#b9de6ace2fab1cff24d2e58d85b74c8fcea39802"
integrity sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==
dependencies:
"@types/esrecurse" "^4.3.1"
"@types/estree" "^1.0.8"
esrecurse "^4.3.0"
estraverse "^5.2.0"
@@ -1937,32 +1890,34 @@ eslint-visitor-keys@^5.0.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz#b9aa1a74aa48c44b3ae46c1597ce7171246a94a9"
integrity sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==
eslint@^9.39.2:
version "9.39.2"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.2.tgz#cb60e6d16ab234c0f8369a3fe7cc87967faf4b6c"
integrity sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==
eslint-visitor-keys@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be"
integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==
eslint@^10.2.0:
version "10.2.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.2.0.tgz#711c80d32fc3fdd3a575bb93977df43887c3ec8e"
integrity sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==
dependencies:
"@eslint-community/eslint-utils" "^4.8.0"
"@eslint-community/regexpp" "^4.12.1"
"@eslint/config-array" "^0.21.1"
"@eslint/config-helpers" "^0.4.2"
"@eslint/core" "^0.17.0"
"@eslint/eslintrc" "^3.3.1"
"@eslint/js" "9.39.2"
"@eslint/plugin-kit" "^0.4.1"
"@eslint-community/regexpp" "^4.12.2"
"@eslint/config-array" "^0.23.4"
"@eslint/config-helpers" "^0.5.4"
"@eslint/core" "^1.2.0"
"@eslint/plugin-kit" "^0.7.0"
"@humanfs/node" "^0.16.6"
"@humanwhocodes/module-importer" "^1.0.1"
"@humanwhocodes/retry" "^0.4.2"
"@types/estree" "^1.0.6"
ajv "^6.12.4"
chalk "^4.0.0"
ajv "^6.14.0"
cross-spawn "^7.0.6"
debug "^4.3.2"
escape-string-regexp "^4.0.0"
eslint-scope "^8.4.0"
eslint-visitor-keys "^4.2.1"
espree "^10.4.0"
esquery "^1.5.0"
eslint-scope "^9.1.2"
eslint-visitor-keys "^5.0.1"
espree "^11.2.0"
esquery "^1.7.0"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
file-entry-cache "^8.0.0"
@@ -1972,12 +1927,11 @@ eslint@^9.39.2:
imurmurhash "^0.1.4"
is-glob "^4.0.0"
json-stable-stringify-without-jsonify "^1.0.1"
lodash.merge "^4.6.2"
minimatch "^3.1.2"
minimatch "^10.2.4"
natural-compare "^1.4.0"
optionator "^0.9.3"
espree@^10.0.1, espree@^10.4.0:
espree@^10.0.1:
version "10.4.0"
resolved "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz"
integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==
@@ -1986,10 +1940,19 @@ espree@^10.0.1, espree@^10.4.0:
acorn-jsx "^5.3.2"
eslint-visitor-keys "^4.2.1"
esquery@^1.5.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7"
integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==
espree@^11.2.0:
version "11.2.0"
resolved "https://registry.yarnpkg.com/espree/-/espree-11.2.0.tgz#01d5e47dc332aaba3059008362454a8cc34ccaa5"
integrity sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==
dependencies:
acorn "^8.16.0"
acorn-jsx "^5.3.2"
eslint-visitor-keys "^5.0.1"
esquery@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d"
integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==
dependencies:
estraverse "^5.1.0"
@@ -2219,10 +2182,10 @@ globals@^14.0.0:
resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz"
integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==
globals@^17.4.0:
version "17.4.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-17.4.0.tgz#33d7d297ed1536b388a0e2f4bcd0ff19c8ff91b5"
integrity sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==
globals@^17.5.0:
version "17.5.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-17.5.0.tgz#a82c641d898f8dfbe0e81f66fdff7d0de43f88c6"
integrity sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==
globalthis@^1.0.3, globalthis@^1.0.4:
version "1.0.4"
@@ -2290,11 +2253,6 @@ has-bigints@^1.0.1, has-bigints@^1.0.2:
resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz"
integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz"
@@ -2767,11 +2725,6 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
loose-envify@^1.1.0:
version "1.4.0"
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
@@ -2821,7 +2774,7 @@ minimatch@^10.2.2:
dependencies:
brace-expansion "^5.0.2"
minimatch@^3.1.2, minimatch@^3.1.3, minimatch@^3.1.5:
minimatch@^10.2.4, minimatch@^3.1.2, minimatch@^3.1.3, minimatch@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e"
integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==
@@ -2993,22 +2946,12 @@ path-parse@^1.0.7:
resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
picomatch@2.3.2:
picomatch@2.3.2, picomatch@^2.3.1:
version "2.3.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.2.tgz#5a942915e26b372dc0f0e6753149a16e6b1c5601"
integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==
picomatch@4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589"
integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==
picomatch@^2.3.1:
version "2.3.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.2.tgz#5a942915e26b372dc0f0e6753149a16e6b1c5601"
integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==
picomatch@^4.0.3:
picomatch@4.0.4, picomatch@^4.0.3:
version "4.0.4"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589"
integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==
@@ -3030,10 +2973,10 @@ prettier-linter-helpers@^1.0.1:
dependencies:
fast-diff "^1.1.2"
prettier@^3.8.1:
version "3.8.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173"
integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==
prettier@^3.8.2:
version "3.8.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.2.tgz#4f52e502193c9aa5b384c3d00852003e551bbd9f"
integrity sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==
punycode.js@^2.3.1:
version "2.3.1"
@@ -3217,7 +3160,18 @@ safe-regex-test@^1.1.0:
es-errors "^1.3.0"
is-regex "^1.2.1"
sass@1.98.0, sass@^1.97.3:
sass@1.99.0:
version "1.99.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.99.0.tgz#ff9d1594da4886249dfaafabbeea2dea2dc74b26"
integrity sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==
dependencies:
chokidar "^4.0.0"
immutable "^5.1.5"
source-map-js ">=0.6.2 <2.0.0"
optionalDependencies:
"@parcel/watcher" "^2.4.1"
sass@^1.97.3:
version "1.98.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.98.0.tgz#924ce85a3745ccaccd976262fdc1bc0c13aa8e57"
integrity sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==
@@ -3435,13 +3389,6 @@ strip-json-comments@^3.1.1:
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
has-flag "^4.0.0"
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
@@ -3492,10 +3439,10 @@ tom-select@2.5.2:
"@orchidjs/sifter" "^1.1.0"
"@orchidjs/unicode-variants" "^1.1.2"
ts-api-utils@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.4.0.tgz#2690579f96d2790253bdcf1ca35d569ad78f9ad8"
integrity sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==
ts-api-utils@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz#4acd4a155e22734990a5ed1fe9e97f113bcb37c1"
integrity sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==
tsconfig-paths@^3.15.0:
version "3.15.0"

View File

@@ -1,3 +1,3 @@
version: "4.5.7"
version: "4.5.8"
edition: "Community"
published: "2026-04-03"
published: "2026-04-14"

View File

@@ -42,13 +42,7 @@ class TenantViewSet(NetBoxModelViewSet):
#
class ContactGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet):
queryset = ContactGroup.objects.add_related_count(
ContactGroup.objects.all(),
Contact,
'groups',
'contact_count',
cumulative=True
)
queryset = ContactGroup.objects.annotate_contacts()
serializer_class = serializers.ContactGroupSerializer
filterset_class = filtersets.ContactGroupFilterSet

View File

@@ -1,12 +1,14 @@
from django.contrib.contenttypes.fields import GenericForeignKey
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models.expressions import RawSQL
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from netbox.models import ChangeLoggedModel, NestedGroupModel, OrganizationalModel, PrimaryModel
from netbox.models.features import CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, has_feature
from tenancy.choices import *
from utilities.mptt import TreeManager
__all__ = (
'Contact',
@@ -16,10 +18,34 @@ __all__ = (
)
class ContactGroupManager(TreeManager):
def annotate_contacts(self):
"""
Annotate the total number of Contacts belonging to each ContactGroup.
This returns both direct children and children of child groups. Raw SQL is used here to avoid double-counting
contacts which are assigned to multiple child groups of the parent.
"""
return self.annotate(
contact_count=RawSQL(
"SELECT COUNT(DISTINCT m2m.contact_id)"
" FROM tenancy_contact_groups m2m"
" INNER JOIN tenancy_contactgroup cg ON m2m.contactgroup_id = cg.id"
" WHERE cg.tree_id = tenancy_contactgroup.tree_id"
" AND cg.lft >= tenancy_contactgroup.lft"
" AND cg.lft <= tenancy_contactgroup.rght",
()
)
)
class ContactGroup(NestedGroupModel):
"""
An arbitrary collection of Contacts.
"""
objects = ContactGroupManager()
class Meta:
ordering = ['name']
# Empty tuple triggers Django migration detection for MPTT indexes

View File

@@ -0,0 +1,72 @@
from django.test import TestCase
from tenancy.models import Contact, ContactGroup
class ContactGroupTestCase(TestCase):
@classmethod
def setUpTestData(cls):
# Create a tree of contact groups:
# - Group A
# - Group A1
# - Group A2
# - Group B
cls.group_a = ContactGroup.objects.create(name='Group A', slug='group-a')
cls.group_a1 = ContactGroup.objects.create(name='Group A1', slug='group-a1', parent=cls.group_a)
cls.group_a2 = ContactGroup.objects.create(name='Group A2', slug='group-a2', parent=cls.group_a)
cls.group_b = ContactGroup.objects.create(name='Group B', slug='group-b')
# Create contacts
cls.contact1 = Contact.objects.create(name='Contact 1')
cls.contact2 = Contact.objects.create(name='Contact 2')
cls.contact3 = Contact.objects.create(name='Contact 3')
cls.contact4 = Contact.objects.create(name='Contact 4')
def test_annotate_contacts_direct(self):
"""Contacts assigned directly to a group should be counted."""
self.contact1.groups.set([self.group_a])
self.contact2.groups.set([self.group_a])
queryset = ContactGroup.objects.annotate_contacts()
self.assertEqual(queryset.get(pk=self.group_a.pk).contact_count, 2)
def test_annotate_contacts_cumulative(self):
"""Contacts assigned to child groups should be included in the parent's count."""
self.contact1.groups.set([self.group_a1])
self.contact2.groups.set([self.group_a2])
queryset = ContactGroup.objects.annotate_contacts()
self.assertEqual(queryset.get(pk=self.group_a.pk).contact_count, 2)
self.assertEqual(queryset.get(pk=self.group_a1.pk).contact_count, 1)
self.assertEqual(queryset.get(pk=self.group_a2.pk).contact_count, 1)
def test_annotate_contacts_no_double_counting(self):
"""A contact assigned to multiple child groups must be counted only once for the parent."""
self.contact1.groups.set([self.group_a1, self.group_a2])
queryset = ContactGroup.objects.annotate_contacts()
self.assertEqual(queryset.get(pk=self.group_a.pk).contact_count, 1)
def test_annotate_contacts_mixed(self):
"""Test a mix of direct and inherited contacts with overlap."""
self.contact1.groups.set([self.group_a])
self.contact2.groups.set([self.group_a1])
self.contact3.groups.set([self.group_a1, self.group_a2])
self.contact4.groups.set([self.group_b])
queryset = ContactGroup.objects.annotate_contacts()
# Group A: contact1 (direct) + contact2 (via A1) + contact3 (via A1 & A2) = 3
self.assertEqual(queryset.get(pk=self.group_a.pk).contact_count, 3)
# Group A1: contact2 + contact3 = 2
self.assertEqual(queryset.get(pk=self.group_a1.pk).contact_count, 2)
# Group A2: contact3 = 1
self.assertEqual(queryset.get(pk=self.group_a2.pk).contact_count, 1)
# Group B: contact4 = 1
self.assertEqual(queryset.get(pk=self.group_b.pk).contact_count, 1)
def test_annotate_contacts_empty(self):
"""Groups with no contacts should return a count of zero."""
queryset = ContactGroup.objects.annotate_contacts()
self.assertEqual(queryset.get(pk=self.group_a.pk).contact_count, 0)
self.assertEqual(queryset.get(pk=self.group_b.pk).contact_count, 0)

View File

@@ -205,13 +205,7 @@ class TenantBulkDeleteView(generic.BulkDeleteView):
@register_model_view(ContactGroup, 'list', path='', detail=False)
class ContactGroupListView(generic.ObjectListView):
queryset = ContactGroup.objects.add_related_count(
ContactGroup.objects.all(),
Contact,
'groups',
'contact_count',
cumulative=True
)
queryset = ContactGroup.objects.annotate_contacts()
filterset = filtersets.ContactGroupFilterSet
filterset_form = forms.ContactGroupFilterForm
table = tables.ContactGroupTable
@@ -254,7 +248,7 @@ class ContactGroupView(GetRelatedModelsMixin, generic.ObjectView):
request,
groups,
extra=(
(Contact.objects.restrict(request.user, 'view').filter(groups__in=groups), 'group_id'),
(Contact.objects.restrict(request.user, 'view').filter(groups__in=groups).distinct(), 'group_id'),
),
),
}
@@ -280,13 +274,7 @@ class ContactGroupBulkImportView(generic.BulkImportView):
@register_model_view(ContactGroup, 'bulk_edit', path='edit', detail=False)
class ContactGroupBulkEditView(generic.BulkEditView):
queryset = ContactGroup.objects.add_related_count(
ContactGroup.objects.all(),
Contact,
'groups',
'contact_count',
cumulative=True
)
queryset = ContactGroup.objects.annotate_contacts()
filterset = filtersets.ContactGroupFilterSet
table = tables.ContactGroupTable
form = forms.ContactGroupBulkEditForm
@@ -300,13 +288,7 @@ class ContactGroupBulkRenameView(generic.BulkRenameView):
@register_model_view(ContactGroup, 'bulk_delete', path='delete', detail=False)
class ContactGroupBulkDeleteView(generic.BulkDeleteView):
queryset = ContactGroup.objects.add_related_count(
ContactGroup.objects.all(),
Contact,
'groups',
'contact_count',
cumulative=True
)
queryset = ContactGroup.objects.annotate_contacts()
filterset = filtersets.ContactGroupFilterSet
table = tables.ContactGroupTable

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-04-10 05:39+0000\n"
"POT-Creation-Date: 2026-04-14 05:39+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -173,8 +173,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:329 netbox/dcim/forms/bulk_edit.py:679
#: netbox/dcim/forms/bulk_edit.py:866 netbox/dcim/forms/bulk_import.py:146
#: netbox/dcim/forms/bulk_import.py:247 netbox/dcim/forms/bulk_import.py:349
#: netbox/dcim/forms/bulk_import.py:640 netbox/dcim/forms/bulk_import.py:1612
#: netbox/dcim/forms/bulk_import.py:1640 netbox/dcim/forms/filtersets.py:106
#: netbox/dcim/forms/bulk_import.py:640 netbox/dcim/forms/bulk_import.py:1659
#: netbox/dcim/forms/bulk_import.py:1687 netbox/dcim/forms/filtersets.py:106
#: netbox/dcim/forms/filtersets.py:256 netbox/dcim/forms/filtersets.py:379
#: netbox/dcim/forms/filtersets.py:483 netbox/dcim/forms/filtersets.py:855
#: netbox/dcim/forms/filtersets.py:1073 netbox/dcim/forms/filtersets.py:1147
@@ -450,7 +450,7 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:611 netbox/dcim/forms/bulk_edit.py:809
#: netbox/dcim/forms/bulk_edit.py:1063 netbox/dcim/forms/bulk_edit.py:1162
#: netbox/dcim/forms/bulk_edit.py:1189 netbox/dcim/forms/bulk_edit.py:1723
#: netbox/dcim/forms/bulk_import.py:1484 netbox/dcim/forms/filtersets.py:1220
#: netbox/dcim/forms/bulk_import.py:1500 netbox/dcim/forms/filtersets.py:1220
#: netbox/dcim/forms/filtersets.py:1545 netbox/dcim/forms/filtersets.py:1761
#: netbox/dcim/forms/filtersets.py:1780 netbox/dcim/forms/filtersets.py:1804
#: netbox/dcim/forms/filtersets.py:1823 netbox/dcim/tables/devices.py:806
@@ -481,8 +481,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_import.py:813 netbox/dcim/forms/bulk_import.py:839
#: netbox/dcim/forms/bulk_import.py:865 netbox/dcim/forms/bulk_import.py:886
#: netbox/dcim/forms/bulk_import.py:972 netbox/dcim/forms/bulk_import.py:1101
#: netbox/dcim/forms/bulk_import.py:1120 netbox/dcim/forms/bulk_import.py:1465
#: netbox/dcim/forms/bulk_import.py:1677 netbox/dcim/forms/filtersets.py:1104
#: netbox/dcim/forms/bulk_import.py:1120 netbox/dcim/forms/bulk_import.py:1481
#: netbox/dcim/forms/bulk_import.py:1724 netbox/dcim/forms/filtersets.py:1104
#: netbox/dcim/forms/filtersets.py:1205 netbox/dcim/forms/filtersets.py:1333
#: netbox/dcim/forms/filtersets.py:1424 netbox/dcim/forms/filtersets.py:1444
#: netbox/dcim/forms/filtersets.py:1464 netbox/dcim/forms/filtersets.py:1484
@@ -539,8 +539,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_import.py:103 netbox/dcim/forms/bulk_import.py:162
#: netbox/dcim/forms/bulk_import.py:265 netbox/dcim/forms/bulk_import.py:374
#: netbox/dcim/forms/bulk_import.py:605 netbox/dcim/forms/bulk_import.py:765
#: netbox/dcim/forms/bulk_import.py:1230 netbox/dcim/forms/bulk_import.py:1453
#: netbox/dcim/forms/bulk_import.py:1672 netbox/dcim/forms/bulk_import.py:1735
#: netbox/dcim/forms/bulk_import.py:1230 netbox/dcim/forms/bulk_import.py:1469
#: netbox/dcim/forms/bulk_import.py:1719 netbox/dcim/forms/bulk_import.py:1782
#: netbox/dcim/forms/filtersets.py:208 netbox/dcim/forms/filtersets.py:268
#: netbox/dcim/forms/filtersets.py:396 netbox/dcim/forms/filtersets.py:504
#: netbox/dcim/forms/filtersets.py:901 netbox/dcim/forms/filtersets.py:1024
@@ -601,8 +601,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:799 netbox/dcim/forms/bulk_edit.py:1746
#: netbox/dcim/forms/bulk_import.py:122 netbox/dcim/forms/bulk_import.py:167
#: netbox/dcim/forms/bulk_import.py:258 netbox/dcim/forms/bulk_import.py:379
#: netbox/dcim/forms/bulk_import.py:579 netbox/dcim/forms/bulk_import.py:1471
#: netbox/dcim/forms/bulk_import.py:1728 netbox/dcim/forms/filtersets.py:143
#: netbox/dcim/forms/bulk_import.py:579 netbox/dcim/forms/bulk_import.py:1487
#: netbox/dcim/forms/bulk_import.py:1775 netbox/dcim/forms/filtersets.py:143
#: netbox/dcim/forms/filtersets.py:202 netbox/dcim/forms/filtersets.py:235
#: netbox/dcim/forms/filtersets.py:363 netbox/dcim/forms/filtersets.py:442
#: netbox/dcim/forms/filtersets.py:463 netbox/dcim/forms/filtersets.py:823
@@ -788,7 +788,7 @@ msgstr ""
#: netbox/circuits/forms/bulk_edit.py:192
#: netbox/circuits/forms/model_forms.py:199
#: netbox/dcim/forms/bulk_import.py:1419 netbox/dcim/forms/bulk_import.py:1444
#: netbox/dcim/forms/bulk_import.py:1427 netbox/dcim/forms/bulk_import.py:1460
msgid "Termination type"
msgstr ""
@@ -915,7 +915,7 @@ msgstr ""
#: netbox/dcim/forms/bulk_import.py:105 netbox/dcim/forms/bulk_import.py:164
#: netbox/dcim/forms/bulk_import.py:267 netbox/dcim/forms/bulk_import.py:376
#: netbox/dcim/forms/bulk_import.py:607 netbox/dcim/forms/bulk_import.py:767
#: netbox/dcim/forms/bulk_import.py:1232 netbox/dcim/forms/bulk_import.py:1674
#: netbox/dcim/forms/bulk_import.py:1232 netbox/dcim/forms/bulk_import.py:1721
#: netbox/ipam/forms/bulk_import.py:200 netbox/ipam/forms/bulk_import.py:264
#: netbox/ipam/forms/bulk_import.py:300 netbox/ipam/forms/bulk_import.py:531
#: netbox/ipam/forms/bulk_import.py:544
@@ -931,8 +931,8 @@ msgstr ""
#: netbox/circuits/forms/bulk_import.py:235
#: netbox/dcim/forms/bulk_import.py:126 netbox/dcim/forms/bulk_import.py:171
#: netbox/dcim/forms/bulk_import.py:383 netbox/dcim/forms/bulk_import.py:583
#: netbox/dcim/forms/bulk_import.py:1475 netbox/dcim/forms/bulk_import.py:1669
#: netbox/dcim/forms/bulk_import.py:1732 netbox/ipam/forms/bulk_import.py:49
#: netbox/dcim/forms/bulk_import.py:1491 netbox/dcim/forms/bulk_import.py:1716
#: netbox/dcim/forms/bulk_import.py:1779 netbox/ipam/forms/bulk_import.py:49
#: netbox/ipam/forms/bulk_import.py:78 netbox/ipam/forms/bulk_import.py:106
#: netbox/ipam/forms/bulk_import.py:126 netbox/ipam/forms/bulk_import.py:146
#: netbox/ipam/forms/bulk_import.py:174 netbox/ipam/forms/bulk_import.py:259
@@ -1005,8 +1005,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:445 netbox/dcim/forms/bulk_edit.py:684
#: netbox/dcim/forms/bulk_edit.py:733 netbox/dcim/forms/bulk_edit.py:875
#: netbox/dcim/forms/bulk_import.py:252 netbox/dcim/forms/bulk_import.py:355
#: netbox/dcim/forms/bulk_import.py:646 netbox/dcim/forms/bulk_import.py:1618
#: netbox/dcim/forms/bulk_import.py:1652 netbox/dcim/forms/filtersets.py:114
#: netbox/dcim/forms/bulk_import.py:646 netbox/dcim/forms/bulk_import.py:1665
#: netbox/dcim/forms/bulk_import.py:1699 netbox/dcim/forms/filtersets.py:114
#: netbox/dcim/forms/filtersets.py:358 netbox/dcim/forms/filtersets.py:393
#: netbox/dcim/forms/filtersets.py:438 netbox/dcim/forms/filtersets.py:491
#: netbox/dcim/forms/filtersets.py:820 netbox/dcim/forms/filtersets.py:864
@@ -1424,7 +1424,7 @@ msgstr ""
#: netbox/extras/models/models.py:172 netbox/extras/models/models.py:314
#: netbox/extras/models/models.py:417 netbox/extras/models/models.py:482
#: netbox/extras/models/models.py:567 netbox/extras/models/models.py:692
#: netbox/extras/models/notifications.py:126 netbox/extras/models/scripts.py:31
#: netbox/extras/models/notifications.py:126 netbox/extras/models/scripts.py:29
#: netbox/ipam/models/asns.py:18 netbox/ipam/models/fhrp.py:24
#: netbox/ipam/models/services.py:51 netbox/ipam/models/services.py:80
#: netbox/ipam/models/vlans.py:38 netbox/ipam/models/vlans.py:217
@@ -1667,7 +1667,7 @@ msgstr ""
#: netbox/dcim/forms/bulk_import.py:1096 netbox/dcim/forms/bulk_import.py:1115
#: netbox/dcim/forms/bulk_import.py:1134 netbox/dcim/forms/bulk_import.py:1146
#: netbox/dcim/forms/bulk_import.py:1194 netbox/dcim/forms/bulk_import.py:1316
#: netbox/dcim/forms/bulk_import.py:1722 netbox/dcim/forms/connections.py:34
#: netbox/dcim/forms/bulk_import.py:1769 netbox/dcim/forms/connections.py:34
#: netbox/dcim/forms/filtersets.py:156 netbox/dcim/forms/filtersets.py:1021
#: netbox/dcim/forms/filtersets.py:1054 netbox/dcim/forms/filtersets.py:1202
#: netbox/dcim/forms/filtersets.py:1418 netbox/dcim/forms/filtersets.py:1441
@@ -4255,8 +4255,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:444 netbox/dcim/forms/bulk_edit.py:897
#: netbox/dcim/forms/bulk_import.py:362 netbox/dcim/forms/bulk_import.py:365
#: netbox/dcim/forms/bulk_import.py:653 netbox/dcim/forms/bulk_import.py:1659
#: netbox/dcim/forms/bulk_import.py:1663 netbox/dcim/forms/filtersets.py:123
#: netbox/dcim/forms/bulk_import.py:653 netbox/dcim/forms/bulk_import.py:1706
#: netbox/dcim/forms/bulk_import.py:1710 netbox/dcim/forms/filtersets.py:123
#: netbox/dcim/forms/filtersets.py:359 netbox/dcim/forms/filtersets.py:448
#: netbox/dcim/forms/filtersets.py:462 netbox/dcim/forms/filtersets.py:501
#: netbox/dcim/forms/filtersets.py:874 netbox/dcim/forms/filtersets.py:1086
@@ -4316,7 +4316,7 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:555 netbox/dcim/forms/bulk_edit.py:562
#: netbox/dcim/forms/bulk_edit.py:793 netbox/dcim/forms/bulk_import.py:460
#: netbox/dcim/forms/bulk_import.py:1459 netbox/dcim/forms/filtersets.py:690
#: netbox/dcim/forms/bulk_import.py:1475 netbox/dcim/forms/filtersets.py:690
#: netbox/dcim/forms/filtersets.py:1215 netbox/dcim/forms/model_forms.py:444
#: netbox/dcim/forms/model_forms.py:457 netbox/dcim/tables/modules.py:43
#: netbox/extras/forms/filtersets.py:413 netbox/extras/forms/model_forms.py:626
@@ -4440,8 +4440,8 @@ msgstr ""
msgid "Length"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:818 netbox/dcim/forms/bulk_import.py:1478
#: netbox/dcim/forms/bulk_import.py:1481 netbox/dcim/forms/filtersets.py:1228
#: netbox/dcim/forms/bulk_edit.py:818 netbox/dcim/forms/bulk_import.py:1494
#: netbox/dcim/forms/bulk_import.py:1497 netbox/dcim/forms/filtersets.py:1228
msgid "Length unit"
msgstr ""
@@ -4449,17 +4449,17 @@ msgstr ""
msgid "Domain"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:892 netbox/dcim/forms/bulk_import.py:1646
#: netbox/dcim/forms/bulk_edit.py:892 netbox/dcim/forms/bulk_import.py:1693
#: netbox/dcim/forms/filtersets.py:1316 netbox/dcim/forms/model_forms.py:891
msgid "Power panel"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:914 netbox/dcim/forms/bulk_import.py:1682
#: netbox/dcim/forms/bulk_edit.py:914 netbox/dcim/forms/bulk_import.py:1729
#: netbox/dcim/forms/filtersets.py:1338
msgid "Supply"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:920 netbox/dcim/forms/bulk_import.py:1687
#: netbox/dcim/forms/bulk_edit.py:920 netbox/dcim/forms/bulk_import.py:1734
#: netbox/dcim/forms/filtersets.py:1343
msgid "Phase"
msgstr ""
@@ -4690,7 +4690,7 @@ msgid "available options"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:149 netbox/dcim/forms/bulk_import.py:643
#: netbox/dcim/forms/bulk_import.py:1643 netbox/ipam/forms/bulk_import.py:512
#: netbox/dcim/forms/bulk_import.py:1690 netbox/ipam/forms/bulk_import.py:512
#: netbox/virtualization/forms/bulk_import.py:64
#: netbox/virtualization/forms/bulk_import.py:102
msgid "Assigned site"
@@ -4753,7 +4753,7 @@ msgstr ""
msgid "Parent site"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:359 netbox/dcim/forms/bulk_import.py:1656
#: netbox/dcim/forms/bulk_import.py:359 netbox/dcim/forms/bulk_import.py:1703
msgid "Rack's location (if any)"
msgstr ""
@@ -4818,7 +4818,7 @@ msgstr ""
msgid "Limit platform assignments to this manufacturer"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:576 netbox/dcim/forms/bulk_import.py:1725
#: netbox/dcim/forms/bulk_import.py:576 netbox/dcim/forms/bulk_import.py:1772
#: netbox/tenancy/forms/bulk_import.py:116
msgid "Assigned role"
msgstr ""
@@ -5021,7 +5021,7 @@ msgid "VDC {vdc} is not assigned to device {device}"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1103 netbox/dcim/forms/bulk_import.py:1121
#: netbox/dcim/forms/bulk_import.py:1468
#: netbox/dcim/forms/bulk_import.py:1484
msgid "Physical medium classification"
msgstr ""
@@ -5118,121 +5118,144 @@ msgstr ""
msgid "Side A device"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1413 netbox/dcim/forms/bulk_import.py:1438
msgid "Device name"
#: netbox/dcim/forms/bulk_import.py:1414 netbox/dcim/forms/bulk_import.py:1447
msgid "Device name (for device component terminations)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1416
#: netbox/dcim/forms/bulk_import.py:1417
msgid "Side A power panel"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1421 netbox/dcim/forms/bulk_import.py:1454
msgid "Power panel name (for power feed terminations)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1424
msgid "Side A type"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1422
#: netbox/dcim/forms/bulk_import.py:1430
msgid "Side A name"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1423 netbox/dcim/forms/bulk_import.py:1448
#: netbox/dcim/forms/bulk_import.py:1431 netbox/dcim/forms/bulk_import.py:1464
msgid "Termination name"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1428
#: netbox/dcim/forms/bulk_import.py:1436
msgid "Side B site"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1432
#: netbox/dcim/forms/bulk_import.py:1440
#: netbox/wireless/forms/bulk_import.py:114
msgid "Site of parent device B (if any)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1435
#: netbox/dcim/forms/bulk_import.py:1443
msgid "Side B device"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1441
#: netbox/dcim/forms/bulk_import.py:1450
msgid "Side B power panel"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1457
msgid "Side B type"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1447
#: netbox/dcim/forms/bulk_import.py:1463
msgid "Side B name"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1456
#: netbox/dcim/forms/bulk_import.py:1472
#: netbox/templates/dcim/panels/connection.html:60
#: netbox/wireless/forms/bulk_import.py:133
msgid "Connection status"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1462
#: netbox/dcim/forms/bulk_import.py:1478
msgid "Cable connection profile"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1487
#: netbox/dcim/forms/bulk_import.py:1503
msgid "Color name (e.g. \"Red\") or hex code (e.g. \"f44336\")"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1542
#: netbox/dcim/forms/bulk_import.py:1564
#, python-brace-format
msgid ""
"Side {side_upper}: {power_panel} {termination_object} is already connected"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1570
#, python-brace-format
msgid "{side_upper} side termination not found: {power_panel} {name}"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1588
#, python-brace-format
msgid "Side {side_upper}: {device} {termination_object} is already connected"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1548
#: netbox/dcim/forms/bulk_import.py:1594
#, python-brace-format
msgid "{side_upper} side termination not found: {device} {name}"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1569
#: netbox/dcim/forms/bulk_import.py:1616
#, python-brace-format
msgid ""
"{color} did not match any used color name and was longer than six "
"characters: invalid hex."
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1594 netbox/dcim/forms/model_forms.py:926
#: netbox/dcim/forms/bulk_import.py:1641 netbox/dcim/forms/model_forms.py:926
#: netbox/dcim/tables/devices.py:1144
#: netbox/templates/dcim/panels/virtual_chassis_members.html:10
msgid "Master"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1598
#: netbox/dcim/forms/bulk_import.py:1645
msgid "Master device"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1615
#: netbox/dcim/forms/bulk_import.py:1662
msgid "Name of parent site"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1649
#: netbox/dcim/forms/bulk_import.py:1696
msgid "Upstream power panel"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1679
#: netbox/dcim/forms/bulk_import.py:1726
msgid "Primary or redundant"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1684
#: netbox/dcim/forms/bulk_import.py:1731
msgid "Supply type (AC/DC)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1689
#: netbox/dcim/forms/bulk_import.py:1736
msgid "Single or three-phase"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1739 netbox/dcim/forms/model_forms.py:1901
#: netbox/dcim/forms/bulk_import.py:1786 netbox/dcim/forms/model_forms.py:1901
#: netbox/dcim/ui/panels.py:110 netbox/dcim/ui/panels.py:354
#: netbox/virtualization/ui/panels.py:28
msgid "Primary IPv4"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1743
#: netbox/dcim/forms/bulk_import.py:1790
msgid "IPv4 address with mask, e.g. 1.2.3.4/24"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1746 netbox/dcim/forms/model_forms.py:1910
#: netbox/dcim/forms/bulk_import.py:1793 netbox/dcim/forms/model_forms.py:1910
#: netbox/dcim/ui/panels.py:115 netbox/dcim/ui/panels.py:359
#: netbox/virtualization/ui/panels.py:33
msgid "Primary IPv6"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1750
#: netbox/dcim/forms/bulk_import.py:1797
msgid "IPv6 address with prefix length, e.g. 2001:db8::1/64"
msgstr ""
@@ -9746,23 +9769,23 @@ msgstr ""
msgid "subscriptions"
msgstr ""
#: netbox/extras/models/scripts.py:43
#: netbox/extras/models/scripts.py:41
msgid "is executable"
msgstr ""
#: netbox/extras/models/scripts.py:65
#: netbox/extras/models/scripts.py:63
msgid "script"
msgstr ""
#: netbox/extras/models/scripts.py:66
#: netbox/extras/models/scripts.py:64
msgid "scripts"
msgstr ""
#: netbox/extras/models/scripts.py:112
#: netbox/extras/models/scripts.py:110
msgid "script module"
msgstr ""
#: netbox/extras/models/scripts.py:113
#: netbox/extras/models/scripts.py:111
msgid "script modules"
msgstr ""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@ def validate_peppers(peppers):
"""
Validate the given dictionary of cryptographic peppers for type & sufficient length.
"""
if type(peppers) is not dict:
if not isinstance(peppers, dict):
raise ImproperlyConfigured("API_TOKEN_PEPPERS must be a dictionary.")
for key, pepper in peppers.items():
if type(key) is not int:

View File

@@ -3,7 +3,7 @@
[project]
name = "netbox"
version = "4.5.7"
version = "4.5.8"
requires-python = ">=3.12"
description = "The premier source of truth powering network automation."
readme = "README.md"

View File

@@ -1,5 +1,5 @@
colorama==0.4.6
Django==5.2.12
Django==5.2.13
django-cors-headers==4.9.0
django-debug-toolbar==6.3.0
django-filter==25.2
@@ -10,7 +10,7 @@ django-pglocks==1.0.4
django-prometheus==2.4.1
django-redis==6.0.0
django-rich==2.2.0
django-rq==4.0.1
django-rq==4.1.0
django-storages==1.14.6
django-tables2==2.8.0
django-taggit==6.1.0
@@ -37,7 +37,7 @@ rq==2.7.0
social-auth-app-django==5.7.0
social-auth-core==4.8.5
sorl-thumbnail==13.0.0
strawberry-graphql==0.312.2
strawberry-graphql==0.314.3
strawberry-graphql-django==0.82.1
svgwrite==1.4.3
tablib==3.9.0