فهرست منبع

Merge branch 'main' into 20911-dropdown

Arthur 3 هفته پیش
والد
کامیت
de19447317
97فایلهای تغییر یافته به همراه5874 افزوده شده و 10901 حذف شده
  1. 1 1
      .github/ISSUE_TEMPLATE/01-feature_request.yaml
  2. 1 1
      .github/ISSUE_TEMPLATE/02-bug_report.yaml
  3. 43 0
      .github/ISSUE_TEMPLATE/03-performance.yaml
  4. 0 0
      .github/ISSUE_TEMPLATE/04-documentation_change.yaml
  5. 0 0
      .github/ISSUE_TEMPLATE/05-translation.yaml
  6. 0 0
      .github/ISSUE_TEMPLATE/06-housekeeping.yaml
  7. 0 0
      .github/ISSUE_TEMPLATE/07-deprecation.yaml
  8. 0 0
      .github/ISSUE_TEMPLATE/08-feature_removal.yaml
  9. 2 2
      .github/workflows/codeql.yml
  10. 298 2734
      contrib/openapi.json
  11. 1 0
      docs/release-notes/version-4.4.md
  12. 38 1
      docs/release-notes/version-4.5.md
  13. 1 0
      netbox/core/api/serializers_/data.py
  14. 15 5
      netbox/core/api/views.py
  15. 4 2
      netbox/core/models/data.py
  16. 10 4
      netbox/core/models/object_types.py
  17. 19 3
      netbox/dcim/forms/mixins.py
  18. 23 53
      netbox/dcim/forms/model_forms.py
  19. 15 0
      netbox/dcim/graphql/filter_mixins.py
  20. 10 6
      netbox/dcim/signals.py
  21. 1 1
      netbox/dcim/ui/panels.py
  22. 1 0
      netbox/dcim/views.py
  23. 2 2
      netbox/extras/api/serializers_/configcontexts.py
  24. 11 0
      netbox/extras/constants.py
  25. 5 1
      netbox/extras/events.py
  26. 0 4
      netbox/extras/forms/bulk_import.py
  27. 6 6
      netbox/extras/forms/model_forms.py
  28. 1 1
      netbox/extras/tables/tables.py
  29. 92 1
      netbox/extras/tests/test_api.py
  30. 51 1
      netbox/extras/tests/test_models.py
  31. 2 1
      netbox/extras/utils.py
  32. 38 0
      netbox/ipam/api/serializers_/ip.py
  33. 4 3
      netbox/ipam/api/views.py
  34. 1 1
      netbox/ipam/forms/filtersets.py
  35. 2 2
      netbox/ipam/forms/model_forms.py
  36. 4 4
      netbox/ipam/graphql/filters.py
  37. 7 0
      netbox/ipam/models/ip.py
  38. 25 0
      netbox/ipam/tests/test_api.py
  39. 26 0
      netbox/netbox/graphql/filter_lookups.py
  40. 2 2
      netbox/netbox/graphql/filters.py
  41. 0 2
      netbox/netbox/models/features.py
  42. 1 1
      netbox/netbox/ui/panels.py
  43. 0 0
      netbox/project-static/dist/netbox.css
  44. 0 0
      netbox/project-static/dist/netbox.js
  45. 0 0
      netbox/project-static/dist/netbox.js.map
  46. 1 1
      netbox/project-static/dist/rack_elevation.css
  47. 8 8
      netbox/project-static/package.json
  48. 40 0
      netbox/project-static/src/forms/clearField.ts
  49. 2 1
      netbox/project-static/src/forms/index.ts
  50. 314 407
      netbox/project-static/yarn.lock
  51. 2 2
      netbox/release.yaml
  52. 2 0
      netbox/templates/ipam/aggregate/prefixes.html
  53. 20 0
      netbox/templates/ipam/inc/max_depth.html
  54. 20 0
      netbox/templates/ipam/inc/max_length.html
  55. 2 0
      netbox/templates/ipam/prefix/prefixes.html
  56. 2 34
      netbox/templates/ipam/prefix_list.html
  57. BIN
      netbox/translations/cs/LC_MESSAGES/django.mo
  58. 292 493
      netbox/translations/cs/LC_MESSAGES/django.po
  59. BIN
      netbox/translations/da/LC_MESSAGES/django.mo
  60. 292 493
      netbox/translations/da/LC_MESSAGES/django.po
  61. BIN
      netbox/translations/de/LC_MESSAGES/django.mo
  62. 293 494
      netbox/translations/de/LC_MESSAGES/django.po
  63. 157 149
      netbox/translations/en/LC_MESSAGES/django.po
  64. BIN
      netbox/translations/es/LC_MESSAGES/django.mo
  65. 292 493
      netbox/translations/es/LC_MESSAGES/django.po
  66. BIN
      netbox/translations/fr/LC_MESSAGES/django.mo
  67. 292 493
      netbox/translations/fr/LC_MESSAGES/django.po
  68. BIN
      netbox/translations/it/LC_MESSAGES/django.mo
  69. 292 493
      netbox/translations/it/LC_MESSAGES/django.po
  70. BIN
      netbox/translations/ja/LC_MESSAGES/django.mo
  71. 292 493
      netbox/translations/ja/LC_MESSAGES/django.po
  72. BIN
      netbox/translations/lv/LC_MESSAGES/django.mo
  73. 315 515
      netbox/translations/lv/LC_MESSAGES/django.po
  74. BIN
      netbox/translations/nl/LC_MESSAGES/django.mo
  75. 292 493
      netbox/translations/nl/LC_MESSAGES/django.po
  76. BIN
      netbox/translations/pl/LC_MESSAGES/django.mo
  77. 292 493
      netbox/translations/pl/LC_MESSAGES/django.po
  78. BIN
      netbox/translations/pt/LC_MESSAGES/django.mo
  79. 292 493
      netbox/translations/pt/LC_MESSAGES/django.po
  80. BIN
      netbox/translations/ru/LC_MESSAGES/django.mo
  81. 292 493
      netbox/translations/ru/LC_MESSAGES/django.po
  82. BIN
      netbox/translations/tr/LC_MESSAGES/django.mo
  83. 293 494
      netbox/translations/tr/LC_MESSAGES/django.po
  84. BIN
      netbox/translations/uk/LC_MESSAGES/django.mo
  85. 292 493
      netbox/translations/uk/LC_MESSAGES/django.po
  86. BIN
      netbox/translations/zh/LC_MESSAGES/django.mo
  87. 293 495
      netbox/translations/zh/LC_MESSAGES/django.po
  88. 4 3
      netbox/users/constants.py
  89. 37 0
      netbox/utilities/forms/widgets/modifiers.py
  90. 16 0
      netbox/utilities/forms/widgets/select.py
  91. 13 0
      netbox/utilities/templatetags/builtins/filters.py
  92. 47 0
      netbox/utilities/tests/test_filter_modifiers.py
  93. 8 4
      netbox/utilities/views.py
  94. 3 3
      netbox/vpn/graphql/filters.py
  95. 1 1
      pyproject.toml
  96. 6 6
      requirements.txt
  97. 0 16
      scripts/git-hooks/pre-commit

+ 1 - 1
.github/ISSUE_TEMPLATE/01-feature_request.yaml

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

+ 1 - 1
.github/ISSUE_TEMPLATE/02-bug_report.yaml

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

+ 43 - 0
.github/ISSUE_TEMPLATE/03-performance.yaml

@@ -0,0 +1,43 @@
+---
+name: 🏁 Performance
+type: Performance
+description: An opportunity to improve application performance
+labels: ["netbox", "type: performance", "status: needs triage"]
+body:
+  - type: input
+    attributes:
+      label: NetBox Version
+      description: What version of NetBox are you currently running?
+      placeholder: v4.5.1
+    validations:
+      required: true
+  - type: dropdown
+    attributes:
+      label: Python Version
+      description: What version of Python are you currently running?
+      options:
+        - "3.12"
+        - "3.13"
+        - "3.14"
+    validations:
+      required: true
+  - type: checkboxes
+    attributes:
+      label: Area(s) of Concern
+      description: Which application interface(s) are affected?
+      options:
+        - label: User Interface
+        - label: REST API
+        - label: GraphQL API
+        - label: Python ORM
+        - label: Other
+    validations:
+      required: true
+  - type: textarea
+    attributes:
+      label: Details
+      description: >
+        Describe in detail the operations being performed and the indications of a performance issue.
+        Include any relevant testing parameters, benchmarks, and expected results.
+    validations:
+      required: true

+ 0 - 0
.github/ISSUE_TEMPLATE/03-documentation_change.yaml → .github/ISSUE_TEMPLATE/04-documentation_change.yaml


+ 0 - 0
.github/ISSUE_TEMPLATE/04-translation.yaml → .github/ISSUE_TEMPLATE/05-translation.yaml


+ 0 - 0
.github/ISSUE_TEMPLATE/05-housekeeping.yaml → .github/ISSUE_TEMPLATE/06-housekeeping.yaml


+ 0 - 0
.github/ISSUE_TEMPLATE/06-deprecation.yaml → .github/ISSUE_TEMPLATE/07-deprecation.yaml


+ 0 - 0
.github/ISSUE_TEMPLATE/07-feature_removal.yaml → .github/ISSUE_TEMPLATE/08-feature_removal.yaml


+ 2 - 2
.github/workflows/codeql.yml

@@ -30,13 +30,13 @@ jobs:
       uses: actions/checkout@v4
 
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@v3
+      uses: github/codeql-action/init@v4
       with:
         languages: ${{ matrix.language }}
         build-mode: ${{ matrix.build-mode }}
         config-file: .github/codeql/codeql-config.yml
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v3
+      uses: github/codeql-action/analyze@v4
       with:
         category: "/language:${{matrix.language}}"

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 298 - 2734
contrib/openapi.json


+ 1 - 0
docs/release-notes/version-4.4.md

