From 0701a42a9434c47346e7f2300356c141d2f931cb Mon Sep 17 00:00:00 2001
From: Jason Novinger
Date: Tue, 4 Aug 2026 16:39:35 -0500
Subject: [PATCH 01/15] Fixes #22812: Avoid loading all jobs into memory when
deleting a JobsMixin object
Deleting a Script (or any JobsMixin object) with thousands of associated jobs could
consume several GB of memory and exhaust the host, because Django's deletion collector
loads every related Job into memory. Jobs can never be fast-deleted (a global pre_delete
receiver forces per-instance signal dispatch), and each Job carries potentially large
data and log_entries payloads.
Two paths loaded the full job set independently, so both are addressed:
- The delete cascade: JobsMixin.delete() now deletes the object's jobs in batches before
delegating to super().delete(), wrapped in a transaction so a failure in the parent
delete rolls the job deletions back. After the loop the cascade collector finds no jobs
to materialize.
- The delete-confirmation page: _get_dependent_objects() uses a ConfirmCollector that
counts the jobs relation rather than descending into it, so the page never instantiates
the jobs. Counted relations render as a non-expandable row (via a CountOnly stand-in)
alongside the itemized dependents.
---
netbox/extras/tests/test_scripts_deletion.py | 236 +++++++++++++++++++
netbox/netbox/constants.py | 5 +
netbox/netbox/models/deletion.py | 56 +++++
netbox/netbox/models/features.py | 22 +-
netbox/netbox/views/generic/object_views.py | 21 +-
netbox/templates/htmx/delete_form.html | 53 +++--
6 files changed, 363 insertions(+), 30 deletions(-)
create mode 100644 netbox/extras/tests/test_scripts_deletion.py
diff --git a/netbox/extras/tests/test_scripts_deletion.py b/netbox/extras/tests/test_scripts_deletion.py
new file mode 100644
index 000000000..9aaf11e0c
--- /dev/null
+++ b/netbox/extras/tests/test_scripts_deletion.py
@@ -0,0 +1,236 @@
+import uuid
+from unittest import mock
+
+from django.contrib.contenttypes.models import ContentType
+from django.db import router
+from django.db.models import QuerySet
+from django.test import TestCase, override_settings
+from django.urls import reverse
+
+from core.choices import ManagedFileRootPathChoices
+from core.models import DataSource, Job
+from extras.models import Script, ScriptModule
+from extras.validators import CustomValidator
+from netbox.models.deletion import ConfirmCollector, CountOnly
+from utilities.exceptions import AbortRequest
+from utilities.testing import TestCase as ViewTestCase
+
+
+class ScriptDeletionTestCase(TestCase):
+ """
+ Regression tests for #22812: deleting a JobsMixin object (Script, ScriptModule, DataSource)
+ with many associated Jobs must not load every Job into memory at once.
+ """
+ @classmethod
+ def setUpTestData(cls):
+ cls.script_ct = ContentType.objects.get_for_model(Script, for_concrete_model=False)
+
+ def _create_module(self):
+ return ScriptModule.objects.create(
+ file_root=ManagedFileRootPathChoices.SCRIPTS,
+ file_path=f'test_{uuid.uuid4().hex[:8]}.py',
+ )
+
+ def _create_script(self, module=None):
+ module = module or self._create_module()
+ script = Script.objects.create(module=module, name=f'S{uuid.uuid4().hex[:8]}')
+ return module, script
+
+ def _add_jobs(self, obj, count, object_type=None):
+ object_type = object_type or ContentType.objects.get_for_model(type(obj), for_concrete_model=False)
+ Job.objects.bulk_create([
+ Job(
+ object_type=object_type,
+ object_id=obj.pk,
+ name='testjob',
+ status='completed',
+ job_id=uuid.uuid4(),
+ data={'output': 'x' * 50},
+ )
+ for _ in range(count)
+ ])
+
+ def test_delete_script_deletes_all_jobs(self):
+ _, script = self._create_script()
+ self._add_jobs(script, 2500)
+ self.assertEqual(script.jobs.count(), 2500)
+
+ script.delete()
+
+ self.assertFalse(Script.objects.filter(pk=script.pk).exists())
+ self.assertEqual(Job.objects.filter(object_type=self.script_ct, object_id=script.pk).count(), 0)
+
+ def test_delete_script_batches_jobs(self):
+ _, script = self._create_script()
+ self._add_jobs(script, 5)
+
+ job_delete_calls = []
+ original_delete = QuerySet.delete
+
+ def counting_delete(qs, *args, **kwargs):
+ if qs.model is Job:
+ job_delete_calls.append(len(qs))
+ return original_delete(qs, *args, **kwargs)
+
+ with mock.patch('netbox.models.features.JOB_DELETE_BATCH_SIZE', 2):
+ with mock.patch.object(QuerySet, 'delete', counting_delete):
+ script.delete()
+
+ # 5 jobs at a batch size of 2 => three batched deletes (2, 2, 1)
+ self.assertEqual(job_delete_calls, [2, 2, 1])
+ self.assertEqual(Job.objects.filter(object_type=self.script_ct, object_id=script.pk).count(), 0)
+
+ def test_delete_scriptmodule_cascades_to_scripts_and_jobs(self):
+ module, script = self._create_script()
+ self._add_jobs(script, 100)
+
+ module.delete()
+
+ self.assertFalse(ScriptModule.objects.filter(pk=module.pk).exists())
+ self.assertFalse(Script.objects.filter(pk=script.pk).exists())
+ self.assertEqual(Job.objects.filter(object_type=self.script_ct, object_id=script.pk).count(), 0)
+
+ def test_delete_datasource_deletes_jobs(self):
+ datasource = DataSource.objects.create(name='DS', type='local', source_url='/tmp/test')
+ self._add_jobs(datasource, 100)
+ ds_ct = ContentType.objects.get_for_model(DataSource, for_concrete_model=False)
+ self.assertEqual(Job.objects.filter(object_type=ds_ct, object_id=datasource.pk).count(), 100)
+
+ datasource.delete()
+
+ self.assertFalse(DataSource.objects.filter(pk=datasource.pk).exists())
+ self.assertEqual(Job.objects.filter(object_type=ds_ct, object_id=datasource.pk).count(), 0)
+
+ def test_soft_delete_preserves_jobs(self):
+ _, script = self._create_script()
+ self._add_jobs(script, 10)
+
+ script.delete(soft_delete=True)
+
+ script.refresh_from_db()
+ self.assertFalse(script.is_executable)
+ self.assertEqual(Job.objects.filter(object_type=self.script_ct, object_id=script.pk).count(), 10)
+
+ @override_settings(PROTECTION_RULES={'extras.script': [CustomValidator({'name': {'eq': ''}})]})
+ def test_delete_rolls_back_jobs_on_parent_failure(self):
+ # A protection rule that no real script can satisfy (name must be empty) makes the
+ # cascade's pre_delete handler raise AbortRequest *after* JobsMixin.delete has already
+ # batch-deleted the jobs. JobsMixin.delete wraps the batch loop and super().delete() in a
+ # transaction, so the job deletions must roll back, leaving no orphaned partial state.
+ # This exercises the real deletion-abort path rather than mocking Django internals.
+ _, script = self._create_script()
+ self._add_jobs(script, 10)
+
+ with self.assertRaises(AbortRequest):
+ script.delete()
+
+ self.assertTrue(Script.objects.filter(pk=script.pk).exists())
+ self.assertEqual(Job.objects.filter(object_type=self.script_ct, object_id=script.pk).count(), 10)
+
+
+class ConfirmCollectorTestCase(TestCase):
+ """
+ #22812: the delete-confirmation page must not materialize every dependent Job.
+ """
+ def _create_script_with_jobs(self, count):
+ module = ScriptModule.objects.create(
+ file_root=ManagedFileRootPathChoices.SCRIPTS,
+ file_path=f'test_{uuid.uuid4().hex[:8]}.py',
+ )
+ script = Script.objects.create(module=module, name=f'S{uuid.uuid4().hex[:8]}')
+ ct = ContentType.objects.get_for_model(Script, for_concrete_model=False)
+ Job.objects.bulk_create([
+ Job(object_type=ct, object_id=script.pk, name='j', status='completed',
+ job_id=uuid.uuid4(), data={'output': 'x' * 50})
+ for _ in range(count)
+ ])
+ return script
+
+ def test_confirm_collector_counts_jobs_without_instantiating(self):
+ script = self._create_script_with_jobs(500)
+
+ init_calls = []
+ original_init = Job.__init__
+
+ def counting_init(self, *args, **kwargs):
+ init_calls.append(1)
+ original_init(self, *args, **kwargs)
+
+ with mock.patch.object(Job, '__init__', counting_init):
+ collector = ConfirmCollector(using=router.db_for_write(Script))
+ collector.collect([script])
+
+ # No Job rows were instantiated; the relation was counted instead.
+ self.assertEqual(len(init_calls), 0)
+ self.assertNotIn(Job, collector.data)
+ self.assertEqual(collector.generic_relation_counts.get(Job), 500)
+ # The non-job cascade (the Script itself) is still collected.
+ self.assertIn(Script, collector.data)
+
+ def test_count_only_wrapper(self):
+ # CountOnly reports its count via len() but iterates empty, so it slots into the
+ # dependent-objects mapping as a non-expandable, non-materializing row.
+ wrapper = CountOnly(3000)
+ self.assertEqual(len(wrapper), 3000)
+ self.assertEqual(list(wrapper), [])
+ self.assertTrue(wrapper.count_only)
+
+
+class ObjectDeleteViewCountsTestCase(ViewTestCase):
+ """
+ #22812: the delete-confirmation view must report a JobsMixin object's jobs as a count
+ (via CountOnly) without materializing them, and _get_dependent_objects must keep returning
+ a single dict.
+ """
+ def test_get_dependent_objects_returns_count_only_for_jobs(self):
+ from netbox.views.generic.object_views import ObjectDeleteView
+
+ module = ScriptModule.objects.create(
+ file_root=ManagedFileRootPathChoices.SCRIPTS,
+ file_path=f'test_{uuid.uuid4().hex[:8]}.py',
+ )
+ script = Script.objects.create(module=module, name=f'S{uuid.uuid4().hex[:8]}')
+ ct = ContentType.objects.get_for_model(Script, for_concrete_model=False)
+ Job.objects.bulk_create([
+ Job(object_type=ct, object_id=script.pk, name='j', status='completed', job_id=uuid.uuid4())
+ for _ in range(50)
+ ])
+
+ view = ObjectDeleteView()
+ view.queryset = ScriptModule.objects.all()
+ dependent_objects = view._get_dependent_objects(module)
+
+ # Single dict returned (not a tuple); jobs represented as a CountOnly.
+ self.assertIsInstance(dependent_objects, dict)
+ self.assertIn(Job, dependent_objects)
+ self.assertIsInstance(dependent_objects[Job], CountOnly)
+ self.assertEqual(len(dependent_objects[Job]), 50)
+
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
+ def test_confirm_page_renders_job_count(self):
+ module = ScriptModule.objects.create(
+ file_root=ManagedFileRootPathChoices.SCRIPTS,
+ file_path=f'test_{uuid.uuid4().hex[:8]}.py',
+ )
+ script = Script.objects.create(module=module, name=f'S{uuid.uuid4().hex[:8]}')
+ ct = ContentType.objects.get_for_model(Script, for_concrete_model=False)
+ Job.objects.bulk_create([
+ Job(object_type=ct, object_id=script.pk, name='j', status='completed', job_id=uuid.uuid4())
+ for _ in range(50)
+ ])
+
+ # ScriptModule is a proxy over core.ManagedFile, so the delete view requires the
+ # concrete model's permission (core.delete_managedfile), not extras.delete_scriptmodule.
+ self.add_permissions('core.delete_managedfile')
+ url = reverse('extras:scriptmodule_delete', kwargs={'pk': module.pk})
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+
+ # Assert on the rendered context, not brittle HTML substrings: Job is present in
+ # dependent_objects as a CountOnly reporting the true count, so the confirmation page
+ # renders it as a summarized (non-expandable) row without materializing 50 Job rows.
+ dependent_objects = response.context['dependent_objects']
+ self.assertIn(Job, dependent_objects)
+ self.assertIsInstance(dependent_objects[Job], CountOnly)
+ self.assertEqual(len(dependent_objects[Job]), 50)
+ self.assertTrue(dependent_objects[Job].count_only)
diff --git a/netbox/netbox/constants.py b/netbox/netbox/constants.py
index 037059583..fddce0ad9 100644
--- a/netbox/netbox/constants.py
+++ b/netbox/netbox/constants.py
@@ -74,3 +74,8 @@ CENSOR_TOKEN_CHANGED = '***CHANGED***'
# Placeholder text for empty tables
EMPTY_TABLE_TEXT = 'No results found'
+
+# Batch size for deleting a JobsMixin object's associated jobs during cascade deletion.
+# Kept small because each Job carries potentially large data/log_entries payloads and
+# cannot be fast-deleted (a global pre_delete receiver forces per-instance signals). See #22812.
+JOB_DELETE_BATCH_SIZE = 100
diff --git a/netbox/netbox/models/deletion.py b/netbox/netbox/models/deletion.py
index e87321ae3..0d9aa2c19 100644
--- a/netbox/netbox/models/deletion.py
+++ b/netbox/netbox/models/deletion.py
@@ -8,6 +8,62 @@ from django.utils.translation import gettext as _
logger = logging.getLogger("netbox.models.deletion")
+class CountOnly:
+ """
+ A stand-in for a list of dependent instances that reports a count without holding any
+ instances. Used on the delete-confirmation page for high-cardinality relations (e.g. a
+ JobsMixin object's jobs) which we deliberately do not materialize (see #22812). It is a
+ lenient, empty iterable: `len()` returns the true row count, but iterating yields nothing,
+ so it slots into the same `{model: }` mapping as real instance lists and renders
+ as a non-expandable row.
+ """
+ # Template flag: distinguishes a count-only entry (no instances to list) from a real list,
+ # so the confirmation page can render it without an expand/collapse affordance.
+ count_only = True
+
+ def __init__(self, count):
+ self.count = count
+
+ def __len__(self):
+ return self.count
+
+ def __iter__(self):
+ return iter(())
+
+
+class ConfirmCollector(Collector):
+ """
+ A display-only Collector used to enumerate the objects that would be deleted along with a
+ given object, for rendering the delete confirmation page. It behaves like Django's stock
+ Collector (preserving the full FK cascade graph and its ProtectedError/RestrictedError
+ behavior) except that it does not descend into the `jobs` GenericRelation. A JobsMixin
+ object can accumulate thousands of Jobs, each carrying large data/log_entries payloads;
+ materializing them all just to render a confirmation page can exhaust memory (see #22812).
+ Instead, the related Jobs are counted and recorded in `generic_relation_counts`.
+ """
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.generic_relation_counts = {}
+
+ def collect(self, objs, source=None, *args, **kwargs):
+ """
+ Override collect() to count the `jobs` GenericRelation rather than descend into it.
+
+ Django's Collector offers no per-relation skip hook, so we intercept the one call it
+ makes when cascading into a GenericRelation: collect(sub_objs, source=model, ...), where
+ `sub_objs` is a queryset of the related model. When that model is Job, we count the rows
+ instead of collecting (and thus instantiating) them, and forward every other call to the
+ stock implementation untouched. A directly-deleted Job (top-level call, source=None)
+ still collects normally.
+ """
+ from core.models import Job
+
+ if source is not None and getattr(objs, 'model', None) is Job:
+ self.generic_relation_counts[Job] = self.generic_relation_counts.get(Job, 0) + objs.count()
+ return None
+ return super().collect(objs, source=source, *args, **kwargs)
+
+
class CustomCollector(Collector):
"""
Override Django's stock Collector to handle GenericRelations and ensure proper ordering of cascading deletions.
diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py
index 1556a22a1..8755f7eaf 100644
--- a/netbox/netbox/models/features.py
+++ b/netbox/netbox/models/features.py
@@ -5,7 +5,7 @@ from functools import cached_property
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.core.validators import ValidationError
-from django.db import models
+from django.db import models, router, transaction
from django.db.models import Q
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
@@ -17,7 +17,7 @@ from extras.constants import CUSTOMFIELD_EMPTY_VALUES
from extras.managers import NetBoxTaggableManager, NetBoxTaggableManagerField
from extras.utils import is_taggable
from netbox.config import get_config
-from netbox.constants import CORE_APPS
+from netbox.constants import CORE_APPS, JOB_DELETE_BATCH_SIZE
from netbox.models.deletion import DeleteMixin
from netbox.plugins import PluginConfig
from netbox.registry import registry
@@ -463,6 +463,24 @@ class JobsMixin(models.Model):
class Meta:
abstract = True
+ def delete(self, *args, **kwargs):
+ from core.models import Job
+
+ # Delete associated jobs in batches so the cascade never has to load thousands of
+ # Job rows (each carrying potentially large data/log_entries payloads) into memory
+ # at once. Wrapped in a transaction so that a failure in the parent delete rolls the
+ # job deletions back as well. As with the prior cascade behavior, this bulk delete does
+ # not invoke Job.delete() and therefore does not cancel the backing RQ job. See #22812.
+ using = router.db_for_write(self.__class__, instance=self)
+ with transaction.atomic(using=using):
+ job_pks = self.jobs.order_by('pk').values_list('pk', flat=True)
+ # Re-slice the queryset each iteration: it re-queries after each batch delete, so the
+ # remaining set shrinks and the loop terminates (do not hoist this into a cursor).
+ while pks := list(job_pks[:JOB_DELETE_BATCH_SIZE]):
+ Job.objects.filter(pk__in=pks).delete()
+ return super().delete(*args, **kwargs)
+ delete.alters_data = True
+
def get_latest_jobs(self):
"""
Return a list of the most recent jobs for this instance.
diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py
index 3e9690030..e815a45cb 100644
--- a/netbox/netbox/views/generic/object_views.py
+++ b/netbox/netbox/views/generic/object_views.py
@@ -4,7 +4,6 @@ from collections import defaultdict
from django.contrib import messages
from django.db import router, transaction
from django.db.models import ProtectedError, RestrictedError
-from django.db.models.deletion import Collector
from django.http import HttpResponse
from django.shortcuts import redirect, render
from django.urls import reverse
@@ -13,6 +12,7 @@ from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _
from core.signals import clear_events
+from netbox.models.deletion import ConfirmCollector, CountOnly
from netbox.object_actions import BulkDelete, BulkEdit, CloneObject, DeleteObject, EditObject
from utilities.error_handlers import handle_protectederror
from utilities.exceptions import AbortRequest, PermissionsViolation
@@ -385,14 +385,19 @@ class ObjectDeleteView(GetReturnURLMixin, BaseObjectView):
def _get_dependent_objects(self, obj):
"""
- Returns a dictionary mapping of dependent objects (organized by model) which will be deleted as a result of
- deleting the requested object.
+ Returns a dictionary mapping each dependent model to the objects (of that model) which will
+ be deleted as a result of deleting the requested object.
+
+ Values are normally a list of instances. For high-cardinality relations that we do not
+ materialize to avoid excessive memory use (currently a JobsMixin object's jobs, see
+ #22812), the value is a `CountOnly` — a lenient empty iterable whose `len()` is the true
+ row count, so it renders as a non-expandable row alongside the itemized relations.
Args:
obj: The object to return dependent objects for
"""
using = router.db_for_write(obj._meta.model)
- collector = Collector(using=using)
+ collector = ConfirmCollector(using=using)
collector.collect([obj])
# Compile a mapping of models to instances
@@ -406,7 +411,13 @@ class ObjectDeleteView(GetReturnURLMixin, BaseObjectView):
continue
dependent_objects[model].append(instances)
- return dict(dependent_objects)
+ # Add count-only entries for relations the collector enumerated by count rather than by
+ # instance (e.g. jobs), so they render as non-expandable rows in the same mapping.
+ dependent_objects = dict(dependent_objects)
+ for model, count in collector.generic_relation_counts.items():
+ dependent_objects[model] = CountOnly(count)
+
+ return dependent_objects
def _handle_protected_objects(self, obj, protected_objects, request, exc):
"""
diff --git a/netbox/templates/htmx/delete_form.html b/netbox/templates/htmx/delete_form.html
index eb267e44c..8f24816a4 100644
--- a/netbox/templates/htmx/delete_form.html
+++ b/netbox/templates/htmx/delete_form.html
@@ -24,31 +24,38 @@
{% for model, instances in dependent_objects.items %}
-
-
-
-
-
-
-
- {% for instance in instances %}
- {% with url=instance.get_absolute_url %}
- {{ instance }}
- {% endwith %}
- {% endfor %}
+ {% with object_count=instances|length %}
+
+
+ {# High-cardinality relations (e.g. jobs) are summarized by count and are not #}
+ {# expandable, since their instances are intentionally not loaded (see #22812). #}
+ {% if instances.count_only %}
+
+ {% for instance in instances %}
+ {% with url=instance.get_absolute_url %}
+ {{ instance }}
+ {% endwith %}
+ {% endfor %}
+
+
-
+ {% endif %}
-
+ {% endwith %}
{% endfor %}
{% endif %}
From f2923f4ce4c2a741be823fd3d2f2f345b0ef1bb7 Mon Sep 17 00:00:00 2001
From: Jason Novinger
Date: Wed, 5 Aug 2026 13:56:01 -0500
Subject: [PATCH 02/15] Fixes #22812: Batch child-script job deletion when
deleting a ScriptModule
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Deleting a Script via the UI is only possible by deleting its parent ScriptModule
(no Script delete view exists). That cascades to the child Script rows, and the
collector materialized every one of those Scripts' jobs — the memory blowup, which
scales with jobs-per-script. JobsMixin.delete() only batched the deleted object's own
jobs, and a ScriptModule has none; the jobs live on its child Scripts.
Extract the chunked job-deletion loop from JobsMixin.delete() into a shared
batch_delete_jobs() helper, and add a ScriptModule.delete() override that batch-deletes
its child Scripts' jobs (in a single queryset keyed on the script PKs, no per-script
loop) before delegating to the cascade. This bounds peak memory to one batch regardless
of how many jobs the module's scripts hold.
---
netbox/extras/models/scripts.py | 27 ++++++++++++++--
netbox/extras/tests/test_scripts_deletion.py | 24 ++++++++++++++
netbox/netbox/models/features.py | 34 +++++++++++++-------
3 files changed, 71 insertions(+), 14 deletions(-)
diff --git a/netbox/extras/models/scripts.py b/netbox/extras/models/scripts.py
index 02f3902d1..d9a2ca28c 100644
--- a/netbox/extras/models/scripts.py
+++ b/netbox/extras/models/scripts.py
@@ -3,7 +3,8 @@ import logging
from functools import cached_property
from django.contrib.contenttypes.fields import GenericRelation
-from django.db import models
+from django.contrib.contenttypes.models import ContentType
+from django.db import models, router, transaction
from django.db.models import Q
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
@@ -11,7 +12,7 @@ from django.utils.translation import gettext_lazy as _
from core.choices import ManagedFileRootPathChoices
from core.models import ManagedFile
from extras.utils import is_script
-from netbox.models.features import EventRulesMixin, JobsMixin
+from netbox.models.features import EventRulesMixin, JobsMixin, batch_delete_jobs
from utilities.querysets import RestrictedQuerySet
from .mixins import PythonModuleMixin
@@ -119,6 +120,28 @@ class ScriptModule(PythonModuleMixin, JobsMixin, ManagedFile):
def __str__(self):
return self.python_name
+ def delete(self, *args, **kwargs):
+ # Job is imported here rather than at module level to avoid a circular import
+ # (core.models.jobs -> core.signals -> extras.events -> extras.models -> this module).
+ from core.models import Job
+
+ # Deleting a ScriptModule cascades (via the Script.module FK) to its child Scripts, and
+ # Django's collector would materialize every one of those Scripts' Jobs to delete them.
+ # A module's scripts can accumulate thousands of jobs, exhausting memory. Batch-delete
+ # the child Scripts' jobs up front, in a single queryset (no per-script loop), before
+ # delegating to the cascade. Wrapped in a transaction so a failure in the parent delete
+ # rolls these deletions back as well. See #22812.
+ using = router.db_for_write(self.__class__, instance=self)
+ with transaction.atomic(using=using):
+ script_type = ContentType.objects.get_for_model(Script, for_concrete_model=False)
+ child_jobs = Job.objects.filter(
+ object_type=script_type,
+ object_id__in=self.scripts.values_list('pk', flat=True),
+ )
+ batch_delete_jobs(child_jobs)
+ return super().delete(*args, **kwargs)
+ delete.alters_data = True
+
@property
def ordered_scripts(self):
script_objects = {s.name: s for s in self.scripts.all()}
diff --git a/netbox/extras/tests/test_scripts_deletion.py b/netbox/extras/tests/test_scripts_deletion.py
index 9aaf11e0c..5b928bd0d 100644
--- a/netbox/extras/tests/test_scripts_deletion.py
+++ b/netbox/extras/tests/test_scripts_deletion.py
@@ -90,6 +90,30 @@ class ScriptDeletionTestCase(TestCase):
self.assertFalse(Script.objects.filter(pk=script.pk).exists())
self.assertEqual(Job.objects.filter(object_type=self.script_ct, object_id=script.pk).count(), 0)
+ def test_delete_scriptmodule_batches_child_script_jobs(self):
+ # The reporter's actual path: a script is only removable via the UI by deleting its
+ # ScriptModule. The module's delete must batch the child Script's jobs.
+ module, script = self._create_script()
+ self._add_jobs(script, 5)
+
+ job_delete_calls = []
+ original_delete = QuerySet.delete
+
+ def counting_delete(qs, *args, **kwargs):
+ if qs.model is Job:
+ job_delete_calls.append(len(qs))
+ return original_delete(qs, *args, **kwargs)
+
+ with mock.patch('netbox.models.features.JOB_DELETE_BATCH_SIZE', 2):
+ with mock.patch.object(QuerySet, 'delete', counting_delete):
+ module.delete()
+
+ # 5 child-script jobs at a batch size of 2 => three batched deletes (2, 2, 1). The module
+ # has no jobs of its own, so JobsMixin.delete adds no further Job deletes.
+ self.assertEqual(job_delete_calls, [2, 2, 1])
+ self.assertFalse(Script.objects.filter(pk=script.pk).exists())
+ self.assertEqual(Job.objects.filter(object_type=self.script_ct, object_id=script.pk).count(), 0)
+
def test_delete_datasource_deletes_jobs(self):
datasource = DataSource.objects.create(name='DS', type='local', source_url='/tmp/test')
self._add_jobs(datasource, 100)
diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py
index 8755f7eaf..f608da50c 100644
--- a/netbox/netbox/models/features.py
+++ b/netbox/netbox/models/features.py
@@ -43,6 +43,7 @@ __all__ = (
'NotificationsMixin',
'SyncedDataMixin',
'TagsMixin',
+ 'batch_delete_jobs',
'get_model_features',
'has_feature',
'model_is_public',
@@ -449,6 +450,23 @@ class NotificationsMixin(models.Model):
abstract = True
+def batch_delete_jobs(job_queryset):
+ """
+ Delete the Jobs in `job_queryset` in JOB_DELETE_BATCH_SIZE chunks, so the caller never has
+ to load thousands of Job rows (each carrying potentially large data/log_entries payloads)
+ into memory at once. Callers are responsible for wrapping this in a transaction. As with the
+ prior cascade behavior, this bulk delete does not invoke Job.delete() and therefore does not
+ cancel the backing RQ job. See #22812.
+ """
+ from core.models import Job
+
+ job_pks = job_queryset.order_by('pk').values_list('pk', flat=True)
+ # Re-slice the queryset each iteration: it re-queries after each batch delete, so the
+ # remaining set shrinks and the loop terminates (do not hoist this into a cursor).
+ while pks := list(job_pks[:JOB_DELETE_BATCH_SIZE]):
+ Job.objects.filter(pk__in=pks).delete()
+
+
class JobsMixin(models.Model):
"""
Enables support for job results.
@@ -464,20 +482,12 @@ class JobsMixin(models.Model):
abstract = True
def delete(self, *args, **kwargs):
- from core.models import Job
-
- # Delete associated jobs in batches so the cascade never has to load thousands of
- # Job rows (each carrying potentially large data/log_entries payloads) into memory
- # at once. Wrapped in a transaction so that a failure in the parent delete rolls the
- # job deletions back as well. As with the prior cascade behavior, this bulk delete does
- # not invoke Job.delete() and therefore does not cancel the backing RQ job. See #22812.
+ # Delete associated jobs in batches so the cascade never has to load thousands of Job
+ # rows into memory at once. Wrapped in a transaction so that a failure in the parent
+ # delete rolls the job deletions back as well. See #22812.
using = router.db_for_write(self.__class__, instance=self)
with transaction.atomic(using=using):
- job_pks = self.jobs.order_by('pk').values_list('pk', flat=True)
- # Re-slice the queryset each iteration: it re-queries after each batch delete, so the
- # remaining set shrinks and the loop terminates (do not hoist this into a cursor).
- while pks := list(job_pks[:JOB_DELETE_BATCH_SIZE]):
- Job.objects.filter(pk__in=pks).delete()
+ batch_delete_jobs(self.jobs)
return super().delete(*args, **kwargs)
delete.alters_data = True
From be69c55a990ba99fbcad774e85b7fdb562b8737e Mon Sep 17 00:00:00 2001
From: Jason Novinger
Date: Wed, 5 Aug 2026 13:59:02 -0500
Subject: [PATCH 03/15] Fixes #22812: Don't show a spurious "0 jobs" row for
jobless objects
Django's Collector calls into the jobs GenericRelation branch unconditionally, so
ConfirmCollector recorded a zero count for objects with no jobs. _get_dependent_objects
then added a CountOnly(0), and the delete-confirmation page rendered "The following
objects will be deleted as a result of this action." plus a "0 jobs" row for every
jobless JobsMixin object. Only record a count when there are actually jobs.
---
netbox/extras/tests/test_scripts_deletion.py | 25 ++++++++++++++++++++
netbox/netbox/models/deletion.py | 7 +++++-
2 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/netbox/extras/tests/test_scripts_deletion.py b/netbox/extras/tests/test_scripts_deletion.py
index 5b928bd0d..1d5c92581 100644
--- a/netbox/extras/tests/test_scripts_deletion.py
+++ b/netbox/extras/tests/test_scripts_deletion.py
@@ -199,6 +199,16 @@ class ConfirmCollectorTestCase(TestCase):
self.assertEqual(list(wrapper), [])
self.assertTrue(wrapper.count_only)
+ def test_confirm_collector_omits_jobs_when_none(self):
+ # A jobless object must not record a zero count, or the confirmation page would show a
+ # spurious "0 jobs" row (#22812 regression).
+ datasource = DataSource.objects.create(name='DS', type='local', source_url='/tmp/test')
+
+ collector = ConfirmCollector(using=router.db_for_write(DataSource))
+ collector.collect([datasource])
+
+ self.assertNotIn(Job, collector.generic_relation_counts)
+
class ObjectDeleteViewCountsTestCase(ViewTestCase):
"""
@@ -258,3 +268,18 @@ class ObjectDeleteViewCountsTestCase(ViewTestCase):
self.assertIsInstance(dependent_objects[Job], CountOnly)
self.assertEqual(len(dependent_objects[Job]), 50)
self.assertTrue(dependent_objects[Job].count_only)
+
+ def test_get_dependent_objects_omits_jobs_when_none(self):
+ from netbox.views.generic.object_views import ObjectDeleteView
+
+ # A module with no jobs must not produce a CountOnly(0) entry (#22812 regression).
+ module = ScriptModule.objects.create(
+ file_root=ManagedFileRootPathChoices.SCRIPTS,
+ file_path=f'test_{uuid.uuid4().hex[:8]}.py',
+ )
+
+ view = ObjectDeleteView()
+ view.queryset = ScriptModule.objects.all()
+ dependent_objects = view._get_dependent_objects(module)
+
+ self.assertNotIn(Job, dependent_objects)
diff --git a/netbox/netbox/models/deletion.py b/netbox/netbox/models/deletion.py
index 0d9aa2c19..a903b7dff 100644
--- a/netbox/netbox/models/deletion.py
+++ b/netbox/netbox/models/deletion.py
@@ -59,7 +59,12 @@ class ConfirmCollector(Collector):
from core.models import Job
if source is not None and getattr(objs, 'model', None) is Job:
- self.generic_relation_counts[Job] = self.generic_relation_counts.get(Job, 0) + objs.count()
+ # Django calls this branch for the jobs relation even when there are none; only record
+ # a count when there are actually jobs, so jobless objects don't get a spurious
+ # "0 jobs" row on the delete-confirmation page.
+ count = objs.count()
+ if count:
+ self.generic_relation_counts[Job] = self.generic_relation_counts.get(Job, 0) + count
return None
return super().collect(objs, source=source, *args, **kwargs)
From 7d46c995f79637f0361fb230a4d8c746d6deb5df Mon Sep 17 00:00:00 2001
From: Jason Novinger
Date: Wed, 5 Aug 2026 14:04:19 -0500
Subject: [PATCH 04/15] Fixes #22812: Defer large Job payload fields during
batched deletion
The batched job delete can't fast-delete (a global pre_delete receiver forces
per-instance signals), so each batch still instantiates its Job rows. Load only the PK
via only('pk') so those instances don't pull the large data/log_entries payloads,
cutting the resident set per batch. Also drop a dead `no-toggle` CSS class from the
delete-confirmation template (it is defined nowhere and, under Tabler, has no effect)
and use JobStatusChoices.STATUS_COMPLETED in the tests instead of a string literal.
---
netbox/extras/tests/test_scripts_deletion.py | 16 +++++++++++-----
netbox/netbox/models/features.py | 5 ++++-
netbox/templates/htmx/delete_form.html | 2 +-
3 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/netbox/extras/tests/test_scripts_deletion.py b/netbox/extras/tests/test_scripts_deletion.py
index 1d5c92581..f6d9bad48 100644
--- a/netbox/extras/tests/test_scripts_deletion.py
+++ b/netbox/extras/tests/test_scripts_deletion.py
@@ -7,7 +7,7 @@ from django.db.models import QuerySet
from django.test import TestCase, override_settings
from django.urls import reverse
-from core.choices import ManagedFileRootPathChoices
+from core.choices import JobStatusChoices, ManagedFileRootPathChoices
from core.models import DataSource, Job
from extras.models import Script, ScriptModule
from extras.validators import CustomValidator
@@ -43,7 +43,7 @@ class ScriptDeletionTestCase(TestCase):
object_type=object_type,
object_id=obj.pk,
name='testjob',
- status='completed',
+ status=JobStatusChoices.STATUS_COMPLETED,
job_id=uuid.uuid4(),
data={'output': 'x' * 50},
)
@@ -164,7 +164,7 @@ class ConfirmCollectorTestCase(TestCase):
script = Script.objects.create(module=module, name=f'S{uuid.uuid4().hex[:8]}')
ct = ContentType.objects.get_for_model(Script, for_concrete_model=False)
Job.objects.bulk_create([
- Job(object_type=ct, object_id=script.pk, name='j', status='completed',
+ Job(object_type=ct, object_id=script.pk, name='j', status=JobStatusChoices.STATUS_COMPLETED,
job_id=uuid.uuid4(), data={'output': 'x' * 50})
for _ in range(count)
])
@@ -226,7 +226,10 @@ class ObjectDeleteViewCountsTestCase(ViewTestCase):
script = Script.objects.create(module=module, name=f'S{uuid.uuid4().hex[:8]}')
ct = ContentType.objects.get_for_model(Script, for_concrete_model=False)
Job.objects.bulk_create([
- Job(object_type=ct, object_id=script.pk, name='j', status='completed', job_id=uuid.uuid4())
+ Job(
+ object_type=ct, object_id=script.pk, name='j',
+ status=JobStatusChoices.STATUS_COMPLETED, job_id=uuid.uuid4(),
+ )
for _ in range(50)
])
@@ -249,7 +252,10 @@ class ObjectDeleteViewCountsTestCase(ViewTestCase):
script = Script.objects.create(module=module, name=f'S{uuid.uuid4().hex[:8]}')
ct = ContentType.objects.get_for_model(Script, for_concrete_model=False)
Job.objects.bulk_create([
- Job(object_type=ct, object_id=script.pk, name='j', status='completed', job_id=uuid.uuid4())
+ Job(
+ object_type=ct, object_id=script.pk, name='j',
+ status=JobStatusChoices.STATUS_COMPLETED, job_id=uuid.uuid4(),
+ )
for _ in range(50)
])
diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py
index f608da50c..4f1d5b684 100644
--- a/netbox/netbox/models/features.py
+++ b/netbox/netbox/models/features.py
@@ -464,7 +464,10 @@ def batch_delete_jobs(job_queryset):
# Re-slice the queryset each iteration: it re-queries after each batch delete, so the
# remaining set shrinks and the loop terminates (do not hoist this into a cursor).
while pks := list(job_pks[:JOB_DELETE_BATCH_SIZE]):
- Job.objects.filter(pk__in=pks).delete()
+ # only('pk'): the batch still can't fast-delete (a global pre_delete receiver forces
+ # per-instance signals), so each Job in the batch is instantiated. Loading just the PK
+ # avoids pulling the large data/log_entries payloads into those instances.
+ Job.objects.filter(pk__in=pks).only('pk').delete()
class JobsMixin(models.Model):
diff --git a/netbox/templates/htmx/delete_form.html b/netbox/templates/htmx/delete_form.html
index 8f24816a4..c07e83d0a 100644
--- a/netbox/templates/htmx/delete_form.html
+++ b/netbox/templates/htmx/delete_form.html
@@ -30,7 +30,7 @@
{# High-cardinality relations (e.g. jobs) are summarized by count and are not #}
{# expandable, since their instances are intentionally not loaded (see #22812). #}
{% if instances.count_only %}
-
From 6bd50ef07d618b1057c661c76768e6fec0eb5c84 Mon Sep 17 00:00:00 2001
From: Jason Novinger
Date: Wed, 5 Aug 2026 14:27:22 -0500
Subject: [PATCH 05/15] Fixes #22812: Note ConfirmCollector is intentionally
Job-specific
---
netbox/netbox/models/deletion.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/netbox/netbox/models/deletion.py b/netbox/netbox/models/deletion.py
index a903b7dff..b0600e9e2 100644
--- a/netbox/netbox/models/deletion.py
+++ b/netbox/netbox/models/deletion.py
@@ -40,6 +40,11 @@ class ConfirmCollector(Collector):
object can accumulate thousands of Jobs, each carrying large data/log_entries payloads;
materializing them all just to render a confirmation page can exhaust memory (see #22812).
Instead, the related Jobs are counted and recorded in `generic_relation_counts`.
+
+ This is intentionally specific to Job, the only high-cardinality GenericRelation in the
+ data model; it is not a general count-out over every GenericRelation. If another relation
+ ever needs the same treatment, extend the check in collect() (and the matching write-path
+ batching in JobsMixin/ScriptModule.delete) rather than assuming this already handles it.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
From f355a3de055c4ce0f76df1174866d0f0b016e8cd Mon Sep 17 00:00:00 2001
From: Jason Novinger
Date: Mon, 10 Aug 2026 09:39:43 -0500
Subject: [PATCH 06/15] =?UTF-8?q?Fixes=20#22812:=20Address=20review=20?=
=?UTF-8?q?=E2=80=94=20DB=20alias,=20batch=20size,=20MRO=20note?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- batch_delete_jobs now writes through the same DB alias it reads from. In JobsMixin.delete
the read queryset is bound to the instance's DB while Job.objects would use the router
default; if those diverged on a multi-DB setup the deleted rows never left the read side
and the batch loop never terminated.
- JobsMixin.delete and ScriptModule.delete honor a caller-supplied `using`, matching
DeleteMixin.delete, instead of always recomputing it.
- Raise JOB_DELETE_BATCH_SIZE from 100 to 1000 and correct its rationale. With only('pk')
the per-batch payload is gone, so the size now bounds per-cycle work rather than memory;
1000 matches EXPORT_CHUNK_SIZE and was the fastest of 100/1000/5000 when benchmarked
against a 200k-job deletion.
- Document that JobsMixin must precede DeleteMixin in the MRO or the batching is bypassed,
and scope the ScriptModule.delete comment so it doesn't imply the on-disk file removal is
transactional.
- Add a module-path rollback test alongside the existing script-path one.
---
netbox/extras/models/scripts.py | 13 +++----
netbox/extras/tests/test_scripts_deletion.py | 15 ++++++++
netbox/netbox/constants.py | 10 +++---
netbox/netbox/models/features.py | 36 +++++++++++++-------
4 files changed, 51 insertions(+), 23 deletions(-)
diff --git a/netbox/extras/models/scripts.py b/netbox/extras/models/scripts.py
index d9a2ca28c..042744fc6 100644
--- a/netbox/extras/models/scripts.py
+++ b/netbox/extras/models/scripts.py
@@ -120,7 +120,7 @@ class ScriptModule(PythonModuleMixin, JobsMixin, ManagedFile):
def __str__(self):
return self.python_name
- def delete(self, *args, **kwargs):
+ def delete(self, using=None, *args, **kwargs):
# Job is imported here rather than at module level to avoid a circular import
# (core.models.jobs -> core.signals -> extras.events -> extras.models -> this module).
from core.models import Job
@@ -129,17 +129,18 @@ class ScriptModule(PythonModuleMixin, JobsMixin, ManagedFile):
# Django's collector would materialize every one of those Scripts' Jobs to delete them.
# A module's scripts can accumulate thousands of jobs, exhausting memory. Batch-delete
# the child Scripts' jobs up front, in a single queryset (no per-script loop), before
- # delegating to the cascade. Wrapped in a transaction so a failure in the parent delete
- # rolls these deletions back as well. See #22812.
- using = router.db_for_write(self.__class__, instance=self)
+ # delegating to the cascade. The transaction rolls the job deletions back if the parent
+ # delete fails; note it does not cover ManagedFile.delete removing the file from disk,
+ # which happens before the DB delete and is not transactional. See #22812.
+ using = using or router.db_for_write(self.__class__, instance=self)
with transaction.atomic(using=using):
script_type = ContentType.objects.get_for_model(Script, for_concrete_model=False)
- child_jobs = Job.objects.filter(
+ child_jobs = Job.objects.using(using).filter(
object_type=script_type,
object_id__in=self.scripts.values_list('pk', flat=True),
)
batch_delete_jobs(child_jobs)
- return super().delete(*args, **kwargs)
+ return super().delete(using, *args, **kwargs)
delete.alters_data = True
@property
diff --git a/netbox/extras/tests/test_scripts_deletion.py b/netbox/extras/tests/test_scripts_deletion.py
index f6d9bad48..7419de359 100644
--- a/netbox/extras/tests/test_scripts_deletion.py
+++ b/netbox/extras/tests/test_scripts_deletion.py
@@ -151,6 +151,21 @@ class ScriptDeletionTestCase(TestCase):
self.assertTrue(Script.objects.filter(pk=script.pk).exists())
self.assertEqual(Job.objects.filter(object_type=self.script_ct, object_id=script.pk).count(), 10)
+ @override_settings(PROTECTION_RULES={'extras.script': [CustomValidator({'name': {'eq': ''}})]})
+ def test_delete_scriptmodule_rolls_back_child_jobs_on_failure(self):
+ # Same abort path via the module: the protection rule fires when the cascade pre_deletes
+ # the child Script, after ScriptModule.delete has already batch-deleted that script's jobs.
+ # The transaction must roll those job deletions back, leaving no orphaned partial state.
+ module, script = self._create_script()
+ self._add_jobs(script, 10)
+
+ with self.assertRaises(AbortRequest):
+ module.delete()
+
+ self.assertTrue(ScriptModule.objects.filter(pk=module.pk).exists())
+ self.assertTrue(Script.objects.filter(pk=script.pk).exists())
+ self.assertEqual(Job.objects.filter(object_type=self.script_ct, object_id=script.pk).count(), 10)
+
class ConfirmCollectorTestCase(TestCase):
"""
diff --git a/netbox/netbox/constants.py b/netbox/netbox/constants.py
index fddce0ad9..096ae145b 100644
--- a/netbox/netbox/constants.py
+++ b/netbox/netbox/constants.py
@@ -75,7 +75,9 @@ CENSOR_TOKEN_CHANGED = '***CHANGED***'
# Placeholder text for empty tables
EMPTY_TABLE_TEXT = 'No results found'
-# Batch size for deleting a JobsMixin object's associated jobs during cascade deletion.
-# Kept small because each Job carries potentially large data/log_entries payloads and
-# cannot be fast-deleted (a global pre_delete receiver forces per-instance signals). See #22812.
-JOB_DELETE_BATCH_SIZE = 100
+# Batch size for deleting a JobsMixin object's associated jobs during cascade deletion. Job
+# cannot be fast-deleted (a global pre_delete receiver forces per-instance signals), so deleting
+# in chunks bounds the work per delete cycle rather than building one huge collection and running
+# one long DELETE. 1000 matches EXPORT_CHUNK_SIZE and, in benchmarking a 200k-job deletion, was
+# the fastest of 100/1000/5000 while keeping peak memory flat. See #22812.
+JOB_DELETE_BATCH_SIZE = 1000
diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py
index 4f1d5b684..1cf46d013 100644
--- a/netbox/netbox/models/features.py
+++ b/netbox/netbox/models/features.py
@@ -452,27 +452,37 @@ class NotificationsMixin(models.Model):
def batch_delete_jobs(job_queryset):
"""
- Delete the Jobs in `job_queryset` in JOB_DELETE_BATCH_SIZE chunks, so the caller never has
- to load thousands of Job rows (each carrying potentially large data/log_entries payloads)
- into memory at once. Callers are responsible for wrapping this in a transaction. As with the
- prior cascade behavior, this bulk delete does not invoke Job.delete() and therefore does not
- cancel the backing RQ job. See #22812.
+ Delete the Jobs in `job_queryset` in JOB_DELETE_BATCH_SIZE chunks. Job cannot be fast-deleted
+ (a global pre_delete receiver forces per-instance signals), so a single delete would build one
+ huge collection of Job instances and run one very long DELETE; batching bounds the per-cycle
+ work. Callers are responsible for wrapping this in a transaction. As with the prior cascade
+ behavior, this bulk delete does not invoke Job.delete() and therefore does not cancel the
+ backing RQ job. See #22812.
"""
from core.models import Job
+ # Route writes to the same database the queryset reads from. In JobsMixin.delete the queryset
+ # is bound to the instance's DB while Job.objects would otherwise use the router default; if
+ # those diverge the deleted rows never leave the read side and the loop below never terminates.
+ jobs = Job.objects.using(job_queryset.db)
+
job_pks = job_queryset.order_by('pk').values_list('pk', flat=True)
# Re-slice the queryset each iteration: it re-queries after each batch delete, so the
# remaining set shrinks and the loop terminates (do not hoist this into a cursor).
while pks := list(job_pks[:JOB_DELETE_BATCH_SIZE]):
- # only('pk'): the batch still can't fast-delete (a global pre_delete receiver forces
- # per-instance signals), so each Job in the batch is instantiated. Loading just the PK
- # avoids pulling the large data/log_entries payloads into those instances.
- Job.objects.filter(pk__in=pks).only('pk').delete()
+ # only('pk'): the batch still can't fast-delete, so each Job in the batch is instantiated;
+ # loading just the PK avoids pulling the large data/log_entries payloads into memory.
+ jobs.filter(pk__in=pks).only('pk').delete()
class JobsMixin(models.Model):
"""
Enables support for job results.
+
+ Note: for the job-batching in delete() to run, JobsMixin must precede DeleteMixin in a
+ model's MRO. DeleteMixin.delete() drives its own collector and does not call super(), so a
+ model declared as e.g. `class Foo(NetBoxModel, JobsMixin)` would reach DeleteMixin first and
+ bypass the batching. Core models that combine both (e.g. DataSource) list JobsMixin first.
"""
jobs = GenericRelation(
to='core.Job',
@@ -484,14 +494,14 @@ class JobsMixin(models.Model):
class Meta:
abstract = True
- def delete(self, *args, **kwargs):
+ def delete(self, using=None, *args, **kwargs):
# Delete associated jobs in batches so the cascade never has to load thousands of Job
# rows into memory at once. Wrapped in a transaction so that a failure in the parent
# delete rolls the job deletions back as well. See #22812.
- using = router.db_for_write(self.__class__, instance=self)
+ using = using or router.db_for_write(self.__class__, instance=self)
with transaction.atomic(using=using):
- batch_delete_jobs(self.jobs)
- return super().delete(*args, **kwargs)
+ batch_delete_jobs(self.jobs.using(using))
+ return super().delete(using, *args, **kwargs)
delete.alters_data = True
def get_latest_jobs(self):
From a7cf21a068d7fa06a23dc8b4ffe9048fc0db8af8 Mon Sep 17 00:00:00 2001
From: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 11 Aug 2026 05:23:07 +0000
Subject: [PATCH 07/15] Update source translation strings
---
netbox/translations/en/LC_MESSAGES/django.po | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po
index 59ce07ef7..417872f50 100644
--- a/netbox/translations/en/LC_MESSAGES/django.po
+++ b/netbox/translations/en/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2026-08-07 05:30+0000\n"
+"POT-Creation-Date: 2026-08-11 05:22+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -8664,12 +8664,12 @@ msgstr ""
msgid "Ignoring invalid action_data on event rule \"{rule}\" (got {data_type})"
msgstr ""
-#: netbox/extras/events.py:276
+#: netbox/extras/events.py:278
#, python-brace-format
msgid "Unknown action type for an event rule: {action_type}"
msgstr ""
-#: netbox/extras/events.py:319
+#: netbox/extras/events.py:321
#, python-brace-format
msgid "Cannot import events pipeline {name} error: {error}"
msgstr ""
From a94878aa08cf425866f56850e5d35996bf292cc3 Mon Sep 17 00:00:00 2001
From: Arthur Hanson
Date: Tue, 11 Aug 2026 05:09:13 -0700
Subject: [PATCH 08/15] 22745 - Enforce object permissions on Script REST API
write operations (#22777)
---
netbox/extras/api/routers.py | 34 ++++
netbox/extras/api/urls.py | 5 +-
netbox/extras/api/views.py | 89 ++++++---
netbox/extras/tests/test_api.py | 230 +++++++++++++++++++++++-
netbox/extras/tests/test_api_routers.py | 52 ++++++
netbox/netbox/api/viewsets/__init__.py | 8 +
6 files changed, 387 insertions(+), 31 deletions(-)
create mode 100644 netbox/extras/api/routers.py
create mode 100644 netbox/extras/tests/test_api_routers.py
diff --git a/netbox/extras/api/routers.py b/netbox/extras/api/routers.py
new file mode 100644
index 000000000..e27797d7b
--- /dev/null
+++ b/netbox/extras/api/routers.py
@@ -0,0 +1,34 @@
+from rest_framework.routers import Route
+
+from netbox.api.routers import NetBoxRouter
+
+from .views import ScriptViewSet
+
+__all__ = (
+ 'ScriptRouter',
+)
+
+
+class ScriptRouter(NetBoxRouter):
+ """
+ Extend NetBoxRouter to map POST on the script detail route to ScriptViewSet.run(). DRF's detail route
+ maps only the standard CRUD methods; absent this, run() must be declared as a raw post() method, which
+ binds to every route of the ViewSet and is invisible to per-action permissions & schema generation.
+ """
+ def get_routes(self, viewset):
+ if not issubclass(viewset, ScriptViewSet):
+ return super().get_routes(viewset)
+
+ # Extend the detail route template. Applied before super() expands the templates so that any
+ # @action routes are untouched; _replace() avoids mutating the templates shared by all routers.
+ routes = self.routes
+ self.routes = [
+ route._replace(mapping={**route.mapping, 'post': 'run'})
+ if isinstance(route, Route) and route.detail else route
+ for route in routes
+ ]
+
+ try:
+ return super().get_routes(viewset)
+ finally:
+ self.routes = routes
diff --git a/netbox/extras/api/urls.py b/netbox/extras/api/urls.py
index cd1a9f683..dcc359c7a 100644
--- a/netbox/extras/api/urls.py
+++ b/netbox/extras/api/urls.py
@@ -1,10 +1,9 @@
from django.urls import include, path
-from netbox.api.routers import NetBoxRouter
-
from . import views
+from .routers import ScriptRouter
-router = NetBoxRouter()
+router = ScriptRouter()
router.APIRootView = views.ExtrasRootView
router.register('event-rules', views.EventRuleViewSet)
diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py
index d7c21c86b..12827b938 100644
--- a/netbox/extras/api/views.py
+++ b/netbox/extras/api/views.py
@@ -1,16 +1,15 @@
from django.http import Http404
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
-from drf_spectacular.utils import OpenApiResponse, OpenApiTypes, extend_schema, extend_schema_view
+from drf_spectacular.utils import OpenApiResponse, OpenApiTypes, extend_schema
from rest_framework import status
from rest_framework.decorators import action
-from rest_framework.exceptions import PermissionDenied
+from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from rest_framework.mixins import CreateModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.routers import APIRootView
-from rest_framework.viewsets import ModelViewSet
from core.choices import ManagedFileRootPathChoices
from extras import filtersets
@@ -22,6 +21,7 @@ from netbox.api.metadata import ContentTypeMetadata
from netbox.api.renderers import TextRenderer
from netbox.api.viewsets import BaseViewSet, NetBoxModelViewSet
from netbox.api.viewsets.mixins import ObjectValidationMixin
+from users.models import Token
from utilities.exceptions import RQWorkerNotRunningException
from utilities.request import copy_safe_request
from utilities.rqworker import any_workers_for_queue
@@ -307,30 +307,44 @@ class ScriptModuleViewSet(ObjectValidationMixin, CreateModelMixin, UpdateModelMi
return obj
-@extend_schema_view(
- update=extend_schema(request=serializers.ScriptInputSerializer),
- partial_update=extend_schema(request=serializers.ScriptInputSerializer),
-)
-class ScriptViewSet(ModelViewSet):
+class ScriptViewSet(ListModelMixin, RetrieveModelMixin, BaseViewSet):
+ # Individual scripts are created, modified, and deleted through their module (see ScriptModuleViewSet),
+ # so the standard write actions are intentionally omitted here. Only listing/retrieving a script (GET)
+ # and running one (POST to the detail route) are supported.
permission_classes = [IsAuthenticatedOrLoginNotRequired]
queryset = Script.objects.all()
serializer_class = serializers.ScriptSerializer
filterset_class = filtersets.ScriptFilterSet
- _ignore_model_permissions = True
lookup_value_regex = '[^/]+' # Allow dots
- def initial(self, request, *args, **kwargs):
- super().initial(request, *args, **kwargs)
+ def get_serializer(self, *args, **kwargs):
+ # A POST to the detail route runs the script, taking ScriptInputSerializer as its request body.
+ # (This is keyed on the request method rather than on self.action, which is unset when generating
+ # OPTIONS metadata.) ScriptInputSerializer is instantiated directly rather than via BaseViewSet,
+ # which would pass it the fields/omit kwargs supported only by BaseModelSerializer.
+ if getattr(self.request, 'method', None) == 'POST':
+ kwargs.setdefault('context', self.get_serializer_context())
+ return serializers.ScriptInputSerializer(*args, **kwargs)
+ return super().get_serializer(*args, **kwargs)
- # Restrict the view's QuerySet to allow only the permitted objects
- if request.user.is_authenticated:
- action = 'run' if request.method == 'POST' else 'view'
- self.queryset = self.queryset.restrict(request.user, action)
+ def get_serializer_context(self):
+ context = super().get_serializer_context()
+
+ # ScriptInputSerializer resolves its field defaults and validates scheduling against the script
+ # being run (set by run() below).
+ context['script'] = getattr(self, 'script', None)
+
+ return context
def _get_script(self, pk):
- # If pk is numeric, retrieve script by ID
- if pk.isnumeric():
+ # Retrieve the script by ID if the PK is all decimal digits. (isdecimal() rather than isnumeric(),
+ # as the latter also matches characters which cannot be cast to an integer.)
+ if pk.isdecimal():
+ try:
+ pk = int(pk)
+ except ValueError:
+ raise Http404
return get_object_or_404(self.queryset, pk=pk)
# Default to retrieval by module & name
@@ -341,26 +355,49 @@ class ScriptViewSet(ModelViewSet):
return get_object_or_404(self.queryset, module__file_path=f'{module_name}.py', name=script_name)
- def retrieve(self, request, pk):
+ def retrieve(self, request, pk, **kwargs):
script = self._get_script(pk)
serializer = serializers.ScriptDetailSerializer(script, context={'request': request})
return Response(serializer.data)
- def post(self, request, pk):
+ @extend_schema(
+ operation_id='extras_scripts_run',
+ request=serializers.ScriptInputSerializer,
+ responses={
+ 200: OpenApiResponse(
+ response=serializers.ScriptDetailSerializer,
+ description=_("The script has been enqueued for execution."),
+ ),
+ },
+ )
+ def run(self, request, pk, **kwargs):
"""
Run a Script identified by its numeric PK or module & name and return the pending Job as the result
"""
+ # Bound to POST on the detail route by ScriptRouter
- script = self._get_script(pk)
+ # Reject read-only tokens before resolving the script, so that an insufficient token is always
+ # reported as such. (Not via TokenWritePermission, which permits token auth only.)
+ if isinstance(request.auth, Token) and not request.auth.write_enabled:
+ raise PermissionDenied(_("This token does not permit write operations (running a script)."))
- if not request.user.has_perm('extras.run_script', obj=script):
- raise PermissionDenied("This user does not have permission to run this script.")
+ # An unauthenticated user can never run a script; report that explicitly, as restrict() below would
+ # match no scripts and yield a misleading 404.
+ if not request.user.is_authenticated:
+ raise PermissionDenied(_("This user does not have permission to run this script."))
- input_serializer = serializers.ScriptInputSerializer(
- data=request.data,
- context={'script': script}
- )
+ # Running a script is a 'run' operation (not the 'add' that BaseViewSet maps to POST), so restrict
+ # the QuerySet on 'run' before resolving the script. A script the user cannot run yields a 404.
+ self.queryset = self.queryset.model.objects.restrict(request.user, 'run')
+ self.script = script = self._get_script(pk)
+
+ # A script whose Python class cannot be resolved (e.g. its module has been modified or the script has
+ # been deleted, retaining the record for its jobs) cannot be run
+ if not script.is_executable or script.python_class is None:
+ raise ValidationError(_("This script is not currently executable."))
+
+ input_serializer = self.get_serializer(data=request.data)
# Check that at least one RQ worker is running
if not any_workers_for_queue('default'):
diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py
index 89af94be6..cc94bd09c 100644
--- a/netbox/extras/tests/test_api.py
+++ b/netbox/extras/tests/test_api.py
@@ -8,13 +8,14 @@ from unittest.mock import MagicMock, patch
from django.contrib.contenttypes.models import ContentType
from django.core.files.uploadedfile import SimpleUploadedFile
from django.db import IntegrityError
+from django.test import override_settings
from django.urls import reverse
from django.utils.timezone import make_aware, now
from rest_framework import status
from core.choices import ManagedFileRootPathChoices
from core.events import *
-from core.models import DataFile, DataSource, ObjectType
+from core.models import DataFile, DataSource, Job, ObjectType
from dcim.models import Device, DeviceRole, DeviceType, Location, Manufacturer, Rack, RackRole, Site
from extras.choices import *
from extras.models import *
@@ -23,7 +24,7 @@ from extras.scripts import Script as PythonClass
from users.constants import TOKEN_PREFIX
from users.models import Group, ObjectPermission, Token, User
from utilities.tables import get_table_for_model
-from utilities.testing import APITestCase, APIViewTestCases
+from utilities.testing import APITestCase, APIViewTestCases, disable_warnings
class AppTestCase(APITestCase):
@@ -1403,6 +1404,34 @@ class ScriptTestCase(APITestCase):
self.assertEqual(response.data['vars']['var2'], 'IntegerVar')
self.assertEqual(response.data['vars']['var3'], 'BooleanVar')
+ def test_list_scripts(self):
+ """
+ The list route is served by BaseViewSet, which resolves the QuerySet's prefetches & annotations (and
+ any fields/omit request parameters) from the serializer.
+ """
+ url = reverse('extras-api:script-list')
+
+ response = self.client.get(url, **self.header)
+ self.assertHttpStatus(response, status.HTTP_200_OK)
+ self.assertEqual(response.data['count'], 1)
+ self.assertEqual(response.data['results'][0]['name'], self.TestScriptClass.Meta.name)
+
+ response = self.client.get(f'{url}?fields=id,name', **self.header)
+ self.assertHttpStatus(response, status.HTTP_200_OK)
+ self.assertEqual(sorted(response.data['results'][0]), ['id', 'name'])
+
+ def test_get_script_by_module_and_name(self):
+ """
+ A script may also be identified by its module & name, e.g. /api/extras/scripts/example.MyReport/.
+ """
+ script = Script.objects.first()
+ url = reverse('extras-api:script-detail', kwargs={'pk': f'script.{script.name}'})
+
+ response = self.client.get(url, **self.header)
+
+ self.assertHttpStatus(response, status.HTTP_200_OK)
+ self.assertEqual(response.data['id'], script.pk)
+
def test_schedule_script_past_time_rejected(self):
"""
Scheduling with past schedule_at should fail.
@@ -1473,6 +1502,203 @@ class ScriptTestCase(APITestCase):
# Restore the original setting for other tests
self.TestScriptClass.Meta.scheduling_enabled = original
+ def test_run_script_without_permission(self):
+ """
+ A user permitted to view a script but not to run it must not be able to enqueue it. (The script is
+ excluded from the restricted QuerySet, so the request yields a 404.)
+ """
+ payload = {'data': {'var1': 'hello', 'var2': 1, 'var3': False}, 'commit': True}
+
+ # setUp() grants only extras.view_script
+ with disable_warnings('django.request'):
+ response = self.client.post(self.url, payload, format='json', **self.header)
+ self.assertHttpStatus(response, status.HTTP_404_NOT_FOUND)
+ self.assertFalse(Job.objects.exists())
+
+ # Granting the run permission permits the same request
+ self.add_permissions('extras.run_script')
+ response = self.client.post(self.url, payload, format='json', **self.header)
+ self.assertHttpStatus(response, status.HTTP_200_OK)
+ self.assertTrue(Job.objects.exists())
+
+ @override_settings(LOGIN_REQUIRED=False, EXEMPT_VIEW_PERMISSIONS=['*'])
+ def test_run_script_anonymous(self):
+ """
+ An unauthenticated user must be told that running a script is not permitted, rather than that the
+ script does not exist.
+ """
+ payload = {'data': {'var1': 'hello', 'var2': 1, 'var3': False}, 'commit': True}
+
+ with disable_warnings('django.request'):
+ response = self.client.post(self.url, payload, format='json')
+ self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
+ self.assertFalse(Job.objects.exists())
+
+ def test_run_script_read_only_token(self):
+ """
+ Running a script is a write operation and must be rejected for a read-only token.
+ """
+ self.add_permissions('extras.run_script')
+ payload = {'data': {'var1': 'hello', 'var2': 1, 'var3': False}, 'commit': True}
+
+ # A write-disabled token should be rejected
+ ro_token = Token.objects.create(version=2, user=self.user, write_enabled=False)
+ ro_header = {'HTTP_AUTHORIZATION': f'Bearer {TOKEN_PREFIX}{ro_token.key}.{ro_token.token}'}
+ response = self.client.post(self.url, payload, format='json', **ro_header)
+ self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
+
+ # The default (write-enabled) token should succeed
+ response = self.client.post(self.url, payload, format='json', **self.header)
+ self.assertHttpStatus(response, status.HTTP_200_OK)
+
+ def test_run_script_read_only_token_without_permission(self):
+ """
+ A read-only token is rejected before the script is resolved, so an insufficient token is reported as
+ such regardless of the user's permission to run the script.
+ """
+ payload = {'data': {'var1': 'hello', 'var2': 1, 'var3': False}, 'commit': True}
+
+ # setUp() grants only extras.view_script
+ ro_token = Token.objects.create(version=2, user=self.user, write_enabled=False)
+ ro_header = {'HTTP_AUTHORIZATION': f'Bearer {TOKEN_PREFIX}{ro_token.key}.{ro_token.token}'}
+ response = self.client.post(self.url, payload, format='json', **ro_header)
+ self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
+
+ def test_run_script_not_executable(self):
+ """
+ A script whose Python class cannot be resolved must be rejected, not raise an exception.
+ """
+ self.add_permissions('extras.run_script')
+ payload = {'data': {'var1': 'hello', 'var2': 1, 'var3': False}, 'commit': True}
+
+ # Simulate a script whose class can no longer be found in its module
+ class_patch = patch.object(Script, 'python_class', None)
+ class_patch.start()
+ self.addCleanup(class_patch.stop)
+
+ response = self.client.post(self.url, payload, format='json', **self.header)
+ self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(Job.objects.exists())
+
+ def test_run_script_by_module_and_name(self):
+ """
+ A script identified by its module & name (rather than by its PK) must also be runnable.
+ """
+ self.add_permissions('extras.run_script')
+ payload = {'data': {'var1': 'hello', 'var2': 1, 'var3': False}, 'commit': True}
+ script = Script.objects.first()
+ url = reverse('extras-api:script-detail', kwargs={'pk': f'script.{script.name}'})
+
+ response = self.client.post(url, payload, format='json', **self.header)
+
+ self.assertHttpStatus(response, status.HTTP_200_OK)
+ self.assertEqual(response.data['id'], script.pk)
+ self.assertTrue(Job.objects.exists())
+
+ def test_run_script_format_suffix(self):
+ """
+ The format-suffix variants of the detail route (e.g. /1.json) must dispatch to run().
+ """
+ self.add_permissions('extras.run_script')
+ payload = {'data': {'var1': 'hello', 'var2': 1, 'var3': False}, 'commit': True}
+ script = Script.objects.first()
+ lookups = (script.pk, f'script.{script.name}')
+
+ for lookup in lookups:
+ with self.subTest(lookup=lookup):
+ url = reverse('extras-api:script-detail', kwargs={'pk': lookup, 'format': 'json'})
+
+ response = self.client.post(url, payload, format='json', **self.header)
+
+ self.assertHttpStatus(response, status.HTTP_200_OK)
+ self.assertEqual(response.data['id'], script.pk)
+
+ self.assertEqual(Job.objects.count(), len(lookups))
+
+ def test_modify_script_methods_disabled(self):
+ """
+ Individual scripts are created, modified, and deleted through their module, so PUT/PATCH/DELETE on
+ the script endpoint are not supported (even for a user holding the corresponding permissions).
+ """
+ self.add_permissions('extras.change_script', 'extras.delete_script')
+ script = Script.objects.first()
+
+ for method in ('put', 'patch', 'delete'):
+ with self.subTest(method=method):
+ with disable_warnings('django.request'):
+ response = getattr(self.client, method)(self.url, {}, format='json', **self.header)
+ self.assertHttpStatus(response, status.HTTP_405_METHOD_NOT_ALLOWED)
+
+ # The script must remain untouched
+ self.assertTrue(Script.objects.filter(pk=script.pk).exists())
+
+ def test_create_script_disabled(self):
+ """
+ Scripts cannot be created via the API: POST is mapped only on the detail route (to run a script),
+ and must be neither permitted nor advertised on the list route.
+ """
+ self.add_permissions('extras.add_script')
+ list_url = reverse('extras-api:script-list')
+
+ with disable_warnings('django.request'):
+ response = self.client.post(list_url, {}, format='json', **self.header)
+ self.assertHttpStatus(response, status.HTTP_405_METHOD_NOT_ALLOWED)
+
+ # OPTIONS must not advertise a create action for the list route
+ response = self.client.options(list_url, **self.header)
+ self.assertHttpStatus(response, status.HTTP_200_OK)
+ self.assertNotIn('POST', response.data.get('actions', {}))
+
+ def test_options_detail_route(self):
+ """
+ POST on the detail route runs a script, so its OPTIONS metadata must describe the run input
+ rather than the Script model's own fields.
+ """
+ self.add_permissions('extras.run_script')
+
+ response = self.client.options(self.url, **self.header)
+ self.assertHttpStatus(response, status.HTTP_200_OK)
+ post_fields = response.data['actions']['POST']
+ self.assertIn('data', post_fields)
+ self.assertIn('commit', post_fields)
+ self.assertNotIn('module', post_fields)
+ self.assertNotIn('name', post_fields)
+
+ def test_options_detail_route_dynamic_fields(self):
+ """
+ The run input serializer does not support the fields/omit query parameters, but their presence must
+ not break the generation of OPTIONS metadata.
+ """
+ self.add_permissions('extras.run_script')
+
+ for query in ('fields=id', 'omit=id'):
+ with self.subTest(query=query):
+ response = self.client.options(f'{self.url}?{query}', **self.header)
+
+ self.assertHttpStatus(response, status.HTTP_200_OK)
+ self.assertIn('data', response.data['actions']['POST'])
+
+ def test_unsupported_method(self):
+ """
+ A request using an HTTP method which maps to no action must be rejected with a 405.
+ """
+ with disable_warnings('django.request'):
+ response = self.client.trace(self.url, **self.header)
+ self.assertHttpStatus(response, status.HTTP_405_METHOD_NOT_ALLOWED)
+
+ def test_get_script_invalid_pk(self):
+ """
+ A PK which cannot be cast to an integer must yield a 404, not a server error. This covers numeric (but
+ non-decimal) characters, as well as a decimal value too long for Python to convert.
+ """
+ for pk in ('½', '1' * 5000):
+ with self.subTest(pk=pk[:10]):
+ url = reverse('extras-api:script-detail', kwargs={'pk': pk})
+
+ with disable_warnings('django.request'):
+ response = self.client.get(url, **self.header)
+ self.assertHttpStatus(response, status.HTTP_404_NOT_FOUND)
+
class CreatedUpdatedFilterTestCase(APITestCase):
diff --git a/netbox/extras/tests/test_api_routers.py b/netbox/extras/tests/test_api_routers.py
new file mode 100644
index 000000000..25c791511
--- /dev/null
+++ b/netbox/extras/tests/test_api_routers.py
@@ -0,0 +1,52 @@
+from django.test import TestCase
+
+from extras.api.routers import ScriptRouter
+from extras.api.views import CustomFieldChoiceSetViewSet, ScriptViewSet, WebhookViewSet
+
+
+class ScriptRouterTestCase(TestCase):
+ """
+ Verify the routes generated by ScriptRouter.
+ """
+ @staticmethod
+ def get_actions(viewset):
+ """
+ Return a mapping of route name to the HTTP methods bound on it for the given ViewSet.
+ """
+ router = ScriptRouter()
+ router.register('dummy', viewset, basename='dummy')
+
+ return {
+ url.name: url.callback.actions
+ for url in router.urls if hasattr(url.callback, 'actions')
+ }
+
+ def test_script_routes(self):
+ actions = self.get_actions(ScriptViewSet)
+
+ # POST on the detail route runs the script; the list route accepts only GET
+ self.assertEqual(actions['dummy-detail'], {'get': 'retrieve', 'post': 'run'})
+ self.assertEqual(actions['dummy-list'], {'get': 'list'})
+
+ def test_script_viewset_subclass(self):
+ # A subclass of ScriptViewSet (e.g. as registered by a plugin) gets the same route mapping
+ class MyScriptViewSet(ScriptViewSet):
+ pass
+
+ actions = self.get_actions(MyScriptViewSet)
+
+ self.assertEqual(actions['dummy-detail'], {'get': 'retrieve', 'post': 'run'})
+ self.assertEqual(actions['dummy-list'], {'get': 'list'})
+
+ def test_other_viewsets_unaffected(self):
+ # Standard ViewSets keep the stock detail route mapping
+ self.assertNotIn('post', self.get_actions(WebhookViewSet)['dummy-detail'])
+
+ # Routes generated for @action methods are untouched
+ self.assertEqual(self.get_actions(CustomFieldChoiceSetViewSet)['dummy-choices'], {'get': 'choices'})
+
+ def test_route_templates_not_mutated(self):
+ router = ScriptRouter()
+ router.get_routes(ScriptViewSet)
+
+ self.assertNotIn('post', router.routes[2].mapping)
diff --git a/netbox/netbox/api/viewsets/__init__.py b/netbox/netbox/api/viewsets/__init__.py
index 95b2351d3..36f330f54 100644
--- a/netbox/netbox/api/viewsets/__init__.py
+++ b/netbox/netbox/api/viewsets/__init__.py
@@ -7,6 +7,7 @@ from django.db.models import ProtectedError, RestrictedError
from django_pglocks import advisory_lock
from rest_framework import mixins as drf_mixins
from rest_framework import status
+from rest_framework.exceptions import MethodNotAllowed
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
@@ -87,6 +88,13 @@ class BaseViewSet(GenericViewSet):
def initial(self, request, *args, **kwargs):
super().initial(request, *args, **kwargs)
+ # Reject any method for which no action has been declared, rather than proceeding against an
+ # unrestricted QuerySet. (A method mapped to None, e.g. OPTIONS, is permitted: it needs no
+ # restriction.) This is the same 405 DRF would return when resolving the handler for an unmapped
+ # method, but it also covers a handler bound to such a method (e.g. @action(methods=['trace'])).
+ if request.method not in HTTP_ACTIONS:
+ raise MethodNotAllowed(request.method)
+
# Restrict the view's QuerySet to allow only the permitted objects
if request.user.is_authenticated:
if action := HTTP_ACTIONS[request.method]:
From 257c322e486312cd1152ac6d531eef8ae8305a99 Mon Sep 17 00:00:00 2001
From: Jeremy Stretch
Date: Tue, 11 Aug 2026 08:50:30 -0400
Subject: [PATCH 09/15] Revert "Fixes #22854: Set USE_SHADOW_DOM=False to fix
GraphiQL queries w/debug enabled"
This reverts commit 30d2c9b5375bd6bfd966ade2c5c4426b90fcd161.
---
netbox/netbox/settings.py | 4 ----
1 file changed, 4 deletions(-)
diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py
index 1f7a92764..f334a73a7 100644
--- a/netbox/netbox/settings.py
+++ b/netbox/netbox/settings.py
@@ -634,10 +634,6 @@ SERIALIZATION_MODULES = {
DEBUG_TOOLBAR_CONFIG = {
'SHOW_TOOLBAR_CALLBACK': 'utilities.debug.show_toolbar',
- # The GraphiQL integration provided by strawberry-django locates the toolbar via
- # document.getElementById('djDebug'), which fails when the toolbar is rendered inside a
- # shadow root (the default as of django-debug-toolbar v7.0).
- 'USE_SHADOW_DOM': False,
}
From 3db98de783c02038775b78f9804a95681f78eb7b Mon Sep 17 00:00:00 2001
From: Jeremy Stretch
Date: Tue, 11 Aug 2026 09:06:07 -0400
Subject: [PATCH 10/15] Release v4.6.8
---
contrib/openapi.json | 260 +++----
docs/release-notes/version-4.6.md | 27 +
netbox/project-static/dist/netbox.js | 8 +-
netbox/project-static/dist/netbox.js.map | 6 +-
netbox/project-static/package.json | 14 +-
netbox/project-static/yarn.lock | 436 ++++++------
netbox/release.yaml | 3 +-
netbox/translations/cs/LC_MESSAGES/django.mo | Bin 282284 -> 282960 bytes
netbox/translations/cs/LC_MESSAGES/django.po | 707 +++++++++---------
netbox/translations/da/LC_MESSAGES/django.mo | Bin 273575 -> 274250 bytes
netbox/translations/da/LC_MESSAGES/django.po | 707 +++++++++---------
netbox/translations/de/LC_MESSAGES/django.mo | Bin 287964 -> 288696 bytes
netbox/translations/de/LC_MESSAGES/django.po | 711 ++++++++++---------
netbox/translations/es/LC_MESSAGES/django.mo | Bin 290174 -> 290871 bytes
netbox/translations/es/LC_MESSAGES/django.po | 707 +++++++++---------
netbox/translations/fr/LC_MESSAGES/django.mo | Bin 292693 -> 293399 bytes
netbox/translations/fr/LC_MESSAGES/django.po | 707 +++++++++---------
netbox/translations/it/LC_MESSAGES/django.mo | Bin 287436 -> 288090 bytes
netbox/translations/it/LC_MESSAGES/django.po | 707 +++++++++---------
netbox/translations/ja/LC_MESSAGES/django.mo | Bin 310187 -> 310991 bytes
netbox/translations/ja/LC_MESSAGES/django.po | 706 +++++++++---------
netbox/translations/ko/LC_MESSAGES/django.mo | Bin 282036 -> 282778 bytes
netbox/translations/ko/LC_MESSAGES/django.po | 705 +++++++++---------
netbox/translations/lv/LC_MESSAGES/django.mo | Bin 281482 -> 282146 bytes
netbox/translations/lv/LC_MESSAGES/django.po | 711 ++++++++++---------
netbox/translations/nl/LC_MESSAGES/django.mo | Bin 282840 -> 283501 bytes
netbox/translations/nl/LC_MESSAGES/django.po | 709 +++++++++---------
netbox/translations/pl/LC_MESSAGES/django.mo | Bin 285565 -> 286253 bytes
netbox/translations/pl/LC_MESSAGES/django.po | 707 +++++++++---------
netbox/translations/pt/LC_MESSAGES/django.mo | Bin 285062 -> 285730 bytes
netbox/translations/pt/LC_MESSAGES/django.po | 709 +++++++++---------
netbox/translations/ru/LC_MESSAGES/django.mo | Bin 367681 -> 368613 bytes
netbox/translations/ru/LC_MESSAGES/django.po | 711 ++++++++++---------
netbox/translations/tr/LC_MESSAGES/django.mo | Bin 278311 -> 279025 bytes
netbox/translations/tr/LC_MESSAGES/django.po | 706 +++++++++---------
netbox/translations/uk/LC_MESSAGES/django.mo | Bin 365640 -> 366554 bytes
netbox/translations/uk/LC_MESSAGES/django.po | 711 ++++++++++---------
netbox/translations/zh/LC_MESSAGES/django.mo | Bin 257512 -> 258150 bytes
netbox/translations/zh/LC_MESSAGES/django.po | 703 +++++++++---------
requirements.txt | 18 +-
40 files changed, 6189 insertions(+), 5907 deletions(-)
diff --git a/contrib/openapi.json b/contrib/openapi.json
index 7a6989337..53cd54c62 100644
--- a/contrib/openapi.json
+++ b/contrib/openapi.json
@@ -2,7 +2,7 @@
"openapi": "3.0.3",
"info": {
"title": "NetBox REST API",
- "version": "4.6.7",
+ "version": "4.6.8",
"license": {
"name": "Apache v2 License"
}
@@ -148505,33 +148505,6 @@
"description": ""
}
}
- },
- "post": {
- "operationId": "extras_scripts_create",
- "description": "Post a list of script objects.",
- "tags": [
- "extras"
- ],
- "security": [
- {
- "cookieAuth": []
- },
- {
- "tokenAuth": []
- }
- ],
- "responses": {
- "201": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/Script"
- }
- }
- },
- "description": ""
- }
- }
}
},
"/api/extras/scripts/{id}/": {
@@ -148597,9 +148570,9 @@
}
}
},
- "put": {
- "operationId": "extras_scripts_update",
- "description": "Put a script object.",
+ "post": {
+ "operationId": "extras_scripts_run",
+ "description": "Run a Script identified by its numeric PK or module & name and return the pending Job as the result",
"parameters": [
{
"in": "path",
@@ -148642,94 +148615,11 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/Script"
+ "$ref": "#/components/schemas/ScriptDetail"
}
}
},
- "description": ""
- }
- }
- },
- "patch": {
- "operationId": "extras_scripts_partial_update",
- "description": "Patch a script object.",
- "parameters": [
- {
- "in": "path",
- "name": "id",
- "schema": {
- "type": "string",
- "pattern": "^[^/]+$"
- },
- "required": true
- }
- ],
- "tags": [
- "extras"
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/PatchedScriptInputRequest"
- }
- },
- "multipart/form-data": {
- "schema": {
- "$ref": "#/components/schemas/PatchedScriptInputRequest"
- }
- }
- }
- },
- "security": [
- {
- "cookieAuth": []
- },
- {
- "tokenAuth": []
- }
- ],
- "responses": {
- "200": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/Script"
- }
- }
- },
- "description": ""
- }
- }
- },
- "delete": {
- "operationId": "extras_scripts_destroy",
- "description": "Delete a script object.",
- "parameters": [
- {
- "in": "path",
- "name": "id",
- "schema": {
- "type": "string",
- "pattern": "^[^/]+$"
- },
- "required": true
- }
- ],
- "tags": [
- "extras"
- ],
- "security": [
- {
- "cookieAuth": []
- },
- {
- "tokenAuth": []
- }
- ],
- "responses": {
- "204": {
- "description": "No response body"
+ "description": "The script has been enqueued for execution."
}
}
}
@@ -241898,16 +241788,6 @@
"user"
]
},
- "BriefJobRequest": {
- "type": "object",
- "properties": {
- "completed": {
- "type": "string",
- "format": "date-time",
- "nullable": true
- }
- }
- },
"BriefL2VPN": {
"type": "object",
"description": "Base serializer class for models inheriting from PrimaryModel.",
@@ -246943,11 +246823,13 @@
"enum": [
"IATA",
"ISO_3166",
- "UN_LOCODE"
+ "UN_LOCODE",
+ null
],
"type": "string",
"description": "* `IATA` - IATA (Airport codes)\n* `ISO_3166` - ISO 3166 (Country codes)\n* `UN_LOCODE` - UN/LOCODE (Location codes)",
- "x-spec-enum-id": "cf0efb5195f85007"
+ "x-spec-enum-id": "cf0efb5195f85007",
+ "nullable": true
},
"extra_choices": {
"type": "array",
@@ -257534,7 +257416,7 @@
"type": "string",
"minLength": 1,
"title": "URL",
- "description": "This URL will be called using the HTTP method defined when the webhook is called. Jinja2 template processing is supported with the same context as the request body.",
+ "description": "This URL will be called using the HTTP method defined when the webhook is called. Must be http:// or https://. Jinja2 template processing is supported (with the same context as the request body) for part or all of the URL.",
"maxLength": 500
},
"http_method": {
@@ -263314,7 +263196,8 @@
"enum": [
"IATA",
"ISO_3166",
- "UN_LOCODE"
+ "UN_LOCODE",
+ null
],
"type": "string",
"description": "* `IATA` - IATA (Airport codes)\n* `ISO_3166` - ISO 3166 (Country codes)\n* `UN_LOCODE` - UN/LOCODE (Location codes)",
@@ -263328,7 +263211,8 @@
"UN/LOCODE (Location codes)"
]
}
- }
+ },
+ "nullable": true
},
"extra_choices": {
"type": "array",
@@ -263420,11 +263304,13 @@
"enum": [
"IATA",
"ISO_3166",
- "UN_LOCODE"
+ "UN_LOCODE",
+ null
],
"type": "string",
"description": "* `IATA` - IATA (Airport codes)\n* `ISO_3166` - ISO 3166 (Country codes)\n* `UN_LOCODE` - UN/LOCODE (Location codes)",
- "x-spec-enum-id": "cf0efb5195f85007"
+ "x-spec-enum-id": "cf0efb5195f85007",
+ "nullable": true
},
"extra_choices": {
"type": "array",
@@ -284515,11 +284401,13 @@
"enum": [
"IATA",
"ISO_3166",
- "UN_LOCODE"
+ "UN_LOCODE",
+ null
],
"type": "string",
"description": "* `IATA` - IATA (Airport codes)\n* `ISO_3166` - ISO 3166 (Country codes)\n* `UN_LOCODE` - UN/LOCODE (Location codes)",
- "x-spec-enum-id": "cf0efb5195f85007"
+ "x-spec-enum-id": "cf0efb5195f85007",
+ "nullable": true
},
"extra_choices": {
"type": "array",
@@ -294889,7 +294777,7 @@
"type": "string",
"minLength": 1,
"title": "URL",
- "description": "This URL will be called using the HTTP method defined when the webhook is called. Jinja2 template processing is supported with the same context as the request body.",
+ "description": "This URL will be called using the HTTP method defined when the webhook is called. Must be http:// or https://. Jinja2 template processing is supported (with the same context as the request body) for part or all of the URL.",
"maxLength": 500
},
"http_method": {
@@ -297774,35 +297662,6 @@
}
}
},
- "PatchedScriptInputRequest": {
- "type": "object",
- "properties": {
- "data": {},
- "commit": {
- "type": "boolean"
- },
- "schedule_at": {
- "type": "string",
- "format": "date-time",
- "nullable": true
- },
- "interval": {
- "type": "integer",
- "nullable": true
- },
- "notifications": {
- "enum": [
- "always",
- "on_failure",
- "never"
- ],
- "type": "string",
- "description": "* `always` - Always\n* `on_failure` - On failure\n* `never` - Never",
- "x-spec-enum-id": "57071f4400340a5c",
- "default": "always"
- }
- }
- },
"PatchedScriptModuleRequest": {
"type": "object",
"description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
@@ -298616,7 +298475,7 @@
"type": "string",
"minLength": 1,
"title": "URL",
- "description": "This URL will be called using the HTTP method defined when the webhook is called. Jinja2 template processing is supported with the same context as the request body.",
+ "description": "This URL will be called using the HTTP method defined when the webhook is called. Must be http:// or https://. Jinja2 template processing is supported (with the same context as the request body) for part or all of the URL.",
"maxLength": 500
},
"http_method": {
@@ -314922,6 +314781,71 @@
"vars"
]
},
+ "ScriptDetail": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "module": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "readOnly": true
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "readOnly": true
+ },
+ "vars": {
+ "nullable": true,
+ "readOnly": true
+ },
+ "result": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/Job"
+ }
+ ],
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "is_executable": {
+ "type": "boolean",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "description",
+ "display",
+ "display_url",
+ "id",
+ "is_executable",
+ "module",
+ "name",
+ "result",
+ "url",
+ "vars"
+ ]
+ },
"ScriptInputRequest": {
"type": "object",
"properties": {
@@ -320938,7 +320862,7 @@
"payload_url": {
"type": "string",
"title": "URL",
- "description": "This URL will be called using the HTTP method defined when the webhook is called. Jinja2 template processing is supported with the same context as the request body.",
+ "description": "This URL will be called using the HTTP method defined when the webhook is called. Must be http:// or https://. Jinja2 template processing is supported (with the same context as the request body) for part or all of the URL.",
"maxLength": 500
},
"http_method": {
@@ -321040,7 +320964,7 @@
"type": "string",
"minLength": 1,
"title": "URL",
- "description": "This URL will be called using the HTTP method defined when the webhook is called. Jinja2 template processing is supported with the same context as the request body.",
+ "description": "This URL will be called using the HTTP method defined when the webhook is called. Must be http:// or https://. Jinja2 template processing is supported (with the same context as the request body) for part or all of the URL.",
"maxLength": 500
},
"http_method": {
diff --git a/docs/release-notes/version-4.6.md b/docs/release-notes/version-4.6.md
index 6d7db1575..77124ba0e 100644
--- a/docs/release-notes/version-4.6.md
+++ b/docs/release-notes/version-4.6.md
@@ -1,5 +1,32 @@
# NetBox v4.6
+## v4.6.8 (2026-08-11)
+
+### Performance Improvements
+
+* [#22787](https://github.com/netbox-community/netbox/issues/22787) - Avoid N+1 queries when resolving generic relations (e.g. assigned objects) via the GraphQL API
+* [#22835](https://github.com/netbox-community/netbox/issues/22835) - Improve performance when provisioning new custom fields
+* [#22837](https://github.com/netbox-community/netbox/issues/22837) - Omit implicit pagination when prefetching to-one relations via the GraphQL API
+* [#22877](https://github.com/netbox-community/netbox/issues/22877) - Improve caching logic when retrieving custom fields via `get_for_model()`
+
+### Bug Fixes
+
+* [#22694](https://github.com/netbox-community/netbox/issues/22694) - Clear a device's stale rack assignment when changing its site
+* [#22745](https://github.com/netbox-community/netbox/issues/22745) - Enforce object permissions on custom script write operations via the REST API
+* [#22805](https://github.com/netbox-community/netbox/issues/22805) - Avoid re-executing the LDAP configuration file on every permission check
+* [#22821](https://github.com/netbox-community/netbox/issues/22821) - Prevent the deletion of a tenant group from creating duplicate tenant names or slugs
+* [#22825](https://github.com/netbox-community/netbox/issues/22825) - Fix cable path tracing for paths which originate from a circuit termination and traverse only pass-through ports
+* [#22828](https://github.com/netbox-community/netbox/issues/22828) - Validate that a webhook's payload URL is a valid URL or Jinja2 template when saving
+* [#22844](https://github.com/netbox-community/netbox/issues/22844) - Allow a null value for `base_choices` when creating a custom field choice set via the REST API
+* [#22848](https://github.com/netbox-community/netbox/issues/22848) - Ensure deterministic ordering of duplicate IP addresses to avoid repeating an object across paginated REST API results
+* [#22852](https://github.com/netbox-community/netbox/issues/22852) - Honor a custom script's `notifications_default` setting when the script is run from an event rule
+* [#22865](https://github.com/netbox-community/netbox/issues/22865) - Reference the appropriate component template types on the GraphQL type for inventory item templates
+* [#22879](https://github.com/netbox-community/netbox/issues/22879) - Improve the contrast of unselected radio buttons and checkboxes in dark mode
+* [#22882](https://github.com/netbox-community/netbox/issues/22882) - Fix support for the `DISTINCT` filter on nested GraphQL list fields
+* [#22894](https://github.com/netbox-community/netbox/issues/22894) - Sanitize the error message rendered when an exception occurs in `CustomLinkColumn`
+
+---
+
## v4.6.7 (2026-07-30)
### Performance Improvements
diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js
index fc52b2992..ad5937ea2 100644
--- a/netbox/project-static/dist/netbox.js
+++ b/netbox/project-static/dist/netbox.js
@@ -1,11 +1,11 @@
-"use strict";(()=>{var su=Object.create;var Di=Object.defineProperty,au=Object.defineProperties,lu=Object.getOwnPropertyDescriptor,cu=Object.getOwnPropertyDescriptors,uu=Object.getOwnPropertyNames,ps=Object.getOwnPropertySymbols,du=Object.getPrototypeOf,ms=Object.prototype.hasOwnProperty,fu=Object.prototype.propertyIsEnumerable;var Wr=(n,e,t)=>e in n?Di(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t,O=(n,e)=>{for(var t in e||(e={}))ms.call(e,t)&&Wr(n,t,e[t]);if(ps)for(var t of ps(e))fu.call(e,t)&&Wr(n,t,e[t]);return n},ae=(n,e)=>au(n,cu(e));var hu=(n,e)=>()=>{try{return e||n((e={exports:{}}).exports,e),e.exports}catch(t){throw e=0,t}},gs=(n,e)=>{for(var t in e)Di(n,t,{get:e[t],enumerable:!0})},pu=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of uu(e))!ms.call(n,r)&&r!==t&&Di(n,r,{get:()=>e[r],enumerable:!(i=lu(e,r))||i.enumerable});return n};var mu=(n,e,t)=>(t=n!=null?su(du(n)):{},pu(e||!n||!n.__esModule?Di(t,"default",{value:n,enumerable:!0}):t,n));var se=(n,e,t)=>Wr(n,typeof e!="symbol"?e+"":e,t);var at=(n,e,t)=>new Promise((i,r)=>{var o=l=>{try{a(t.next(l))}catch(c){r(c)}},s=l=>{try{a(t.throw(l))}catch(c){r(c)}},a=l=>l.done?i(l.value):Promise.resolve(l.value).then(o,s);a((t=t.apply(n,e)).next())});var xc=hu((pi,ns)=>{(function(e,t){typeof pi=="object"&&typeof ns=="object"?ns.exports=t():typeof define=="function"&&define.amd?define([],t):typeof pi=="object"?pi.ClipboardJS=t():e.ClipboardJS=t()})(pi,function(){return(function(){var n={686:(function(i,r,o){"use strict";o.d(r,{default:function(){return Ie}});var s=o(279),a=o.n(s),l=o(370),c=o.n(l),u=o(817),d=o.n(u);function p(q){try{return document.execCommand(q)}catch(M){return!1}}var y=function(M){var D=d()(M);return p("cut"),D},m=y;function g(q){var M=document.documentElement.getAttribute("dir")==="rtl",D=document.createElement("textarea");D.style.fontSize="12pt",D.style.border="0",D.style.padding="0",D.style.margin="0",D.style.position="absolute",D.style[M?"right":"left"]="-9999px";var B=window.pageYOffset||document.documentElement.scrollTop;return D.style.top="".concat(B,"px"),D.setAttribute("readonly",""),D.value=q,D}var _=function(M,D){var B=g(M);D.container.appendChild(B);var V=d()(B);return p("copy"),B.remove(),V},x=function(M){var D=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},B="";return typeof M=="string"?B=_(M,D):M instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(M==null?void 0:M.type)?B=_(M.value,D):(B=d()(M),p("copy")),B},A=x;function w(q){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?w=function(D){return typeof D}:w=function(D){return D&&typeof Symbol=="function"&&D.constructor===Symbol&&D!==Symbol.prototype?"symbol":typeof D},w(q)}var C=function(){var M=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},D=M.action,B=D===void 0?"copy":D,V=M.container,U=M.target,Y=M.text;if(B!=="copy"&&B!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(U!==void 0)if(U&&w(U)==="object"&&U.nodeType===1){if(B==="copy"&&U.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(B==="cut"&&(U.hasAttribute("readonly")||U.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Y)return A(Y,{container:V});if(U)return B==="cut"?m(U):A(U,{container:V})},$=C;function j(q){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?j=function(D){return typeof D}:j=function(D){return D&&typeof Symbol=="function"&&D.constructor===Symbol&&D!==Symbol.prototype?"symbol":typeof D},j(q)}function R(q,M){if(!(q instanceof M))throw new TypeError("Cannot call a class as a function")}function H(q,M){for(var D=0;D0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof V.action=="function"?V.action:this.defaultAction,this.target=typeof V.target=="function"?V.target:this.defaultTarget,this.text=typeof V.text=="function"?V.text:this.defaultText,this.container=j(V.container)==="object"?V.container:document.body}},{key:"listenClick",value:function(V){var U=this;this.listener=c()(V,"click",function(Y){return U.onClick(Y)})}},{key:"onClick",value:function(V){var U=V.delegateTarget||V.currentTarget,Y=this.action(U)||"copy",ee=$({action:Y,container:this.container,target:this.target(U),text:this.text(U)});this.emit(ee?"success":"error",{action:Y,text:ee,trigger:U,clearSelection:function(){U&&U.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(V){return ne("action",V)}},{key:"defaultTarget",value:function(V){var U=ne("target",V);if(U)return document.querySelector(U)}},{key:"defaultText",value:function(V){return ne("text",V)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(V){var U=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return A(V,U)}},{key:"cut",value:function(V){return m(V)}},{key:"isSupported",value:function(){var V=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],U=typeof V=="string"?[V]:V,Y=!!document.queryCommandSupported;return U.forEach(function(ee){Y=Y&&!!document.queryCommandSupported(ee)}),Y}}]),D})(a()),Ie=Ue}),828:(function(i){var r=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var o=Element.prototype;o.matches=o.matchesSelector||o.mozMatchesSelector||o.msMatchesSelector||o.oMatchesSelector||o.webkitMatchesSelector}function s(a,l){for(;a&&a.nodeType!==r;){if(typeof a.matches=="function"&&a.matches(l))return a;a=a.parentNode}}i.exports=s}),438:(function(i,r,o){var s=o(828);function a(u,d,p,y,m){var g=c.apply(this,arguments);return u.addEventListener(p,g,m),{destroy:function(){u.removeEventListener(p,g,m)}}}function l(u,d,p,y,m){return typeof u.addEventListener=="function"?a.apply(null,arguments):typeof p=="function"?a.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(g){return a(g,d,p,y,m)}))}function c(u,d,p,y){return function(m){m.delegateTarget=s(m.target,d),m.delegateTarget&&y.call(u,m)}}i.exports=l}),879:(function(i,r){r.node=function(o){return o!==void 0&&o instanceof HTMLElement&&o.nodeType===1},r.nodeList=function(o){var s=Object.prototype.toString.call(o);return o!==void 0&&(s==="[object NodeList]"||s==="[object HTMLCollection]")&&"length"in o&&(o.length===0||r.node(o[0]))},r.string=function(o){return typeof o=="string"||o instanceof String},r.fn=function(o){var s=Object.prototype.toString.call(o);return s==="[object Function]"}}),370:(function(i,r,o){var s=o(879),a=o(438);function l(p,y,m){if(!p&&!y&&!m)throw new Error("Missing required arguments");if(!s.string(y))throw new TypeError("Second argument must be a String");if(!s.fn(m))throw new TypeError("Third argument must be a Function");if(s.node(p))return c(p,y,m);if(s.nodeList(p))return u(p,y,m);if(s.string(p))return d(p,y,m);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(p,y,m){return p.addEventListener(y,m),{destroy:function(){p.removeEventListener(y,m)}}}function u(p,y,m){return Array.prototype.forEach.call(p,function(g){g.addEventListener(y,m)}),{destroy:function(){Array.prototype.forEach.call(p,function(g){g.removeEventListener(y,m)})}}}function d(p,y,m){return a(document.body,p,y,m)}i.exports=l}),817:(function(i){function r(o){var s;if(o.nodeName==="SELECT")o.focus(),s=o.value;else if(o.nodeName==="INPUT"||o.nodeName==="TEXTAREA"){var a=o.hasAttribute("readonly");a||o.setAttribute("readonly",""),o.select(),o.setSelectionRange(0,o.value.length),a||o.removeAttribute("readonly"),s=o.value}else{o.hasAttribute("contenteditable")&&o.focus();var l=window.getSelection(),c=document.createRange();c.selectNodeContents(o),l.removeAllRanges(),l.addRange(c),s=l.toString()}return s}i.exports=r}),279:(function(i){function r(){}r.prototype={on:function(o,s,a){var l=this.e||(this.e={});return(l[o]||(l[o]=[])).push({fn:s,ctx:a}),this},once:function(o,s,a){var l=this;function c(){l.off(o,c),s.apply(a,arguments)}return c._=s,this.on(o,c,a)},emit:function(o){var s=[].slice.call(arguments,1),a=((this.e||(this.e={}))[o]||[]).slice(),l=0,c=a.length;for(l;lws,afterRead:()=>Es,afterWrite:()=>Cs,applyStyles:()=>fn,arrow:()=>Li,auto:()=>Bn,basePlacements:()=>lt,beforeMain:()=>bs,beforeRead:()=>vs,beforeWrite:()=>xs,bottom:()=>ge,clippingParents:()=>qr,computeStyles:()=>pn,createPopper:()=>Kn,createPopperBase:()=>Rs,createPopperLite:()=>Hs,detectOverflow:()=>ke,end:()=>bt,eventListeners:()=>mn,flip:()=>Ri,hide:()=>Hi,left:()=>pe,main:()=>_s,modifierPhases:()=>Yr,offset:()=>Ii,placements:()=>zn,popper:()=>$t,popperGenerator:()=>Yt,popperOffsets:()=>yn,preventOverflow:()=>Pi,read:()=>ys,reference:()=>Ur,right:()=>me,start:()=>rt,top:()=>de,variationPlacements:()=>Oi,viewport:()=>Vn,write:()=>Ts});var de="top",ge="bottom",me="right",pe="left",Bn="auto",lt=[de,ge,me,pe],rt="start",bt="end",qr="clippingParents",Vn="viewport",$t="popper",Ur="reference",Oi=lt.reduce(function(n,e){return n.concat([e+"-"+rt,e+"-"+bt])},[]),zn=[].concat(lt,[Bn]).reduce(function(n,e){return n.concat([e,e+"-"+rt,e+"-"+bt])},[]),vs="beforeRead",ys="read",Es="afterRead",bs="beforeMain",_s="main",ws="afterMain",xs="beforeWrite",Ts="write",Cs="afterWrite",Yr=[vs,ys,Es,bs,_s,ws,xs,Ts,Cs];function xe(n){return n?(n.nodeName||"").toLowerCase():null}function ce(n){if(n==null)return window;if(n.toString()!=="[object Window]"){var e=n.ownerDocument;return e&&e.defaultView||window}return n}function Ye(n){var e=ce(n).Element;return n instanceof e||n instanceof Element}function _e(n){var e=ce(n).HTMLElement;return n instanceof e||n instanceof HTMLElement}function dn(n){if(typeof ShadowRoot=="undefined")return!1;var e=ce(n).ShadowRoot;return n instanceof e||n instanceof ShadowRoot}function gu(n){var e=n.state;Object.keys(e.elements).forEach(function(t){var i=e.styles[t]||{},r=e.attributes[t]||{},o=e.elements[t];!_e(o)||!xe(o)||(Object.assign(o.style,i),Object.keys(r).forEach(function(s){var a=r[s];a===!1?o.removeAttribute(s):o.setAttribute(s,a===!0?"":a)}))})}function vu(n){var e=n.state,t={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,t.popper),e.styles=t,e.elements.arrow&&Object.assign(e.elements.arrow.style,t.arrow),function(){Object.keys(e.elements).forEach(function(i){var r=e.elements[i],o=e.attributes[i]||{},s=Object.keys(e.styles.hasOwnProperty(i)?e.styles[i]:t[i]),a=s.reduce(function(l,c){return l[c]="",l},{});!_e(r)||!xe(r)||(Object.assign(r.style,a),Object.keys(o).forEach(function(l){r.removeAttribute(l)}))})}}var fn={name:"applyStyles",enabled:!0,phase:"write",fn:gu,effect:vu,requires:["computeStyles"]};function Te(n){return n.split("-")[0]}var Ze=Math.max,Bt=Math.min,ct=Math.round;function hn(){var n=navigator.userAgentData;return n!=null&&n.brands&&Array.isArray(n.brands)?n.brands.map(function(e){return e.brand+"/"+e.version}).join(" "):navigator.userAgent}function jn(){return!/^((?!chrome|android).)*safari/i.test(hn())}function Ge(n,e,t){e===void 0&&(e=!1),t===void 0&&(t=!1);var i=n.getBoundingClientRect(),r=1,o=1;e&&_e(n)&&(r=n.offsetWidth>0&&ct(i.width)/n.offsetWidth||1,o=n.offsetHeight>0&&ct(i.height)/n.offsetHeight||1);var s=Ye(n)?ce(n):window,a=s.visualViewport,l=!jn()&&t,c=(i.left+(l&&a?a.offsetLeft:0))/r,u=(i.top+(l&&a?a.offsetTop:0))/o,d=i.width/r,p=i.height/o;return{width:d,height:p,top:u,right:c+d,bottom:u+p,left:c,x:c,y:u}}function Vt(n){var e=Ge(n),t=n.offsetWidth,i=n.offsetHeight;return Math.abs(e.width-t)<=1&&(t=e.width),Math.abs(e.height-i)<=1&&(i=e.height),{x:n.offsetLeft,y:n.offsetTop,width:t,height:i}}function Wn(n,e){var t=e.getRootNode&&e.getRootNode();if(n.contains(e))return!0;if(t&&dn(t)){var i=e;do{if(i&&n.isSameNode(i))return!0;i=i.parentNode||i.host}while(i)}return!1}function Ne(n){return ce(n).getComputedStyle(n)}function Gr(n){return["table","td","th"].indexOf(xe(n))>=0}function Se(n){return((Ye(n)?n.ownerDocument:n.document)||window.document).documentElement}function ut(n){return xe(n)==="html"?n:n.assignedSlot||n.parentNode||(dn(n)?n.host:null)||Se(n)}function Ss(n){return!_e(n)||Ne(n).position==="fixed"?null:n.offsetParent}function yu(n){var e=/firefox/i.test(hn()),t=/Trident/i.test(hn());if(t&&_e(n)){var i=Ne(n);if(i.position==="fixed")return null}var r=ut(n);for(dn(r)&&(r=r.host);_e(r)&&["html","body"].indexOf(xe(r))<0;){var o=Ne(r);if(o.transform!=="none"||o.perspective!=="none"||o.contain==="paint"||["transform","perspective"].indexOf(o.willChange)!==-1||e&&o.willChange==="filter"||e&&o.filter&&o.filter!=="none")return r;r=r.parentNode}return null}function et(n){for(var e=ce(n),t=Ss(n);t&&Gr(t)&&Ne(t).position==="static";)t=Ss(t);return t&&(xe(t)==="html"||xe(t)==="body"&&Ne(t).position==="static")?e:t||yu(n)||e}function zt(n){return["top","bottom"].indexOf(n)>=0?"x":"y"}function jt(n,e,t){return Ze(n,Bt(e,t))}function As(n,e,t){var i=jt(n,e,t);return i>t?t:i}function qn(){return{top:0,right:0,bottom:0,left:0}}function Un(n){return Object.assign({},qn(),n)}function Yn(n,e){return e.reduce(function(t,i){return t[i]=n,t},{})}var Eu=function(e,t){return e=typeof e=="function"?e(Object.assign({},t.rects,{placement:t.placement})):e,Un(typeof e!="number"?e:Yn(e,lt))};function bu(n){var e,t=n.state,i=n.name,r=n.options,o=t.elements.arrow,s=t.modifiersData.popperOffsets,a=Te(t.placement),l=zt(a),c=[pe,me].indexOf(a)>=0,u=c?"height":"width";if(!(!o||!s)){var d=Eu(r.padding,t),p=Vt(o),y=l==="y"?de:pe,m=l==="y"?ge:me,g=t.rects.reference[u]+t.rects.reference[l]-s[l]-t.rects.popper[u],_=s[l]-t.rects.reference[l],x=et(o),A=x?l==="y"?x.clientHeight||0:x.clientWidth||0:0,w=g/2-_/2,C=d[y],$=A-p[u]-d[m],j=A/2-p[u]/2+w,R=jt(C,j,$),H=l;t.modifiersData[i]=(e={},e[H]=R,e.centerOffset=R-j,e)}}function _u(n){var e=n.state,t=n.options,i=t.element,r=i===void 0?"[data-popper-arrow]":i;r!=null&&(typeof r=="string"&&(r=e.elements.popper.querySelector(r),!r)||Wn(e.elements.popper,r)&&(e.elements.arrow=r))}var Li={name:"arrow",enabled:!0,phase:"main",fn:bu,effect:_u,requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Ke(n){return n.split("-")[1]}var wu={top:"auto",right:"auto",bottom:"auto",left:"auto"};function xu(n,e){var t=n.x,i=n.y,r=e.devicePixelRatio||1;return{x:ct(t*r)/r||0,y:ct(i*r)/r||0}}function Ds(n){var e,t=n.popper,i=n.popperRect,r=n.placement,o=n.variation,s=n.offsets,a=n.position,l=n.gpuAcceleration,c=n.adaptive,u=n.roundOffsets,d=n.isFixed,p=s.x,y=p===void 0?0:p,m=s.y,g=m===void 0?0:m,_=typeof u=="function"?u({x:y,y:g}):{x:y,y:g};y=_.x,g=_.y;var x=s.hasOwnProperty("x"),A=s.hasOwnProperty("y"),w=pe,C=de,$=window;if(c){var j=et(t),R="clientHeight",H="clientWidth";if(j===ce(t)&&(j=Se(t),Ne(j).position!=="static"&&a==="absolute"&&(R="scrollHeight",H="scrollWidth")),j=j,r===de||(r===pe||r===me)&&o===bt){C=ge;var L=d&&j===$&&$.visualViewport?$.visualViewport.height:j[R];g-=L-i.height,g*=l?1:-1}if(r===pe||(r===de||r===ge)&&o===bt){w=me;var W=d&&j===$&&$.visualViewport?$.visualViewport.width:j[H];y-=W-i.width,y*=l?1:-1}}var G=Object.assign({position:a},c&&wu),Q=u===!0?xu({x:y,y:g},ce(t)):{x:y,y:g};if(y=Q.x,g=Q.y,l){var Z;return Object.assign({},G,(Z={},Z[C]=A?"0":"",Z[w]=x?"0":"",Z.transform=($.devicePixelRatio||1)<=1?"translate("+y+"px, "+g+"px)":"translate3d("+y+"px, "+g+"px, 0)",Z))}return Object.assign({},G,(e={},e[C]=A?g+"px":"",e[w]=x?y+"px":"",e.transform="",e))}function Tu(n){var e=n.state,t=n.options,i=t.gpuAcceleration,r=i===void 0?!0:i,o=t.adaptive,s=o===void 0?!0:o,a=t.roundOffsets,l=a===void 0?!0:a,c={placement:Te(e.placement),variation:Ke(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:r,isFixed:e.options.strategy==="fixed"};e.modifiersData.popperOffsets!=null&&(e.styles.popper=Object.assign({},e.styles.popper,Ds(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:s,roundOffsets:l})))),e.modifiersData.arrow!=null&&(e.styles.arrow=Object.assign({},e.styles.arrow,Ds(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})}var pn={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:Tu,data:{}};var Mi={passive:!0};function Cu(n){var e=n.state,t=n.instance,i=n.options,r=i.scroll,o=r===void 0?!0:r,s=i.resize,a=s===void 0?!0:s,l=ce(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach(function(u){u.addEventListener("scroll",t.update,Mi)}),a&&l.addEventListener("resize",t.update,Mi),function(){o&&c.forEach(function(u){u.removeEventListener("scroll",t.update,Mi)}),a&&l.removeEventListener("resize",t.update,Mi)}}var mn={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:Cu,data:{}};var Su={left:"right",right:"left",bottom:"top",top:"bottom"};function gn(n){return n.replace(/left|right|bottom|top/g,function(e){return Su[e]})}var Au={start:"end",end:"start"};function Ni(n){return n.replace(/start|end/g,function(e){return Au[e]})}function Wt(n){var e=ce(n),t=e.pageXOffset,i=e.pageYOffset;return{scrollLeft:t,scrollTop:i}}function qt(n){return Ge(Se(n)).left+Wt(n).scrollLeft}function Kr(n,e){var t=ce(n),i=Se(n),r=t.visualViewport,o=i.clientWidth,s=i.clientHeight,a=0,l=0;if(r){o=r.width,s=r.height;var c=jn();(c||!c&&e==="fixed")&&(a=r.offsetLeft,l=r.offsetTop)}return{width:o,height:s,x:a+qt(n),y:l}}function Xr(n){var e,t=Se(n),i=Wt(n),r=(e=n.ownerDocument)==null?void 0:e.body,o=Ze(t.scrollWidth,t.clientWidth,r?r.scrollWidth:0,r?r.clientWidth:0),s=Ze(t.scrollHeight,t.clientHeight,r?r.scrollHeight:0,r?r.clientHeight:0),a=-i.scrollLeft+qt(n),l=-i.scrollTop;return Ne(r||t).direction==="rtl"&&(a+=Ze(t.clientWidth,r?r.clientWidth:0)-o),{width:o,height:s,x:a,y:l}}function Ut(n){var e=Ne(n),t=e.overflow,i=e.overflowX,r=e.overflowY;return/auto|scroll|overlay|hidden/.test(t+r+i)}function ki(n){return["html","body","#document"].indexOf(xe(n))>=0?n.ownerDocument.body:_e(n)&&Ut(n)?n:ki(ut(n))}function _t(n,e){var t;e===void 0&&(e=[]);var i=ki(n),r=i===((t=n.ownerDocument)==null?void 0:t.body),o=ce(i),s=r?[o].concat(o.visualViewport||[],Ut(i)?i:[]):i,a=e.concat(s);return r?a:a.concat(_t(ut(s)))}function vn(n){return Object.assign({},n,{left:n.x,top:n.y,right:n.x+n.width,bottom:n.y+n.height})}function Du(n,e){var t=Ge(n,!1,e==="fixed");return t.top=t.top+n.clientTop,t.left=t.left+n.clientLeft,t.bottom=t.top+n.clientHeight,t.right=t.left+n.clientWidth,t.width=n.clientWidth,t.height=n.clientHeight,t.x=t.left,t.y=t.top,t}function Os(n,e,t){return e===Vn?vn(Kr(n,t)):Ye(e)?Du(e,t):vn(Xr(Se(n)))}function Ou(n){var e=_t(ut(n)),t=["absolute","fixed"].indexOf(Ne(n).position)>=0,i=t&&_e(n)?et(n):n;return Ye(i)?e.filter(function(r){return Ye(r)&&Wn(r,i)&&xe(r)!=="body"}):[]}function Qr(n,e,t,i){var r=e==="clippingParents"?Ou(n):[].concat(e),o=[].concat(r,[t]),s=o[0],a=o.reduce(function(l,c){var u=Os(n,c,i);return l.top=Ze(u.top,l.top),l.right=Bt(u.right,l.right),l.bottom=Bt(u.bottom,l.bottom),l.left=Ze(u.left,l.left),l},Os(n,s,i));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}function Gn(n){var e=n.reference,t=n.element,i=n.placement,r=i?Te(i):null,o=i?Ke(i):null,s=e.x+e.width/2-t.width/2,a=e.y+e.height/2-t.height/2,l;switch(r){case de:l={x:s,y:e.y-t.height};break;case ge:l={x:s,y:e.y+e.height};break;case me:l={x:e.x+e.width,y:a};break;case pe:l={x:e.x-t.width,y:a};break;default:l={x:e.x,y:e.y}}var c=r?zt(r):null;if(c!=null){var u=c==="y"?"height":"width";switch(o){case rt:l[c]=l[c]-(e[u]/2-t[u]/2);break;case bt:l[c]=l[c]+(e[u]/2-t[u]/2);break;default:}}return l}function ke(n,e){e===void 0&&(e={});var t=e,i=t.placement,r=i===void 0?n.placement:i,o=t.strategy,s=o===void 0?n.strategy:o,a=t.boundary,l=a===void 0?qr:a,c=t.rootBoundary,u=c===void 0?Vn:c,d=t.elementContext,p=d===void 0?$t:d,y=t.altBoundary,m=y===void 0?!1:y,g=t.padding,_=g===void 0?0:g,x=Un(typeof _!="number"?_:Yn(_,lt)),A=p===$t?Ur:$t,w=n.rects.popper,C=n.elements[m?A:p],$=Qr(Ye(C)?C:C.contextElement||Se(n.elements.popper),l,u,s),j=Ge(n.elements.reference),R=Gn({reference:j,element:w,strategy:"absolute",placement:r}),H=vn(Object.assign({},w,R)),L=p===$t?H:j,W={top:$.top-L.top+x.top,bottom:L.bottom-$.bottom+x.bottom,left:$.left-L.left+x.left,right:L.right-$.right+x.right},G=n.modifiersData.offset;if(p===$t&&G){var Q=G[r];Object.keys(W).forEach(function(Z){var he=[me,ge].indexOf(Z)>=0?1:-1,Ce=[de,ge].indexOf(Z)>=0?"y":"x";W[Z]+=Q[Ce]*he})}return W}function Jr(n,e){e===void 0&&(e={});var t=e,i=t.placement,r=t.boundary,o=t.rootBoundary,s=t.padding,a=t.flipVariations,l=t.allowedAutoPlacements,c=l===void 0?zn:l,u=Ke(i),d=u?a?Oi:Oi.filter(function(m){return Ke(m)===u}):lt,p=d.filter(function(m){return c.indexOf(m)>=0});p.length===0&&(p=d);var y=p.reduce(function(m,g){return m[g]=ke(n,{placement:g,boundary:r,rootBoundary:o,padding:s})[Te(g)],m},{});return Object.keys(y).sort(function(m,g){return y[m]-y[g]})}function Lu(n){if(Te(n)===Bn)return[];var e=gn(n);return[Ni(n),e,Ni(e)]}function Mu(n){var e=n.state,t=n.options,i=n.name;if(!e.modifiersData[i]._skip){for(var r=t.mainAxis,o=r===void 0?!0:r,s=t.altAxis,a=s===void 0?!0:s,l=t.fallbackPlacements,c=t.padding,u=t.boundary,d=t.rootBoundary,p=t.altBoundary,y=t.flipVariations,m=y===void 0?!0:y,g=t.allowedAutoPlacements,_=e.options.placement,x=Te(_),A=x===_,w=l||(A||!m?[gn(_)]:Lu(_)),C=[_].concat(w).reduce(function(V,U){return V.concat(Te(U)===Bn?Jr(e,{placement:U,boundary:u,rootBoundary:d,padding:c,flipVariations:m,allowedAutoPlacements:g}):U)},[]),$=e.rects.reference,j=e.rects.popper,R=new Map,H=!0,L=C[0],W=0;W=0,Ce=he?"width":"height",ie=ke(e,{placement:G,boundary:u,rootBoundary:d,altBoundary:p,padding:c}),ne=he?Z?me:pe:Z?ge:de;$[Ce]>j[Ce]&&(ne=gn(ne));var Ue=gn(ne),Ie=[];if(o&&Ie.push(ie[Q]<=0),a&&Ie.push(ie[ne]<=0,ie[Ue]<=0),Ie.every(function(V){return V})){L=G,H=!1;break}R.set(G,Ie)}if(H)for(var q=m?3:1,M=function(U){var Y=C.find(function(ee){var oe=R.get(ee);if(oe)return oe.slice(0,U).every(function(Et){return Et})});if(Y)return L=Y,"break"},D=q;D>0;D--){var B=M(D);if(B==="break")break}e.placement!==L&&(e.modifiersData[i]._skip=!0,e.placement=L,e.reset=!0)}}var Ri={name:"flip",enabled:!0,phase:"main",fn:Mu,requiresIfExists:["offset"],data:{_skip:!1}};function Ls(n,e,t){return t===void 0&&(t={x:0,y:0}),{top:n.top-e.height-t.y,right:n.right-e.width+t.x,bottom:n.bottom-e.height+t.y,left:n.left-e.width-t.x}}function Ms(n){return[de,me,ge,pe].some(function(e){return n[e]>=0})}function Nu(n){var e=n.state,t=n.name,i=e.rects.reference,r=e.rects.popper,o=e.modifiersData.preventOverflow,s=ke(e,{elementContext:"reference"}),a=ke(e,{altBoundary:!0}),l=Ls(s,i),c=Ls(a,r,o),u=Ms(l),d=Ms(c);e.modifiersData[t]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:u,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":u,"data-popper-escaped":d})}var Hi={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:Nu};function ku(n,e,t){var i=Te(n),r=[pe,de].indexOf(i)>=0?-1:1,o=typeof t=="function"?t(Object.assign({},e,{placement:n})):t,s=o[0],a=o[1];return s=s||0,a=(a||0)*r,[pe,me].indexOf(i)>=0?{x:a,y:s}:{x:s,y:a}}function Ru(n){var e=n.state,t=n.options,i=n.name,r=t.offset,o=r===void 0?[0,0]:r,s=zn.reduce(function(u,d){return u[d]=ku(d,e.rects,o),u},{}),a=s[e.placement],l=a.x,c=a.y;e.modifiersData.popperOffsets!=null&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[i]=s}var Ii={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:Ru};function Hu(n){var e=n.state,t=n.name;e.modifiersData[t]=Gn({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})}var yn={name:"popperOffsets",enabled:!0,phase:"read",fn:Hu,data:{}};function Zr(n){return n==="x"?"y":"x"}function Iu(n){var e=n.state,t=n.options,i=n.name,r=t.mainAxis,o=r===void 0?!0:r,s=t.altAxis,a=s===void 0?!1:s,l=t.boundary,c=t.rootBoundary,u=t.altBoundary,d=t.padding,p=t.tether,y=p===void 0?!0:p,m=t.tetherOffset,g=m===void 0?0:m,_=ke(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:u}),x=Te(e.placement),A=Ke(e.placement),w=!A,C=zt(x),$=Zr(C),j=e.modifiersData.popperOffsets,R=e.rects.reference,H=e.rects.popper,L=typeof g=="function"?g(Object.assign({},e.rects,{placement:e.placement})):g,W=typeof L=="number"?{mainAxis:L,altAxis:L}:Object.assign({mainAxis:0,altAxis:0},L),G=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,Q={x:0,y:0};if(j){if(o){var Z,he=C==="y"?de:pe,Ce=C==="y"?ge:me,ie=C==="y"?"height":"width",ne=j[C],Ue=ne+_[he],Ie=ne-_[Ce],q=y?-H[ie]/2:0,M=A===rt?R[ie]:H[ie],D=A===rt?-H[ie]:-R[ie],B=e.elements.arrow,V=y&&B?Vt(B):{width:0,height:0},U=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:qn(),Y=U[he],ee=U[Ce],oe=jt(0,R[ie],V[ie]),Et=w?R[ie]/2-q-oe-Y-W.mainAxis:M-oe-Y-W.mainAxis,Fr=w?-R[ie]/2+q+oe+ee+W.mainAxis:D+oe+ee+W.mainAxis,rn=e.elements.arrow&&et(e.elements.arrow),on=rn?C==="y"?rn.clientTop||0:rn.clientLeft||0:0,_i=(Z=G==null?void 0:G[C])!=null?Z:0,$r=ne+Et-_i-on,wi=ne+Fr-_i,xi=jt(y?Bt(Ue,$r):Ue,ne,y?Ze(Ie,wi):Ie);j[C]=xi,Q[C]=xi-ne}if(a){var In,Ti=C==="x"?de:pe,sn=C==="x"?ge:me,ot=j[$],an=$==="y"?"height":"width",Pn=ot+_[Ti],ln=ot-_[sn],cn=[de,pe].indexOf(x)!==-1,Ft=(In=G==null?void 0:G[$])!=null?In:0,Ci=cn?Pn:ot-R[an]-H[an]-Ft+W.altAxis,Fn=cn?ot+R[an]+H[an]-Ft-W.altAxis:ln,Si=y&&cn?As(Ci,ot,Fn):jt(y?Ci:Pn,ot,y?Fn:ln);j[$]=Si,Q[$]=Si-ot}e.modifiersData[i]=Q}}var Pi={name:"preventOverflow",enabled:!0,phase:"main",fn:Iu,requiresIfExists:["offset"]};function eo(n){return{scrollLeft:n.scrollLeft,scrollTop:n.scrollTop}}function to(n){return n===ce(n)||!_e(n)?Wt(n):eo(n)}function Pu(n){var e=n.getBoundingClientRect(),t=ct(e.width)/n.offsetWidth||1,i=ct(e.height)/n.offsetHeight||1;return t!==1||i!==1}function no(n,e,t){t===void 0&&(t=!1);var i=_e(e),r=_e(e)&&Pu(e),o=Se(e),s=Ge(n,r,t),a={scrollLeft:0,scrollTop:0},l={x:0,y:0};return(i||!i&&!t)&&((xe(e)!=="body"||Ut(o))&&(a=to(e)),_e(e)?(l=Ge(e,!0),l.x+=e.clientLeft,l.y+=e.clientTop):o&&(l.x=qt(o))),{x:s.left+a.scrollLeft-l.x,y:s.top+a.scrollTop-l.y,width:s.width,height:s.height}}function Fu(n){var e=new Map,t=new Set,i=[];n.forEach(function(o){e.set(o.name,o)});function r(o){t.add(o.name);var s=[].concat(o.requires||[],o.requiresIfExists||[]);s.forEach(function(a){if(!t.has(a)){var l=e.get(a);l&&r(l)}}),i.push(o)}return n.forEach(function(o){t.has(o.name)||r(o)}),i}function io(n){var e=Fu(n);return Yr.reduce(function(t,i){return t.concat(e.filter(function(r){return r.phase===i}))},[])}function ro(n){var e;return function(){return e||(e=new Promise(function(t){Promise.resolve().then(function(){e=void 0,t(n())})})),e}}function oo(n){var e=n.reduce(function(t,i){var r=t[i.name];return t[i.name]=r?Object.assign({},r,i,{options:Object.assign({},r.options,i.options),data:Object.assign({},r.data,i.data)}):i,t},{});return Object.keys(e).map(function(t){return e[t]})}var Ns={placement:"bottom",modifiers:[],strategy:"absolute"};function ks(){for(var n=arguments.length,e=new Array(n),t=0;t(n&&window.CSS&&window.CSS.escape&&(n=n.replace(/#([^\s"#']+)/g,(e,t)=>`#${CSS.escape(t)}`)),n),ju=n=>n==null?`${n}`:Object.prototype.toString.call(n).match(/\s([a-z]+)/i)[1].toLowerCase(),Wu=n=>{do n+=Math.floor(Math.random()*Vu);while(document.getElementById(n));return n},qu=n=>{if(!n)return 0;let{transitionDuration:e,transitionDelay:t}=window.getComputedStyle(n),i=Number.parseFloat(e),r=Number.parseFloat(t);return!i&&!r?0:(e=e.split(",")[0],t=t.split(",")[0],(Number.parseFloat(e)+Number.parseFloat(t))*zu)},da=n=>{n.dispatchEvent(new Event(xo))},dt=n=>!n||typeof n!="object"?!1:(typeof n.jquery!="undefined"&&(n=n[0]),typeof n.nodeType!="undefined"),xt=n=>dt(n)?n.jquery?n[0]:n:typeof n=="string"&&n.length>0?document.querySelector(ua(n)):null,Cn=n=>{if(!dt(n)||n.getClientRects().length===0)return!1;let e=getComputedStyle(n).getPropertyValue("visibility")==="visible",t=n.closest("details:not([open])");if(!t)return e;if(t!==n){let i=n.closest("summary");if(i&&i.parentNode!==t||i===null)return!1}return e},Tt=n=>!n||n.nodeType!==Node.ELEMENT_NODE||n.classList.contains("disabled")?!0:typeof n.disabled!="undefined"?n.disabled:n.hasAttribute("disabled")&&n.getAttribute("disabled")!=="false",fa=n=>{if(!document.documentElement.attachShadow)return null;if(typeof n.getRootNode=="function"){let e=n.getRootNode();return e instanceof ShadowRoot?e:null}return n instanceof ShadowRoot?n:n.parentNode?fa(n.parentNode):null},Yi=()=>{},ei=n=>{n.offsetHeight},ha=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,ao=[],Uu=n=>{document.readyState==="loading"?(ao.length||document.addEventListener("DOMContentLoaded",()=>{for(let e of ao)e()}),ao.push(n)):n()},Xe=()=>document.documentElement.dir==="rtl",Je=n=>{Uu(()=>{let e=ha();if(e){let t=n.NAME,i=e.fn[t];e.fn[t]=n.jQueryInterface,e.fn[t].Constructor=n,e.fn[t].noConflict=()=>(e.fn[t]=i,n.jQueryInterface)}})},Pe=(n,e=[],t=n)=>typeof n=="function"?n.call(...e):t,pa=(n,e,t=!0)=>{if(!t){Pe(n);return}let r=qu(e)+5,o=!1,s=({target:a})=>{a===e&&(o=!0,e.removeEventListener(xo,s),Pe(n))};e.addEventListener(xo,s),setTimeout(()=>{o||da(e)},r)},Ao=(n,e,t,i)=>{let r=n.length,o=n.indexOf(e);return o===-1?!t&&i?n[r-1]:n[0]:(o+=t?1:-1,i&&(o=(o+r)%r),n[Math.max(0,Math.min(o,r-1))])},Yu=/[^.]*(?=\..*)\.|.*/,Gu=/\..*/,Ku=/::\d+$/,lo={},Is=1,ma={mouseenter:"mouseover",mouseleave:"mouseout"},Xu=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function ga(n,e){return e&&`${e}::${Is++}`||n.uidEvent||Is++}function va(n){let e=ga(n);return n.uidEvent=e,lo[e]=lo[e]||{},lo[e]}function Qu(n,e){return function t(i){return Do(i,{delegateTarget:n}),t.oneOff&&T.off(n,i.type,e),e.apply(n,[i])}}function Ju(n,e,t){return function i(r){let o=n.querySelectorAll(e);for(let{target:s}=r;s&&s!==this;s=s.parentNode)for(let a of o)if(a===s)return Do(r,{delegateTarget:s}),i.oneOff&&T.off(n,r.type,e,t),t.apply(s,[r])}}function ya(n,e,t=null){return Object.values(n).find(i=>i.callable===e&&i.delegationSelector===t)}function Ea(n,e,t){let i=typeof e=="string",r=i?t:e||t,o=ba(n);return Xu.has(o)||(o=n),[i,r,o]}function Ps(n,e,t,i,r){if(typeof e!="string"||!n)return;let[o,s,a]=Ea(e,t,i);e in ma&&(s=(m=>function(g){if(!g.relatedTarget||g.relatedTarget!==g.delegateTarget&&!g.delegateTarget.contains(g.relatedTarget))return m.call(this,g)})(s));let l=va(n),c=l[a]||(l[a]={}),u=ya(c,s,o?t:null);if(u){u.oneOff=u.oneOff&&r;return}let d=ga(s,e.replace(Yu,"")),p=o?Ju(n,t,s):Qu(n,s);p.delegationSelector=o?t:null,p.callable=s,p.oneOff=r,p.uidEvent=d,c[d]=p,n.addEventListener(a,p,o)}function To(n,e,t,i,r){let o=ya(e[t],i,r);o&&(n.removeEventListener(t,o,!!r),delete e[t][o.uidEvent])}function Zu(n,e,t,i){let r=e[t]||{};for(let[o,s]of Object.entries(r))o.includes(i)&&To(n,e,t,s.callable,s.delegationSelector)}function ba(n){return n=n.replace(Gu,""),ma[n]||n}var T={on(n,e,t,i){Ps(n,e,t,i,!1)},one(n,e,t,i){Ps(n,e,t,i,!0)},off(n,e,t,i){if(typeof e!="string"||!n)return;let[r,o,s]=Ea(e,t,i),a=s!==e,l=va(n),c=l[s]||{},u=e.startsWith(".");if(typeof o!="undefined"){if(!Object.keys(c).length)return;To(n,l,s,o,r?t:null);return}if(u)for(let d of Object.keys(l))Zu(n,l,d,e.slice(1));for(let[d,p]of Object.entries(c)){let y=d.replace(Ku,"");(!a||e.includes(y))&&To(n,l,s,p.callable,p.delegationSelector)}},trigger(n,e,t){if(typeof e!="string"||!n)return null;let i=ha(),r=ba(e),o=e!==r,s=null,a=!0,l=!0,c=!1;o&&i&&(s=i.Event(e,t),i(n).trigger(s),a=!s.isPropagationStopped(),l=!s.isImmediatePropagationStopped(),c=s.isDefaultPrevented());let u=Do(new Event(e,{bubbles:a,cancelable:!0}),t);return c&&u.preventDefault(),l&&n.dispatchEvent(u),u.defaultPrevented&&s&&s.preventDefault(),u}};function Do(n,e={}){for(let[t,i]of Object.entries(e))try{n[t]=i}catch(r){Object.defineProperty(n,t,{configurable:!0,get(){return i}})}return n}function Fs(n){if(n==="true")return!0;if(n==="false")return!1;if(n===Number(n).toString())return Number(n);if(n===""||n==="null")return null;if(typeof n!="string")return n;try{return JSON.parse(decodeURIComponent(n))}catch(e){return n}}function co(n){return n.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}var ft={setDataAttribute(n,e,t){n.setAttribute(`data-bs-${co(e)}`,t)},removeDataAttribute(n,e){n.removeAttribute(`data-bs-${co(e)}`)},getDataAttributes(n){if(!n)return{};let e={},t=Object.keys(n.dataset).filter(i=>i.startsWith("bs")&&!i.startsWith("bsConfig"));for(let i of t){let r=i.replace(/^bs/,"");r=r.charAt(0).toLowerCase()+r.slice(1),e[r]=Fs(n.dataset[i])}return e},getDataAttribute(n,e){return Fs(n.getAttribute(`data-bs-${co(e)}`))}},Xt=class{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(e){return e=this._mergeConfigObj(e),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}_configAfterMerge(e){return e}_mergeConfigObj(e,t){let i=dt(t)?ft.getDataAttribute(t,"config"):{};return O(O(O(O({},this.constructor.Default),typeof i=="object"?i:{}),dt(t)?ft.getDataAttributes(t):{}),typeof e=="object"?e:{})}_typeCheckConfig(e,t=this.constructor.DefaultType){for(let[i,r]of Object.entries(t)){let o=e[i],s=dt(o)?"element":ju(o);if(!new RegExp(r).test(s))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${i}" provided type "${s}" but expected type "${r}".`)}}},ed="5.3.8",qe=class extends Xt{constructor(e,t){super(),e=xt(e),e&&(this._element=e,this._config=this._getConfig(t),so.set(this._element,this.constructor.DATA_KEY,this))}dispose(){so.remove(this._element,this.constructor.DATA_KEY),T.off(this._element,this.constructor.EVENT_KEY);for(let e of Object.getOwnPropertyNames(this))this[e]=null}_queueCallback(e,t,i=!0){pa(e,t,i)}_getConfig(e){return e=this._mergeConfigObj(e,this._element),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}static getInstance(e){return so.get(xt(e),this.DATA_KEY)}static getOrCreateInstance(e,t={}){return this.getInstance(e)||new this(e,typeof t=="object"?t:null)}static get VERSION(){return ed}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(e){return`${e}${this.EVENT_KEY}`}},uo=n=>{let e=n.getAttribute("data-bs-target");if(!e||e==="#"){let t=n.getAttribute("href");if(!t||!t.includes("#")&&!t.startsWith("."))return null;t.includes("#")&&!t.startsWith("#")&&(t=`#${t.split("#")[1]}`),e=t&&t!=="#"?t.trim():null}return e?e.split(",").map(t=>ua(t)).join(","):null},z={find(n,e=document.documentElement){return[].concat(...Element.prototype.querySelectorAll.call(e,n))},findOne(n,e=document.documentElement){return Element.prototype.querySelector.call(e,n)},children(n,e){return[].concat(...n.children).filter(t=>t.matches(e))},parents(n,e){let t=[],i=n.parentNode.closest(e);for(;i;)t.push(i),i=i.parentNode.closest(e);return t},prev(n,e){let t=n.previousElementSibling;for(;t;){if(t.matches(e))return[t];t=t.previousElementSibling}return[]},next(n,e){let t=n.nextElementSibling;for(;t;){if(t.matches(e))return[t];t=t.nextElementSibling}return[]},focusableChildren(n){let e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map(t=>`${t}:not([tabindex^="-"])`).join(",");return this.find(e,n).filter(t=>!Tt(t)&&Cn(t))},getSelectorFromElement(n){let e=uo(n);return e&&z.findOne(e)?e:null},getElementFromSelector(n){let e=uo(n);return e?z.findOne(e):null},getMultipleElementsFromSelector(n){let e=uo(n);return e?z.find(e):[]}},tr=(n,e="hide")=>{let t=`click.dismiss${n.EVENT_KEY}`,i=n.NAME;T.on(document,t,`[data-bs-dismiss="${i}"]`,function(r){if(["A","AREA"].includes(this.tagName)&&r.preventDefault(),Tt(this))return;let o=z.getElementFromSelector(this)||this.closest(`.${i}`);n.getOrCreateInstance(o)[e]()})},td="alert",nd="bs.alert",_a=`.${nd}`,id=`close${_a}`,rd=`closed${_a}`,od="fade",sd="show",Gi=class n extends qe{static get NAME(){return td}close(){if(T.trigger(this._element,id).defaultPrevented)return;this._element.classList.remove(sd);let t=this._element.classList.contains(od);this._queueCallback(()=>this._destroyElement(),this._element,t)}_destroyElement(){this._element.remove(),T.trigger(this._element,rd),this.dispose()}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this);if(typeof e=="string"){if(t[e]===void 0||e.startsWith("_")||e==="constructor")throw new TypeError(`No method named "${e}"`);t[e](this)}})}};tr(Gi,"close");Je(Gi);var ad="button",ld="bs.button",cd=`.${ld}`,ud=".data-api",dd="active",$s='[data-bs-toggle="button"]',fd=`click${cd}${ud}`,Ki=class n extends qe{static get NAME(){return ad}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle(dd))}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this);e==="toggle"&&t[e]()})}};T.on(document,fd,$s,n=>{n.preventDefault();let e=n.target.closest($s);Ki.getOrCreateInstance(e).toggle()});Je(Ki);var hd="swipe",Sn=".bs.swipe",pd=`touchstart${Sn}`,md=`touchmove${Sn}`,gd=`touchend${Sn}`,vd=`pointerdown${Sn}`,yd=`pointerup${Sn}`,Ed="touch",bd="pen",_d="pointer-event",wd=40,xd={endCallback:null,leftCallback:null,rightCallback:null},Td={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"},Xi=class n extends Xt{constructor(e,t){super(),this._element=e,!(!e||!n.isSupported())&&(this._config=this._getConfig(t),this._deltaX=0,this._supportPointerEvents=!!window.PointerEvent,this._initEvents())}static get Default(){return xd}static get DefaultType(){return Td}static get NAME(){return hd}dispose(){T.off(this._element,Sn)}_start(e){if(!this._supportPointerEvents){this._deltaX=e.touches[0].clientX;return}this._eventIsPointerPenTouch(e)&&(this._deltaX=e.clientX)}_end(e){this._eventIsPointerPenTouch(e)&&(this._deltaX=e.clientX-this._deltaX),this._handleSwipe(),Pe(this._config.endCallback)}_move(e){this._deltaX=e.touches&&e.touches.length>1?0:e.touches[0].clientX-this._deltaX}_handleSwipe(){let e=Math.abs(this._deltaX);if(e<=wd)return;let t=e/this._deltaX;this._deltaX=0,t&&Pe(t>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(T.on(this._element,vd,e=>this._start(e)),T.on(this._element,yd,e=>this._end(e)),this._element.classList.add(_d)):(T.on(this._element,pd,e=>this._start(e)),T.on(this._element,md,e=>this._move(e)),T.on(this._element,gd,e=>this._end(e)))}_eventIsPointerPenTouch(e){return this._supportPointerEvents&&(e.pointerType===bd||e.pointerType===Ed)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}},Cd="carousel",Sd="bs.carousel",Dt=`.${Sd}`,wa=".data-api",Ad="ArrowLeft",Dd="ArrowRight",Od=500,Xn="next",En="prev",_n="left",qi="right",Ld=`slide${Dt}`,fo=`slid${Dt}`,Md=`keydown${Dt}`,Nd=`mouseenter${Dt}`,kd=`mouseleave${Dt}`,Rd=`dragstart${Dt}`,Hd=`load${Dt}${wa}`,Id=`click${Dt}${wa}`,xa="carousel",$i="active",Pd="slide",Fd="carousel-item-end",$d="carousel-item-start",Bd="carousel-item-next",Vd="carousel-item-prev",Ta=".active",Ca=".carousel-item",zd=Ta+Ca,jd=".carousel-item img",Wd=".carousel-indicators",qd="[data-bs-slide], [data-bs-slide-to]",Ud='[data-bs-ride="carousel"]',Yd={[Ad]:qi,[Dd]:_n},Gd={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},Kd={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"},Jn=class n extends qe{constructor(e,t){super(e,t),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=z.findOne(Wd,this._element),this._addEventListeners(),this._config.ride===xa&&this.cycle()}static get Default(){return Gd}static get DefaultType(){return Kd}static get NAME(){return Cd}next(){this._slide(Xn)}nextWhenVisible(){!document.hidden&&Cn(this._element)&&this.next()}prev(){this._slide(En)}pause(){this._isSliding&&da(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval(()=>this.nextWhenVisible(),this._config.interval)}_maybeEnableCycle(){if(this._config.ride){if(this._isSliding){T.one(this._element,fo,()=>this.cycle());return}this.cycle()}}to(e){let t=this._getItems();if(e>t.length-1||e<0)return;if(this._isSliding){T.one(this._element,fo,()=>this.to(e));return}let i=this._getItemIndex(this._getActive());if(i===e)return;let r=e>i?Xn:En;this._slide(r,t[e])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(e){return e.defaultInterval=e.interval,e}_addEventListeners(){this._config.keyboard&&T.on(this._element,Md,e=>this._keydown(e)),this._config.pause==="hover"&&(T.on(this._element,Nd,()=>this.pause()),T.on(this._element,kd,()=>this._maybeEnableCycle())),this._config.touch&&Xi.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(let i of z.find(jd,this._element))T.on(i,Rd,r=>r.preventDefault());let t={leftCallback:()=>this._slide(this._directionToOrder(_n)),rightCallback:()=>this._slide(this._directionToOrder(qi)),endCallback:()=>{this._config.pause==="hover"&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout(()=>this._maybeEnableCycle(),Od+this._config.interval))}};this._swipeHelper=new Xi(this._element,t)}_keydown(e){if(/input|textarea/i.test(e.target.tagName))return;let t=Yd[e.key];t&&(e.preventDefault(),this._slide(this._directionToOrder(t)))}_getItemIndex(e){return this._getItems().indexOf(e)}_setActiveIndicatorElement(e){if(!this._indicatorsElement)return;let t=z.findOne(Ta,this._indicatorsElement);t.classList.remove($i),t.removeAttribute("aria-current");let i=z.findOne(`[data-bs-slide-to="${e}"]`,this._indicatorsElement);i&&(i.classList.add($i),i.setAttribute("aria-current","true"))}_updateInterval(){let e=this._activeElement||this._getActive();if(!e)return;let t=Number.parseInt(e.getAttribute("data-bs-interval"),10);this._config.interval=t||this._config.defaultInterval}_slide(e,t=null){if(this._isSliding)return;let i=this._getActive(),r=e===Xn,o=t||Ao(this._getItems(),i,r,this._config.wrap);if(o===i)return;let s=this._getItemIndex(o),a=y=>T.trigger(this._element,y,{relatedTarget:o,direction:this._orderToDirection(e),from:this._getItemIndex(i),to:s});if(a(Ld).defaultPrevented||!i||!o)return;let c=!!this._interval;this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(s),this._activeElement=o;let u=r?$d:Fd,d=r?Bd:Vd;o.classList.add(d),ei(o),i.classList.add(u),o.classList.add(u);let p=()=>{o.classList.remove(u,d),o.classList.add($i),i.classList.remove($i,d,u),this._isSliding=!1,a(fo)};this._queueCallback(p,i,this._isAnimated()),c&&this.cycle()}_isAnimated(){return this._element.classList.contains(Pd)}_getActive(){return z.findOne(zd,this._element)}_getItems(){return z.find(Ca,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(e){return Xe()?e===_n?En:Xn:e===_n?Xn:En}_orderToDirection(e){return Xe()?e===En?_n:qi:e===En?qi:_n}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this,e);if(typeof e=="number"){t.to(e);return}if(typeof e=="string"){if(t[e]===void 0||e.startsWith("_")||e==="constructor")throw new TypeError(`No method named "${e}"`);t[e]()}})}};T.on(document,Id,qd,function(n){let e=z.getElementFromSelector(this);if(!e||!e.classList.contains(xa))return;n.preventDefault();let t=Jn.getOrCreateInstance(e),i=this.getAttribute("data-bs-slide-to");if(i){t.to(i),t._maybeEnableCycle();return}if(ft.getDataAttribute(this,"slide")==="next"){t.next(),t._maybeEnableCycle();return}t.prev(),t._maybeEnableCycle()});T.on(window,Hd,()=>{let n=z.find(Ud);for(let e of n)Jn.getOrCreateInstance(e)});Je(Jn);var Xd="collapse",Qd="bs.collapse",ti=`.${Qd}`,Jd=".data-api",Zd=`show${ti}`,ef=`shown${ti}`,tf=`hide${ti}`,nf=`hidden${ti}`,rf=`click${ti}${Jd}`,ho="show",xn="collapse",Bi="collapsing",of="collapsed",sf=`:scope .${xn} .${xn}`,af="collapse-horizontal",lf="width",cf="height",uf=".collapse.show, .collapse.collapsing",Co='[data-bs-toggle="collapse"]',df={parent:null,toggle:!0},ff={parent:"(null|element)",toggle:"boolean"},Ct=class n extends qe{constructor(e,t){super(e,t),this._isTransitioning=!1,this._triggerArray=[];let i=z.find(Co);for(let r of i){let o=z.getSelectorFromElement(r),s=z.find(o).filter(a=>a===this._element);o!==null&&s.length&&this._triggerArray.push(r)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return df}static get DefaultType(){return ff}static get NAME(){return Xd}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let e=[];if(this._config.parent&&(e=this._getFirstLevelChildren(uf).filter(a=>a!==this._element).map(a=>n.getOrCreateInstance(a,{toggle:!1}))),e.length&&e[0]._isTransitioning||T.trigger(this._element,Zd).defaultPrevented)return;for(let a of e)a.hide();let i=this._getDimension();this._element.classList.remove(xn),this._element.classList.add(Bi),this._element.style[i]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;let r=()=>{this._isTransitioning=!1,this._element.classList.remove(Bi),this._element.classList.add(xn,ho),this._element.style[i]="",T.trigger(this._element,ef)},s=`scroll${i[0].toUpperCase()+i.slice(1)}`;this._queueCallback(r,this._element,!0),this._element.style[i]=`${this._element[s]}px`}hide(){if(this._isTransitioning||!this._isShown()||T.trigger(this._element,tf).defaultPrevented)return;let t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,ei(this._element),this._element.classList.add(Bi),this._element.classList.remove(xn,ho);for(let r of this._triggerArray){let o=z.getElementFromSelector(r);o&&!this._isShown(o)&&this._addAriaAndCollapsedClass([r],!1)}this._isTransitioning=!0;let i=()=>{this._isTransitioning=!1,this._element.classList.remove(Bi),this._element.classList.add(xn),T.trigger(this._element,nf)};this._element.style[t]="",this._queueCallback(i,this._element,!0)}_isShown(e=this._element){return e.classList.contains(ho)}_configAfterMerge(e){return e.toggle=!!e.toggle,e.parent=xt(e.parent),e}_getDimension(){return this._element.classList.contains(af)?lf:cf}_initializeChildren(){if(!this._config.parent)return;let e=this._getFirstLevelChildren(Co);for(let t of e){let i=z.getElementFromSelector(t);i&&this._addAriaAndCollapsedClass([t],this._isShown(i))}}_getFirstLevelChildren(e){let t=z.find(sf,this._config.parent);return z.find(e,this._config.parent).filter(i=>!t.includes(i))}_addAriaAndCollapsedClass(e,t){if(e.length)for(let i of e)i.classList.toggle(of,!t),i.setAttribute("aria-expanded",t)}static jQueryInterface(e){let t={};return typeof e=="string"&&/show|hide/.test(e)&&(t.toggle=!1),this.each(function(){let i=n.getOrCreateInstance(this,t);if(typeof e=="string"){if(typeof i[e]=="undefined")throw new TypeError(`No method named "${e}"`);i[e]()}})}};T.on(document,rf,Co,function(n){(n.target.tagName==="A"||n.delegateTarget&&n.delegateTarget.tagName==="A")&&n.preventDefault();for(let e of z.getMultipleElementsFromSelector(this))Ct.getOrCreateInstance(e,{toggle:!1}).toggle()});Je(Ct);var Bs="dropdown",hf="bs.dropdown",Jt=`.${hf}`,Oo=".data-api",pf="Escape",Vs="Tab",mf="ArrowUp",zs="ArrowDown",gf=2,vf=`hide${Jt}`,yf=`hidden${Jt}`,Ef=`show${Jt}`,bf=`shown${Jt}`,Sa=`click${Jt}${Oo}`,Aa=`keydown${Jt}${Oo}`,_f=`keyup${Jt}${Oo}`,wn="show",wf="dropup",xf="dropend",Tf="dropstart",Cf="dropup-center",Sf="dropdown-center",Gt='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',Af=`${Gt}.${wn}`,Ui=".dropdown-menu",Df=".navbar",Of=".navbar-nav",Lf=".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",Mf=Xe()?"top-end":"top-start",Nf=Xe()?"top-start":"top-end",kf=Xe()?"bottom-end":"bottom-start",Rf=Xe()?"bottom-start":"bottom-end",Hf=Xe()?"left-start":"right-start",If=Xe()?"right-start":"left-start",Pf="top",Ff="bottom",$f={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},Bf={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"},St=class n extends qe{constructor(e,t){super(e,t),this._popper=null,this._parent=this._element.parentNode,this._menu=z.next(this._element,Ui)[0]||z.prev(this._element,Ui)[0]||z.findOne(Ui,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return $f}static get DefaultType(){return Bf}static get NAME(){return Bs}toggle(){return this._isShown()?this.hide():this.show()}show(){if(Tt(this._element)||this._isShown())return;let e={relatedTarget:this._element};if(!T.trigger(this._element,Ef,e).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(Of))for(let i of[].concat(...document.body.children))T.on(i,"mouseover",Yi);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(wn),this._element.classList.add(wn),T.trigger(this._element,bf,e)}}hide(){if(Tt(this._element)||!this._isShown())return;let e={relatedTarget:this._element};this._completeHide(e)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(e){if(!T.trigger(this._element,vf,e).defaultPrevented){if("ontouchstart"in document.documentElement)for(let i of[].concat(...document.body.children))T.off(i,"mouseover",Yi);this._popper&&this._popper.destroy(),this._menu.classList.remove(wn),this._element.classList.remove(wn),this._element.setAttribute("aria-expanded","false"),ft.removeDataAttribute(this._menu,"popper"),T.trigger(this._element,yf,e)}}_getConfig(e){if(e=super._getConfig(e),typeof e.reference=="object"&&!dt(e.reference)&&typeof e.reference.getBoundingClientRect!="function")throw new TypeError(`${Bs.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return e}_createPopper(){if(typeof Fi=="undefined")throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org/docs/v2/)");let e=this._element;this._config.reference==="parent"?e=this._parent:dt(this._config.reference)?e=xt(this._config.reference):typeof this._config.reference=="object"&&(e=this._config.reference);let t=this._getPopperConfig();this._popper=Kn(e,this._menu,t)}_isShown(){return this._menu.classList.contains(wn)}_getPlacement(){let e=this._parent;if(e.classList.contains(xf))return Hf;if(e.classList.contains(Tf))return If;if(e.classList.contains(Cf))return Pf;if(e.classList.contains(Sf))return Ff;let t=getComputedStyle(this._menu).getPropertyValue("--bs-position").trim()==="end";return e.classList.contains(wf)?t?Nf:Mf:t?Rf:kf}_detectNavbar(){return this._element.closest(Df)!==null}_getOffset(){let{offset:e}=this._config;return typeof e=="string"?e.split(",").map(t=>Number.parseInt(t,10)):typeof e=="function"?t=>e(t,this._element):e}_getPopperConfig(){let e={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||this._config.display==="static")&&(ft.setDataAttribute(this._menu,"popper","static"),e.modifiers=[{name:"applyStyles",enabled:!1}]),O(O({},e),Pe(this._config.popperConfig,[void 0,e]))}_selectMenuItem({key:e,target:t}){let i=z.find(Lf,this._menu).filter(r=>Cn(r));i.length&&Ao(i,t,e===zs,!i.includes(t)).focus()}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this,e);if(typeof e=="string"){if(typeof t[e]=="undefined")throw new TypeError(`No method named "${e}"`);t[e]()}})}static clearMenus(e){if(e.button===gf||e.type==="keyup"&&e.key!==Vs)return;let t=z.find(Af);for(let i of t){let r=n.getInstance(i);if(!r||r._config.autoClose===!1)continue;let o=e.composedPath(),s=o.includes(r._menu);if(o.includes(r._element)||r._config.autoClose==="inside"&&!s||r._config.autoClose==="outside"&&s||r._menu.contains(e.target)&&(e.type==="keyup"&&e.key===Vs||/input|select|option|textarea|form/i.test(e.target.tagName)))continue;let a={relatedTarget:r._element};e.type==="click"&&(a.clickEvent=e),r._completeHide(a)}}static dataApiKeydownHandler(e){let t=/input|textarea/i.test(e.target.tagName),i=e.key===pf,r=[mf,zs].includes(e.key);if(!r&&!i||t&&!i)return;e.preventDefault();let o=this.matches(Gt)?this:z.prev(this,Gt)[0]||z.next(this,Gt)[0]||z.findOne(Gt,e.delegateTarget.parentNode),s=n.getOrCreateInstance(o);if(r){e.stopPropagation(),s.show(),s._selectMenuItem(e);return}s._isShown()&&(e.stopPropagation(),s.hide(),o.focus())}};T.on(document,Aa,Gt,St.dataApiKeydownHandler);T.on(document,Aa,Ui,St.dataApiKeydownHandler);T.on(document,Sa,St.clearMenus);T.on(document,_f,St.clearMenus);T.on(document,Sa,Gt,function(n){n.preventDefault(),St.getOrCreateInstance(this).toggle()});Je(St);var Da="backdrop",Vf="fade",js="show",Ws=`mousedown.bs.${Da}`,zf={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},jf={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"},Qi=class extends Xt{constructor(e){super(),this._config=this._getConfig(e),this._isAppended=!1,this._element=null}static get Default(){return zf}static get DefaultType(){return jf}static get NAME(){return Da}show(e){if(!this._config.isVisible){Pe(e);return}this._append();let t=this._getElement();this._config.isAnimated&&ei(t),t.classList.add(js),this._emulateAnimation(()=>{Pe(e)})}hide(e){if(!this._config.isVisible){Pe(e);return}this._getElement().classList.remove(js),this._emulateAnimation(()=>{this.dispose(),Pe(e)})}dispose(){this._isAppended&&(T.off(this._element,Ws),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){let e=document.createElement("div");e.className=this._config.className,this._config.isAnimated&&e.classList.add(Vf),this._element=e}return this._element}_configAfterMerge(e){return e.rootElement=xt(e.rootElement),e}_append(){if(this._isAppended)return;let e=this._getElement();this._config.rootElement.append(e),T.on(e,Ws,()=>{Pe(this._config.clickCallback)}),this._isAppended=!0}_emulateAnimation(e){pa(e,this._getElement(),this._config.isAnimated)}},Wf="focustrap",qf="bs.focustrap",Ji=`.${qf}`,Uf=`focusin${Ji}`,Yf=`keydown.tab${Ji}`,Gf="Tab",Kf="forward",qs="backward",Xf={autofocus:!0,trapElement:null},Qf={autofocus:"boolean",trapElement:"element"},Zi=class extends Xt{constructor(e){super(),this._config=this._getConfig(e),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return Xf}static get DefaultType(){return Qf}static get NAME(){return Wf}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),T.off(document,Ji),T.on(document,Uf,e=>this._handleFocusin(e)),T.on(document,Yf,e=>this._handleKeydown(e)),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,T.off(document,Ji))}_handleFocusin(e){let{trapElement:t}=this._config;if(e.target===document||e.target===t||t.contains(e.target))return;let i=z.focusableChildren(t);i.length===0?t.focus():this._lastTabNavDirection===qs?i[i.length-1].focus():i[0].focus()}_handleKeydown(e){e.key===Gf&&(this._lastTabNavDirection=e.shiftKey?qs:Kf)}},Us=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",Ys=".sticky-top",Vi="padding-right",Gs="margin-right",Zn=class{constructor(){this._element=document.body}getWidth(){let e=document.documentElement.clientWidth;return Math.abs(window.innerWidth-e)}hide(){let e=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,Vi,t=>t+e),this._setElementAttributes(Us,Vi,t=>t+e),this._setElementAttributes(Ys,Gs,t=>t-e)}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,Vi),this._resetElementAttributes(Us,Vi),this._resetElementAttributes(Ys,Gs)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(e,t,i){let r=this.getWidth(),o=s=>{if(s!==this._element&&window.innerWidth>s.clientWidth+r)return;this._saveInitialAttribute(s,t);let a=window.getComputedStyle(s).getPropertyValue(t);s.style.setProperty(t,`${i(Number.parseFloat(a))}px`)};this._applyManipulationCallback(e,o)}_saveInitialAttribute(e,t){let i=e.style.getPropertyValue(t);i&&ft.setDataAttribute(e,t,i)}_resetElementAttributes(e,t){let i=r=>{let o=ft.getDataAttribute(r,t);if(o===null){r.style.removeProperty(t);return}ft.removeDataAttribute(r,t),r.style.setProperty(t,o)};this._applyManipulationCallback(e,i)}_applyManipulationCallback(e,t){if(dt(e)){t(e);return}for(let i of z.find(e,this._element))t(i)}},Jf="modal",Zf="bs.modal",Qe=`.${Zf}`,eh=".data-api",th="Escape",nh=`hide${Qe}`,ih=`hidePrevented${Qe}`,Oa=`hidden${Qe}`,La=`show${Qe}`,rh=`shown${Qe}`,oh=`resize${Qe}`,sh=`click.dismiss${Qe}`,ah=`mousedown.dismiss${Qe}`,lh=`keydown.dismiss${Qe}`,ch=`click${Qe}${eh}`,Ks="modal-open",uh="fade",Xs="show",po="modal-static",dh=".modal.show",fh=".modal-dialog",hh=".modal-body",ph='[data-bs-toggle="modal"]',mh={backdrop:!0,focus:!0,keyboard:!0},gh={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"},tt=class n extends qe{constructor(e,t){super(e,t),this._dialog=z.findOne(fh,this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new Zn,this._addEventListeners()}static get Default(){return mh}static get DefaultType(){return gh}static get NAME(){return Jf}toggle(e){return this._isShown?this.hide():this.show(e)}show(e){this._isShown||this._isTransitioning||T.trigger(this._element,La,{relatedTarget:e}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(Ks),this._adjustDialog(),this._backdrop.show(()=>this._showElement(e)))}hide(){!this._isShown||this._isTransitioning||T.trigger(this._element,nh).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Xs),this._queueCallback(()=>this._hideModal(),this._element,this._isAnimated()))}dispose(){T.off(window,Qe),T.off(this._dialog,Qe),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Qi({isVisible:!!this._config.backdrop,isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Zi({trapElement:this._element})}_showElement(e){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;let t=z.findOne(hh,this._dialog);t&&(t.scrollTop=0),ei(this._element),this._element.classList.add(Xs);let i=()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,T.trigger(this._element,rh,{relatedTarget:e})};this._queueCallback(i,this._dialog,this._isAnimated())}_addEventListeners(){T.on(this._element,lh,e=>{if(e.key===th){if(this._config.keyboard){this.hide();return}this._triggerBackdropTransition()}}),T.on(window,oh,()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()}),T.on(this._element,ah,e=>{T.one(this._element,sh,t=>{if(!(this._element!==e.target||this._element!==t.target)){if(this._config.backdrop==="static"){this._triggerBackdropTransition();return}this._config.backdrop&&this.hide()}})})}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide(()=>{document.body.classList.remove(Ks),this._resetAdjustments(),this._scrollBar.reset(),T.trigger(this._element,Oa)})}_isAnimated(){return this._element.classList.contains(uh)}_triggerBackdropTransition(){if(T.trigger(this._element,ih).defaultPrevented)return;let t=this._element.scrollHeight>document.documentElement.clientHeight,i=this._element.style.overflowY;i==="hidden"||this._element.classList.contains(po)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(po),this._queueCallback(()=>{this._element.classList.remove(po),this._queueCallback(()=>{this._element.style.overflowY=i},this._dialog)},this._dialog),this._element.focus())}_adjustDialog(){let e=this._element.scrollHeight>document.documentElement.clientHeight,t=this._scrollBar.getWidth(),i=t>0;if(i&&!e){let r=Xe()?"paddingLeft":"paddingRight";this._element.style[r]=`${t}px`}if(!i&&e){let r=Xe()?"paddingRight":"paddingLeft";this._element.style[r]=`${t}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(e,t){return this.each(function(){let i=n.getOrCreateInstance(this,e);if(typeof e=="string"){if(typeof i[e]=="undefined")throw new TypeError(`No method named "${e}"`);i[e](t)}})}};T.on(document,ch,ph,function(n){let e=z.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&n.preventDefault(),T.one(e,La,r=>{r.defaultPrevented||T.one(e,Oa,()=>{Cn(this)&&this.focus()})});let t=z.findOne(dh);t&&tt.getInstance(t).hide(),tt.getOrCreateInstance(e).toggle(this)});tr(tt);Je(tt);var vh="offcanvas",yh="bs.offcanvas",mt=`.${yh}`,Ma=".data-api",Eh=`load${mt}${Ma}`,bh="Escape",Qs="show",Js="showing",Zs="hiding",_h="offcanvas-backdrop",Na=".offcanvas.show",wh=`show${mt}`,xh=`shown${mt}`,Th=`hide${mt}`,ea=`hidePrevented${mt}`,ka=`hidden${mt}`,Ch=`resize${mt}`,Sh=`click${mt}${Ma}`,Ah=`keydown.dismiss${mt}`,Dh='[data-bs-toggle="offcanvas"]',Oh={backdrop:!0,keyboard:!0,scroll:!1},Lh={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"},At=class n extends qe{constructor(e,t){super(e,t),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return Oh}static get DefaultType(){return Lh}static get NAME(){return vh}toggle(e){return this._isShown?this.hide():this.show(e)}show(e){if(this._isShown||T.trigger(this._element,wh,{relatedTarget:e}).defaultPrevented)return;this._isShown=!0,this._backdrop.show(),this._config.scroll||new Zn().hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Js);let i=()=>{(!this._config.scroll||this._config.backdrop)&&this._focustrap.activate(),this._element.classList.add(Qs),this._element.classList.remove(Js),T.trigger(this._element,xh,{relatedTarget:e})};this._queueCallback(i,this._element,!0)}hide(){if(!this._isShown||T.trigger(this._element,Th).defaultPrevented)return;this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(Zs),this._backdrop.hide();let t=()=>{this._element.classList.remove(Qs,Zs),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||new Zn().reset(),T.trigger(this._element,ka)};this._queueCallback(t,this._element,!0)}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){let e=()=>{if(this._config.backdrop==="static"){T.trigger(this._element,ea);return}this.hide()},t=!!this._config.backdrop;return new Qi({className:_h,isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?e:null})}_initializeFocusTrap(){return new Zi({trapElement:this._element})}_addEventListeners(){T.on(this._element,Ah,e=>{if(e.key===bh){if(this._config.keyboard){this.hide();return}T.trigger(this._element,ea)}})}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this,e);if(typeof e=="string"){if(t[e]===void 0||e.startsWith("_")||e==="constructor")throw new TypeError(`No method named "${e}"`);t[e](this)}})}};T.on(document,Sh,Dh,function(n){let e=z.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&n.preventDefault(),Tt(this))return;T.one(e,ka,()=>{Cn(this)&&this.focus()});let t=z.findOne(Na);t&&t!==e&&At.getInstance(t).hide(),At.getOrCreateInstance(e).toggle(this)});T.on(window,Eh,()=>{for(let n of z.find(Na))At.getOrCreateInstance(n).show()});T.on(window,Ch,()=>{for(let n of z.find("[aria-modal][class*=show][class*=offcanvas-]"))getComputedStyle(n).position!=="fixed"&&At.getOrCreateInstance(n).hide()});tr(At);Je(At);var Mh=/^aria-[\w-]*$/i,Ra={"*":["class","dir","id","lang","role",Mh],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],dd:[],div:[],dl:[],dt:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Nh=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),kh=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Rh=(n,e)=>{let t=n.nodeName.toLowerCase();return e.includes(t)?Nh.has(t)?!!kh.test(n.nodeValue):!0:e.filter(i=>i instanceof RegExp).some(i=>i.test(t))};function Hh(n,e,t){if(!n.length)return n;if(t&&typeof t=="function")return t(n);let r=new window.DOMParser().parseFromString(n,"text/html"),o=[].concat(...r.body.querySelectorAll("*"));for(let s of o){let a=s.nodeName.toLowerCase();if(!Object.keys(e).includes(a)){s.remove();continue}let l=[].concat(...s.attributes),c=[].concat(e["*"]||[],e[a]||[]);for(let u of l)Rh(u,c)||s.removeAttribute(u.nodeName)}return r.body.innerHTML}var Ih="TemplateFactory",Ph={allowList:Ra,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:""},Fh={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},$h={entry:"(string|element|function|null)",selector:"(string|element)"},So=class extends Xt{constructor(e){super(),this._config=this._getConfig(e)}static get Default(){return Ph}static get DefaultType(){return Fh}static get NAME(){return Ih}getContent(){return Object.values(this._config.content).map(e=>this._resolvePossibleFunction(e)).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(e){return this._checkContent(e),this._config.content=O(O({},this._config.content),e),this}toHtml(){let e=document.createElement("div");e.innerHTML=this._maybeSanitize(this._config.template);for(let[r,o]of Object.entries(this._config.content))this._setContent(e,o,r);let t=e.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&t.classList.add(...i.split(" ")),t}_typeCheckConfig(e){super._typeCheckConfig(e),this._checkContent(e.content)}_checkContent(e){for(let[t,i]of Object.entries(e))super._typeCheckConfig({selector:t,entry:i},$h)}_setContent(e,t,i){let r=z.findOne(i,e);if(r){if(t=this._resolvePossibleFunction(t),!t){r.remove();return}if(dt(t)){this._putElementInTemplate(xt(t),r);return}if(this._config.html){r.innerHTML=this._maybeSanitize(t);return}r.textContent=t}}_maybeSanitize(e){return this._config.sanitize?Hh(e,this._config.allowList,this._config.sanitizeFn):e}_resolvePossibleFunction(e){return Pe(e,[void 0,this])}_putElementInTemplate(e,t){if(this._config.html){t.innerHTML="",t.append(e);return}t.textContent=e.textContent}},Bh="tooltip",Vh=new Set(["sanitize","allowList","sanitizeFn"]),mo="fade",zh="modal",zi="show",jh=".tooltip-inner",ta=`.${zh}`,na="hide.bs.modal",Qn="hover",go="focus",vo="click",Wh="manual",qh="hide",Uh="hidden",Yh="show",Gh="shown",Kh="inserted",Xh="click",Qh="focusin",Jh="focusout",Zh="mouseenter",ep="mouseleave",tp={AUTO:"auto",TOP:"top",RIGHT:Xe()?"left":"right",BOTTOM:"bottom",LEFT:Xe()?"right":"left"},np={allowList:Ra,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'
',title:"",trigger:"hover focus"},ip={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"},ht=class n extends qe{constructor(e,t){if(typeof Fi=="undefined")throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org/docs/v2/)");super(e,t),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return np}static get DefaultType(){return ip}static get NAME(){return Bh}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){if(this._isEnabled){if(this._isShown()){this._leave();return}this._enter()}}dispose(){clearTimeout(this._timeout),T.off(this._element.closest(ta),na,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if(this._element.style.display==="none")throw new Error("Please use show on visible elements");if(!(this._isWithContent()&&this._isEnabled))return;let e=T.trigger(this._element,this.constructor.eventName(Yh)),i=(fa(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(e.defaultPrevented||!i)return;this._disposePopper();let r=this._getTipElement();this._element.setAttribute("aria-describedby",r.getAttribute("id"));let{container:o}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(o.append(r),T.trigger(this._element,this.constructor.eventName(Kh))),this._popper=this._createPopper(r),r.classList.add(zi),"ontouchstart"in document.documentElement)for(let a of[].concat(...document.body.children))T.on(a,"mouseover",Yi);let s=()=>{T.trigger(this._element,this.constructor.eventName(Gh)),this._isHovered===!1&&this._leave(),this._isHovered=!1};this._queueCallback(s,this.tip,this._isAnimated())}hide(){if(!this._isShown()||T.trigger(this._element,this.constructor.eventName(qh)).defaultPrevented)return;if(this._getTipElement().classList.remove(zi),"ontouchstart"in document.documentElement)for(let r of[].concat(...document.body.children))T.off(r,"mouseover",Yi);this._activeTrigger[vo]=!1,this._activeTrigger[go]=!1,this._activeTrigger[Qn]=!1,this._isHovered=null;let i=()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),T.trigger(this._element,this.constructor.eventName(Uh)))};this._queueCallback(i,this.tip,this._isAnimated())}update(){this._popper&&this._popper.update()}_isWithContent(){return!!this._getTitle()}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(e){let t=this._getTemplateFactory(e).toHtml();if(!t)return null;t.classList.remove(mo,zi),t.classList.add(`bs-${this.constructor.NAME}-auto`);let i=Wu(this.constructor.NAME).toString();return t.setAttribute("id",i),this._isAnimated()&&t.classList.add(mo),t}setContent(e){this._newContent=e,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(e){return this._templateFactory?this._templateFactory.changeContent(e):this._templateFactory=new So(ae(O({},this._config),{content:e,extraClass:this._resolvePossibleFunction(this._config.customClass)})),this._templateFactory}_getContentForTemplate(){return{[jh]:this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(e){return this.constructor.getOrCreateInstance(e.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(mo)}_isShown(){return this.tip&&this.tip.classList.contains(zi)}_createPopper(e){let t=Pe(this._config.placement,[this,e,this._element]),i=tp[t.toUpperCase()];return Kn(this._element,e,this._getPopperConfig(i))}_getOffset(){let{offset:e}=this._config;return typeof e=="string"?e.split(",").map(t=>Number.parseInt(t,10)):typeof e=="function"?t=>e(t,this._element):e}_resolvePossibleFunction(e){return Pe(e,[this._element,this._element])}_getPopperConfig(e){let t={placement:e,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:i=>{this._getTipElement().setAttribute("data-popper-placement",i.state.placement)}}]};return O(O({},t),Pe(this._config.popperConfig,[void 0,t]))}_setListeners(){let e=this._config.trigger.split(" ");for(let t of e)if(t==="click")T.on(this._element,this.constructor.eventName(Xh),this._config.selector,i=>{let r=this._initializeOnDelegatedTarget(i);r._activeTrigger[vo]=!(r._isShown()&&r._activeTrigger[vo]),r.toggle()});else if(t!==Wh){let i=t===Qn?this.constructor.eventName(Zh):this.constructor.eventName(Qh),r=t===Qn?this.constructor.eventName(ep):this.constructor.eventName(Jh);T.on(this._element,i,this._config.selector,o=>{let s=this._initializeOnDelegatedTarget(o);s._activeTrigger[o.type==="focusin"?go:Qn]=!0,s._enter()}),T.on(this._element,r,this._config.selector,o=>{let s=this._initializeOnDelegatedTarget(o);s._activeTrigger[o.type==="focusout"?go:Qn]=s._element.contains(o.relatedTarget),s._leave()})}this._hideModalHandler=()=>{this._element&&this.hide()},T.on(this._element.closest(ta),na,this._hideModalHandler)}_fixTitle(){let e=this._element.getAttribute("title");e&&(!this._element.getAttribute("aria-label")&&!this._element.textContent.trim()&&this._element.setAttribute("aria-label",e),this._element.setAttribute("data-bs-original-title",e),this._element.removeAttribute("title"))}_enter(){if(this._isShown()||this._isHovered){this._isHovered=!0;return}this._isHovered=!0,this._setTimeout(()=>{this._isHovered&&this.show()},this._config.delay.show)}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout(()=>{this._isHovered||this.hide()},this._config.delay.hide))}_setTimeout(e,t){clearTimeout(this._timeout),this._timeout=setTimeout(e,t)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(e){let t=ft.getDataAttributes(this._element);for(let i of Object.keys(t))Vh.has(i)&&delete t[i];return e=O(O({},t),typeof e=="object"&&e?e:{}),e=this._mergeConfigObj(e),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}_configAfterMerge(e){return e.container=e.container===!1?document.body:xt(e.container),typeof e.delay=="number"&&(e.delay={show:e.delay,hide:e.delay}),typeof e.title=="number"&&(e.title=e.title.toString()),typeof e.content=="number"&&(e.content=e.content.toString()),e}_getDelegateConfig(){let e={};for(let[t,i]of Object.entries(this._config))this.constructor.Default[t]!==i&&(e[t]=i);return e.selector=!1,e.trigger="manual",e}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this,e);if(typeof e=="string"){if(typeof t[e]=="undefined")throw new TypeError(`No method named "${e}"`);t[e]()}})}};Je(ht);var rp="popover",op=".popover-header",sp=".popover-body",ap=ae(O({},ht.Default),{content:"",offset:[0,8],placement:"right",template:'
'},n.settings.render),o.addEventListener("scroll",()=>{n.settings.shouldLoadMore.call(n)&&y(n.lastValue)&&(s||(s=!0,n.load.call(n,n.lastValue)))})})}we.define("change_listener",il);we.define("checkbox_options",ol);we.define("clear_button",sl);we.define("drag_drop",al);we.define("dropdown_header",ll);we.define("caret_position",cl);we.define("dropdown_input",dl);we.define("input_autogrow",fl);we.define("no_backspace_delete",hl);we.define("no_active_items",pl);we.define("optgroup_columns",ml);we.define("remove_button",El);we.define("restore_on_backspace",bl);we.define("virtual_scroll",_l);var wl=we;function en(n){return"error"in n}function ye(n){let e=["","null","undefined"];return Array.isArray(n)?n.length>0:typeof n=="string"&&!e.includes(n)||typeof n=="number"||typeof n=="boolean"?!0:typeof n=="object"&&n!==null}function hr(n){return typeof n!==null&&typeof n!="undefined"}function cg(n,e,t){return at(this,null,function*(){let i=window.CSRF_TOKEN,r=new Headers({"X-CSRFToken":i}),o;typeof t!="undefined"&&(o=JSON.stringify(t),r.set("content-type","application/json"));let s=yield fetch(n,{method:e,body:o,headers:r,credentials:"same-origin"}),a=s.headers.get("Content-Type");if(typeof a=="string"&&a.includes("text"))return{error:yield s.text()};let l=yield s.json();return!s.ok&&Array.isArray(l)?{error:l.join(`
-`)}:!s.ok&&"detail"in l?{error:l.detail}:l})}function Dn(n,e){return at(this,null,function*(){return yield cg(n,"PATCH",e)})}function*k(...n){for(let e of n)for(let t of document.querySelectorAll(e))t!==null&&(yield t)}function ui(n){return document.getElementById(n)}function xl(n,e="select"){let t=[];for(let i of n.querySelectorAll(e))if(i!==null){let r={name:i.name,options:[]};for(let o of i.options)o.selected&&r.options.push(o.value);t=[...t,r]}return t}function Tl(n,e,t){function i(o){return!!(typeof t=="string"&&o!==null&&o.matches(t))}function r(o){if(o!==null&&o.parentElement!==null&&!i(o)){for(let s of o.parentElement.querySelectorAll(e))if(s!==null)return s;return r(o.parentElement.parentElement)}return null}return r(n)}function jo(n,e,t=null,i=[]){let r=document.createElement(n);if(e!==null)for(let o of Object.keys(e)){let s=o,a=e[s];s in r&&(r[s]=a)}t!==null&&t.length>0&&r.classList.add(...t);for(let o of i)r.appendChild(o);return r}function Wo(n,e,t){if(typeof n!="string")throw new TypeError("replaceAll 'input' argument must be a string");if(typeof e!="string"&&!(e instanceof RegExp))throw new TypeError("replaceAll 'pattern' argument must be a string or RegExp instance");switch(typeof t){case"boolean":t=String(t);break;case"number":t=String(t);break;case"string":break;default:throw new TypeError("replaceAll 'replacement' argument must be stringifyable")}if(e instanceof RegExp){let i=Array.from(new Set([...e.flags.split(""),"g"])).join("");e=new RegExp(e.source,i)}else e=new RegExp(e,"g");return n.replace(e,t)}function Cl(){for(let n of k("[data-requires-fields]")){let e=n.getAttribute("data-requires-fields");if(!e)continue;let t=e.split(",").map(i=>i.trim());for(let i of t){let r=document.querySelector(`[name="${i}"]`);r&&r.addEventListener("change",()=>{if(!r.value||r.value===""){let o=n.tomselect;o?o.clear():n.value=""}})}}}function ug(){for(let n of k("select.select-all option"))n.selected=!0}function Sl(){for(let n of k("form")){let e=n.querySelectorAll("button[type=submit]");for(let i of e)i.addEventListener("click",()=>ug());let t=document.querySelector("button[data-reset-select]");t!==null&&t.addEventListener("click",()=>{window.location.assign(window.location.origin+window.location.pathname)})}}var di="empty_true",pr="empty_false";function Dl(){for(let n of k("form")){let e=n.querySelectorAll(".modifier-select");e.length!==0&&(fg(n),e.forEach(t=>{t.addEventListener("change",()=>Al(t)),Al(t)}),n.addEventListener("submit",t=>{t.preventDefault();let i=new FormData(n);dg(n,i);let r=new URLSearchParams;for(let[s,a]of i.entries())a&&String(a).trim()&&r.append(s,String(a));let o=n.getAttribute("action")||n.action;window.location.href=`${o}?${r.toString()}`}))}}function Al(n){let e=n.closest(".filter-modifier-group");if(!e)return;let t=e.querySelector(".filter-value-container");if(!t)return;let i=t.querySelector("input, select, textarea");if(!i)return;let r=n.value;if(r===di||r===pr){i.disabled=!0,i.value="";let o=n.dataset.emptyPlaceholder||"(automatically set)";i.setAttribute("placeholder",o)}else i.disabled=!1,i.removeAttribute("placeholder")}function dg(n,e){let t=n.querySelectorAll(".filter-modifier-group");for(let i of t){let r=i.querySelector(".modifier-select"),o=i.querySelector(".filter-value-container");if(!o)continue;let s=o.querySelector("input, select, textarea");if(!r||!s)continue;let a=s.name,l=r.value;if(l===di||l===pr){e.delete(a);let c=l===di?"true":"false";e.set(`${a}__empty`,c)}else{let c=e.getAll(a);if(c.length>0&&c.some(u=>String(u).trim())){e.delete(a);let u=l==="exact"?a:`${a}__${l}`;for(let d of c)String(d).trim()&&e.append(u,d)}else e.delete(a)}}}function fg(n){let e=new URLSearchParams(window.location.search),t=n.querySelectorAll(".filter-modifier-group");for(let i of t){let r=i.querySelector(".modifier-select"),o=i.querySelector(".filter-value-container");if(!o)continue;let s=o.querySelector("input, select, textarea");if(!r||!s)continue;let a=s.name,l=`${a}__empty`;if(e.has(l)){let u=e.get(l)==="true"?di:pr;r.value=u;continue}for(let c of r.options){let u=c.value;if(u===di||u===pr)continue;let d=u==="exact"?a:`${a}__${u}`;if(e.has(d)){if(r.value=u,s instanceof HTMLSelectElement&&s.multiple){let p=e.getAll(d);for(let y of s.options)y.selected=p.includes(y.value)}else s.value=e.get(d)||"";break}}}}function Ol(){for(let e of k("a.set_field_value"))if(e!==null){let t=function(i){i.preventDefault();let r=e.getAttribute("data"),o=document.getElementById(e.target);o!==null&&r!==null&&(o.value=r)};var n=t;e.addEventListener("click",t)}}function mr(){for(let n of[Sl,Ol,Dl,Cl])n()}window.Collapse=Ct;window.Modal=tt;window.Popover=Tn;window.Toast=pt;window.Tooltip=ht;function hg(){for(let n of k('[data-bs-toggle="tooltip"]'))new ht(n,{container:"body"})}function pg(){for(let n of k('[data-bs-toggle="modal"]'))new tt(n)}function kt(n,e,t,i){let r="mdi-alert";switch(n){case"warning":r="mdi-alert";break;case"success":r="mdi-check-circle";break;case"info":r="mdi-information";break;case"danger":r="mdi-alert";break}let o=document.createElement("div");o.setAttribute("class","toast-container position-fixed bottom-0 end-0 m-3");let s=document.createElement("div");s.setAttribute("class",`toast bg-${n}`),s.setAttribute("role","alert"),s.setAttribute("aria-live","assertive"),s.setAttribute("aria-atomic","true");let a=document.createElement("div");a.setAttribute("class",`toast-header bg-${n} text-body`);let l=document.createElement("i");l.setAttribute("class",`mdi ${r}`);let c=document.createElement("strong");c.setAttribute("class","me-auto ms-1"),c.innerText=e;let u=document.createElement("button");u.setAttribute("type","button"),u.setAttribute("class","btn-close"),u.setAttribute("data-bs-dismiss","toast"),u.setAttribute("aria-label","Close");let d=document.createElement("div");if(d.setAttribute("class","toast-body"),a.appendChild(l),a.appendChild(c),typeof i!="undefined"){let y=document.createElement("small");y.setAttribute("class","text-muted"),a.appendChild(y)}return a.appendChild(u),d.innerText=t.trim(),s.appendChild(a),s.appendChild(d),o.appendChild(s),document.body.appendChild(o),new pt(s)}function mg(){let{hash:n}=location;if(n&&n.match(/^#tab_.+$/)){let e=n.replace("tab_","");for(let t of k(`ul.nav.nav-tabs .nav-link[data-bs-target="${e}"]`))new Qt(t).show()}}function gg(){let n=document.querySelectorAll(".sidebar .accordion-item");function e(t){for(let i of n)i!==t?i.classList.remove("is-open"):i.classList.toggle("is-open")}for(let t of n)for(let i of t.querySelectorAll(".accordion-button"))i.addEventListener("click",()=>{e(t)})}function vg(){var n;for(let e of k("a.image-preview")){let t=(n=e.dataset.previewUrl)!=null?n:e.href,i=jo("img",{src:t});i.loading="lazy",i.decoding="async";let r=jo("div",null,null,[i]);new Tn(e,{customClass:"image-preview-popover",trigger:"hover",html:!0,content:r})}}function gr(){for(let n of[hg,pg,mg,vg,gg])n()}function Ll(n){let e=n.currentTarget,t=document.getElementById("quicksearch_clear");ye(t)&&(e.value===""?t.classList.add("invisible"):t.classList.remove("invisible"))}function Ml(){let n=document.getElementById("export_current_view"),e=n==null?void 0:n.href.split("&")[0];n.setAttribute("href",e)}function yg(n){let e=n.currentTarget;if(Ml(),e!=null){let t=document.getElementById("export_current_view"),i=new URLSearchParams;i.set("q",e.value);let r=i.toString(),o=(t==null?void 0:t.href)+"&"+r;t.setAttribute("href",o)}}function Nl(){let n=document.getElementById("quicksearch"),e=document.getElementById("quicksearch_clear");ye(n)&&(n.addEventListener("keyup",Ll,{passive:!0}),n.addEventListener("search",Ll,{passive:!0}),n.addEventListener("change",yg,{passive:!0}),ye(e)&&e.addEventListener("click",()=>at(null,null,function*(){let t=new Event("search");n.value="",yield new Promise(i=>setTimeout(i,100)),n.dispatchEvent(t),Ml()}),{passive:!0}))}var tn=class extends wl{setup(){super.setup(),this.input.setAttribute("aria-hidden","true")}focus(){if(this.isDisabled||this.isReadOnly)return;this.ignoreFocus=!0;let e=this.control_input.offsetWidth?this.control_input:this.focus_node;e.focus(),setTimeout(()=>{this.ignoreFocus=!1,(document.activeElement===e||this.control.contains(document.activeElement))&&this.onFocus()},0)}};function fi(n){let e={};return n.required||(e.clear_button={html:t=>``}),n.hasAttribute("multiple")&&(e.remove_button={title:"Remove"}),n.hasAttribute("multiple")&&(e.drag_drop={}),{plugins:e}}function kl(){for(let n of k("select:not(.tomselected):not(.no-ts):not([size]):not(.api-select):not(.color-select)"))new tn(n,ae(O({},fi(n)),{maxOptions:void 0}))}function Rl(){function n(e,t){return`
${t(e.text)}
`}for(let e of k("select.color-select:not(.tomselected)"))new tn(e,ae(O({},fi(e)),{maxOptions:void 0,render:{option:n,item:n}}))}var Hl=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)};var Il=(n,...e)=>{var t=Fl(e);n=$l(n),n.map(i=>{t.map(r=>{i.classList.add(r)})})},Pl=(n,...e)=>{var t=Fl(e);n=$l(n),n.map(i=>{t.map(r=>{i.classList.remove(r)})})},Fl=n=>{var e=[];return Hl(n,t=>{typeof t=="string"&&(t=t.trim().split(/[\t\n\f\r\s]/)),Array.isArray(t)&&(e=e.concat(t))}),e.filter(Boolean)},$l=n=>(Array.isArray(n)||(n=[n]),n);var Qo={};gs(Qo,{exclude:()=>Sg,extract:()=>Ko,parse:()=>Xo,parseUrl:()=>Xl,pick:()=>Go,stringify:()=>Kl,stringifyUrl:()=>Ql});var zl="%[a-f0-9]{2}",Bl=new RegExp("("+zl+")|([^%]+?)","gi"),Vl=new RegExp("("+zl+")+","gi");function qo(n,e){try{return[decodeURIComponent(n.join(""))]}catch(r){}if(n.length===1)return n;e=e||1;let t=n.slice(0,e),i=n.slice(e);return Array.prototype.concat.call([],qo(t),qo(i))}function Eg(n){try{return decodeURIComponent(n)}catch(e){let t=n.match(Bl)||[];for(let i=1;in==null,wg=n=>encodeURIComponent(n).replaceAll(/[!'()*]/g,e=>`%${e.charCodeAt(0).toString(16).toUpperCase()}`),Yo=Symbol("encodeFragmentIdentifier");function xg(n){switch(n.arrayFormat){case"index":return e=>(t,i)=>{let r=t.length;return i===void 0||n.skipNull&&i===null||n.skipEmptyString&&i===""?t:i===null?(t.push([Ee(e,n),"[",r,"]"].join("")),t):(t.push([Ee(e,n),"[",Ee(r,n),"]=",Ee(i,n)].join("")),t)};case"bracket":return e=>(t,i)=>i===void 0||n.skipNull&&i===null||n.skipEmptyString&&i===""?t:i===null?(t.push([Ee(e,n),"[]"].join("")),t):(t.push([Ee(e,n),"[]=",Ee(i,n)].join("")),t);case"colon-list-separator":return e=>(t,i)=>i===void 0||n.skipNull&&i===null||n.skipEmptyString&&i===""?t:i===null?(t.push([Ee(e,n),":list="].join("")),t):(t.push([Ee(e,n),":list=",Ee(i,n)].join("")),t);case"comma":case"separator":case"bracket-separator":{let e=n.arrayFormat==="bracket-separator"?"[]=":"=";return t=>(i,r)=>r===void 0||n.skipNull&&r===null||n.skipEmptyString&&r===""?i:(r=r===null?"":r,i.length===0?(i.push([Ee(t,n),e,Ee(r,n)].join("")),i):(i.push(Ee(r,n)),i))}default:return e=>(t,i)=>i===void 0||n.skipNull&&i===null||n.skipEmptyString&&i===""?t:i===null?(t.push(Ee(e,n)),t):(t.push([Ee(e,n),"=",Ee(i,n)].join("")),t)}}function Tg(n){let e;switch(n.arrayFormat){case"index":return(t,i,r)=>{if(e=/\[(\d*)]$/.exec(t),t=t.replace(/\[\d*]$/,""),!e){r[t]=i;return}r[t]===void 0&&(r[t]={}),r[t][e[1]]=i};case"bracket":return(t,i,r)=>{if(e=/(\[])$/.exec(t),t=t.replace(/\[]$/,""),!e){r[t]=i;return}if(r[t]===void 0){r[t]=[i];return}if(!Array.isArray(r[t])){r[t]=[r[t],i];return}r[t].push(i)};case"colon-list-separator":return(t,i,r)=>{if(e=/(:list)$/.exec(t),t=t.replace(/:list$/,""),!e){r[t]=i;return}if(r[t]===void 0){r[t]=[i];return}if(!Array.isArray(r[t])){r[t]=[r[t],i];return}r[t].push(i)};case"comma":case"separator":return(t,i,r)=>{let s=typeof i=="string"&&i.includes(n.arrayFormatSeparator)?i.split(n.arrayFormatSeparator).map(a=>nn(a,n)):i===null?i:nn(i,n);r[t]=s};case"bracket-separator":return(t,i,r)=>{let o=/(\[])$/.test(t);if(t=t.replace(/\[]$/,""),!o){r[t]=i&&nn(i,n);return}let s=i===null?[]:nn(i,n).split(n.arrayFormatSeparator);if(r[t]===void 0){r[t]=s;return}Array.isArray(r[t])||(r[t]=[r[t]]);for(let a of s)r[t].push(a)};default:return(t,i,r)=>{if(r[t]===void 0){r[t]=i;return}if(Array.isArray(r[t])){r[t].push(i);return}r[t]=[r[t],i]}}}function ql(n){if(typeof n!="string"||n.length!==1)throw new TypeError("arrayFormatSeparator must be single character string")}function Ee(n,e){return e.encode?e.strict?wg(n):encodeURIComponent(n):n}function nn(n,e){return e.decode?Uo(n):n}function Ul(n){return Array.isArray(n)?n.sort():typeof n=="object"?Ul(Object.keys(n)).sort((e,t)=>Number(e)-Number(t)).map(e=>n[e]):n}function Yl(n){let e=n.indexOf("#");return e!==-1&&(n=n.slice(0,e)),n}function Cg(n){let e="",t=n.indexOf("#");return t!==-1&&(e=n.slice(t)),e}function Gl(n){let e=n.indexOf("?");return e===-1?n:n.slice(0,e)}function Wl(n,e,t){return t==="string"&&typeof n=="string"?n:typeof t=="function"&&typeof n=="string"?t(n):t==="boolean"&&n===null?!0:t==="boolean"&&n!==null&&(n.toLowerCase()==="true"||n.toLowerCase()==="false")?n.toLowerCase()==="true":t==="boolean"&&n!==null&&(n.toLowerCase()==="1"||n.toLowerCase()==="0")?n.toLowerCase()==="1":t==="string[]"&&e.arrayFormat!=="none"&&typeof n=="string"?[n]:t==="number[]"&&e.arrayFormat!=="none"&&!Number.isNaN(Number(n))&&typeof n=="string"&&n.trim()!==""?[Number(n)]:t==="number"&&!Number.isNaN(Number(n))&&typeof n=="string"&&n.trim()!==""?Number(n):e.parseBooleans&&n!==null&&(n.toLowerCase()==="true"||n.toLowerCase()==="false")?n.toLowerCase()==="true":e.parseNumbers&&!Number.isNaN(Number(n))&&typeof n=="string"&&n.trim()!==""?Number(n):n}function Ko(n){n=Yl(n);let e=n.indexOf("?");return e===-1?"":n.slice(e+1)}function Xo(n,e){e=O({decode:!0,sort:!0,arrayFormat:"none",arrayFormatSeparator:",",parseNumbers:!1,parseBooleans:!1,types:Object.create(null)},e),ql(e.arrayFormatSeparator);let t=Tg(e),i=Object.create(null);if(typeof n!="string"||(n=n.trim().replace(/^[?#&]/,""),!n))return i;let r=0;for(let o=0;o<=n.length;o++){if(o{let a=i[s];return o[s]=a&&typeof a=="object"&&!Array.isArray(a)?Ul(a):a,o},Object.create(null))}function Kl(n,e){if(!n)return"";e=O({encode:!0,strict:!0,arrayFormat:"none",arrayFormatSeparator:","},e),ql(e.arrayFormatSeparator);let t=s=>e.skipNull&&_g(n[s])||e.skipEmptyString&&n[s]==="",i=xg(e),r={};for(let[s,a]of Object.entries(n))t(s)||(r[s]=a);let o=Object.keys(r);return e.sort!==!1&&o.sort(e.sort),o.map(s=>{let a=n[s];if(e.replacer&&(a=e.replacer(s,a),a===void 0)||a===void 0)return"";if(a===null)return Ee(s,e);if(Array.isArray(a)){if(a.length===0&&e.arrayFormat==="bracket-separator")return Ee(s,e)+"[]";let l=a;e.replacer&&(l=a.map((d,p)=>e.replacer(`${s}[${p}]`,d)).filter(d=>d!==void 0));let c=l.reduce(i(s),[]),u=["comma","separator","bracket-separator"].includes(e.arrayFormat)?e.arrayFormatSeparator:"&";return c.join(u)}return Ee(s,e)+"="+Ee(a,e)}).filter(s=>s.length>0).join("&")}function Xl(n,e){e=O({decode:!0},e);let[t,i]=vr(n,"#");return t===void 0&&(t=n),O({url:Gl(t!=null?t:""),query:Xo(Ko(n),e)},e&&e.parseFragmentIdentifier&&i?{fragmentIdentifier:nn(i,e)}:{})}function Ql(n,e){e=O({encode:!0,strict:!0,[Yo]:!0},e);let t=Gl(Yl(n.url))||"",i=Ko(n.url),r=O(O({},Xo(i,O({sort:!1},e))),n.query),o=Kl(r,e);o&&(o=`?${o}`);let s=Cg(n.url);if(typeof n.fragmentIdentifier=="string"){let a=new URL(t,"https://query-string.invalid");a.hash=n.fragmentIdentifier,s=e[Yo]?a.hash:`#${n.fragmentIdentifier}`}return`${t}${o}${s}`}function Go(n,e,t){t=O({parseFragmentIdentifier:!0,[Yo]:!1},t);let{url:i,query:r,fragmentIdentifier:o}=Xl(n,t);return Ql({url:i,query:jl(r,e),fragmentIdentifier:o},t)}function Sg(n,e,t){if(Array.isArray(e)){let i=new Set(e);return Go(n,r=>!i.has(r),t)}return Go(n,(i,r)=>!e(i,r),t)}var Jl=Qo;function Zl(n){if(Array.isArray(n)){for(let e of n)if(typeof e=="object"&&e!==null&&"fieldName"in e&&"queryParam"in e)return typeof e.fieldName=="string"&&typeof e.queryParam=="string"}return!1}var yr=class extends Map{queryParam(e){let t=this.get(e);return typeof t!="undefined"?t.queryParam:null}queryValue(e){let t=this.get(e);return typeof t!="undefined"?t.queryValue:[]}updateValue(e,t){let i=this.get(e);if(ye(i)){let{queryParam:r}=i;return this.set(e,{queryParam:r,queryValue:t}),!0}return!1}addFromJson(e){if(ye(e)){let t=JSON.parse(e);if(Zl(t))for(let{queryParam:i,fieldName:r}of t)this.set(r,{queryParam:i,queryValue:[]});else throw new Error(`Data from 'data-dynamic-params' attribute is improperly formatted: '${e}'`)}}};var Er=class extends tn{constructor(t,i){super(t,i);se(this,"nullOption",null);se(this,"queryParams",new Map);se(this,"staticParams",new Map);se(this,"dynamicParams",new yr);se(this,"pathValues",new Map);se(this,"loadSequence",0);se(this,"pendingRestoreValue");this.api_url=this.input.getAttribute("data-url"),this.valueField=this.input.getAttribute("ts-value-field")||this.settings.valueField,this.labelField=this.input.getAttribute("ts-label-field")||this.settings.labelField,this.disabledField=this.input.getAttribute("ts-disabled-field")||this.settings.disabledField,this.descriptionField=this.input.getAttribute("ts-description-field")||"description",this.depthField=this.input.getAttribute("ts-depth-field")||"_depth",this.parentField=this.input.getAttribute("ts-parent-field")||null,this.countField=this.input.getAttribute("ts-count-field")||null;let r=this.input.getAttribute("data-null-option");if(r){let o=this.settings.valueField,s=this.settings.labelField;this.nullOption={},this.nullOption[o]="null",this.nullOption[s]=r}this.getStaticParams();for(let[o,s]of this.staticParams.entries())this.queryParams.set(o,s);this.getDynamicParams();for(let o of this.dynamicParams.keys())this.updateQueryParams(o);this.getPathKeys();for(let o of this.pathValues.keys())this.updatePathValues(o);this.addEventListeners()}load(t,i){let r=this;r.loadSequence+=1;let o=r.loadSequence;(Array.isArray(i)?i.length>0:i!==void 0)&&(r.pendingRestoreValue=i),r.clearOptions(),r.nullOption&&!t&&r.addOption(r.nullOption);let a=r.getRequestUrl(t);if(!a){r.pendingRestoreValue=void 0;return}Il(r.wrapper,r.settings.loadingClass),r.loading++,fetch(a).then(l=>l.json()).then(l=>{let c=l.results,u=[];for(let d of c){let p=r.getOptionFromData(d);u.push(p)}return u}).then(l=>{if(o!==r.loadSequence){r.finalizeStaleLoad();return}if(r.loadCallback(l,[]),r.pendingRestoreValue!==void 0){let u=(Array.isArray(r.pendingRestoreValue)?r.pendingRestoreValue:[r.pendingRestoreValue]).filter(d=>d!==""&&d in r.options);u.length>0&&r.setValue(u.length===1?u[0]:u,!0),r.pendingRestoreValue=void 0}}).catch(()=>{if(o!==r.loadSequence){r.finalizeStaleLoad();return}r.pendingRestoreValue=void 0,r.loadCallback([],[])})}finalizeStaleLoad(){this.loading=Math.max(this.loading-1,0),this.loading||(Pl(this.wrapper,this.settings.loadingClass),this.refreshOptions(!1))}getRequestUrl(t){let i=this.api_url,r={};for(let[o,s]of this.queryParams.entries())r[o]=s;for(let[o,s]of this.pathValues.entries())for(let a of this.api_url.matchAll(new RegExp(`({{${o}}})`,"g")))if(s)i=Wo(i,a[1],s.toString());else return"";return t&&(r.q=[t]),r.brief=[!0],r.limit=[this.settings.maxOptions],Jl.stringifyUrl({url:i,query:r})}getOptionFromData(t){let i={id:t[this.valueField],display:t[this.labelField],depth:t[this.depthField]||null,description:t[this.descriptionField]||null};if(t[this.parentField]){let r=t[this.parentField];i.parent=r[this.labelField]}return t[this.countField]&&(i.count=t[this.countField]),t[this.disabledField]&&(i.disabled=t[this.disabledField]),i}getStaticParams(){let t=this.input.getAttribute("data-static-params");try{if(t){let i=JSON.parse(t);if(i)for(let{queryParam:r,queryValue:o}of i)Array.isArray(o)?this.staticParams.set(r,o):this.staticParams.set(r,[o])}}catch(i){console.group(`Unable to determine static query parameters for select field '${this.name}'`),console.warn(i),console.groupEnd()}}getDynamicParams(){let t=this.input.getAttribute("data-dynamic-params");try{this.dynamicParams.addFromJson(t)}catch(i){console.group(`Unable to determine dynamic query parameters for select field '${this.name}'`),console.warn(i),console.groupEnd()}}getPathKeys(){for(let t of this.api_url.matchAll(new RegExp("{{(.+)}}","g")))this.pathValues.set(t[1],"")}updateQueryParams(t){let i=document.querySelector(`[name="${t}"]`);if(i!==null){let r=[];if(i.multiple?r=Array.from(i.options).filter(o=>o.selected).map(o=>o.value):i.value!==""&&(r=[i.value]),r.length>0){this.dynamicParams.updateValue(t,r);let o=this.dynamicParams.get(t);if(typeof o!="undefined"){let{queryParam:s,queryValue:a}=o,l=[];if(this.staticParams.has(s)){let c=this.staticParams.get(s);typeof c!="undefined"&&(l=[...c,...a])}else l=a;l.length>0?this.queryParams.set(s,l):this.queryParams.delete(s)}}else{let o=this.dynamicParams.queryParam(t);o!==null&&this.queryParams.delete(o)}}}updatePathValues(t){let i=Wo(t,/^id_/i,""),r=ui(`id_${i}`);r!==null&&this.api_url.includes("{{")&&this.api_url.match(new RegExp(`({{(${t})}})`,"g"))&&(r.value?this.pathValues.set(t,r.value):this.pathValues.set(t,""))}addEventListeners(){let t=new Set([...this.dynamicParams.keys(),...this.pathValues.keys()]);for(let i of t){let r=document.querySelector(`[name="${i}"]`);r!==null&&r.addEventListener("change",o=>this.handleEvent(o)),this.input.addEventListener(`netbox.select.onload.${i}`,o=>this.handleEvent(o))}}handleEvent(t){let i=t.target,r=this.getValue();this.updateQueryParams(i.name),this.updatePathValues(i.name),this.clear();let o=r!==""&&r!==null?r:void 0;this.load(this.lastValue,o)}};var Ag="id",br="display",Dg=100;function Og(n,e){let t="
'},n.settings.render),o.addEventListener("scroll",()=>{n.settings.shouldLoadMore.call(n)&&y(n.lastValue)&&(s||(s=!0,n.load.call(n,n.lastValue)))})})}we.define("change_listener",nl);we.define("checkbox_options",rl);we.define("clear_button",ol);we.define("drag_drop",sl);we.define("dropdown_header",al);we.define("caret_position",ll);we.define("dropdown_input",ul);we.define("input_autogrow",dl);we.define("no_backspace_delete",fl);we.define("no_active_items",hl);we.define("optgroup_columns",pl);we.define("remove_button",yl);we.define("restore_on_backspace",El);we.define("virtual_scroll",bl);var _l=we;function en(n){return"error"in n}function ye(n){let e=["","null","undefined"];return Array.isArray(n)?n.length>0:typeof n=="string"&&!e.includes(n)||typeof n=="number"||typeof n=="boolean"?!0:typeof n=="object"&&n!==null}function hr(n){return typeof n!==null&&typeof n!="undefined"}function ag(n,e,t){return at(this,null,function*(){let i=window.CSRF_TOKEN,r=new Headers({"X-CSRFToken":i}),o;typeof t!="undefined"&&(o=JSON.stringify(t),r.set("content-type","application/json"));let s=yield fetch(n,{method:e,body:o,headers:r,credentials:"same-origin"}),a=s.headers.get("Content-Type");if(typeof a=="string"&&a.includes("text"))return{error:yield s.text()};let l=yield s.json();return!s.ok&&Array.isArray(l)?{error:l.join(`
+`)}:!s.ok&&"detail"in l?{error:l.detail}:l})}function Dn(n,e){return at(this,null,function*(){return yield ag(n,"PATCH",e)})}function*k(...n){for(let e of n)for(let t of document.querySelectorAll(e))t!==null&&(yield t)}function ui(n){return document.getElementById(n)}function wl(n,e="select"){let t=[];for(let i of n.querySelectorAll(e))if(i!==null){let r={name:i.name,options:[]};for(let o of i.options)o.selected&&r.options.push(o.value);t=[...t,r]}return t}function xl(n,e,t){function i(o){return!!(typeof t=="string"&&o!==null&&o.matches(t))}function r(o){if(o!==null&&o.parentElement!==null&&!i(o)){for(let s of o.parentElement.querySelectorAll(e))if(s!==null)return s;return r(o.parentElement.parentElement)}return null}return r(n)}function jo(n,e,t=null,i=[]){let r=document.createElement(n);if(e!==null)for(let o of Object.keys(e)){let s=o,a=e[s];s in r&&(r[s]=a)}t!==null&&t.length>0&&r.classList.add(...t);for(let o of i)r.appendChild(o);return r}function Wo(n,e,t){if(typeof n!="string")throw new TypeError("replaceAll 'input' argument must be a string");if(typeof e!="string"&&!(e instanceof RegExp))throw new TypeError("replaceAll 'pattern' argument must be a string or RegExp instance");switch(typeof t){case"boolean":t=String(t);break;case"number":t=String(t);break;case"string":break;default:throw new TypeError("replaceAll 'replacement' argument must be stringifyable")}if(e instanceof RegExp){let i=Array.from(new Set([...e.flags.split(""),"g"])).join("");e=new RegExp(e.source,i)}else e=new RegExp(e,"g");return n.replace(e,t)}function Tl(){for(let n of k("[data-requires-fields]")){let e=n.getAttribute("data-requires-fields");if(!e)continue;let t=e.split(",").map(i=>i.trim());for(let i of t){let r=document.querySelector(`[name="${i}"]`);r&&r.addEventListener("change",()=>{if(!r.value||r.value===""){let o=n.tomselect;o?o.clear():n.value=""}})}}}function lg(){for(let n of k("select.select-all option"))n.selected=!0}function Cl(){for(let n of k("form")){let e=n.querySelectorAll("button[type=submit]");for(let i of e)i.addEventListener("click",()=>lg());let t=document.querySelector("button[data-reset-select]");t!==null&&t.addEventListener("click",()=>{window.location.assign(window.location.origin+window.location.pathname)})}}var di="empty_true",pr="empty_false";function Al(){for(let n of k("form")){let e=n.querySelectorAll(".modifier-select");e.length!==0&&(ug(n),e.forEach(t=>{t.addEventListener("change",()=>Sl(t)),Sl(t)}),n.addEventListener("submit",t=>{t.preventDefault();let i=new FormData(n);cg(n,i);let r=new URLSearchParams;for(let[s,a]of i.entries())a&&String(a).trim()&&r.append(s,String(a));let o=n.getAttribute("action")||n.action;window.location.href=`${o}?${r.toString()}`}))}}function Sl(n){let e=n.closest(".filter-modifier-group");if(!e)return;let t=e.querySelector(".filter-value-container");if(!t)return;let i=t.querySelector("input, select, textarea");if(!i)return;let r=n.value;if(r===di||r===pr){i.disabled=!0,i.value="";let o=n.dataset.emptyPlaceholder||"(automatically set)";i.setAttribute("placeholder",o)}else i.disabled=!1,i.removeAttribute("placeholder")}function cg(n,e){let t=n.querySelectorAll(".filter-modifier-group");for(let i of t){let r=i.querySelector(".modifier-select"),o=i.querySelector(".filter-value-container");if(!o)continue;let s=o.querySelector("input, select, textarea");if(!r||!s)continue;let a=s.name,l=r.value;if(l===di||l===pr){e.delete(a);let u=l===di?"true":"false";e.set(`${a}__empty`,u)}else{let u=e.getAll(a);if(u.length>0&&u.some(c=>String(c).trim())){e.delete(a);let c=l==="exact"?a:`${a}__${l}`;for(let d of u)String(d).trim()&&e.append(c,d)}else e.delete(a)}}}function ug(n){let e=new URLSearchParams(window.location.search),t=n.querySelectorAll(".filter-modifier-group");for(let i of t){let r=i.querySelector(".modifier-select"),o=i.querySelector(".filter-value-container");if(!o)continue;let s=o.querySelector("input, select, textarea");if(!r||!s)continue;let a=s.name,l=`${a}__empty`;if(e.has(l)){let c=e.get(l)==="true"?di:pr;r.value=c;continue}for(let u of r.options){let c=u.value;if(c===di||c===pr)continue;let d=c==="exact"?a:`${a}__${c}`;if(e.has(d)){if(r.value=c,s instanceof HTMLSelectElement&&s.multiple){let p=e.getAll(d);for(let y of s.options)y.selected=p.includes(y.value)}else s.value=e.get(d)||"";break}}}}function Dl(){for(let e of k("a.set_field_value"))if(e!==null){let t=function(i){i.preventDefault();let r=e.getAttribute("data"),o=document.getElementById(e.target);o!==null&&r!==null&&(o.value=r)};var n=t;e.addEventListener("click",t)}}function mr(){for(let n of[Cl,Dl,Al,Tl])n()}window.Collapse=Ct;window.Modal=tt;window.Popover=Tn;window.Toast=pt;window.Tooltip=ht;function dg(){for(let n of k('[data-bs-toggle="tooltip"]'))new ht(n,{container:"body"})}function fg(){for(let n of k('[data-bs-toggle="modal"]'))new tt(n)}function kt(n,e,t,i){let r="mdi-alert";switch(n){case"warning":r="mdi-alert";break;case"success":r="mdi-check-circle";break;case"info":r="mdi-information";break;case"danger":r="mdi-alert";break}let o=document.createElement("div");o.setAttribute("class","toast-container position-fixed bottom-0 end-0 m-3");let s=document.createElement("div");s.setAttribute("class",`toast bg-${n}`),s.setAttribute("role","alert"),s.setAttribute("aria-live","assertive"),s.setAttribute("aria-atomic","true");let a=document.createElement("div");a.setAttribute("class",`toast-header bg-${n} text-body`);let l=document.createElement("i");l.setAttribute("class",`mdi ${r}`);let u=document.createElement("strong");u.setAttribute("class","me-auto ms-1"),u.innerText=e;let c=document.createElement("button");c.setAttribute("type","button"),c.setAttribute("class","btn-close"),c.setAttribute("data-bs-dismiss","toast"),c.setAttribute("aria-label","Close");let d=document.createElement("div");if(d.setAttribute("class","toast-body"),a.appendChild(l),a.appendChild(u),typeof i!="undefined"){let y=document.createElement("small");y.setAttribute("class","text-muted"),a.appendChild(y)}return a.appendChild(c),d.innerText=t.trim(),s.appendChild(a),s.appendChild(d),o.appendChild(s),document.body.appendChild(o),new pt(s)}function hg(){let{hash:n}=location;if(n&&n.match(/^#tab_.+$/)){let e=n.replace("tab_","");for(let t of k(`ul.nav.nav-tabs .nav-link[data-bs-target="${e}"]`))new Qt(t).show()}}function pg(){let n=document.querySelectorAll(".sidebar .accordion-item");function e(t){for(let i of n)i!==t?i.classList.remove("is-open"):i.classList.toggle("is-open")}for(let t of n)for(let i of t.querySelectorAll(".accordion-button"))i.addEventListener("click",()=>{e(t)})}function mg(){var n;for(let e of k("a.image-preview")){let t=(n=e.dataset.previewUrl)!=null?n:e.href,i=jo("img",{src:t});i.loading="lazy",i.decoding="async";let r=jo("div",null,null,[i]);new Tn(e,{customClass:"image-preview-popover",trigger:"hover",html:!0,content:r})}}function gr(){for(let n of[dg,fg,hg,mg,pg])n()}function Ol(n){let e=n.currentTarget,t=document.getElementById("quicksearch_clear");ye(t)&&(e.value===""?t.classList.add("invisible"):t.classList.remove("invisible"))}function Ll(){let n=document.getElementById("export_current_view"),e=n==null?void 0:n.href.split("&")[0];n.setAttribute("href",e)}function gg(n){let e=n.currentTarget;if(Ll(),e!=null){let t=document.getElementById("export_current_view"),i=new URLSearchParams;i.set("q",e.value);let r=i.toString(),o=(t==null?void 0:t.href)+"&"+r;t.setAttribute("href",o)}}function Ml(){let n=document.getElementById("quicksearch"),e=document.getElementById("quicksearch_clear");ye(n)&&(n.addEventListener("keyup",Ol,{passive:!0}),n.addEventListener("search",Ol,{passive:!0}),n.addEventListener("change",gg,{passive:!0}),ye(e)&&e.addEventListener("click",()=>at(null,null,function*(){let t=new Event("search");n.value="",yield new Promise(i=>setTimeout(i,100)),n.dispatchEvent(t),Ll()}),{passive:!0}))}var tn=class extends _l{setup(){super.setup(),this.input.setAttribute("aria-hidden","true")}focus(){if(this.isDisabled||this.isReadOnly)return;this.ignoreFocus=!0;let e=this.control_input.offsetWidth?this.control_input:this.focus_node;e.focus(),setTimeout(()=>{this.ignoreFocus=!1,(document.activeElement===e||this.control.contains(document.activeElement))&&this.onFocus()},0)}};function fi(n){let e={};return n.required||(e.clear_button={html:t=>``}),n.hasAttribute("multiple")&&(e.remove_button={title:"Remove"}),n.hasAttribute("multiple")&&(e.drag_drop={}),{plugins:e}}function Nl(){for(let n of k("select:not(.tomselected):not(.no-ts):not([size]):not(.api-select):not(.color-select)"))new tn(n,ae(O({},fi(n)),{maxOptions:void 0}))}function kl(){function n(e,t){return`
${t(e.text)}
`}for(let e of k("select.color-select:not(.tomselected)"))new tn(e,ae(O({},fi(e)),{maxOptions:void 0,render:{option:n,item:n}}))}var Hl=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)};var Rl=(n,...e)=>{var t=Pl(e);n=Fl(n),n.map(i=>{t.map(r=>{i.classList.add(r)})})},Il=(n,...e)=>{var t=Pl(e);n=Fl(n),n.map(i=>{t.map(r=>{i.classList.remove(r)})})},Pl=n=>{var e=[];return Hl(n,t=>{typeof t=="string"&&(t=t.trim().split(/[\t\n\f\r\s]/)),Array.isArray(t)&&(e=e.concat(t))}),e.filter(Boolean)},Fl=n=>(Array.isArray(n)||(n=[n]),n);var Xo={};ms(Xo,{exclude:()=>Dg,extract:()=>Go,parse:()=>Ko,parseUrl:()=>Gl,pick:()=>Yo,stringify:()=>Yl,stringifyUrl:()=>Kl});var vg="%[a-f0-9]{2}",$l=new RegExp(`(${vg})+`,"gi"),yg=/^[a-f\d]{2}$/i;function Bl(n,e){if(n.codePointAt(e)!==37||e+3>n.length)return;let t=n.slice(e+1,e+3);if(yg.test(t))return{byte:Number.parseInt(t,16),next:e+3}}function Eg(n){return n<=127?1:n>=194&&n<=223?2:n>=224&&n<=239?3:n>=240&&n<=244?4:0}function bg(n){return n>=128&&n<=191}function _g(n){try{return decodeURIComponent(n)}catch(e){let t="",i=0;for(;in==null,Tg=n=>encodeURIComponent(n).replaceAll(/[!'()*]/g,e=>`%${e.charCodeAt(0).toString(16).toUpperCase()}`),Uo=Symbol("encodeFragmentIdentifier");function Cg(n){switch(n.arrayFormat){case"index":return e=>(t,i)=>{let r=t.length;return i===void 0||n.skipNull&&i===null||n.skipEmptyString&&i===""?t:i===null?(t.push([Ee(e,n),"[",r,"]"].join("")),t):(t.push([Ee(e,n),"[",Ee(r,n),"]=",Ee(i,n)].join("")),t)};case"bracket":return e=>(t,i)=>i===void 0||n.skipNull&&i===null||n.skipEmptyString&&i===""?t:i===null?(t.push([Ee(e,n),"[]"].join("")),t):(t.push([Ee(e,n),"[]=",Ee(i,n)].join("")),t);case"colon-list-separator":return e=>(t,i)=>i===void 0||n.skipNull&&i===null||n.skipEmptyString&&i===""?t:i===null?(t.push([Ee(e,n),":list="].join("")),t):(t.push([Ee(e,n),":list=",Ee(i,n)].join("")),t);case"comma":case"separator":case"bracket-separator":{let e=n.arrayFormat==="bracket-separator"?"[]=":"=";return t=>(i,r)=>r===void 0||n.skipNull&&r===null||n.skipEmptyString&&r===""?i:(r=r===null?"":r,i.length===0?(i.push([Ee(t,n),e,Ee(r,n)].join("")),i):(i.push(Ee(r,n)),i))}default:return e=>(t,i)=>i===void 0||n.skipNull&&i===null||n.skipEmptyString&&i===""?t:i===null?(t.push(Ee(e,n)),t):(t.push([Ee(e,n),"=",Ee(i,n)].join("")),t)}}function Sg(n){let e;switch(n.arrayFormat){case"index":return(t,i,r)=>{if(e=/\[(\d*)]$/.exec(t),t=t.replace(/\[\d*]$/,""),!e){r[t]=i;return}r[t]===void 0&&(r[t]={}),r[t][e[1]]=i};case"bracket":return(t,i,r)=>{if(e=/(\[])$/.exec(t),t=t.replace(/\[]$/,""),!e){r[t]=i;return}if(r[t]===void 0){r[t]=[i];return}if(!Array.isArray(r[t])){r[t]=[r[t],i];return}r[t].push(i)};case"colon-list-separator":return(t,i,r)=>{if(e=/(:list)$/.exec(t),t=t.replace(/:list$/,""),!e){r[t]=i;return}if(r[t]===void 0){r[t]=[i];return}if(!Array.isArray(r[t])){r[t]=[r[t],i];return}r[t].push(i)};case"comma":case"separator":return(t,i,r)=>{let s=typeof i=="string"&&i.includes(n.arrayFormatSeparator)?i.split(n.arrayFormatSeparator).map(a=>nn(a,n)):i===null?i:nn(i,n);r[t]=s};case"bracket-separator":return(t,i,r)=>{let o=/(\[])$/.test(t);if(t=t.replace(/\[]$/,""),!o){r[t]=i&&nn(i,n);return}let s=i===null?[]:nn(i,n).split(n.arrayFormatSeparator);if(r[t]===void 0){r[t]=s;return}Array.isArray(r[t])||(r[t]=[r[t]]);for(let a of s)r[t].push(a)};default:return(t,i,r)=>{if(r[t]===void 0){r[t]=i;return}if(Array.isArray(r[t])){r[t].push(i);return}r[t]=[r[t],i]}}}function jl(n){if(typeof n!="string"||n.length!==1)throw new TypeError("arrayFormatSeparator must be single character string")}function Ee(n,e){return e.encode?e.strict?Tg(n):encodeURIComponent(n):n}function nn(n,e){return e.decode?qo(n):n}function Wl(n){return Array.isArray(n)?n.sort():typeof n=="object"?Wl(Object.keys(n)).sort((e,t)=>Number(e)-Number(t)).map(e=>n[e]):n}function ql(n){let e=n.indexOf("#");return e!==-1&&(n=n.slice(0,e)),n}function Ag(n){let e="",t=n.indexOf("#");return t!==-1&&(e=n.slice(t)),e}function Ul(n){let e=n.indexOf("?");return e===-1?n:n.slice(0,e)}function zl(n,e,t){return t==="string"&&typeof n=="string"?n:typeof t=="function"&&typeof n=="string"?t(n):t==="boolean"&&n===null?!0:t==="boolean"&&n!==null&&(n.toLowerCase()==="true"||n.toLowerCase()==="false")?n.toLowerCase()==="true":t==="boolean"&&n!==null&&(n.toLowerCase()==="1"||n.toLowerCase()==="0")?n.toLowerCase()==="1":t==="string[]"&&e.arrayFormat!=="none"&&typeof n=="string"?[n]:t==="number[]"&&e.arrayFormat!=="none"&&!Number.isNaN(Number(n))&&typeof n=="string"&&n.trim()!==""?[Number(n)]:t==="number"&&!Number.isNaN(Number(n))&&typeof n=="string"&&n.trim()!==""?Number(n):e.parseBooleans&&n!==null&&(n.toLowerCase()==="true"||n.toLowerCase()==="false")?n.toLowerCase()==="true":e.parseNumbers&&!Number.isNaN(Number(n))&&typeof n=="string"&&n.trim()!==""?Number(n):n}function Go(n){n=ql(n);let e=n.indexOf("?");return e===-1?"":n.slice(e+1)}function Ko(n,e){e=O({decode:!0,sort:!0,arrayFormat:"none",arrayFormatSeparator:",",parseNumbers:!1,parseBooleans:!1,types:Object.create(null)},e),jl(e.arrayFormatSeparator);let t=Sg(e),i=Object.create(null);if(typeof n!="string"||(n=n.trim().replace(/^[?#&]/,""),!n)||/^&+$/.test(n))return i;let r=0,o=n.indexOf("&");o===-1&&(o=n.length);for(let s=o;s<=n.length;s++){if(s{let l=i[a];return s[a]=l&&typeof l=="object"&&!Array.isArray(l)?Wl(l):l,s},Object.create(null))}function Yl(n,e){if(!n)return"";e=O({encode:!0,strict:!0,arrayFormat:"none",arrayFormatSeparator:","},e),jl(e.arrayFormatSeparator);let t=s=>e.skipNull&&xg(n[s])||e.skipEmptyString&&n[s]==="",i=Cg(e),r={};for(let[s,a]of Object.entries(n))t(s)||(r[s]=a);let o=Object.keys(r);return e.sort!==!1&&o.sort(e.sort),o.map(s=>{let a=n[s];if(e.replacer&&(a=e.replacer(s,a),a===void 0)||a===void 0)return"";if(a===null)return Ee(s,e);if(Array.isArray(a)){if(a.length===0&&e.arrayFormat==="bracket-separator")return Ee(s,e)+"[]";let l=a;e.replacer&&(l=a.map((d,p)=>e.replacer(`${s}[${p}]`,d)).filter(d=>d!==void 0));let u=l.reduce(i(s),[]),c=["comma","separator","bracket-separator"].includes(e.arrayFormat)?e.arrayFormatSeparator:"&";return u.join(c)}return Ee(s,e)+"="+Ee(a,e)}).filter(s=>s.length>0).join("&")}function Gl(n,e){e=O({decode:!0},e);let[t,i]=vr(n,"#");return t===void 0&&(t=n),O({url:Ul(t!=null?t:""),query:Ko(Go(n),e)},e&&e.parseFragmentIdentifier&&i?{fragmentIdentifier:nn(i,e)}:{})}function Kl(n,e){e=O({encode:!0,strict:!0,[Uo]:!0},e);let t=Ul(ql(n.url))||"",i=Go(n.url),r=O(O({},Ko(i,O({sort:!1},e))),n.query),o=Yl(r,e);o&&(o=`?${o}`);let s=Ag(n.url);if(typeof n.fragmentIdentifier=="string"){let a=new URL(t,"https://query-string.invalid");a.hash=n.fragmentIdentifier,s=e[Uo]?a.hash:`#${n.fragmentIdentifier}`}return`${t}${o}${s}`}function Yo(n,e,t){t=O({parseFragmentIdentifier:!0,[Uo]:!1},t);let{url:i,query:r,fragmentIdentifier:o}=Gl(n,t);return Kl({url:i,query:Vl(r,e),fragmentIdentifier:o},t)}function Dg(n,e,t){if(Array.isArray(e)){let i=new Set(e);return Yo(n,r=>!i.has(r),t)}return Yo(n,(i,r)=>!e(i,r),t)}var Xl=Xo;function Ql(n){if(Array.isArray(n)){for(let e of n)if(typeof e=="object"&&e!==null&&"fieldName"in e&&"queryParam"in e)return typeof e.fieldName=="string"&&typeof e.queryParam=="string"}return!1}var yr=class extends Map{queryParam(e){let t=this.get(e);return typeof t!="undefined"?t.queryParam:null}queryValue(e){let t=this.get(e);return typeof t!="undefined"?t.queryValue:[]}updateValue(e,t){let i=this.get(e);if(ye(i)){let{queryParam:r}=i;return this.set(e,{queryParam:r,queryValue:t}),!0}return!1}addFromJson(e){if(ye(e)){let t=JSON.parse(e);if(Ql(t))for(let{queryParam:i,fieldName:r}of t)this.set(r,{queryParam:i,queryValue:[]});else throw new Error(`Data from 'data-dynamic-params' attribute is improperly formatted: '${e}'`)}}};var Er=class extends tn{constructor(t,i){super(t,i);se(this,"nullOption",null);se(this,"queryParams",new Map);se(this,"staticParams",new Map);se(this,"dynamicParams",new yr);se(this,"pathValues",new Map);se(this,"loadSequence",0);se(this,"pendingRestoreValue");this.api_url=this.input.getAttribute("data-url"),this.valueField=this.input.getAttribute("ts-value-field")||this.settings.valueField,this.labelField=this.input.getAttribute("ts-label-field")||this.settings.labelField,this.disabledField=this.input.getAttribute("ts-disabled-field")||this.settings.disabledField,this.descriptionField=this.input.getAttribute("ts-description-field")||"description",this.depthField=this.input.getAttribute("ts-depth-field")||"_depth",this.parentField=this.input.getAttribute("ts-parent-field")||null,this.countField=this.input.getAttribute("ts-count-field")||null;let r=this.input.getAttribute("data-null-option");if(r){let o=this.settings.valueField,s=this.settings.labelField;this.nullOption={},this.nullOption[o]="null",this.nullOption[s]=r}this.getStaticParams();for(let[o,s]of this.staticParams.entries())this.queryParams.set(o,s);this.getDynamicParams();for(let o of this.dynamicParams.keys())this.updateQueryParams(o);this.getPathKeys();for(let o of this.pathValues.keys())this.updatePathValues(o);this.addEventListeners()}load(t,i){let r=this;r.loadSequence+=1;let o=r.loadSequence;(Array.isArray(i)?i.length>0:i!==void 0)&&(r.pendingRestoreValue=i),r.clearOptions(),r.nullOption&&!t&&r.addOption(r.nullOption);let a=r.getRequestUrl(t);if(!a){r.pendingRestoreValue=void 0;return}Rl(r.wrapper,r.settings.loadingClass),r.loading++,fetch(a).then(l=>l.json()).then(l=>{let u=l.results,c=[];for(let d of u){let p=r.getOptionFromData(d);c.push(p)}return c}).then(l=>{if(o!==r.loadSequence){r.finalizeStaleLoad();return}if(r.loadCallback(l,[]),r.pendingRestoreValue!==void 0){let c=(Array.isArray(r.pendingRestoreValue)?r.pendingRestoreValue:[r.pendingRestoreValue]).filter(d=>d!==""&&d in r.options);c.length>0&&r.setValue(c.length===1?c[0]:c,!0),r.pendingRestoreValue=void 0}}).catch(()=>{if(o!==r.loadSequence){r.finalizeStaleLoad();return}r.pendingRestoreValue=void 0,r.loadCallback([],[])})}finalizeStaleLoad(){this.loading=Math.max(this.loading-1,0),this.loading||(Il(this.wrapper,this.settings.loadingClass),this.refreshOptions(!1))}getRequestUrl(t){let i=this.api_url,r={};for(let[o,s]of this.queryParams.entries())r[o]=s;for(let[o,s]of this.pathValues.entries())for(let a of this.api_url.matchAll(new RegExp(`({{${o}}})`,"g")))if(s)i=Wo(i,a[1],s.toString());else return"";return t&&(r.q=[t]),r.brief=[!0],r.limit=[this.settings.maxOptions],Xl.stringifyUrl({url:i,query:r})}getOptionFromData(t){let i={id:t[this.valueField],display:t[this.labelField],depth:t[this.depthField]||null,description:t[this.descriptionField]||null};if(t[this.parentField]){let r=t[this.parentField];i.parent=r[this.labelField]}return t[this.countField]&&(i.count=t[this.countField]),t[this.disabledField]&&(i.disabled=t[this.disabledField]),i}getStaticParams(){let t=this.input.getAttribute("data-static-params");try{if(t){let i=JSON.parse(t);if(i)for(let{queryParam:r,queryValue:o}of i)Array.isArray(o)?this.staticParams.set(r,o):this.staticParams.set(r,[o])}}catch(i){console.group(`Unable to determine static query parameters for select field '${this.name}'`),console.warn(i),console.groupEnd()}}getDynamicParams(){let t=this.input.getAttribute("data-dynamic-params");try{this.dynamicParams.addFromJson(t)}catch(i){console.group(`Unable to determine dynamic query parameters for select field '${this.name}'`),console.warn(i),console.groupEnd()}}getPathKeys(){for(let t of this.api_url.matchAll(new RegExp("{{(.+)}}","g")))this.pathValues.set(t[1],"")}updateQueryParams(t){let i=document.querySelector(`[name="${t}"]`);if(i!==null){let r=[];if(i.multiple?r=Array.from(i.options).filter(o=>o.selected).map(o=>o.value):i.value!==""&&(r=[i.value]),r.length>0){this.dynamicParams.updateValue(t,r);let o=this.dynamicParams.get(t);if(typeof o!="undefined"){let{queryParam:s,queryValue:a}=o,l=[];if(this.staticParams.has(s)){let u=this.staticParams.get(s);typeof u!="undefined"&&(l=[...u,...a])}else l=a;l.length>0?this.queryParams.set(s,l):this.queryParams.delete(s)}}else{let o=this.dynamicParams.queryParam(t);o!==null&&this.queryParams.delete(o)}}}updatePathValues(t){let i=Wo(t,/^id_/i,""),r=ui(`id_${i}`);r!==null&&this.api_url.includes("{{")&&this.api_url.match(new RegExp(`({{(${t})}})`,"g"))&&(r.value?this.pathValues.set(t,r.value):this.pathValues.set(t,""))}addEventListeners(){let t=new Set([...this.dynamicParams.keys(),...this.pathValues.keys()]);for(let i of t){let r=document.querySelector(`[name="${i}"]`);r!==null&&r.addEventListener("change",o=>this.handleEvent(o)),this.input.addEventListener(`netbox.select.onload.${i}`,o=>this.handleEvent(o))}}handleEvent(t){let i=t.target,r=this.getValue();this.updateQueryParams(i.name),this.updatePathValues(i.name),this.clear();let o=r!==""&&r!==null?r:void 0;this.load(this.lastValue,o)}};var Og="id",br="display",Lg=100;function Mg(n,e){let t="