@@ -40,6 +40,7 @@
 * [#20912](https://github.com/netbox-community/netbox/issues/20912) - Fix inconsistent clearing of `module` field on ModuleBay
 * [#20944](https://github.com/netbox-community/netbox/issues/20944) - Ensure cached scope is updated on child objects when a parent region/site/location is changed
 * [#20948](https://github.com/netbox-community/netbox/issues/20948) - Handle the deletion of related objects with `on_delete=RESTRICT` the same as `CASCADE`
+* [#20966](https://github.com/netbox-community/netbox/issues/20966) - Fix UI rendering issue when scrolling list of object types in permissions form
 * [#20969](https://github.com/netbox-community/netbox/issues/20969) - Fix querying of front port templates by `rear_port_id`
 * [#21011](https://github.com/netbox-community/netbox/issues/21011) - Avoid writing to the database when loading active ConfigRevision
 * [#21032](https://github.com/netbox-community/netbox/issues/21032) - Avoid SQL subquery in RestrictedQuerySet where unnecessary

+ 38 - 1
docs/release-notes/version-4.5.md

@@ -1,4 +1,41 @@
-## v4.5.0 (FUTURE)
+# NetBox v4.5
+
+## v4.5.1 (2026-01-20)
+
+### Enhancements
+
+* [#21018](https://github.com/netbox-community/netbox/issues/21018) - Enable filtering prefixes by location/site/site group/region directly via GraphQL API
+* [#21142](https://github.com/netbox-community/netbox/issues/21142) - Enable filtering device components by site/location/rack directly via GraphQL API
+* [#21144](https://github.com/netbox-community/netbox/issues/21144) - Enable specifying a prefix length for IP addresses when utilizing the `/api/ipam/prefixes/<id>/available-ips/` REST API endpoint
+* [#21165](https://github.com/netbox-community/netbox/issues/21165) - VLAN selector should default to group (instead of site)
+* [#21178](https://github.com/netbox-community/netbox/issues/21178) - Improve consistency of rack measurements in UI
+
+### Bug Fixes
+
+* [#19901](https://github.com/netbox-community/netbox/issues/19901) - Fix `RelatedObjectDoesNotExist` exception when importing modules into unnamed devices
+* [#20239](https://github.com/netbox-community/netbox/issues/20239) - Prevent shared mutable state in PluginMenuItem & PluginMenuButton
+* [#20933](https://github.com/netbox-community/netbox/issues/20933) - Fix writable `data_file` assignment for ConfigContext and ConfigContextProfile via the REST API
+* [#21039](https://github.com/netbox-community/netbox/issues/21039) - Fix support for AVIF image uploads
+* [#21050](https://github.com/netbox-community/netbox/issues/21050) - Clear device OOB IP assignments when reassigning IP addresses
+* [#21051](https://github.com/netbox-community/netbox/issues/21051) - Remove irrelevant object types from permissions form
+* [#21097](https://github.com/netbox-community/netbox/issues/21097) - Fix comparison lookups for ID filters in GraphQL API
+* [#21102](https://github.com/netbox-community/netbox/issues/21102) - Fix GraphiQL explorer UI
+* [#21117](https://github.com/netbox-community/netbox/issues/21117) - Avoid `ValueError` exception when `API_TOKEN_PEPPERS` is not defined
+* [#21118](https://github.com/netbox-community/netbox/issues/21118) - Address performance issue when saving sites with many assigned objects
+* [#21124](https://github.com/netbox-community/netbox/issues/21124) - Fix front/rear port mapping for module types
+* [#21134](https://github.com/netbox-community/netbox/issues/21134) - Fix bulk renaming for module types
+* [#21139](https://github.com/netbox-community/netbox/issues/21139) - Support `fields` parameter for job, object change, and object type REST API endpoints
+* [#21140](https://github.com/netbox-community/netbox/issues/21140) - Restore translation for object attribute labels on several UI views
+* [#21160](https://github.com/netbox-community/netbox/issues/21160) - Fix performance issue loading UI views caused by unintended `APISelect` choices resolution
+* [#21166](https://github.com/netbox-community/netbox/issues/21166) - Fix support for 32-bit ASN filtering in GraphQL API
+* [#21175](https://github.com/netbox-community/netbox/issues/21175) - Fix pending migrations warning when `DEFAULT_LANGUAGE` is set
+* [#21181](https://github.com/netbox-community/netbox/issues/21181) - Handle `AuthenticationFailed` exception when using an invalid API token to fetch media files
+* [#21213](https://github.com/netbox-community/netbox/issues/21213) - Tag weight field should be marked as required in UI forms
+* [#21231](https://github.com/netbox-community/netbox/issues/21231) - Presence of object types table should be checked only during migration (performance improvement)
+
+---
+
+## v4.5.0 (2026-01-06)
 
 ### Breaking Changes
 

+ 1 - 0
netbox/core/api/serializers_/data.py

@@ -44,3 +44,4 @@ class DataFileSerializer(NetBoxModelSerializer):
             'id', 'url', 'display_url', 'display', 'source', 'path', 'last_updated', 'size', 'hash',
         ]
         brief_fields = ('id', 'url', 'display', 'path')
+        read_only_fields = ['path', 'last_updated', 'size', 'hash']

+ 15 - 5
netbox/core/api/views.py

@@ -11,7 +11,6 @@ from rest_framework.decorators import action
 from rest_framework.exceptions import PermissionDenied
 from rest_framework.response import Response
 from rest_framework.routers import APIRootView
-from rest_framework.viewsets import ReadOnlyModelViewSet
 from rq.job import Job as RQ_Job
 from rq.worker import Worker
 
@@ -64,7 +63,7 @@ class DataFileViewSet(NetBoxReadOnlyModelViewSet):
     filterset_class = filtersets.DataFileFilterSet
 
 
-class JobViewSet(ReadOnlyModelViewSet):
+class JobViewSet(NetBoxReadOnlyModelViewSet):
     """
     Retrieve a list of job results
     """
@@ -73,19 +72,20 @@ class JobViewSet(ReadOnlyModelViewSet):
     filterset_class = filtersets.JobFilterSet
 
 
-class ObjectChangeViewSet(ReadOnlyModelViewSet):
+class ObjectChangeViewSet(NetBoxReadOnlyModelViewSet):
     """
     Retrieve a list of recent changes.
     """
     metadata_class = ContentTypeMetadata
+    queryset = ObjectChange.objects.all()
     serializer_class = serializers.ObjectChangeSerializer
     filterset_class = filtersets.ObjectChangeFilterSet
 
     def get_queryset(self):
-        return ObjectChange.objects.valid_models()
+        return super().get_queryset().valid_models()
 
 
-class ObjectTypeViewSet(ReadOnlyModelViewSet):
+class ObjectTypeViewSet(NetBoxReadOnlyModelViewSet):
     """
     Read-only list of ObjectTypes.
     """
@@ -94,6 +94,16 @@ class ObjectTypeViewSet(ReadOnlyModelViewSet):
     serializer_class = serializers.ObjectTypeSerializer
     filterset_class = filtersets.ObjectTypeFilterSet
 
+    def initial(self, request, *args, **kwargs):
+        """
+        Override initial() to skip the restrict() call since ObjectType (a ContentType proxy)
+        doesn't use RestrictedQuerySet and is publicly accessible metadata.
+        """
+        # Call GenericViewSet.initial() directly, skipping BaseViewSet.initial()
+        # which would try to call restrict() on the queryset
+        from rest_framework.viewsets import GenericViewSet
+        GenericViewSet.initial(self, request, *args, **kwargs)
+
 
 class BaseRQViewSet(viewsets.ViewSet):
     """

+ 4 - 2
netbox/core/models/data.py

@@ -12,7 +12,7 @@ from django.core.validators import RegexValidator
 from django.db import models
 from django.urls import reverse
 from django.utils import timezone
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 from netbox.constants import CENSOR_TOKEN, CENSOR_TOKEN_CHANGED
 from netbox.models import PrimaryModel
@@ -128,7 +128,9 @@ class DataSource(JobsMixin, PrimaryModel):
         # Ensure URL scheme matches selected type
         if self.backend_class.is_local and self.url_scheme not in ('file', ''):
             raise ValidationError({
-                'source_url': "URLs for local sources must start with file:// (or specify no scheme)"
+                'source_url': _("URLs for local sources must start with {scheme} (or specify no scheme)").format(
+                    scheme='file://'
+                )
             })
 
     def save(self, *args, **kwargs):

+ 10 - 4
netbox/core/models/object_types.py

@@ -35,6 +35,10 @@ class ObjectTypeQuerySet(models.QuerySet):
 
 class ObjectTypeManager(models.Manager):
 
+    # TODO: Remove this in NetBox v5.0
+    # Cache the result of introspection to avoid repeated queries.
+    _table_exists = False
+
     def get_queryset(self):
         return ObjectTypeQuerySet(self.model, using=self._db)
 
@@ -69,10 +73,12 @@ class ObjectTypeManager(models.Manager):
         # TODO: Remove this in NetBox v5.0
         # If the ObjectType table has not yet been provisioned (e.g. because we're in a pre-v4.4 migration),
         # fall back to ContentType.
-        if 'core_objecttype' not in connection.introspection.table_names():
-            ct = ContentType.objects.get_for_model(model, for_concrete_model=for_concrete_model)
-            ct.features = get_model_features(ct.model_class())
-            return ct
+        if not ObjectTypeManager._table_exists:
+            if 'core_objecttype' not in connection.introspection.table_names():
+                ct = ContentType.objects.get_for_model(model, for_concrete_model=for_concrete_model)
+                ct.features = get_model_features(ct.model_class())
+                return ct
+            ObjectTypeManager._table_exists = True
 
         if not inspect.isclass(model):
             model = model.__class__

+ 19 - 3
netbox/dcim/forms/mixins.py

@@ -140,9 +140,6 @@ class FrontPortFormMixin(forms.Form):
         widget=forms.SelectMultiple(attrs={'size': 8})
     )
 
-    port_mapping_model = PortMapping
-    parent_field = 'device'
-
     def clean(self):
         super().clean()
 
@@ -203,3 +200,22 @@ class FrontPortFormMixin(forms.Form):
                 using=connection,
                 update_fields=None
             )
+
+    def _get_rear_port_choices(self, parent_filter, front_port):
+        """
+        Return a list of choices representing each available rear port & position pair on the parent object (identified
+        by a Q filter), excluding those assigned to the specified instance.
+        """
+        occupied_rear_port_positions = [
+            f'{mapping.rear_port_id}:{mapping.rear_port_position}'
+            for mapping in self.port_mapping_model.objects.filter(parent_filter).exclude(front_port=front_port.pk)
+        ]
+
+        choices = []
+        for rear_port in self.rear_port_model.objects.filter(parent_filter):
+            for i in range(1, rear_port.positions + 1):
+                pair_id = f'{rear_port.pk}:{i}'
+                if pair_id not in occupied_rear_port_positions:
+                    pair_label = f'{rear_port.name}:{i}'
+                    choices.append((pair_id, pair_label))
+        return choices

+ 23 - 53
netbox/dcim/forms/model_forms.py

@@ -20,7 +20,9 @@ from utilities.forms.fields import (
     DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, NumericArrayField, SlugField,
 )
 from utilities.forms.rendering import FieldSet, InlineFields, TabbedGroups
-from utilities.forms.widgets import APISelect, ClearableFileInput, HTMXSelect, NumberWithOptions, SelectWithPK
+from utilities.forms.widgets import (
+    APISelect, ClearableFileInput, ClearableSelect, HTMXSelect, NumberWithOptions, SelectWithPK,
+)
 from utilities.jsonschema import JSONSchemaProperty
 from virtualization.models import Cluster, VMInterface
 from wireless.models import WirelessLAN, WirelessLANGroup
@@ -592,6 +594,14 @@ class DeviceForm(TenancyForm, PrimaryModelForm):
             },
         )
     )
+    face = forms.ChoiceField(
+        label=_('Face'),
+        choices=add_blank_choice(DeviceFaceChoices),
+        required=False,
+        widget=ClearableSelect(
+            requires_fields=['rack']
+        )
+    )
     device_type = DynamicModelChoiceField(
         label=_('Device type'),
         queryset=DeviceType.objects.all(),
@@ -1124,9 +1134,8 @@ class FrontPortTemplateForm(FrontPortFormMixin, ModularComponentTemplateForm):
         ),
     )
 
-    # Override FrontPortFormMixin attrs
     port_mapping_model = PortTemplateMapping
-    parent_field = 'device_type'
+    rear_port_model = RearPortTemplate
 
     class Meta:
         model = FrontPortTemplate
@@ -1137,13 +1146,14 @@ class FrontPortTemplateForm(FrontPortFormMixin, ModularComponentTemplateForm):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
+        # Populate rear port choices based on parent DeviceType or ModuleType
         if device_type_id := self.data.get('device_type') or self.initial.get('device_type'):
-            device_type = DeviceType.objects.get(pk=device_type_id)
+            parent_filter = Q(device_type=device_type_id)
+        elif module_type_id := self.data.get('module_type') or self.initial.get('module_type'):
+            parent_filter = Q(module_type=module_type_id)
         else:
             return
-
-        # Populate rear port choices
-        self.fields['rear_ports'].choices = self._get_rear_port_choices(device_type, self.instance)
+        self.fields['rear_ports'].choices = self._get_rear_port_choices(parent_filter, self.instance)
 
         # Set initial rear port mappings
         if self.instance.pk:
@@ -1152,27 +1162,6 @@ class FrontPortTemplateForm(FrontPortFormMixin, ModularComponentTemplateForm):
                 for mapping in PortTemplateMapping.objects.filter(front_port_id=self.instance.pk)
             ]
 
-    def _get_rear_port_choices(self, device_type, front_port):
-        """
-        Return a list of choices representing each available rear port & position pair on the device type, excluding
-        those assigned to the specified instance.
-        """
-        occupied_rear_port_positions = [
-            f'{mapping.rear_port_id}:{mapping.rear_port_position}'
-            for mapping in device_type.port_mappings.exclude(front_port=front_port.pk)
-        ]
-
-        choices = []
-        for rear_port in RearPortTemplate.objects.filter(device_type=device_type):
-            for i in range(1, rear_port.positions + 1):
-                pair_id = f'{rear_port.pk}:{i}'
-                if pair_id not in occupied_rear_port_positions:
-                    pair_label = f'{rear_port.name}:{i}'
-                    choices.append(
-                        (pair_id, pair_label)
-                    )
-        return choices
-
 
 class RearPortTemplateForm(ModularComponentTemplateForm):
     fieldsets = (
@@ -1619,6 +1608,9 @@ class FrontPortForm(FrontPortFormMixin, ModularDeviceComponentForm):
         ),
     )
 
+    port_mapping_model = PortMapping
+    rear_port_model = RearPort
+
     class Meta:
         model = FrontPort
         fields = [
@@ -1629,13 +1621,12 @@ class FrontPortForm(FrontPortFormMixin, ModularDeviceComponentForm):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
+        # Populate rear port choices
         if device_id := self.data.get('device') or self.initial.get('device'):
-            device = Device.objects.get(pk=device_id)
+            parent_filter = Q(device=device_id)
         else:
             return
-
-        # Populate rear port choices
-        self.fields['rear_ports'].choices = self._get_rear_port_choices(device, self.instance)
+        self.fields['rear_ports'].choices = self._get_rear_port_choices(parent_filter, self.instance)
 
         # Set initial rear port mappings
         if self.instance.pk:
@@ -1644,27 +1635,6 @@ class FrontPortForm(FrontPortFormMixin, ModularDeviceComponentForm):
                 for mapping in PortMapping.objects.filter(front_port_id=self.instance.pk)
             ]
 
-    def _get_rear_port_choices(self, device, front_port):
-        """
-        Return a list of choices representing each available rear port & position pair on the device, excluding those
-        assigned to the specified instance.
-        """
-        occupied_rear_port_positions = [
-            f'{mapping.rear_port_id}:{mapping.rear_port_position}'
-            for mapping in device.port_mappings.exclude(front_port=front_port.pk)
-        ]
-
-        choices = []
-        for rear_port in RearPort.objects.filter(device=device):
-            for i in range(1, rear_port.positions + 1):
-                pair_id = f'{rear_port.pk}:{i}'
-                if pair_id not in occupied_rear_port_positions:
-                    pair_label = f'{rear_port.name}:{i}'
-                    choices.append(
-                        (pair_id, pair_label)
-                    )
-        return choices
-
 
 class RearPortForm(ModularDeviceComponentForm):
     fieldsets = (

+ 15 - 0
netbox/dcim/graphql/filter_mixins.py

@@ -13,6 +13,7 @@ if TYPE_CHECKING:
     from netbox.graphql.filter_lookups import IntegerLookup
     from extras.graphql.filters import ConfigTemplateFilter
     from ipam.graphql.filters import VLANFilter, VLANTranslationPolicyFilter
+    from dcim.graphql.filters import LocationFilter, RegionFilter, SiteFilter, SiteGroupFilter
     from .filters import *
 
 __all__ = (
@@ -35,6 +36,20 @@ class ScopedFilterMixin:
     )
     scope_id: ID | None = strawberry_django.filter_field()
 
+    # Cached relations
+    _location: Annotated['LocationFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
+        strawberry_django.filter_field(name='location')
+    )
+    _region: Annotated['RegionFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
+        strawberry_django.filter_field(name='region')
+    )
+    _site_group: Annotated['SiteGroupFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
+        strawberry_django.filter_field(name='site_group')
+    )
+    _site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
+        strawberry_django.filter_field(name='site')
+    )
+
 
 @dataclass
 class ComponentModelFilterMixin:

+ 10 - 6
netbox/dcim/signals.py

@@ -211,12 +211,16 @@ def sync_cached_scope_fields(instance, created, **kwargs):
     for model in (Prefix, Cluster, WirelessLAN):
         qs = model.objects.filter(**filters)
 
+        # Bulk update cached fields to avoid O(N) performance issues with large datasets.
+        # This does not trigger post_save signals, avoiding spurious change log entries.
+        objects_to_update = []
         for obj in qs:
             # Recompute cache using the same logic as save()
             obj.cache_related_objects()
-            obj.save(update_fields=[
-                '_location',
-                '_site',
-                '_site_group',
-                '_region',
-            ])
+            objects_to_update.append(obj)
+
+        if objects_to_update:
+            model.objects.bulk_update(
+                objects_to_update,
+                ['_location', '_site', '_site_group', '_region']
+            )

+ 1 - 1
netbox/dcim/ui/panels.py

@@ -31,7 +31,7 @@ class RackDimensionsPanel(panels.ObjectAttributesPanel):
     outer_width = attrs.NumericAttr('outer_width', unit_accessor='get_outer_unit_display')
     outer_height = attrs.NumericAttr('outer_height', unit_accessor='get_outer_unit_display')
     outer_depth = attrs.NumericAttr('outer_depth', unit_accessor='get_outer_unit_display')
-    mounting_depth = attrs.TextAttr('mounting_depth', format_string='{}mm')
+    mounting_depth = attrs.TextAttr('mounting_depth', format_string=_('{} millimeters'))
 
 
 class RackNumberingPanel(panels.ObjectAttributesPanel):

+ 1 - 0
netbox/dcim/views.py

@@ -1845,6 +1845,7 @@ class ModuleTypeBulkEditView(generic.BulkEditView):
 class ModuleTypeBulkRenameView(generic.BulkRenameView):
     queryset = ModuleType.objects.all()
     filterset = filtersets.ModuleTypeFilterSet
+    field_name = 'model'
 
 
 @register_model_view(ModuleType, 'bulk_delete', path='delete', detail=False)

+ 2 - 2
netbox/extras/api/serializers_/configcontexts.py

@@ -28,7 +28,7 @@ class ConfigContextProfileSerializer(PrimaryModelSerializer):
     )
     data_file = DataFileSerializer(
         nested=True,
-        read_only=True
+        required=False
     )
 
     class Meta:
@@ -143,7 +143,7 @@ class ConfigContextSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedM
     )
     data_file = DataFileSerializer(
         nested=True,
-        read_only=True
+        required=False
     )
 
     class Meta:

+ 11 - 0
netbox/extras/constants.py

@@ -4,6 +4,17 @@ from extras.choices import LogLevelChoices
 # Custom fields
 CUSTOMFIELD_EMPTY_VALUES = (None, '', [])
 
+# ImageAttachment
+IMAGE_ATTACHMENT_IMAGE_FORMATS = {
+    'avif': 'image/avif',
+    'bmp': 'image/bmp',
+    'gif': 'image/gif',
+    'jpeg': 'image/jpeg',
+    'jpg': 'image/jpeg',
+    'png': 'image/png',
+    'webp': 'image/webp',
+}
+
 # Template Export
 DEFAULT_MIME_TYPE = 'text/plain; charset=utf-8'
 

+ 5 - 1
netbox/extras/events.py

@@ -86,7 +86,7 @@ def enqueue_event(queue, instance, request, event_type):
 
 
 def process_event_rules(event_rules, object_type, event_type, data, username=None, snapshots=None, request=None):
-    user = User.objects.get(username=username) if username else None
+    user = None  # To be resolved from the username if needed
 
     for event_rule in event_rules:
 
@@ -134,6 +134,10 @@ def process_event_rules(event_rules, object_type, event_type, data, username=Non
             # Resolve the script from action parameters
             script = event_rule.action_object.python_class()
 
+            # Retrieve the User if not already resolved
+            if user is None:
+                user = User.objects.get(username=username)
+
             # Enqueue a Job to record the script's execution
             from extras.jobs import ScriptJob
             params = {

+ 0 - 4
netbox/extras/forms/bulk_import.py

@@ -271,10 +271,6 @@ class EventRuleImportForm(OwnerCSVMixin, NetBoxModelImportForm):
 
 class TagImportForm(OwnerCSVMixin, CSVModelForm):
     slug = SlugField()
-    weight = forms.IntegerField(
-        label=_('Weight'),
-        required=False
-    )
     object_types = CSVMultipleContentTypeField(
         label=_('Object types'),
         queryset=ObjectType.objects.with_feature('tags'),

+ 6 - 6
netbox/extras/forms/model_forms.py

@@ -9,6 +9,7 @@ from django.utils.translation import gettext_lazy as _
 from core.forms.mixins import SyncedDataMixin
 from core.models import ObjectType
 from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
+from extras.constants import IMAGE_ATTACHMENT_IMAGE_FORMATS
 from extras.choices import *
 from extras.models import *
 from netbox.events import get_event_type_choices
@@ -570,10 +571,6 @@ class TagForm(ChangelogMessageMixin, OwnerMixin, forms.ModelForm):
         queryset=ObjectType.objects.with_feature('tags'),
         required=False
     )
-    weight = forms.IntegerField(
-        label=_('Weight'),
-        required=False
-    )
 
     fieldsets = (
         FieldSet('name', 'slug', 'color', 'weight', 'description', 'object_types', name=_('Tag')),
@@ -784,8 +781,11 @@ class ImageAttachmentForm(forms.ModelForm):
         fields = [
             'image', 'name', 'description',
         ]
-        help_texts = {
-            'name': _("If no name is specified, the file name will be used.")
+        # Explicitly set 'image/avif' to support AVIF selection in Firefox
+        widgets = {
+            'image': forms.ClearableFileInput(
+                attrs={'accept': ','.join(sorted(set(IMAGE_ATTACHMENT_IMAGE_FORMATS.values())))}
+            ),
         }
 
 

+ 1 - 1
netbox/extras/tables/tables.py

@@ -43,7 +43,7 @@ IMAGEATTACHMENT_IMAGE = """
   <a href="{{ record.image.url }}" target="_blank" class="image-preview" data-bs-placement="top">
     <i class="mdi mdi-image"></i></a>
 {% endif %}
-<a href="{{ record.get_absolute_url }}">{{ record }}</a>
+<a href="{{ record.get_absolute_url }}">{{ record.filename|truncate_middle:16 }}</a>
 """
 
 NOTIFICATION_ICON = """

+ 92 - 1
netbox/extras/tests/test_api.py

@@ -1,4 +1,5 @@
 import datetime
+import hashlib
 
 from django.contrib.contenttypes.models import ContentType
 from django.urls import reverse
@@ -7,7 +8,7 @@ from rest_framework import status
 
 from core.choices import ManagedFileRootPathChoices
 from core.events import *
-from core.models import ObjectType
+from core.models import DataFile, DataSource, ObjectType
 from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, Location, RackRole, Site
 from extras.choices import *
 from extras.models import *
@@ -731,6 +732,51 @@ class ConfigContextProfileTest(APIViewTestCases.APIViewTestCase):
         )
         ConfigContextProfile.objects.bulk_create(profiles)
 
+    def test_update_data_source_and_data_file(self):
+        """
+        Regression test: Ensure data_source and data_file can be assigned via the API.
+
+        This specifically covers PATCHing a ConfigContext with integer IDs for both fields.
+        """
+        self.add_permissions(
+            'core.view_datafile',
+            'core.view_datasource',
+            'extras.view_configcontextprofile',
+            'extras.change_configcontextprofile',
+        )
+        config_context_profile = ConfigContextProfile.objects.first()
+
+        # Create a data source and file
+        datasource = DataSource.objects.create(
+            name='Data Source 1',
+            type='local',
+            source_url='file:///tmp/netbox-datasource/',
+        )
+        # Generate a valid dummy YAML file
+        file_data = b'profile: configcontext\n'
+        datafile = DataFile.objects.create(
+            source=datasource,
+            path='dir1/file1.yml',
+            last_updated=now(),
+            size=len(file_data),
+            hash=hashlib.sha256(file_data).hexdigest(),
+            data=file_data,
+        )
+
+        url = self._get_detail_url(config_context_profile)
+        payload = {
+            'data_source': datasource.pk,
+            'data_file': datafile.pk,
+        }
+        response = self.client.patch(url, payload, format='json', **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+
+        config_context_profile.refresh_from_db()
+        self.assertEqual(config_context_profile.data_source_id, datasource.pk)
+        self.assertEqual(config_context_profile.data_file_id, datafile.pk)
+        self.assertEqual(response.data['data_source']['id'], datasource.pk)
+        self.assertEqual(response.data['data_file']['id'], datafile.pk)
+
 
 class ConfigContextTest(APIViewTestCases.APIViewTestCase):
     model = ConfigContext
@@ -812,6 +858,51 @@ class ConfigContextTest(APIViewTestCases.APIViewTestCase):
         rendered_context = device.get_config_context()
         self.assertEqual(rendered_context['bar'], 456)
 
+    def test_update_data_source_and_data_file(self):
+        """
+        Regression test: Ensure data_source and data_file can be assigned via the API.
+
+        This specifically covers PATCHing a ConfigContext with integer IDs for both fields.
+        """
+        self.add_permissions(
+            'core.view_datafile',
+            'core.view_datasource',
+            'extras.view_configcontext',
+            'extras.change_configcontext',
+        )
+        config_context = ConfigContext.objects.first()
+
+        # Create a data source and file
+        datasource = DataSource.objects.create(
+            name='Data Source 1',
+            type='local',
+            source_url='file:///tmp/netbox-datasource/',
+        )
+        # Generate a valid dummy YAML file
+        file_data = b'context: config\n'
+        datafile = DataFile.objects.create(
+            source=datasource,
+            path='dir1/file1.yml',
+            last_updated=now(),
+            size=len(file_data),
+            hash=hashlib.sha256(file_data).hexdigest(),
+            data=file_data,
+        )
+
+        url = self._get_detail_url(config_context)
+        payload = {
+            'data_source': datasource.pk,
+            'data_file': datafile.pk,
+        }
+        response = self.client.patch(url, payload, format='json', **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+
+        config_context.refresh_from_db()
+        self.assertEqual(config_context.data_source_id, datasource.pk)
+        self.assertEqual(config_context.data_file_id, datafile.pk)
+        self.assertEqual(response.data['data_source']['id'], datasource.pk)
+        self.assertEqual(response.data['data_file']['id'], datafile.pk)
+
 
 class ConfigTemplateTest(APIViewTestCases.APIViewTestCase):
     model = ConfigTemplate

+ 51 - 1
netbox/extras/tests/test_models.py

@@ -6,7 +6,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
 from django.forms import ValidationError
 from django.test import tag, TestCase
 
-from core.models import DataSource, ObjectType
+from core.models import AutoSyncRecord, DataSource, ObjectType
 from dcim.models import Device, DeviceRole, DeviceType, Location, Manufacturer, Platform, Region, Site, SiteGroup
 from extras.models import ConfigContext, ConfigContextProfile, ConfigTemplate, ImageAttachment, Tag, TaggedItem
 from tenancy.models import Tenant, TenantGroup
@@ -754,3 +754,53 @@ class ConfigTemplateTest(TestCase):
     @tag('regression')
     def test_config_template_with_data_source_nested_templates(self):
         self.assertEqual(self.BASE_TEMPLATE, self.main_config_template.render({}))
+
+    @tag('regression')
+    def test_autosyncrecord_cleanup_on_detach(self):
+        """Test that AutoSyncRecord is deleted when detaching from DataSource."""
+        with tempfile.TemporaryDirectory() as temp_dir:
+            templates_dir = Path(temp_dir) / "templates"
+            templates_dir.mkdir(parents=True, exist_ok=True)
+
+            self._create_template_file(templates_dir, 'test.j2', 'Test content')
+
+            data_source = DataSource(
+                name="Test DataSource for Detach",
+                type="local",
+                source_url=str(templates_dir),
+            )
+            data_source.save()
+            data_source.sync()
+
+            data_file = data_source.datafiles.filter(path__endswith='test.j2').first()
+
+            # Create a ConfigTemplate with data_file and auto_sync_enabled
+            config_template = ConfigTemplate(
+                name="TestTemplateForDetach",
+                data_file=data_file,
+                auto_sync_enabled=True
+            )
+            config_template.clean()
+            config_template.save()
+
+            # Verify AutoSyncRecord was created
+            object_type = ObjectType.objects.get_for_model(ConfigTemplate)
+            autosync_records = AutoSyncRecord.objects.filter(
+                object_type=object_type,
+                object_id=config_template.pk
+            )
+            self.assertEqual(autosync_records.count(), 1, "AutoSyncRecord should be created")
+
+            # Detach from DataSource
+            config_template.data_file = None
+            config_template.data_source = None
+            config_template.auto_sync_enabled = False
+            config_template.clean()
+            config_template.save()
+
+            # Verify AutoSyncRecord was deleted
+            autosync_records = AutoSyncRecord.objects.filter(
+                object_type=object_type,
+                object_id=config_template.pk
+            )
+            self.assertEqual(autosync_records.count(), 0, "AutoSyncRecord should be deleted after detaching")

+ 2 - 1
netbox/extras/utils.py

@@ -10,6 +10,7 @@ from taggit.managers import _TaggableManager
 
 from netbox.context import current_request
 
+from .constants import IMAGE_ATTACHMENT_IMAGE_FORMATS
 from .validators import CustomValidator
 
 __all__ = (
@@ -78,7 +79,7 @@ def image_upload(instance, filename):
     """
     upload_dir = 'image-attachments'
     default_filename = 'unnamed'
-    allowed_img_extensions = ('bmp', 'gif', 'jpeg', 'jpg', 'png', 'webp')
+    allowed_img_extensions = IMAGE_ATTACHMENT_IMAGE_FORMATS.keys()
 
     # Normalize Windows paths and create a Path object.
     normalized_filename = str(filename).replace('\\', '/')

+ 38 - 0
netbox/ipam/api/serializers_/ip.py

@@ -19,6 +19,7 @@ from ..field_serializers import IPAddressField, IPNetworkField
 __all__ = (
     'AggregateSerializer',
     'AvailableIPSerializer',
+    'AvailableIPRequestSerializer',
     'AvailablePrefixSerializer',
     'IPAddressSerializer',
     'IPRangeSerializer',
@@ -147,6 +148,43 @@ class IPRangeSerializer(PrimaryModelSerializer):
 # IP addresses
 #
 
+class AvailableIPRequestSerializer(serializers.Serializer):
+    """
+    Request payload for creating IP addresses from the available-ips endpoint.
+    """
+    prefix_length = serializers.IntegerField(required=False)
+
+    def to_internal_value(self, data):
+        data = super().to_internal_value(data)
+
+        prefix_length = data.get('prefix_length')
+        if prefix_length is None:
+            # No override requested; the parent prefix/range mask length will be used.
+            return data
+
+        parent = self.context.get('parent')
+        if parent is None:
+            return data
+
+        # Validate the requested prefix length
+        if prefix_length < parent.mask_length:
+            raise serializers.ValidationError({
+                'prefix_length': 'Prefix length must be greater than or equal to the parent mask length ({})'.format(
+                    parent.mask_length
+                )
+            })
+        elif parent.family == 4 and prefix_length > 32:
+            raise serializers.ValidationError({
+                'prefix_length': 'Invalid prefix length ({}) for IPv6'.format(prefix_length)
+            })
+        elif parent.family == 6 and prefix_length > 128:
+            raise serializers.ValidationError({
+                'prefix_length': 'Invalid prefix length ({}) for IPv4'.format(prefix_length)
+            })
+
+        return data
+
+
 class IPAddressSerializer(PrimaryModelSerializer):
     family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True)
     address = IPAddressField()

+ 4 - 3
netbox/ipam/api/views.py

@@ -400,7 +400,7 @@ class AvailablePrefixesView(AvailableObjectsView):
 class AvailableIPAddressesView(AvailableObjectsView):
     queryset = IPAddress.objects.all()
     read_serializer_class = serializers.AvailableIPSerializer
-    write_serializer_class = serializers.AvailableIPSerializer
+    write_serializer_class = serializers.AvailableIPRequestSerializer
     advisory_lock_key = 'available-ips'
 
     def get_available_objects(self, parent, limit=None):
@@ -421,8 +421,9 @@ class AvailableIPAddressesView(AvailableObjectsView):
     def prep_object_data(self, requested_objects, available_objects, parent):
         available_ips = iter(available_objects)
         for i, request_data in enumerate(requested_objects):
+            prefix_length = request_data.pop('prefix_length', None) or parent.mask_length
             request_data.update({
-                'address': f'{next(available_ips)}/{parent.mask_length}',
+                'address': f'{next(available_ips)}/{prefix_length}',
                 'vrf': parent.vrf.pk if parent.vrf else None,
             })
 
@@ -435,7 +436,7 @@ class AvailableIPAddressesView(AvailableObjectsView):
     @extend_schema(
         methods=["post"],
         responses={201: serializers.IPAddressSerializer(many=True)},
-        request=serializers.IPAddressSerializer(many=True),
+        request=serializers.AvailableIPRequestSerializer(many=True),
     )
     def post(self, request, pk):
         return super().post(request, pk)

+ 1 - 1
netbox/ipam/forms/filtersets.py

@@ -538,7 +538,7 @@ class VLANFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
         FieldSet('qinq_role', 'qinq_svlan_id', name=_('Q-in-Q/802.1ad')),
         FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
     )
-    selector_fields = ('filter_id', 'q', 'site_id')
+    selector_fields = ('filter_id', 'q', 'group_id')
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         required=False,

+ 2 - 2
netbox/ipam/forms/model_forms.py

@@ -372,8 +372,8 @@ class IPAddressForm(TenancyForm, PrimaryModelForm):
                     'virtual_machine_id': instance.assigned_object.virtual_machine.pk,
                 })
 
-        # Disable object assignment fields if the IP address is designated as primary
-        if self.initial.get('primary_for_parent'):
+        # Disable object assignment fields if the IP address is designated as primary or OOB
+        if self.initial.get('primary_for_parent') or self.initial.get('oob_for_parent'):
             self.fields['interface'].disabled = True
             self.fields['vminterface'].disabled = True
             self.fields['fhrpgroup'].disabled = True

+ 4 - 4
netbox/ipam/graphql/filters.py

@@ -20,7 +20,7 @@ from tenancy.graphql.filter_mixins import ContactFilterMixin, TenancyFilterMixin
 from virtualization.models import VMInterface
 
 if TYPE_CHECKING:
-    from netbox.graphql.filter_lookups import IntegerLookup, IntegerRangeArrayLookup
+    from netbox.graphql.filter_lookups import BigIntegerLookup, IntegerLookup, IntegerRangeArrayLookup
     from circuits.graphql.filters import ProviderFilter
     from core.graphql.filters import ContentTypeFilter
     from dcim.graphql.filters import SiteFilter
@@ -53,7 +53,7 @@ __all__ = (
 class ASNFilter(TenancyFilterMixin, PrimaryModelFilter):
     rir: Annotated['RIRFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
     rir_id: ID | None = strawberry_django.filter_field()
-    asn: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
+    asn: Annotated['BigIntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
     sites: (
@@ -70,10 +70,10 @@ class ASNRangeFilter(TenancyFilterMixin, OrganizationalModelFilter):
     slug: FilterLookup[str] | None = strawberry_django.filter_field()
     rir: Annotated['RIRFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
     rir_id: ID | None = strawberry_django.filter_field()
-    start: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
+    start: Annotated['BigIntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    end: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
+    end: Annotated['BigIntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
 

+ 7 - 0
netbox/ipam/models/ip.py

@@ -940,6 +940,13 @@ class IPAddress(ContactsMixin, PrimaryModel):
                     _("Cannot reassign IP address while it is designated as the primary IP for the parent object")
                 )
 
+            # can't use is_oob_ip as self.assigned_object might be changed
+            if hasattr(original_parent, 'oob_ip') and original_parent.oob_ip_id == self.pk:
+                if parent != original_parent:
+                    raise ValidationError(
+                        _("Cannot reassign IP address while it is designated as the OOB IP for the parent object")
+                    )
+
         # Validate IP status selection
         if self.status == IPAddressStatusChoices.STATUS_SLAAC and self.family != 6:
             raise ValidationError({

+ 25 - 0
netbox/ipam/tests/test_api.py

@@ -595,6 +595,31 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
         self.assertHttpStatus(response, status.HTTP_201_CREATED)
         self.assertEqual(len(response.data), 8)
 
+    def test_create_available_ip_with_mask(self):
+        """
+        Test the creation of an available IP address with a specific prefix length.
+        """
+        prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'))
+        url = reverse('ipam-api:prefix-available-ips', kwargs={'pk': prefix.pk})
+        self.add_permissions('ipam.view_prefix', 'ipam.add_ipaddress')
+
+        # Create an available IP with a specific prefix length
+        data = {
+            'prefix_length': 32,
+            'description': 'Test IP 1',
+        }
+        response = self.client.post(url, data, format='json', **self.header)
+        self.assertHttpStatus(response, status.HTTP_201_CREATED)
+        self.assertEqual(response.data['address'], '192.0.2.1/32')
+        self.assertEqual(response.data['description'], data['description'])
+
+        # Attempt to create an available IP with a prefix length less than its parent prefix
+        data = {
+            'prefix_length': 23,  # Prefix is a /24
+        }
+        response = self.client.post(url, data, format='json', **self.header)
+        self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
+
     @tag('regression')
     def test_graphql_tenant_prefixes_contains_nested_skips_invalid(self):
         """

+ 26 - 0
netbox/netbox/graphql/filter_lookups.py

@@ -19,8 +19,11 @@ from strawberry_django import (
     process_filters,
 )
 
+from netbox.graphql.scalars import BigInt
+
 __all__ = (
     'ArrayLookup',
+    'BigIntegerLookup',
     'FloatArrayLookup',
     'FloatLookup',
     'IntegerArrayLookup',
@@ -78,6 +81,29 @@ class IntegerLookup:
         return process_filters(filters=filters, queryset=queryset, info=info, prefix=prefix)
 
 
+@strawberry.input(one_of=True, description='Lookup for BigInteger fields. Only one of the lookup fields can be set.')
+class BigIntegerLookup:
+    filter_lookup: FilterLookup[BigInt] | None = strawberry_django.filter_field()
+    range_lookup: RangeLookup[BigInt] | None = strawberry_django.filter_field()
+    comparison_lookup: ComparisonFilterLookup[BigInt] | None = strawberry_django.filter_field()
+
+    def get_filter(self):
+        for field in self.__strawberry_definition__.fields:
+            value = getattr(self, field.name, None)
+            if value is not strawberry.UNSET:
+                return value
+        return None
+
+    @strawberry_django.filter_field
+    def filter(self, info: Info, queryset: QuerySet, prefix: DirectiveValue[str] = '') -> Tuple[QuerySet, Q]:
+        filters = self.get_filter()
+
+        if not filters:
+            return queryset, Q()
+
+        return process_filters(filters=filters, queryset=queryset, info=info, prefix=prefix)
+
+
 @strawberry.input(one_of=True, description='Lookup for Float fields. Only one of the lookup fields can be set.')
 class FloatLookup:
     filter_lookup: FilterLookup[float] | None = strawberry_django.filter_field()

+ 2 - 2
netbox/netbox/graphql/filters.py

@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
 
 import strawberry_django
 from strawberry import ID
-from strawberry_django import FilterLookup
+from strawberry_django import ComparisonFilterLookup, FilterLookup
 
 from core.graphql.filter_mixins import ChangeLoggingMixin
 from extras.graphql.filter_mixins import CustomFieldsFilterMixin, JournalEntriesFilterMixin, TagsFilterMixin
@@ -23,7 +23,7 @@ __all__ = (
 
 @dataclass
 class BaseModelFilter:
-    id: FilterLookup[ID] | None = strawberry_django.filter_field()
+    id: ComparisonFilterLookup[ID] | None = strawberry_django.filter_field()
 
 
 class ChangeLoggedModelFilter(ChangeLoggingMixin, BaseModelFilter):

+ 0 - 2
netbox/netbox/models/features.py

@@ -569,7 +569,6 @@ class SyncedDataMixin(models.Model):
             )
         else:
             AutoSyncRecord.objects.filter(
-                datafile=self.data_file,
                 object_type=object_type,
                 object_id=self.pk
             ).delete()
@@ -582,7 +581,6 @@ class SyncedDataMixin(models.Model):
         # Delete AutoSyncRecord
         object_type = ObjectType.objects.get_for_model(self)
         AutoSyncRecord.objects.filter(
-            datafile=self.data_file,
             object_type=object_type,
             object_id=self.pk
         ).delete()

+ 1 - 1
netbox/netbox/ui/panels.py

@@ -164,7 +164,7 @@ class ObjectAttributesPanel(ObjectPanel, metaclass=ObjectAttributesPanelMeta):
         """
         label = name[:1].upper() + name[1:]
         label = label.replace('_', ' ')
-        return label
+        return _(label)
 
     def get_context(self, context):
         # Determine which attributes to display in the panel based on only/exclude args

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
netbox/project-static/dist/netbox.css


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
netbox/project-static/dist/netbox.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
netbox/project-static/dist/netbox.js.map


+ 1 - 1
netbox/project-static/dist/rack_elevation.css

@@ -1 +1 @@
-svg{--nbx-rack-bg: var(--tblr-bg-surface-secondary);--nbx-rack-border: #000;--nbx-rack-slot-bg: #e9ecef;--nbx-rack-slot-border: #adb5bd;--nbx-rack-slot-hover-bg: #ced4da;--nbx-rack-link-color: #0d6efd;--nbx-rack-unit-color: #6c757d}svg[data-bs-theme=dark]{--nbx-rack-bg: rgb(27, 41, 58);--nbx-rack-border: #6c757d;--nbx-rack-slot-bg: #343a40;--nbx-rack-slot-border: #495057;--nbx-rack-slot-hover-bg: #212529;--nbx-rack-link-color: #9ec5fe;--nbx-rack-unit-color: #adb5bd}rect{box-sizing:border-box}text{text-anchor:middle;dominant-baseline:middle}svg{background-color:var(--nbx-rack-bg);font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica Neue,Noto Sans,Liberation Sans,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-size:.875rem}svg .unit{margin:0;padding:5px 0;fill:var(--nbx-rack-unit-color)}svg .hidden{visibility:hidden}svg rect.shaded,svg image.shaded{opacity:25%}svg text.shaded{opacity:50%}svg .rack{fill:none;stroke-width:2px;stroke:var(--nbx-rack-border)}svg .slot{fill:var(--nbx-rack-slot-bg);stroke:var(--nbx-rack-slot-border)}svg .slot:hover{fill:var(--nbx-rack-slot-hover-bg)}svg .slot+.add-device{fill:var(--nbx-rack-link-color);opacity:0;pointer-events:none}svg .slot:hover+.add-device{opacity:1}svg .slot.occupied[class],svg .slot.occupied:hover[class]{fill:url(#occupied)}svg .slot.blocked[class],svg .slot.blocked:hover[class]{fill:url(#blocked)}svg .slot.blocked:hover+.add-device{opacity:0}svg .reservation[class]{fill:url(#reserved)}
+svg{--nbx-rack-bg: var(--tblr-bg-surface-secondary);--nbx-rack-border: #000;--nbx-rack-slot-bg: #e9ecef;--nbx-rack-slot-border: #adb5bd;--nbx-rack-slot-hover-bg: #ced4da;--nbx-rack-link-color: #0d6efd;--nbx-rack-unit-color: #6c757d}svg[data-bs-theme=dark]{--nbx-rack-bg: rgb(27, 41, 58);--nbx-rack-border: #6c757d;--nbx-rack-slot-bg: #343a40;--nbx-rack-slot-border: #495057;--nbx-rack-slot-hover-bg: #212529;--nbx-rack-link-color: rgb(158.2, 197, 254.2);--nbx-rack-unit-color: #adb5bd}rect{box-sizing:border-box}text{text-anchor:middle;dominant-baseline:middle}svg{background-color:var(--nbx-rack-bg);font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica Neue,Noto Sans,Liberation Sans,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-size:.875rem}svg .unit{margin:0;padding:5px 0;fill:var(--nbx-rack-unit-color)}svg .hidden{visibility:hidden}svg rect.shaded,svg image.shaded{opacity:25%}svg text.shaded{opacity:50%}svg .rack{fill:none;stroke-width:2px;stroke:var(--nbx-rack-border)}svg .slot{fill:var(--nbx-rack-slot-bg);stroke:var(--nbx-rack-slot-border)}svg .slot:hover{fill:var(--nbx-rack-slot-hover-bg)}svg .slot+.add-device{fill:var(--nbx-rack-link-color);opacity:0;pointer-events:none}svg .slot:hover+.add-device{opacity:1}svg .slot.occupied[class],svg .slot.occupied:hover[class]{fill:url(#occupied)}svg .slot.blocked[class],svg .slot.blocked:hover[class]{fill:url(#blocked)}svg .slot.blocked:hover+.add-device{opacity:0}svg .reservation[class]{fill:url(#reserved)}

+ 8 - 8
netbox/project-static/package.json

@@ -37,23 +37,23 @@
     "typeface-roboto-mono": "1.1.13"
   },
   "devDependencies": {
-    "@eslint/compat": "^2.0.0",
+    "@eslint/compat": "^2.0.1",
     "@eslint/eslintrc": "^3.3.3",
     "@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.48.1",
-    "@typescript-eslint/parser": "^8.48.1",
-    "esbuild": "^0.27.1",
-    "esbuild-sass-plugin": "^3.3.1",
+    "@typescript-eslint/eslint-plugin": "^8.53.1",
+    "@typescript-eslint/parser": "^8.53.1",
+    "esbuild": "^0.27.2",
+    "esbuild-sass-plugin": "^3.6.0",
     "eslint": "^9.39.2",
     "eslint-config-prettier": "^10.1.8",
     "eslint-import-resolver-typescript": "^4.4.4",
     "eslint-plugin-import": "^2.32.0",
-    "eslint-plugin-prettier": "^5.5.1",
-    "globals": "^16.5.0",
-    "prettier": "^3.7.4",
+    "eslint-plugin-prettier": "^5.5.5",
+    "globals": "^17.0.0",
+    "prettier": "^3.8.0",
     "typescript": "^5.9.3"
   },
   "resolutions": {

+ 40 - 0
netbox/project-static/src/forms/clearField.ts

@@ -0,0 +1,40 @@
+import TomSelect from 'tom-select';
+import { getElements } from '../util';
+
+/**
+ * Initialize clear-field dependencies.
+ * When a required field is cleared, dependent fields with data-requires-fields attribute will also be cleared.
+ */
+export function initClearField(): void {
+  // Find all fields with data-requires-fields attribute
+  for (const field of getElements<HTMLSelectElement>('[data-requires-fields]')) {
+    const requiredFieldsAttr = field.getAttribute('data-requires-fields');
+    if (!requiredFieldsAttr) continue;
+
+    // Parse the comma-separated list of required field names
+    const requiredFields = requiredFieldsAttr.split(',').map(name => name.trim());
+
+    // Set up listeners for each required field
+    for (const requiredFieldName of requiredFields) {
+      const requiredField = document.querySelector<HTMLSelectElement>(
+        `[name="${requiredFieldName}"]`,
+      );
+      if (!requiredField) continue;
+
+      // Listen for changes on the required field
+      requiredField.addEventListener('change', () => {
+        // If required field is cleared, also clear this dependent field
+        if (!requiredField.value || requiredField.value === '') {
+          // Check if this field uses TomSelect
+          const tomselect = (field as HTMLSelectElement & { tomselect?: TomSelect }).tomselect;
+          if (tomselect) {
+            tomselect.clear();
+          } else {
+            // Regular select field
+            field.value = '';
+          }
+        }
+      });
+    }
+  }
+}

+ 2 - 1
netbox/project-static/src/forms/index.ts

@@ -1,9 +1,10 @@
+import { initClearField } from './clearField';
 import { initFormElements } from './elements';
 import { initFilterModifiers } from './filterModifiers';
 import { initSpeedSelector } from './speedSelector';
 
 export function initForms(): void {
-  for (const func of [initFormElements, initSpeedSelector, initFilterModifiers]) {
+  for (const func of [initFormElements, initSpeedSelector, initFilterModifiers, initClearField]) {
     func();
   }
 }

+ 314 - 407
netbox/project-static/yarn.lock

@@ -24,142 +24,135 @@
   dependencies:
     tslib "^2.4.0"
 
-"@esbuild/aix-ppc64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz#116edcd62c639ed8ab551e57b38251bb28384de4"
-  integrity sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==
-
-"@esbuild/android-arm64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz#31c00d864c80f6de1900a11de8a506dbfbb27349"
-  integrity sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==
-
-"@esbuild/android-arm@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.1.tgz#d2b73ab0ba894923a1d1378fd4b15cc20985f436"
-  integrity sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==
-
-"@esbuild/android-x64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.1.tgz#d9f74d8278191317250cfe0c15a13f410540b122"
-  integrity sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==
-
-"@esbuild/darwin-arm64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz#baf6914b8c57ed9d41f9de54023aa3ff9b084680"
-  integrity sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==
-
-"@esbuild/darwin-x64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz#64e37400795f780a76c858a118ff19681a64b4e0"
-  integrity sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==
-
-"@esbuild/freebsd-arm64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz#6572f2f235933eee906e070dfaae54488ee60acd"
-  integrity sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==
-
-"@esbuild/freebsd-x64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz#83105dba9cf6ac4f44336799446d7f75c8c3a1e1"
-  integrity sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==
-
-"@esbuild/linux-arm64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz#035ff647d4498bdf16eb2d82801f73b366477dfa"
-  integrity sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==
-
-"@esbuild/linux-arm@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz#3516c74d2afbe305582dbb546d60f7978a8ece7f"
-  integrity sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==
-
-"@esbuild/linux-ia32@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz#788db5db8ecd3d75dd41c42de0fe8f1fd967a4a7"
-  integrity sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==
-
-"@esbuild/linux-loong64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz#8211f08b146916a6302ec2b8f87ec0cc4b62c49e"
-  integrity sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==
-
-"@esbuild/linux-mips64el@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz#cc58586ea83b3f171e727a624e7883a1c3eb4c04"
-  integrity sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==
-
-"@esbuild/linux-ppc64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz#632477bbd98175cf8e53a7c9952d17fb2d6d4115"
-  integrity sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==
-
-"@esbuild/linux-riscv64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz#35435a82435a8a750edf433b83ac0d10239ac3fe"
-  integrity sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==
-
-"@esbuild/linux-s390x@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz#172edd7086438edacd86c0e2ea25ac9dbb62aac5"
-  integrity sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==
-
-"@esbuild/linux-x64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz#09c771de9e2d8169d5969adf298ae21581f08c7f"
-  integrity sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==
-
-"@esbuild/netbsd-arm64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz#475ac0ce7edf109a358b1669f67759de4bcbb7c4"
-  integrity sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==
-
-"@esbuild/netbsd-x64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz#3c31603d592477dc43b63df1ae100000f7fb59d7"
-  integrity sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==
-
-"@esbuild/openbsd-arm64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz#482067c847665b10d66431e936d4bc5fa8025abf"
-  integrity sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==
-
-"@esbuild/openbsd-x64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz#687a188c2b184e5b671c5f74a6cd6247c0718c52"
-  integrity sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==
-
-"@esbuild/openharmony-arm64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz#9929ee7fa8c1db2f33ef4d86198018dac9c1744f"
-  integrity sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==
-
-"@esbuild/sunos-x64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz#94071a146f313e7394c6424af07b2b564f1f994d"
-  integrity sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==
-
-"@esbuild/win32-arm64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz#869fde72a3576fdf48824085d05493fceebe395d"
-  integrity sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==
-
-"@esbuild/win32-ia32@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz#31d7585893ed7b54483d0b8d87a4bfeba0ecfff5"
-  integrity sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==
-
-"@esbuild/win32-x64@0.27.1":
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz#5efe5a112938b1180e98c76685ff9185cfa4f16e"
-  integrity sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==
-
-"@eslint-community/eslint-utils@^4.7.0":
-  version "4.7.0"
-  resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz"
-  integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==
-  dependencies:
-    eslint-visitor-keys "^3.4.3"
+"@esbuild/aix-ppc64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz#521cbd968dcf362094034947f76fa1b18d2d403c"
+  integrity sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==
+
+"@esbuild/android-arm64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz#61ea550962d8aa12a9b33194394e007657a6df57"
+  integrity sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==
+
+"@esbuild/android-arm@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.2.tgz#554887821e009dd6d853f972fde6c5143f1de142"
+  integrity sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==
+
+"@esbuild/android-x64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.2.tgz#a7ce9d0721825fc578f9292a76d9e53334480ba2"
+  integrity sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==
+
+"@esbuild/darwin-arm64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz#2cb7659bd5d109803c593cfc414450d5430c8256"
+  integrity sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==
+
+"@esbuild/darwin-x64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz#e741fa6b1abb0cd0364126ba34ca17fd5e7bf509"
+  integrity sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==
+
+"@esbuild/freebsd-arm64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz#2b64e7116865ca172d4ce034114c21f3c93e397c"
+  integrity sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==
+
+"@esbuild/freebsd-x64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz#e5252551e66f499e4934efb611812f3820e990bb"
+  integrity sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==
+
+"@esbuild/linux-arm64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz#dc4acf235531cd6984f5d6c3b13dbfb7ddb303cb"
+  integrity sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==
+
+"@esbuild/linux-arm@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz#56a900e39240d7d5d1d273bc053daa295c92e322"
+  integrity sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==
+
+"@esbuild/linux-ia32@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz#d4a36d473360f6870efcd19d52bbfff59a2ed1cc"
+  integrity sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==
+
+"@esbuild/linux-loong64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz#fcf0ab8c3eaaf45891d0195d4961cb18b579716a"
+  integrity sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==
+
+"@esbuild/linux-mips64el@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz#598b67d34048bb7ee1901cb12e2a0a434c381c10"
+  integrity sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==
+
+"@esbuild/linux-ppc64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz#3846c5df6b2016dab9bc95dde26c40f11e43b4c0"
+  integrity sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==
+
+"@esbuild/linux-riscv64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz#173d4475b37c8d2c3e1707e068c174bb3f53d07d"
+  integrity sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==
+
+"@esbuild/linux-s390x@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz#f7a4790105edcab8a5a31df26fbfac1aa3dacfab"
+  integrity sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==
+
+"@esbuild/linux-x64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz#2ecc1284b1904aeb41e54c9ddc7fcd349b18f650"
+  integrity sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==
+
+"@esbuild/netbsd-arm64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz#e2863c2cd1501845995cb11adf26f7fe4be527b0"
+  integrity sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==
+
+"@esbuild/netbsd-x64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz#93f7609e2885d1c0b5a1417885fba8d1fcc41272"
+  integrity sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==
+
+"@esbuild/openbsd-arm64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz#a1985604a203cdc325fd47542e106fafd698f02e"
+  integrity sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==
+
+"@esbuild/openbsd-x64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz#8209e46c42f1ffbe6e4ef77a32e1f47d404ad42a"
+  integrity sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==
+
+"@esbuild/openharmony-arm64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz#8fade4441893d9cc44cbd7dcf3776f508ab6fb2f"
+  integrity sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==
+
+"@esbuild/sunos-x64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz#980d4b9703a16f0f07016632424fc6d9a789dfc2"
+  integrity sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==
+
+"@esbuild/win32-arm64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz#1c09a3633c949ead3d808ba37276883e71f6111a"
+  integrity sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==
+
+"@esbuild/win32-ia32@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz#1b1e3a63ad4bef82200fef4e369e0fff7009eee5"
+  integrity sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==
+
+"@esbuild/win32-x64@0.27.2":
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz#9e585ab6086bef994c6e8a5b3a0481219ada862b"
+  integrity sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==
 
 "@eslint-community/eslint-utils@^4.8.0":
   version "4.9.0"
@@ -168,22 +161,24 @@
   dependencies:
     eslint-visitor-keys "^3.4.3"
 
-"@eslint-community/regexpp@^4.10.0":
-  version "4.12.1"
-  resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz"
-  integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==
+"@eslint-community/eslint-utils@^4.9.1":
+  version "4.9.1"
+  resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595"
+  integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==
+  dependencies:
+    eslint-visitor-keys "^3.4.3"
 
-"@eslint-community/regexpp@^4.12.1":
+"@eslint-community/regexpp@^4.12.1", "@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.0":
-  version "2.0.0"
-  resolved "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.0.tgz"
-  integrity sha512-T9AfE1G1uv4wwq94ozgTGio5EUQBqAVe1X9qsQtSNVEYW6j3hvtZVm8Smr4qL1qDPFg+lOB2cL5RxTRMzq4CTA==
+"@eslint/compat@^2.0.1":
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/@eslint/compat/-/compat-2.0.1.tgz#5894516f8ce9ba884f4d4ba5ecb6b6459b231144"
+  integrity sha512-yl/JsgplclzuvGFNqwNYV4XNPhP3l62ZOP9w/47atNAdmDtIFCx6X7CSk/SlWUuBGkT4Et/5+UD+WyvX2iiIWA==
   dependencies:
-    "@eslint/core" "^1.0.0"
+    "@eslint/core" "^1.0.1"
 
 "@eslint/config-array@^0.21.1":
   version "0.21.1"
@@ -208,10 +203,10 @@
   dependencies:
     "@types/json-schema" "^7.0.15"
 
-"@eslint/core@^1.0.0":
-  version "1.0.0"
-  resolved "https://registry.npmjs.org/@eslint/core/-/core-1.0.0.tgz"
-  integrity sha512-PRfWP+8FOldvbApr6xL7mNCw4cJcSTq4GA7tYbgq15mRb0kWKO/wEB2jr+uwjFH3sZvEZneZyCUGTxsv4Sahyw==
+"@eslint/core@^1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@eslint/core/-/core-1.0.1.tgz#701ff760cbd279f9490bef0ce54095f4088d4def"
+  integrity sha512-r18fEAj9uCk+VjzGt2thsbOmychS+4kxI14spVNibUO2vqKX7obOG+ymZljAwuPZl+S3clPGwCwTDtrdqTiY6Q==
   dependencies:
     "@types/json-schema" "^7.0.15"
 
@@ -940,101 +935,100 @@
   dependencies:
     "@types/estree" "*"
 
-"@typescript-eslint/eslint-plugin@^8.48.1":
-  version "8.48.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.1.tgz#c772d1dbdd97cfddf85f5a161a97783233643631"
-  integrity sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==
-  dependencies:
-    "@eslint-community/regexpp" "^4.10.0"
-    "@typescript-eslint/scope-manager" "8.48.1"
-    "@typescript-eslint/type-utils" "8.48.1"
-    "@typescript-eslint/utils" "8.48.1"
-    "@typescript-eslint/visitor-keys" "8.48.1"
-    graphemer "^1.4.0"
-    ignore "^7.0.0"
+"@typescript-eslint/eslint-plugin@^8.53.1":
+  version "8.53.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz#f6640f6f8749b71d9ab457263939e8932a3c6b46"
+  integrity sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==
+  dependencies:
+    "@eslint-community/regexpp" "^4.12.2"
+    "@typescript-eslint/scope-manager" "8.53.1"
+    "@typescript-eslint/type-utils" "8.53.1"
+    "@typescript-eslint/utils" "8.53.1"
+    "@typescript-eslint/visitor-keys" "8.53.1"
+    ignore "^7.0.5"
     natural-compare "^1.4.0"
-    ts-api-utils "^2.1.0"
-
-"@typescript-eslint/parser@^8.48.1":
-  version "8.48.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.48.1.tgz#4e3c66d9ec20683ec142417fafeadab61c479c3f"
-  integrity sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==
-  dependencies:
-    "@typescript-eslint/scope-manager" "8.48.1"
-    "@typescript-eslint/types" "8.48.1"
-    "@typescript-eslint/typescript-estree" "8.48.1"
-    "@typescript-eslint/visitor-keys" "8.48.1"
-    debug "^4.3.4"
-
-"@typescript-eslint/project-service@8.48.1":
-  version "8.48.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.48.1.tgz#cfe1741613b9112d85ae766de9e09b27a7d3f2f1"
-  integrity sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==
-  dependencies:
-    "@typescript-eslint/tsconfig-utils" "^8.48.1"
-    "@typescript-eslint/types" "^8.48.1"
-    debug "^4.3.4"
-
-"@typescript-eslint/scope-manager@8.48.1":
-  version "8.48.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.48.1.tgz#8bc70643e7cca57864b1ff95dd350fc27756bec0"
-  integrity sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==
-  dependencies:
-    "@typescript-eslint/types" "8.48.1"
-    "@typescript-eslint/visitor-keys" "8.48.1"
-
-"@typescript-eslint/tsconfig-utils@8.48.1", "@typescript-eslint/tsconfig-utils@^8.48.1":
-  version "8.48.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.1.tgz#68139ce2d258f984e2b33a95389158f1212af646"
-  integrity sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==
-
-"@typescript-eslint/type-utils@8.48.1":
-  version "8.48.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.48.1.tgz#955bd3ddd648450f0a627925ff12ade63fb7516d"
-  integrity sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==
-  dependencies:
-    "@typescript-eslint/types" "8.48.1"
-    "@typescript-eslint/typescript-estree" "8.48.1"
-    "@typescript-eslint/utils" "8.48.1"
-    debug "^4.3.4"
-    ts-api-utils "^2.1.0"
-
-"@typescript-eslint/types@8.48.1", "@typescript-eslint/types@^8.48.1":
-  version "8.48.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.48.1.tgz#a9ff808f5f798f28767d5c0b015a88fa7ce46bd7"
-  integrity sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==
-
-"@typescript-eslint/typescript-estree@8.48.1":
-  version "8.48.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.1.tgz#0d0e31fc47c5796c6463ab50cde19e1718d465b1"
-  integrity sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==
-  dependencies:
-    "@typescript-eslint/project-service" "8.48.1"
-    "@typescript-eslint/tsconfig-utils" "8.48.1"
-    "@typescript-eslint/types" "8.48.1"
-    "@typescript-eslint/visitor-keys" "8.48.1"
-    debug "^4.3.4"
-    minimatch "^9.0.4"
-    semver "^7.6.0"
+    ts-api-utils "^2.4.0"
+
+"@typescript-eslint/parser@^8.53.1":
+  version "8.53.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.53.1.tgz#58d4a70cc2daee2becf7d4521d65ea1782d6ec68"
+  integrity sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==
+  dependencies:
+    "@typescript-eslint/scope-manager" "8.53.1"
+    "@typescript-eslint/types" "8.53.1"
+    "@typescript-eslint/typescript-estree" "8.53.1"
+    "@typescript-eslint/visitor-keys" "8.53.1"
+    debug "^4.4.3"
+
+"@typescript-eslint/project-service@8.53.1":
+  version "8.53.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.53.1.tgz#4e47856a0b14a1ceb28b0294b4badef3be1e9734"
+  integrity sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==
+  dependencies:
+    "@typescript-eslint/tsconfig-utils" "^8.53.1"
+    "@typescript-eslint/types" "^8.53.1"
+    debug "^4.4.3"
+
+"@typescript-eslint/scope-manager@8.53.1":
+  version "8.53.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz#6c4b8c82cd45ae3b365afc2373636e166743a8fa"
+  integrity sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==
+  dependencies:
+    "@typescript-eslint/types" "8.53.1"
+    "@typescript-eslint/visitor-keys" "8.53.1"
+
+"@typescript-eslint/tsconfig-utils@8.53.1", "@typescript-eslint/tsconfig-utils@^8.53.1":
+  version "8.53.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz#efe80b8d019cd49e5a1cf46c2eb0cd2733076424"
+  integrity sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==
+
+"@typescript-eslint/type-utils@8.53.1":
+  version "8.53.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.53.1.tgz#95de2651a96d580bf5c6c6089ddd694284d558ad"
+  integrity sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==
+  dependencies:
+    "@typescript-eslint/types" "8.53.1"
+    "@typescript-eslint/typescript-estree" "8.53.1"
+    "@typescript-eslint/utils" "8.53.1"
+    debug "^4.4.3"
+    ts-api-utils "^2.4.0"
+
+"@typescript-eslint/types@8.53.1", "@typescript-eslint/types@^8.53.1":
+  version "8.53.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.53.1.tgz#101f203f0807a63216cceceedb815fabe21d5793"
+  integrity sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==
+
+"@typescript-eslint/typescript-estree@8.53.1":
+  version "8.53.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz#b6dce2303c9e27e95b8dcd8c325868fff53e488f"
+  integrity sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==
+  dependencies:
+    "@typescript-eslint/project-service" "8.53.1"
+    "@typescript-eslint/tsconfig-utils" "8.53.1"
+    "@typescript-eslint/types" "8.53.1"
+    "@typescript-eslint/visitor-keys" "8.53.1"
+    debug "^4.4.3"
+    minimatch "^9.0.5"
+    semver "^7.7.3"
     tinyglobby "^0.2.15"
-    ts-api-utils "^2.1.0"
+    ts-api-utils "^2.4.0"
 
-"@typescript-eslint/utils@8.48.1":
-  version "8.48.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.48.1.tgz#6cf7b99e0943b33a983ef687b9a86b65578b5c32"
-  integrity sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==
+"@typescript-eslint/utils@8.53.1":
+  version "8.53.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.53.1.tgz#81fe6c343de288701b774f4d078382f567e6edaa"
+  integrity sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==
   dependencies:
-    "@eslint-community/eslint-utils" "^4.7.0"
-    "@typescript-eslint/scope-manager" "8.48.1"
-    "@typescript-eslint/types" "8.48.1"
-    "@typescript-eslint/typescript-estree" "8.48.1"
+    "@eslint-community/eslint-utils" "^4.9.1"
+    "@typescript-eslint/scope-manager" "8.53.1"
+    "@typescript-eslint/types" "8.53.1"
+    "@typescript-eslint/typescript-estree" "8.53.1"
 
-"@typescript-eslint/visitor-keys@8.48.1":
-  version "8.48.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.1.tgz#247d4fe6dcc044f45b7f1c15110bf95e5d73b334"
-  integrity sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==
+"@typescript-eslint/visitor-keys@8.53.1":
+  version "8.53.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz#405f04959be22b9be364939af8ac19c3649b6eb7"
+  integrity sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==
   dependencies:
-    "@typescript-eslint/types" "8.48.1"
+    "@typescript-eslint/types" "8.53.1"
     eslint-visitor-keys "^4.2.1"
 
 "@unrs/resolver-binding-android-arm-eabi@1.11.1":
@@ -1161,14 +1155,6 @@ ansi-styles@^4.1.0:
   dependencies:
     color-convert "^2.0.1"
 
-anymatch@~3.1.2:
-  version "3.1.3"
-  resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz"
-  integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
-  dependencies:
-    normalize-path "^3.0.0"
-    picomatch "^2.0.4"
-
 argparse@^2.0.1:
   version "2.0.1"
   resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
@@ -1288,11 +1274,6 @@ balanced-match@^1.0.0:
   resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
   integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
 
-binary-extensions@^2.0.0:
-  version "2.3.0"
-  resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz"
-  integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
-
 bootstrap@5.3.7:
   version "5.3.7"
   resolved "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz"
@@ -1318,7 +1299,7 @@ brace-expansion@^2.0.1:
   dependencies:
     balanced-match "^1.0.0"
 
-braces@^3.0.3, braces@~3.0.2:
+braces@^3.0.3:
   version "3.0.3"
   resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz"
   integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
@@ -1375,21 +1356,6 @@ chalk@^4.0.0:
     ansi-styles "^4.1.0"
     supports-color "^7.1.0"
 
-"chokidar@>=3.0.0 <4.0.0":
-  version "3.6.0"
-  resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz"
-  integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
-  dependencies:
-    anymatch "~3.1.2"
-    braces "~3.0.2"
-    glob-parent "~5.1.2"
-    is-binary-path "~2.1.0"
-    is-glob "~4.0.1"
-    normalize-path "~3.0.0"
-    readdirp "~3.6.0"
-  optionalDependencies:
-    fsevents "~2.3.2"
-
 chokidar@^4.0.0:
   version "4.0.1"
   resolved "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz"
@@ -1533,20 +1499,13 @@ debug@^3.2.7:
   dependencies:
     ms "^2.1.1"
 
-debug@^4.3.1, debug@^4.3.2, debug@^4.4.1:
+debug@^4.3.1, debug@^4.3.2, debug@^4.4.1, debug@^4.4.3:
   version "4.4.3"
   resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz"
   integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
   dependencies:
     ms "^2.1.3"
 
-debug@^4.3.4:
-  version "4.4.1"
-  resolved "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz"
-  integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
-  dependencies:
-    ms "^2.1.3"
-
 decode-uri-component@^0.4.1:
   version "0.4.1"
   resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.4.1.tgz"
@@ -1805,46 +1764,45 @@ es-to-primitive@^1.3.0:
     is-date-object "^1.0.5"
     is-symbol "^1.0.4"
 
-esbuild-sass-plugin@^3.3.1:
-  version "3.3.1"
-  resolved "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-3.3.1.tgz"
-  integrity sha512-SnO1ls+d52n6j8gRRpjexXI8MsHEaumS0IdDHaYM29Y6gakzZYMls6i9ql9+AWMSQk/eryndmUpXEgT34QrX1A==
+esbuild-sass-plugin@^3.6.0:
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/esbuild-sass-plugin/-/esbuild-sass-plugin-3.6.0.tgz#6e93d0aec87b6ab7bde2e459c5f1ab472088bd41"
+  integrity sha512-lzPJQSEXcnj5amBPPib5lBjsDNPzvdMnX+1Rf7eha9BIpLSM5Ad2pi+Rqg5CAlWMduCgLntS2hLAqG7v1fxWGw==
   dependencies:
-    resolve "^1.22.8"
-    safe-identifier "^0.4.2"
-    sass "^1.71.1"
+    resolve "^1.22.11"
+    sass "^1.97.2"
 
-esbuild@^0.27.1:
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.1.tgz#56bf43e6a4b4d2004642ec7c091b78de02b0831a"
-  integrity sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==
+esbuild@^0.27.2:
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.2.tgz#d83ed2154d5813a5367376bb2292a9296fc83717"
+  integrity sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==
   optionalDependencies:
-    "@esbuild/aix-ppc64" "0.27.1"
-    "@esbuild/android-arm" "0.27.1"
-    "@esbuild/android-arm64" "0.27.1"
-    "@esbuild/android-x64" "0.27.1"
-    "@esbuild/darwin-arm64" "0.27.1"
-    "@esbuild/darwin-x64" "0.27.1"
-    "@esbuild/freebsd-arm64" "0.27.1"
-    "@esbuild/freebsd-x64" "0.27.1"
-    "@esbuild/linux-arm" "0.27.1"
-    "@esbuild/linux-arm64" "0.27.1"
-    "@esbuild/linux-ia32" "0.27.1"
-    "@esbuild/linux-loong64" "0.27.1"
-    "@esbuild/linux-mips64el" "0.27.1"
-    "@esbuild/linux-ppc64" "0.27.1"
-    "@esbuild/linux-riscv64" "0.27.1"
-    "@esbuild/linux-s390x" "0.27.1"
-    "@esbuild/linux-x64" "0.27.1"
-    "@esbuild/netbsd-arm64" "0.27.1"
-    "@esbuild/netbsd-x64" "0.27.1"
-    "@esbuild/openbsd-arm64" "0.27.1"
-    "@esbuild/openbsd-x64" "0.27.1"
-    "@esbuild/openharmony-arm64" "0.27.1"
-    "@esbuild/sunos-x64" "0.27.1"
-    "@esbuild/win32-arm64" "0.27.1"
-    "@esbuild/win32-ia32" "0.27.1"
-    "@esbuild/win32-x64" "0.27.1"
+    "@esbuild/aix-ppc64" "0.27.2"
+    "@esbuild/android-arm" "0.27.2"
+    "@esbuild/android-arm64" "0.27.2"
+    "@esbuild/android-x64" "0.27.2"
+    "@esbuild/darwin-arm64" "0.27.2"
+    "@esbuild/darwin-x64" "0.27.2"
+    "@esbuild/freebsd-arm64" "0.27.2"
+    "@esbuild/freebsd-x64" "0.27.2"
+    "@esbuild/linux-arm" "0.27.2"
+    "@esbuild/linux-arm64" "0.27.2"
+    "@esbuild/linux-ia32" "0.27.2"
+    "@esbuild/linux-loong64" "0.27.2"
+    "@esbuild/linux-mips64el" "0.27.2"
+    "@esbuild/linux-ppc64" "0.27.2"
+    "@esbuild/linux-riscv64" "0.27.2"
+    "@esbuild/linux-s390x" "0.27.2"
+    "@esbuild/linux-x64" "0.27.2"
+    "@esbuild/netbsd-arm64" "0.27.2"
+    "@esbuild/netbsd-x64" "0.27.2"
+    "@esbuild/openbsd-arm64" "0.27.2"
+    "@esbuild/openbsd-x64" "0.27.2"
+    "@esbuild/openharmony-arm64" "0.27.2"
+    "@esbuild/sunos-x64" "0.27.2"
+    "@esbuild/win32-arm64" "0.27.2"
+    "@esbuild/win32-ia32" "0.27.2"
+    "@esbuild/win32-x64" "0.27.2"
 
 escape-string-regexp@^4.0.0:
   version "4.0.0"
@@ -1918,13 +1876,13 @@ eslint-plugin-import@^2.32.0:
     string.prototype.trimend "^1.0.9"
     tsconfig-paths "^3.15.0"
 
-eslint-plugin-prettier@^5.5.1:
-  version "5.5.4"
-  resolved "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz"
-  integrity sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==
+eslint-plugin-prettier@^5.5.5:
+  version "5.5.5"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz#9eae11593faa108859c26f9a9c367d619a0769c0"
+  integrity sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==
   dependencies:
-    prettier-linter-helpers "^1.0.0"
-    synckit "^0.11.7"
+    prettier-linter-helpers "^1.0.1"
+    synckit "^0.11.12"
 
 eslint-scope@^8.4.0:
   version "8.4.0"
@@ -2110,11 +2068,6 @@ framer-motion@^12:
     motion-utils "^12.23.6"
     tslib "^2.4.0"
 
-fsevents@~2.3.2:
-  version "2.3.3"
-  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
-  integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
-
 function-bind@^1.1.2:
   version "1.1.2"
   resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
@@ -2226,22 +2179,15 @@ glob-parent@^6.0.2:
   dependencies:
     is-glob "^4.0.3"
 
-glob-parent@~5.1.2:
-  version "5.1.2"
-  resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz"
-  integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
-  dependencies:
-    is-glob "^4.0.1"
-
 globals@^14.0.0:
   version "14.0.0"
   resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz"
   integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==
 
-globals@^16.5.0:
-  version "16.5.0"
-  resolved "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz"
-  integrity sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==
+globals@^17.0.0:
+  version "17.0.0"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-17.0.0.tgz#a4196d9cfeb4d627ba165b4647b1f5853bf90a30"
+  integrity sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw==
 
 globalthis@^1.0.3, globalthis@^1.0.4:
   version "1.0.4"
@@ -2270,11 +2216,6 @@ gopd@^1.2.0:
   resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz"
   integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
 
-graphemer@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz"
-  integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
-
 graphiql-explorer@^0.9.0:
   version "0.9.0"
   resolved "https://registry.npmjs.org/graphiql-explorer/-/graphiql-explorer-0.9.0.tgz"
@@ -2372,16 +2313,11 @@ ignore@^5.2.0:
   resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz"
   integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
 
-ignore@^7.0.0:
+ignore@^7.0.5:
   version "7.0.5"
-  resolved "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9"
   integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==
 
-immutable@^4.0.0:
-  version "4.3.7"
-  resolved "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz"
-  integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==
-
 immutable@^5.0.2:
   version "5.0.3"
   resolved "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz"
@@ -2460,13 +2396,6 @@ is-bigint@^1.1.0:
   dependencies:
     has-bigints "^1.0.2"
 
-is-binary-path@~2.1.0:
-  version "2.1.0"
-  resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz"
-  integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
-  dependencies:
-    binary-extensions "^2.0.0"
-
 is-boolean-object@^1.1.0:
   version "1.1.2"
   resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz"
@@ -2562,7 +2491,7 @@ is-generator-function@^1.0.10:
     has-tostringtag "^1.0.2"
     safe-regex-test "^1.1.0"
 
-is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
+is-glob@^4.0.0, is-glob@^4.0.3:
   version "4.0.3"
   resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz"
   integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
@@ -2857,7 +2786,7 @@ minimatch@^3.1.2:
   dependencies:
     brace-expansion "^1.1.7"
 
-minimatch@^9.0.4:
+minimatch@^9.0.5:
   version "9.0.5"
   resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz"
   integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
@@ -2901,11 +2830,6 @@ node-addon-api@^7.0.0:
   resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz"
   integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
 
-normalize-path@^3.0.0, normalize-path@~3.0.0:
-  version "3.0.0"
-  resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz"
-  integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
-
 nullthrows@^1.0.0:
   version "1.1.1"
   resolved "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz"
@@ -3034,7 +2958,7 @@ 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.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
+picomatch@^2.3.1:
   version "2.3.1"
   resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
   integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
@@ -3054,17 +2978,17 @@ prelude-ls@^1.2.1:
   resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
   integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
 
-prettier-linter-helpers@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz"
-  integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
+prettier-linter-helpers@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz#6a31f88a4bad6c7adda253de12ba4edaea80ebcd"
+  integrity sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==
   dependencies:
     fast-diff "^1.1.2"
 
-prettier@^3.7.4:
-  version "3.7.4"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.7.4.tgz#d2f8335d4b1cec47e1c8098645411b0c9dff9c0f"
-  integrity sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==
+prettier@^3.8.0:
+  version "3.8.0"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.0.tgz#f72cf71505133f40cfa2ef77a2668cdc558fcd69"
+  integrity sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==
 
 punycode.js@^2.3.1:
   version "2.3.1"
@@ -3137,13 +3061,6 @@ readdirp@^4.0.1:
   resolved "https://registry.npmjs.org/readdirp/-/readdirp-4.0.1.tgz"
   integrity sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==
 
-readdirp@~3.6.0:
-  version "3.6.0"
-  resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz"
-  integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
-  dependencies:
-    picomatch "^2.2.1"
-
 reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
   version "1.0.10"
   resolved "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz"
@@ -3190,7 +3107,16 @@ resolve-pkg-maps@^1.0.0:
   resolved "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz"
   integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==
 
-resolve@^1.22.4, resolve@^1.22.8:
+resolve@^1.22.11:
+  version "1.22.11"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262"
+  integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==
+  dependencies:
+    is-core-module "^2.16.1"
+    path-parse "^1.0.7"
+    supports-preserve-symlinks-flag "^1.0.0"
+
+resolve@^1.22.4:
   version "1.22.8"
   resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz"
   integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
@@ -3220,11 +3146,6 @@ safe-array-concat@^1.1.3:
     has-symbols "^1.1.0"
     isarray "^2.0.5"
 
-safe-identifier@^0.4.2:
-  version "0.4.2"
-  resolved "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.2.tgz"
-  integrity sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==
-
 safe-push-apply@^1.0.0:
   version "1.0.0"
   resolved "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz"
@@ -3251,7 +3172,7 @@ safe-regex-test@^1.1.0:
     es-errors "^1.3.0"
     is-regex "^1.2.1"
 
-sass@1.97.2:
+sass@1.97.2, sass@^1.97.2:
   version "1.97.2"
   resolved "https://registry.yarnpkg.com/sass/-/sass-1.97.2.tgz#e515a319092fd2c3b015228e3094b40198bff0da"
   integrity sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw==
@@ -3262,15 +3183,6 @@ sass@1.97.2:
   optionalDependencies:
     "@parcel/watcher" "^2.4.1"
 
-sass@^1.71.1:
-  version "1.77.8"
-  resolved "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz"
-  integrity sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==
-  dependencies:
-    chokidar ">=3.0.0 <4.0.0"
-    immutable "^4.0.0"
-    source-map-js ">=0.6.2 <2.0.0"
-
 scheduler@^0.23.2:
   version "0.23.2"
   resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz"
@@ -3288,12 +3200,7 @@ semver@^6.3.1:
   resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
   integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
 
-semver@^7.6.0:
-  version "7.7.2"
-  resolved "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz"
-  integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
-
-semver@^7.7.1:
+semver@^7.7.1, semver@^7.7.3:
   version "7.7.3"
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946"
   integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==
@@ -3495,10 +3402,10 @@ supports-preserve-symlinks-flag@^1.0.0:
   resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
   integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
 
-synckit@^0.11.7:
-  version "0.11.11"
-  resolved "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz"
-  integrity sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==
+synckit@^0.11.12:
+  version "0.11.12"
+  resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.12.tgz#abe74124264fbc00a48011b0d98bdc1cffb64a7b"
+  integrity sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==
   dependencies:
     "@pkgr/core" "^0.2.9"
 
@@ -3540,10 +3447,10 @@ tom-select@2.4.3:
     "@orchidjs/sifter" "^1.1.0"
     "@orchidjs/unicode-variants" "^1.1.2"
 
-ts-api-utils@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz"
-  integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==
+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==
 
 tsconfig-paths@^3.15.0:
   version "3.15.0"

+ 2 - 2
netbox/release.yaml

@@ -1,3 +1,3 @@
-version: "4.5.0"
+version: "4.5.1"
 edition: "Community"
-published: "2026-01-06"
+published: "2026-01-20"

+ 2 - 0
netbox/templates/ipam/aggregate/prefixes.html

@@ -3,6 +3,8 @@
 
 {% block extra_controls %}
   {% include 'ipam/inc/toggle_available.html' %}
+  {% include 'ipam/inc/max_depth.html' %}
+  {% include 'ipam/inc/max_length.html' %}  
   {% if perms.ipam.add_prefix and first_available_prefix %}
     <a href="{% url 'ipam:prefix_add' %}?prefix={{ first_available_prefix }}" class="btn btn-primary">
       <i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add Prefix" %}

+ 20 - 0
netbox/templates/ipam/inc/max_depth.html

@@ -0,0 +1,20 @@
+{% load i18n %}
+{% load helpers %}
+
+<div class="dropdown">
+    <button class="btn btn-outline-secondary dropdown-toggle" type="button" id="max_depth" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
+        {% trans "Max Depth" %}{% if "depth__lte" in request.GET %}: {{ request.GET.depth__lte }}{% endif %}
+    </button>
+    <ul class="dropdown-menu" aria-labelledby="max_depth">
+        {% if request.GET.depth__lte %}
+            <li>
+                <a class="dropdown-item" href="{{ request.path }}{% querystring request depth__lte=None page=1 %}">{% trans "Clear" %}</a>
+            </li>
+        {% endif %}
+        {% for i in 16|as_range %}
+            <li><a class="dropdown-item" href="{{ request.path }}{% querystring request depth__lte=i page=1 %}">
+                {{ i }} {% if request.GET.depth__lte == i %}<i class="mdi mdi-check-bold"></i>{% endif %}
+            </a></li>
+        {% endfor %}
+    </ul>
+</div>

+ 20 - 0
netbox/templates/ipam/inc/max_length.html

@@ -0,0 +1,20 @@
+{% load i18n %}
+{% load helpers %}
+
+<div class="dropdown">
+    <button class="btn btn-outline-secondary dropdown-toggle" type="button" id="max_length" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
+        {% trans "Max Length" %}{% if "mask_length__lte" in request.GET %}: {{ request.GET.mask_length__lte }}{% endif %}
+    </button>
+    <ul class="dropdown-menu" aria-labelledby="max_length">
+        {% if request.GET.mask_length__lte %}
+            <li>
+                <a class="dropdown-item" href="{{ request.path }}{% querystring request mask_length__lte=None page=1 %}">{% trans "Clear" %}</a>
+            </li>
+        {% endif %}
+        {% for i in "4,8,12,16,20,24,28,32,40,48,56,64"|split %}
+            <li><a class="dropdown-item" href="{{ request.path }}{% querystring request mask_length__lte=i page=1 %}">
+                {{ i }} {% if request.GET.mask_length__lte == i %}<i class="mdi mdi-check-bold"></i>{% endif %}
+            </a></li>
+        {% endfor %}
+    </ul>
+</div>

+ 2 - 0
netbox/templates/ipam/prefix/prefixes.html

@@ -3,6 +3,8 @@
 
 {% block extra_controls %}
   {% include 'ipam/inc/toggle_available.html' %}
+  {% include 'ipam/inc/max_depth.html' %}
+  {% include 'ipam/inc/max_length.html' %}  
   {% if perms.ipam.add_prefix and first_available_prefix %}
     <a href="{% url 'ipam:prefix_add' %}?prefix={{ first_available_prefix }}&vrf={{ object.vrf.pk }}&site={{ object.site.pk }}&tenant_group={{ object.tenant.group.pk }}&tenant={{ object.tenant.pk }}" class="btn btn-primary">
       <i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add Prefix" %}

+ 2 - 34
netbox/templates/ipam/prefix_list.html

@@ -6,38 +6,6 @@
     <button class="btn btn-outline-secondary toggle-depth" type="button">
         {% trans "Hide Depth Indicators" %}
     </button>
-    <div class="dropdown">
-        <button class="btn btn-outline-secondary dropdown-toggle" type="button" id="max_depth" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
-            {% trans "Max Depth" %}{% if "depth__lte" in request.GET %}: {{ request.GET.depth__lte }}{% endif %}
-        </button>
-        <ul class="dropdown-menu" aria-labelledby="max_depth">
-            {% if request.GET.depth__lte %}
-                <li>
-                    <a class="dropdown-item" href="{% url 'ipam:prefix_list' %}{% querystring request depth__lte=None page=1 %}">{% trans "Clear" %}</a>
-                </li>
-            {% endif %}
-            {% for i in 16|as_range %}
-                <li><a class="dropdown-item" href="{% url 'ipam:prefix_list' %}{% querystring request depth__lte=i page=1 %}">
-                    {{ i }} {% if request.GET.depth__lte == i %}<i class="mdi mdi-check-bold"></i>{% endif %}
-                </a></li>
-            {% endfor %}
-        </ul>
-    </div>
-    <div class="dropdown">
-        <button class="btn btn-outline-secondary dropdown-toggle" type="button" id="max_length" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
-            {% trans "Max Length" %}{% if "mask_length__lte" in request.GET %}: {{ request.GET.mask_length__lte }}{% endif %}
-        </button>
-        <ul class="dropdown-menu" aria-labelledby="max_length">
-            {% if request.GET.mask_length__lte %}
-                <li>
-                    <a class="dropdown-item" href="{% url 'ipam:prefix_list' %}{% querystring request mask_length__lte=None page=1 %}">{% trans "Clear" %}</a>
-                </li>
-            {% endif %}
-            {% for i in "4,8,12,16,20,24,28,32,40,48,56,64"|split %}
-                <li><a class="dropdown-item" href="{% url 'ipam:prefix_list' %}{% querystring request mask_length__lte=i page=1 %}">
-                    {{ i }} {% if request.GET.mask_length__lte == i %}<i class="mdi mdi-check-bold"></i>{% endif %}
-                </a></li>
-            {% endfor %}
-        </ul>
-    </div>
+    {% include 'ipam/inc/max_depth.html' %}
+    {% include 'ipam/inc/max_length.html' %}
 {% endblock %}

BIN
netbox/translations/cs/LC_MESSAGES/django.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 292 - 493
netbox/translations/cs/LC_MESSAGES/django.po


BIN
netbox/translations/da/LC_MESSAGES/django.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 292 - 493
netbox/translations/da/LC_MESSAGES/django.po


BIN
netbox/translations/de/LC_MESSAGES/django.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 293 - 494
netbox/translations/de/LC_MESSAGES/django.po


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 157 - 149
netbox/translations/en/LC_MESSAGES/django.po


BIN
netbox/translations/es/LC_MESSAGES/django.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 292 - 493
netbox/translations/es/LC_MESSAGES/django.po


BIN
netbox/translations/fr/LC_MESSAGES/django.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 292 - 493
netbox/translations/fr/LC_MESSAGES/django.po


BIN
netbox/translations/it/LC_MESSAGES/django.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 292 - 493
netbox/translations/it/LC_MESSAGES/django.po


BIN
netbox/translations/ja/LC_MESSAGES/django.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 292 - 493
netbox/translations/ja/LC_MESSAGES/django.po


BIN
netbox/translations/lv/LC_MESSAGES/django.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 315 - 515
netbox/translations/lv/LC_MESSAGES/django.po


BIN
netbox/translations/nl/LC_MESSAGES/django.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 292 - 493
netbox/translations/nl/LC_MESSAGES/django.po


BIN
netbox/translations/pl/LC_MESSAGES/django.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 292 - 493
netbox/translations/pl/LC_MESSAGES/django.po


BIN
netbox/translations/pt/LC_MESSAGES/django.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 292 - 493
netbox/translations/pt/LC_MESSAGES/django.po


BIN
netbox/translations/ru/LC_MESSAGES/django.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 292 - 493
netbox/translations/ru/LC_MESSAGES/django.po


BIN
netbox/translations/tr/LC_MESSAGES/django.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 293 - 494
netbox/translations/tr/LC_MESSAGES/django.po


BIN
netbox/translations/uk/LC_MESSAGES/django.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 292 - 493
netbox/translations/uk/LC_MESSAGES/django.po


BIN
netbox/translations/zh/LC_MESSAGES/django.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 293 - 495
netbox/translations/zh/LC_MESSAGES/django.po


+ 4 - 3
netbox/users/constants.py

@@ -3,9 +3,10 @@ import string
 from django.db.models import Q
 
 
-OBJECTPERMISSION_OBJECT_TYPES = Q(
-    ~Q(app_label__in=['account', 'admin', 'auth', 'contenttypes', 'sessions', 'taggit', 'users']) |
-    Q(app_label='users', model__in=['objectpermission', 'token', 'group', 'user', 'owner'])
+OBJECTPERMISSION_OBJECT_TYPES = (
+    (Q(public=True) & ~Q(app_label='core', model='objecttype'))
+    | Q(app_label='core', model__in=['managedfile'])
+    | Q(app_label='extras', model__in=['scriptmodule', 'taggeditem'])
 )
 
 CONSTRAINT_TOKEN_USER = '$user'

+ 37 - 0
netbox/utilities/forms/widgets/modifiers.py

@@ -1,6 +1,9 @@
 from django import forms
+from django.conf import settings
 from django.utils.translation import gettext_lazy as _
 
+from utilities.forms.widgets.apiselect import APISelect, APISelectMultiple
+
 __all__ = (
     'FilterModifierWidget',
     'MODIFIER_EMPTY_FALSE',
@@ -94,9 +97,43 @@ class FilterModifierWidget(forms.Widget):
         # to the original widget before rendering
         self.original_widget.attrs.update(self.attrs)
 
+        # For APISelect/APISelectMultiple widgets, temporarily clear choices to prevent queryset evaluation
+        original_choices = None
+        if isinstance(self.original_widget, (APISelect, APISelectMultiple)):
+            original_choices = self.original_widget.choices
+
+            # Only keep selected choices to preserve the current selection in HTML
+            if value:
+                values = value if isinstance(value, (list, tuple)) else [value]
+
+                if hasattr(original_choices, 'queryset'):
+                    # Extract valid PKs (exclude special null choice string)
+                    pk_values = [v for v in values if v != settings.FILTERS_NULL_CHOICE_VALUE]
+
+                    # Build a minimal choice list with just the selected values
+                    choices = []
+                    if pk_values:
+                        selected_objects = original_choices.queryset.filter(pk__in=pk_values)
+                        choices = [(obj.pk, str(obj)) for obj in selected_objects]
+
+                    # Re-add the "None" option if it was selected via the null choice value
+                    if settings.FILTERS_NULL_CHOICE_VALUE in values:
+                        choices.append((settings.FILTERS_NULL_CHOICE_VALUE, settings.FILTERS_NULL_CHOICE_LABEL))
+
+                    self.original_widget.choices = choices
+                else:
+                    self.original_widget.choices = [choice for choice in original_choices if choice[0] in values]
+            else:
+                # No selection - render empty select element
+                self.original_widget.choices = []
+
         # Get context from the original widget
         original_context = self.original_widget.get_context(name, value, attrs)
 
+        # Restore original choices if we modified them
+        if original_choices is not None:
+            self.original_widget.choices = original_choices
+
         # Build our wrapper context
         context = super().get_context(name, value, attrs)
         context['widget']['original_widget'] = original_context['widget']

+ 16 - 0
netbox/utilities/forms/widgets/select.py

@@ -5,6 +5,7 @@ from ..utils import add_blank_choice
 
 __all__ = (
     'BulkEditNullBooleanSelect',
+    'ClearableSelect',
     'ColorSelect',
     'HTMXSelect',
     'SelectWithPK',
@@ -28,6 +29,21 @@ class BulkEditNullBooleanSelect(forms.NullBooleanSelect):
         )
 
 
+class ClearableSelect(forms.Select):
+    """
+    A Select widget that will be automatically cleared when one or more required fields are cleared.
+
+    Args:
+        requires_fields: A list of field names that this field depends on. When any of these fields
+                        are cleared, this field will also be cleared automatically via JavaScript.
+    """
+
+    def __init__(self, *args, requires_fields=None, **kwargs):
+        super().__init__(*args, **kwargs)
+        if requires_fields:
+            self.attrs['data-requires-fields'] = ','.join(requires_fields)
+
+
 class ColorSelect(forms.Select):
     """
     Extends the built-in Select widget to colorize each <option>.

+ 13 - 0
netbox/utilities/templatetags/builtins/filters.py

@@ -252,3 +252,16 @@ def isodatetime(value, spec='seconds'):
     else:
         return ''
     return mark_safe(f'<span title="{naturaltime(value)}">{text}</span>')
+
+
+@register.filter
+def truncate_middle(value, length):
+    if len(value) <= length:
+        return value
+
+    # Calculate split points for the two parts
+    half_len = (length - 1) // 2  # 1 for the ellipsis
+    first_part = value[:half_len]
+    second_part = value[len(value) - (length - 1 - half_len):]
+
+    return mark_safe(f"{first_part}&hellip;{second_part}")

+ 47 - 0
netbox/utilities/tests/test_filter_modifiers.py

@@ -1,4 +1,5 @@
 from django import forms
+from django.conf import settings
 from django.db import models
 from django.http import QueryDict
 from django.template import Context
@@ -14,6 +15,7 @@ from utilities.forms.fields import TagFilterField
 from utilities.forms.mixins import FilterModifierMixin
 from utilities.forms.widgets import FilterModifierWidget
 from utilities.templatetags.helpers import applied_filters
+from tenancy.models import Tenant
 
 
 # Test model for FilterModifierMixin tests
@@ -99,6 +101,51 @@ class FilterModifierWidgetTest(TestCase):
         self.assertEqual(context['widget']['current_modifier'], 'exact')  # Defaults to exact, JS updates from URL
         self.assertEqual(context['widget']['current_value'], 'test')
 
+    def test_get_context_handles_null_selection(self):
+        """Widget should preserve the 'null' choice when rendering."""
+
+        null_value = settings.FILTERS_NULL_CHOICE_VALUE
+        null_label = settings.FILTERS_NULL_CHOICE_LABEL
+
+        # Simulate a query for objects with no tenant assigned (?tenant_id=null)
+        query_params = QueryDict(f'tenant_id={null_value}')
+        form = DeviceFilterForm(query_params)
+
+        # Rendering the field triggers FilterModifierWidget.get_context()
+        try:
+            html = form['tenant_id'].as_widget()
+        except ValueError as e:
+            # ValueError: Field 'id' expected a number but got 'null'
+            self.fail(f"FilterModifierWidget raised ValueError on 'null' selection: {e}")
+
+        # Verify the "None" option is rendered so user selection is preserved in the UI
+        self.assertIn(f'value="{null_value}"', html)
+        self.assertIn(null_label, html)
+
+    def test_get_context_handles_mixed_selection(self):
+        """Widget should preserve both real objects and the 'null' choice together."""
+
+        null_value = settings.FILTERS_NULL_CHOICE_VALUE
+
+        # Create a tenant to simulate a real object
+        tenant = Tenant.objects.create(name='Tenant A', slug='tenant-a')
+
+        # Simulate a selection containing both a real PK and the null sentinel
+        query_params = QueryDict('', mutable=True)
+        query_params.setlist('tenant_id', [str(tenant.pk), null_value])
+        form = DeviceFilterForm(query_params)
+
+        # Rendering the field triggers FilterModifierWidget.get_context()
+        try:
+            html = form['tenant_id'].as_widget()
+        except ValueError as e:
+            # ValueError: Field 'id' expected a number but got 'null'
+            self.fail(f"FilterModifierWidget raised ValueError on 'null' selection: {e}")
+
+        # Verify both the real object and the null option are present in the output
+        self.assertIn(f'value="{tenant.pk}"', html)
+        self.assertIn(f'value="{null_value}"', html)
+
     def test_widget_renders_modifier_dropdown_and_input(self):
         """Widget should render modifier dropdown alongside original input."""
         widget = FilterModifierWidget(

+ 8 - 4
netbox/utilities/views.py

@@ -5,9 +5,11 @@ from django.conf import settings
 from django.contrib.auth.mixins import AccessMixin
 from django.core.exceptions import ImproperlyConfigured
 from django.db.models import QuerySet
+from django.http import HttpResponseForbidden
 from django.urls import reverse
 from django.urls.exceptions import NoReverseMatch
 from django.utils.translation import gettext_lazy as _
+from rest_framework.exceptions import AuthenticationFailed
 
 from netbox.api.authentication import TokenAuthentication
 from netbox.plugins import PluginConfig
@@ -50,10 +52,12 @@ class TokenConditionalLoginRequiredMixin(ConditionalLoginRequiredMixin):
         # Attempt to authenticate the user using a DRF token, if provided
         if settings.LOGIN_REQUIRED and not request.user.is_authenticated:
             authenticator = TokenAuthentication()
-            auth_info = authenticator.authenticate(request)
-            if auth_info is not None:
-                request.user = auth_info[0]  # User object
-                request.auth = auth_info[1]
+            try:
+                if (auth_info := authenticator.authenticate(request)) is not None:
+                    request.user = auth_info[0]  # User object
+                    request.auth = auth_info[1]
+            except AuthenticationFailed:
+                return HttpResponseForbidden("Invalid token")
 
         return super().dispatch(request, *args, **kwargs)
 

+ 3 - 3
netbox/vpn/graphql/filters.py

@@ -15,7 +15,7 @@ from vpn import models
 if TYPE_CHECKING:
     from core.graphql.filters import ContentTypeFilter
     from ipam.graphql.filters import IPAddressFilter, RouteTargetFilter
-    from netbox.graphql.filter_lookups import IntegerLookup
+    from netbox.graphql.filter_lookups import BigIntegerLookup, IntegerLookup
     from .enums import *
 
 __all__ = (
@@ -75,7 +75,7 @@ class TunnelFilter(TenancyFilterMixin, PrimaryModelFilter):
     ipsec_profile: Annotated['IPSecProfileFilter', strawberry.lazy('vpn.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
-    tunnel_id: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
+    tunnel_id: Annotated['BigIntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
     terminations: Annotated['TunnelTerminationFilter', strawberry.lazy('vpn.graphql.filters')] | None = (
@@ -187,7 +187,7 @@ class L2VPNFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter):
     type: BaseFilterLookup[Annotated['L2VPNTypeEnum', strawberry.lazy('vpn.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    identifier: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
+    identifier: Annotated['BigIntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
     import_targets: Annotated['RouteTargetFilter', strawberry.lazy('ipam.graphql.filters')] | None = (

+ 1 - 1
pyproject.toml

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

+ 6 - 6
requirements.txt

@@ -1,7 +1,7 @@
 colorama==0.4.6
-Django==5.2.9
+Django==5.2.10
 django-cors-headers==4.9.0
-django-debug-toolbar==6.1.0
+django-debug-toolbar==6.2.0
 django-filter==25.2
 django-graphiql-debug-toolbar==0.2.0
 django-htmx==1.27.0
@@ -21,10 +21,10 @@ drf-spectacular-sidecar==2026.1.1
 feedparser==6.0.12
 gunicorn==23.0.0
 Jinja2==3.1.6
-jsonschema==4.25.1
+jsonschema==4.26.0
 Markdown==3.10
 mkdocs-material==9.7.1
-mkdocstrings==1.0.0
+mkdocstrings==1.0.1
 mkdocstrings-python==2.0.1
 netaddr==1.3.0
 nh3==0.3.2
@@ -36,8 +36,8 @@ rq==2.6.1
 social-auth-app-django==5.7.0
 social-auth-core==4.8.3
 sorl-thumbnail==12.11.0
-strawberry-graphql==0.288.2
-strawberry-graphql-django==0.73.0
+strawberry-graphql==0.289.2
+strawberry-graphql-django==0.74.1
 svgwrite==1.4.3
 tablib==3.9.0
 tzdata==2025.3

+ 0 - 16
scripts/git-hooks/pre-commit

@@ -1,16 +0,0 @@
-#!/bin/sh
-# TODO: Remove this file in NetBox v4.3
-# This script has been maintained to ease transition to the pre-commit tool.
-
-exec 1>&2
-
-EXIT=0
-RED='\033[0;31m'
-YELLOW='\033[0;33m'
-NOCOLOR='\033[0m'
-
-printf "${YELLOW}The pre-commit hook script is obsolete. Please use pre-commit instead:${NOCOLOR}\n"
-printf "  pip install pre-commit\n"
-printf "  pre-commit install${NOCOLOR}\n"
-
-exit 1

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است