From 0a336465f24a34a074482319e2e9fe3f8ceffaa9 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 4 Sep 2025 15:44:34 -0400 Subject: [PATCH 01/40] Closes #19428: Add u_height column to devices table --- netbox/dcim/tables/devices.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 8287e36669d..fe07bb57faa 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -195,6 +195,11 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable): linkify=True, verbose_name=_('Type') ) + u_height = columns.TemplateColumn( + accessor=tables.A('device_type.u_height'), + verbose_name=_('U Height'), + template_code='{{ value|floatformat }}' + ) platform = tables.Column( linkify=True, verbose_name=_('Platform') From f9159ad9bd7fc66b6214f8058245c184b99449bb Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Fri, 5 Sep 2025 10:55:58 +0200 Subject: [PATCH 02/40] fix(plugins): Add accessor for is_loaded in TemplateColumn Adds the `accessor` attribute with `tables.A('is_loaded')` to the `is_installed` column in the plugin's table. This ensures proper data access and improves the table's functionality. Fixes #19744 --- netbox/core/tables/plugins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netbox/core/tables/plugins.py b/netbox/core/tables/plugins.py index e1b80af4233..f02442b3681 100644 --- a/netbox/core/tables/plugins.py +++ b/netbox/core/tables/plugins.py @@ -61,6 +61,7 @@ class CatalogPluginTable(BaseTable): verbose_name=_('Local') ) is_installed = columns.TemplateColumn( + accessor=tables.A('is_loaded'), verbose_name=_('Active'), template_code=PLUGIN_IS_INSTALLED ) From 2ba2864a6a9c456913a88d28abde8145e019fb39 Mon Sep 17 00:00:00 2001 From: Jason Novinger Date: Fri, 5 Sep 2025 10:37:39 -0500 Subject: [PATCH 03/40] Fixes #20215: Make ConfigContextFilter filters optional --- netbox/extras/graphql/filters.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/netbox/extras/graphql/filters.py b/netbox/extras/graphql/filters.py index e7d5ef573d4..13221982c92 100644 --- a/netbox/extras/graphql/filters.py +++ b/netbox/extras/graphql/filters.py @@ -43,12 +43,12 @@ __all__ = ( @strawberry_django.filter_type(models.ConfigContext, lookups=True) class ConfigContextFilter(BaseObjectTypeFilterMixin, SyncedDataFilterMixin, ChangeLogFilterMixin): - name: FilterLookup[str] = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - description: FilterLookup[str] = strawberry_django.filter_field() - is_active: FilterLookup[bool] = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + is_active: FilterLookup[bool] | None = strawberry_django.filter_field() regions: Annotated['RegionFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( strawberry_django.filter_field() ) From c9dc2005b019750b23b064be58c3d61bac84069e Mon Sep 17 00:00:00 2001 From: mr1716 Date: Sat, 6 Sep 2025 11:09:01 -0400 Subject: [PATCH 04/40] Update planning.md to cleanup grammar --- docs/getting-started/planning.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/planning.md b/docs/getting-started/planning.md index 9641cd98bd4..7893db55b59 100644 --- a/docs/getting-started/planning.md +++ b/docs/getting-started/planning.md @@ -17,7 +17,7 @@ Dedicate some time to take stock of your own sources of truth for your infrastru * **Multiple conflicting sources** for a given domain. For example, there may be multiple versions of a spreadsheet circulating, each of which asserts a conflicting set of data. * **Sources with no domain defined.** You may encounter that different teams within your organization use different tools for the same purpose, with no normal definition of when either should be used. -* **Inaccessible data formatting.** Some tools are better suited for programmatic usage than others. For example, spreadsheets are generally very easy to parse and export, however free-form notes on wiki or similar application are much more difficult to consume. +* **Inaccessible data formatting.** Some tools are better suited for programmatic usage than others. For example, spreadsheets are generally very easy to parse and export; however, free-form notes on wiki or similar application are much more difficult to consume. * **There is no source of truth.** Sometimes you'll find that a source of truth simply doesn't exist for a domain. For example, when assigning IP addresses, operators may be just using any (presumed) available IP from a subnet without ever recording its usage. See if you can identify each domain of infrastructure data for your organization, and the source of truth for each. Once you have these compiled, you'll need to determine what belongs in NetBox. From c3b37db8f788334ff88c0b841f336a224ccb7a0e Mon Sep 17 00:00:00 2001 From: mr1716 Date: Sat, 6 Sep 2025 11:15:15 -0400 Subject: [PATCH 05/40] Update netbox-shell.md To Reflect Proper Grammar --- docs/administration/netbox-shell.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/administration/netbox-shell.md b/docs/administration/netbox-shell.md index 21cef01b200..a74c5114ba5 100644 --- a/docs/administration/netbox-shell.md +++ b/docs/administration/netbox-shell.md @@ -106,7 +106,7 @@ This approach can span multiple levels of relations. For example, the following ``` !!! note - While the above query is functional, it's not very efficient. There are ways to optimize such requests, however they are out of scope for this document. For more information, see the [Django queryset method reference](https://docs.djangoproject.com/en/stable/ref/models/querysets/) documentation. + While the above query is functional, it's not very efficient. There are ways to optimize such requests; however, they are out of scope for this document. For more information, see the [Django queryset method reference](https://docs.djangoproject.com/en/stable/ref/models/querysets/) documentation. Reverse relationships can be traversed as well. For example, the following will find all devices with an interface named "em0": From 1eeede0931ced96fb94255ce469b6283a5703991 Mon Sep 17 00:00:00 2001 From: mr1716 Date: Sun, 7 Sep 2025 08:35:59 -0400 Subject: [PATCH 06/40] Update Grammar --- SECURITY.md | 2 +- docs/administration/authentication/microsoft-entra-id.md | 2 +- docs/plugins/development/index.md | 4 ++-- docs/release-notes/version-3.0.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 58b73cbb780..c00fe610741 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -34,4 +34,4 @@ For any security concerns regarding the community-maintained Docker image for Ne ### Bug Bounties -As NetBox is provided as free open source software, we do not offer any monetary compensation for vulnerability or bug reports, however your contributions are greatly appreciated. +As NetBox is provided as free open source software, we do not offer any monetary compensation for vulnerability or bug reports; however, your contributions are greatly appreciated. diff --git a/docs/administration/authentication/microsoft-entra-id.md b/docs/administration/authentication/microsoft-entra-id.md index b44499fbe07..66794ae0c95 100644 --- a/docs/administration/authentication/microsoft-entra-id.md +++ b/docs/administration/authentication/microsoft-entra-id.md @@ -25,7 +25,7 @@ Once finished, make note of the application (client) ID; this will be used when ![Completed app registration](../../media/authentication/azure_ad_app_registration_created.png) !!! tip "Multitenant authentication" - NetBox also supports multitenant authentication via Azure AD, however it requires a different backend and an additional configuration parameter. Please see the [`python-social-auth` documentation](https://python-social-auth.readthedocs.io/en/latest/backends/azuread.html#tenant-support) for details concerning multitenant authentication. + NetBox also supports multitenant authentication via Azure AD; however, it requires a different backend and an additional configuration parameter. Please see the [`python-social-auth` documentation](https://python-social-auth.readthedocs.io/en/latest/backends/azuread.html#tenant-support) for details concerning multitenant authentication. ### 3. Create a secret diff --git a/docs/plugins/development/index.md b/docs/plugins/development/index.md index 56bde5e41fa..0acf5f90ace 100644 --- a/docs/plugins/development/index.md +++ b/docs/plugins/development/index.md @@ -66,7 +66,7 @@ The top level is the project root, which can have any name that you like. Immedi * `README.md` - A brief introduction to your plugin, how to install and configure it, where to find help, and any other pertinent information. It is recommended to write `README` files using a markup language such as Markdown to enable human-friendly display. * The plugin source directory. This must be a valid Python package name, typically comprising only lowercase letters, numbers, and underscores. -The plugin source directory contains all the actual Python code and other resources used by your plugin. Its structure is left to the author's discretion, however it is recommended to follow best practices as outlined in the [Django documentation](https://docs.djangoproject.com/en/stable/intro/reusable-apps/). At a minimum, this directory **must** contain an `__init__.py` file containing an instance of NetBox's `PluginConfig` class, discussed below. +The plugin source directory contains all the actual Python code and other resources used by your plugin. Its structure is left to the author's discretion; however, it is recommended to follow best practices as outlined in the [Django documentation](https://docs.djangoproject.com/en/stable/intro/reusable-apps/). At a minimum, this directory **must** contain an `__init__.py` file containing an instance of NetBox's `PluginConfig` class, discussed below. **Note:** The [Cookiecutter NetBox Plugin](https://github.com/netbox-community/cookiecutter-netbox-plugin) can be used to auto-generate all the needed directories and files for a new plugin. @@ -186,7 +186,7 @@ Many of these are self-explanatory, but for more information, see the [pyproject ## Create a Virtual Environment -It is strongly recommended to create a Python [virtual environment](https://docs.python.org/3/tutorial/venv.html) for the development of your plugin, as opposed to using system-wide packages. This will afford you complete control over the installed versions of all dependencies and avoid conflict with system packages. This environment can live wherever you'd like, however it should be excluded from revision control. (A popular convention is to keep all virtual environments in the user's home directory, e.g. `~/.virtualenvs/`.) +It is strongly recommended to create a Python [virtual environment](https://docs.python.org/3/tutorial/venv.html) for the development of your plugin, as opposed to using system-wide packages. This will afford you complete control over the installed versions of all dependencies and avoid conflict with system packages. This environment can live wherever you'd like;however, it should be excluded from revision control. (A popular convention is to keep all virtual environments in the user's home directory, e.g. `~/.virtualenvs/`.) ```shell python3 -m venv ~/.virtualenvs/my_plugin diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index a7003eedfe6..ae290b02ae6 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -357,7 +357,7 @@ And the response: ... ``` -All GraphQL requests are made at the `/graphql` URL (which also serves the GraphiQL UI). The API is currently read-only, however users who wish to disable it until needed can do so by setting the `GRAPHQL_ENABLED` configuration parameter to False. For more detail on NetBox's GraphQL implementation, see [the GraphQL API documentation](../integrations/graphql-api.md). +All GraphQL requests are made at the `/graphql` URL (which also serves the GraphiQL UI). The API is currently read-only; however, users who wish to disable it until needed can do so by setting the `GRAPHQL_ENABLED` configuration parameter to False. For more detail on NetBox's GraphQL implementation, see [the GraphQL API documentation](../integrations/graphql-api.md). #### IP Ranges ([#834](https://github.com/netbox-community/netbox/issues/834)) From 099f3b2f343e26719f38a2876f7765c3661ab5e8 Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Sat, 6 Sep 2025 10:53:47 +0200 Subject: [PATCH 07/40] feat(core): Add Sync button for DataSource actions Introduces a sync button in the DataSource table for improved user interaction. Enables users to trigger sync actions directly from the table, with context-sensitive availability based on permissions and record status. Closes #19547 --- netbox/core/tables/data.py | 4 ++++ netbox/core/tables/template_code.py | 16 ++++++++++++++++ netbox/core/views.py | 12 +++++++++--- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/netbox/core/tables/data.py b/netbox/core/tables/data.py index 5c6ccebcf05..226a480810f 100644 --- a/netbox/core/tables/data.py +++ b/netbox/core/tables/data.py @@ -4,6 +4,7 @@ import django_tables2 as tables from core.models import * from netbox.tables import NetBoxTable, columns from .columns import BackendTypeColumn +from .template_code import DATA_SOURCE_SYNC_BUTTON __all__ = ( 'DataFileTable', @@ -37,6 +38,9 @@ class DataSourceTable(NetBoxTable): tags = columns.TagColumn( url_name='core:datasource_list', ) + actions = columns.ActionsColumn( + extra_buttons=DATA_SOURCE_SYNC_BUTTON, + ) class Meta(NetBoxTable.Meta): model = DataSource diff --git a/netbox/core/tables/template_code.py b/netbox/core/tables/template_code.py index 9fc652c4c58..f49231a3982 100644 --- a/netbox/core/tables/template_code.py +++ b/netbox/core/tables/template_code.py @@ -26,3 +26,19 @@ PLUGIN_IS_INSTALLED = """ {% endif %} """ + +DATA_SOURCE_SYNC_BUTTON = """ +{% load helpers %} +{% load i18n %} +{% if perms.core.sync_datasource %} + {% if record.ready_for_sync %} + + {% else %} + + {% endif %} +{% endif %} +""" diff --git a/netbox/core/views.py b/netbox/core/views.py index b1893730825..0959e1c12a0 100644 --- a/netbox/core/views.py +++ b/netbox/core/views.py @@ -33,7 +33,13 @@ from utilities.forms import ConfirmationForm from utilities.htmx import htmx_partial from utilities.json import ConfigJSONEncoder from utilities.query import count_related -from utilities.views import ContentTypePermissionRequiredMixin, GetRelatedModelsMixin, ViewTab, register_model_view +from utilities.views import ( + ContentTypePermissionRequiredMixin, + GetRelatedModelsMixin, + GetReturnURLMixin, + ViewTab, + register_model_view, +) from . import filtersets, forms, tables from .jobs import SyncDataSourceJob from .models import * @@ -66,7 +72,7 @@ class DataSourceView(GetRelatedModelsMixin, generic.ObjectView): @register_model_view(DataSource, 'sync') -class DataSourceSyncView(BaseObjectView): +class DataSourceSyncView(GetReturnURLMixin, BaseObjectView): queryset = DataSource.objects.all() def get_required_permission(self): @@ -85,7 +91,7 @@ class DataSourceSyncView(BaseObjectView): request, _("Queued job #{id} to sync {datasource}").format(id=job.pk, datasource=datasource) ) - return redirect(datasource.get_absolute_url()) + return redirect(self.get_return_url(request, datasource)) @register_model_view(DataSource, 'add', detail=False) From a611ade5d34e50ff42161707276cc483e9f1d793 Mon Sep 17 00:00:00 2001 From: Elliott Balsley <3991046+llamafilm@users.noreply.github.com> Date: Mon, 8 Sep 2025 07:51:01 -0700 Subject: [PATCH 08/40] Fixes #19729: GraphQL filter interfaces by kind (#20289) --- netbox/dcim/graphql/enums.py | 2 ++ netbox/dcim/graphql/filters.py | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/netbox/dcim/graphql/enums.py b/netbox/dcim/graphql/enums.py index 5f888cfb51a..62a666b4544 100644 --- a/netbox/dcim/graphql/enums.py +++ b/netbox/dcim/graphql/enums.py @@ -12,6 +12,7 @@ __all__ = ( 'DeviceFaceEnum', 'DeviceStatusEnum', 'InterfaceDuplexEnum', + 'InterfaceKindEnum', 'InterfaceModeEnum', 'InterfacePoEModeEnum', 'InterfacePoETypeEnum', @@ -48,6 +49,7 @@ DeviceAirflowEnum = strawberry.enum(DeviceAirflowChoices.as_enum(prefix='airflow DeviceFaceEnum = strawberry.enum(DeviceFaceChoices.as_enum(prefix='face')) DeviceStatusEnum = strawberry.enum(DeviceStatusChoices.as_enum(prefix='status')) InterfaceDuplexEnum = strawberry.enum(InterfaceDuplexChoices.as_enum(prefix='duplex')) +InterfaceKindEnum = strawberry.enum(InterfaceKindChoices.as_enum(prefix='kind')) InterfaceModeEnum = strawberry.enum(InterfaceModeChoices.as_enum(prefix='mode')) InterfacePoEModeEnum = strawberry.enum(InterfacePoEModeChoices.as_enum(prefix='mode')) InterfacePoETypeEnum = strawberry.enum(InterfacePoETypeChoices.as_enum()) diff --git a/netbox/dcim/graphql/filters.py b/netbox/dcim/graphql/filters.py index a8a6c2a5e03..af2922e1362 100644 --- a/netbox/dcim/graphql/filters.py +++ b/netbox/dcim/graphql/filters.py @@ -1,5 +1,6 @@ from typing import Annotated, TYPE_CHECKING +from django.db.models import Q import strawberry import strawberry_django from strawberry.scalars import ID @@ -7,6 +8,8 @@ from strawberry_django import FilterLookup from core.graphql.filter_mixins import ChangeLogFilterMixin from dcim import models +from dcim.constants import * +from dcim.graphql.enums import InterfaceKindEnum from extras.graphql.filter_mixins import ConfigContextFilterMixin from netbox.graphql.filter_mixins import ( PrimaryModelFilterMixin, @@ -485,6 +488,27 @@ class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin strawberry_django.filter_field() ) + @strawberry_django.filter_field + def connected(self, queryset, value: bool, prefix: str): + if value is True: + return queryset, Q(**{f"{prefix}_path__is_active": True}) + else: + return queryset, Q(**{f"{prefix}_path__isnull": True}) | Q(**{f"{prefix}_path__is_active": False}) + + @strawberry_django.filter_field + def kind( + self, + queryset, + value: Annotated['InterfaceKindEnum', strawberry.lazy('dcim.graphql.enums')], + prefix: str + ): + if value == InterfaceKindEnum.KIND_PHYSICAL: + return queryset, ~Q(**{f"{prefix}type__in": NONCONNECTABLE_IFACE_TYPES}) + elif value == InterfaceKindEnum.KIND_VIRTUAL: + return queryset, Q(**{f"{prefix}type__in": VIRTUAL_IFACE_TYPES}) + elif value == InterfaceKindEnum.KIND_WIRELESS: + return queryset, Q(**{f"{prefix}type__in": WIRELESS_IFACE_TYPES}) + @strawberry_django.filter_type(models.InterfaceTemplate, lookups=True) class InterfaceTemplateFilter(ModularComponentTemplateFilterMixin): From b24f8fb340ecb6974157e46dd039c1666fc669c0 Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Mon, 8 Sep 2025 18:44:00 +0200 Subject: [PATCH 09/40] feat(core): Update plugin title rendering with default icon Replaces inline plugin title HTML with a reusable template in `template_code.py`. Adds a default icon for plugins without custom icons and updates the table logic to use this template. Removes redundant logic from the `render_title_long` method to improve maintainability. Changes the `order_by` field in `plugins.py` from `name` to `title_long`. Fixes #20264 --- netbox/core/tables/plugins.py | 18 ++---------------- netbox/core/tables/template_code.py | 10 ++++++++++ netbox/project-static/img/plugin-default.svg | 1 + 3 files changed, 13 insertions(+), 16 deletions(-) create mode 100644 netbox/project-static/img/plugin-default.svg diff --git a/netbox/core/tables/plugins.py b/netbox/core/tables/plugins.py index e1b80af4233..8903227fe4c 100644 --- a/netbox/core/tables/plugins.py +++ b/netbox/core/tables/plugins.py @@ -1,10 +1,8 @@ import django_tables2 as tables -from django.urls import reverse -from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ from netbox.tables import BaseTable, columns -from .template_code import PLUGIN_IS_INSTALLED +from .template_code import PLUGIN_IS_INSTALLED, PLUGIN_NAME_TEMPLATE __all__ = ( 'CatalogPluginTable', @@ -12,12 +10,6 @@ __all__ = ( ) -PLUGIN_NAME_TEMPLATE = """ - -{{ record.title_long }} -""" - - class PluginVersionTable(BaseTable): version = tables.Column( verbose_name=_('Version') @@ -93,10 +85,4 @@ class CatalogPluginTable(BaseTable): ) # List installed plugins first, then certified plugins, then # everything else (with each tranche ordered alphabetically) - order_by = ('-is_installed', '-is_certified', 'name') - - def render_title_long(self, value, record): - if record.static: - return value - url = reverse('core:plugin', args=[record.config_name]) - return mark_safe(f"{value}") + order_by = ('-is_installed', '-is_certified', 'title_long') diff --git a/netbox/core/tables/template_code.py b/netbox/core/tables/template_code.py index f49231a3982..dc604876f8b 100644 --- a/netbox/core/tables/template_code.py +++ b/netbox/core/tables/template_code.py @@ -27,6 +27,16 @@ PLUGIN_IS_INSTALLED = """ {% endif %} """ +PLUGIN_NAME_TEMPLATE = """ +{% load static %} +{% if record.icon_url %} + +{% else %} + +{% endif %} +{{ record.title_long }} +""" + DATA_SOURCE_SYNC_BUTTON = """ {% load helpers %} {% load i18n %} diff --git a/netbox/project-static/img/plugin-default.svg b/netbox/project-static/img/plugin-default.svg new file mode 100644 index 00000000000..1493e72d485 --- /dev/null +++ b/netbox/project-static/img/plugin-default.svg @@ -0,0 +1 @@ + \ No newline at end of file From 291010737a9fbd5ed776821bd8756a39a11d089d Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 8 Sep 2025 13:05:14 -0400 Subject: [PATCH 10/40] Closes #20296: Misc updates to issue templates (#20293) --- .github/ISSUE_TEMPLATE/02-bug_report.yaml | 16 ++++++++-------- .github/ISSUE_TEMPLATE/config.yml | 3 --- README.md | 1 - 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/02-bug_report.yaml b/.github/ISSUE_TEMPLATE/02-bug_report.yaml index f25b70b1923..ee0643ff44c 100644 --- a/.github/ISSUE_TEMPLATE/02-bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/02-bug_report.yaml @@ -8,19 +8,19 @@ body: attributes: value: > **NOTE:** This form is only for reporting _reproducible bugs_ in a current NetBox - installation. If you're having trouble with installation or just looking for - assistance with using NetBox, please visit our + release. If you're having trouble with installation or just looking for assistance + using NetBox, please visit our [discussion forum](https://github.com/netbox-community/netbox/discussions) instead. - type: dropdown attributes: - label: Deployment Type + label: NetBox Edition description: > - How are you running NetBox? (For issues with the Docker image, please go to the - [netbox-docker](https://github.com/netbox-community/netbox-docker) repo.) + Users of [NetBox Cloud](https://netboxlabs.com/netbox-cloud/) or + [NetBox Enterprise](https://netboxlabs.com/netbox-enterprise/), please contact the + [NetBox Labs](https://netboxlabs.com/) support team for assistance to ensure your + request receives immediate attention. options: - - NetBox Cloud - - NetBox Enterprise - - Self-hosted + - NetBox Community validations: required: true - type: input diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 5b18f45252f..2bee327a73c 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -13,9 +13,6 @@ contact_links: - name: 🌎 Correct a Translation url: https://explore.transifex.com/netbox-community/netbox/ about: "Spot an incorrect translation? You can propose a fix on Transifex." - - name: 💡 Plugin Idea - url: https://plugin-ideas.netbox.dev - about: "Have an idea for a plugin? Head over to the ideas board!" - name: 💬 Community Slack url: https://netdev.chat about: "Join #netbox on the NetDev Community Slack for assistance with installation issues and other problems." diff --git a/README.md b/README.md index 66560dd2a98..1fd8e2f5a5a 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,6 @@ NetBox automatically logs the creation, modification, and deletion of all manage * Join the conversation on [the discussion forum](https://github.com/netbox-community/netbox/discussions) and [Slack](https://netdev.chat/)! * Already a power user? You can [suggest a feature](https://github.com/netbox-community/netbox/issues/new?assignees=&labels=type%3A+feature&template=feature_request.yaml) or [report a bug](https://github.com/netbox-community/netbox/issues/new?assignees=&labels=type%3A+bug&template=bug_report.yaml) on GitHub. * Contributions from the community are encouraged and appreciated! Check out our [contributing guide](CONTRIBUTING.md) to get started. -* [Share your idea](https://plugin-ideas.netbox.dev/) for a new plugin, or [learn how to build one](https://github.com/netbox-community/netbox-plugin-tutorial) yourself! ## Screenshots From 9d0e80571cac3ef3cc541d4c027f900d4c100e92 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 8 Sep 2025 13:28:14 -0400 Subject: [PATCH 11/40] Closes #20277: Add support for attribute assignment to deserialize_object() (#20281) --- netbox/utilities/serialization.py | 27 ++++++++--- netbox/utilities/tests/test_serialization.py | 49 ++++++++++++++++++++ 2 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 netbox/utilities/tests/test_serialization.py diff --git a/netbox/utilities/serialization.py b/netbox/utilities/serialization.py index 03797774217..5509867ae64 100644 --- a/netbox/utilities/serialization.py +++ b/netbox/utilities/serialization.py @@ -51,30 +51,45 @@ def serialize_object(obj, resolve_tags=True, extra=None, exclude=None): return data -def deserialize_object(model, fields, pk=None): +def deserialize_object(model, data, pk=None): """ Instantiate an object from the given model and field data. Functions as the complement to serialize_object(). """ content_type = ContentType.objects.get_for_model(model) + data = data.copy() m2m_data = {} # Account for custom field data - if 'custom_fields' in fields: - fields['custom_field_data'] = fields.pop('custom_fields') + if 'custom_fields' in data: + data['custom_field_data'] = data.pop('custom_fields') # Pop any assigned tags to handle the M2M relationships manually - if is_taggable(model) and fields.get('tags'): + if is_taggable(model) and data.get('tags'): Tag = apps.get_model('extras', 'Tag') - m2m_data['tags'] = Tag.objects.filter(name__in=fields.pop('tags')) + m2m_data['tags'] = Tag.objects.filter(name__in=data.pop('tags')) + # Separate any non-field attributes for assignment after deserialization of the object + model_fields = [ + field.name for field in model._meta.get_fields() + ] + attrs = { + name: data.pop(name) for name in list(data.keys()) + if name not in model_fields + } + + # Employ Django's native Python deserializer to produce the instance data = { 'model': '.'.join(content_type.natural_key()), 'pk': pk, - 'fields': fields, + 'fields': data, } instance = list(serializers.deserialize('python', [data]))[0] + # Assign non-field attributes + for name, value in attrs.items(): + setattr(instance.object, name, value) + # Apply any additional M2M assignments instance.m2m_data.update(**m2m_data) diff --git a/netbox/utilities/tests/test_serialization.py b/netbox/utilities/tests/test_serialization.py new file mode 100644 index 00000000000..044b52cc1d1 --- /dev/null +++ b/netbox/utilities/tests/test_serialization.py @@ -0,0 +1,49 @@ +from django.test import TestCase + +from dcim.choices import SiteStatusChoices +from dcim.models import Site +from extras.models import Tag +from utilities.serialization import deserialize_object, serialize_object + + +class SerializationTestCase(TestCase): + + @classmethod + def setUpTestData(cls): + tags = ( + Tag(name='Tag 1', slug='tag-1'), + Tag(name='Tag 2', slug='tag-2'), + Tag(name='Tag 3', slug='tag-3'), + ) + Tag.objects.bulk_create(tags) + + def test_serialize_object(self): + site = Site.objects.create( + name='Site 1', + slug='site=1', + description='Ignore me', + ) + site.tags.set(Tag.objects.all()) + + data = serialize_object(site, extra={'foo': 123}, exclude=['description']) + self.assertEqual(data['name'], site.name) + self.assertEqual(data['slug'], site.slug) + self.assertEqual(data['tags'], [tag.name for tag in Tag.objects.all()]) + self.assertEqual(data['foo'], 123) + self.assertNotIn('description', data) + + def test_deserialize_object(self): + data = { + 'name': 'Site 1', + 'slug': 'site-1', + 'tags': ['Tag 1', 'Tag 2', 'Tag 3'], + 'foo': 123, + } + + instance = deserialize_object(Site, data, pk=123) + self.assertEqual(instance.object.pk, 123) + self.assertEqual(instance.object.name, data['name']) + self.assertEqual(instance.object.slug, data['slug']) + self.assertEqual(instance.object.status, SiteStatusChoices.STATUS_ACTIVE) # Default field value + self.assertEqual(instance.object.foo, data['foo']) # Non-field attribute + self.assertEqual(list(instance.m2m_data['tags']), list(Tag.objects.all())) From 1d9d7f2d840792f2adeb6e5bcdc6c4653b3a98ce Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 05:02:37 +0000 Subject: [PATCH 12/40] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 262 +++++++++---------- 1 file changed, 131 insertions(+), 131 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index 58975d783e3..1da1c788a50 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: 2025-09-06 05:02+0000\n" +"POT-Creation-Date: 2025-09-09 05:02+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -96,7 +96,7 @@ msgstr "" msgid "Provisioning" msgstr "" -#: netbox/circuits/choices.py:23 netbox/core/tables/plugins.py:64 +#: netbox/circuits/choices.py:23 netbox/core/tables/plugins.py:57 #: netbox/core/tables/tasks.py:23 netbox/dcim/choices.py:22 #: netbox/dcim/choices.py:103 netbox/dcim/choices.py:155 #: netbox/dcim/choices.py:203 netbox/dcim/choices.py:256 @@ -663,8 +663,8 @@ msgstr "" #: netbox/dcim/forms/bulk_edit.py:1813 netbox/dcim/forms/bulk_import.py:1435 #: netbox/dcim/forms/filtersets.py:1142 netbox/dcim/forms/filtersets.py:1400 #: netbox/dcim/forms/filtersets.py:1553 netbox/dcim/forms/filtersets.py:1577 -#: netbox/dcim/tables/devices.py:752 netbox/dcim/tables/devices.py:808 -#: netbox/dcim/tables/devices.py:1049 netbox/dcim/tables/devicetypes.py:256 +#: netbox/dcim/tables/devices.py:757 netbox/dcim/tables/devices.py:813 +#: netbox/dcim/tables/devices.py:1054 netbox/dcim/tables/devicetypes.py:256 #: netbox/dcim/tables/devicetypes.py:271 netbox/dcim/tables/racks.py:33 #: netbox/extras/forms/bulk_edit.py:306 netbox/extras/tables/tables.py:512 #: netbox/templates/circuits/circuittype.html:30 @@ -687,7 +687,7 @@ msgstr "" #: netbox/circuits/tables/circuits.py:65 netbox/circuits/tables/circuits.py:200 #: netbox/circuits/tables/virtual_circuits.py:58 #: netbox/core/forms/bulk_edit.py:19 netbox/core/forms/filtersets.py:33 -#: netbox/core/tables/change_logging.py:32 netbox/core/tables/data.py:20 +#: netbox/core/tables/change_logging.py:32 netbox/core/tables/data.py:21 #: netbox/core/tables/jobs.py:20 netbox/dcim/forms/bulk_edit.py:857 #: netbox/dcim/forms/bulk_edit.py:996 netbox/dcim/forms/bulk_edit.py:1068 #: netbox/dcim/forms/bulk_edit.py:1087 netbox/dcim/forms/bulk_edit.py:1110 @@ -707,7 +707,7 @@ msgstr "" #: netbox/dcim/forms/model_forms.py:823 netbox/dcim/forms/object_import.py:84 #: netbox/dcim/forms/object_import.py:113 #: netbox/dcim/forms/object_import.py:146 netbox/dcim/tables/devices.py:196 -#: netbox/dcim/tables/devices.py:860 netbox/dcim/tables/power.py:77 +#: netbox/dcim/tables/devices.py:865 netbox/dcim/tables/power.py:77 #: netbox/dcim/tables/racks.py:141 netbox/extras/forms/bulk_import.py:43 #: netbox/extras/tables/tables.py:474 netbox/extras/tables/tables.py:534 #: netbox/netbox/tables/tables.py:272 netbox/templates/circuits/circuit.html:30 @@ -759,7 +759,7 @@ msgstr "" #: netbox/circuits/forms/bulk_import.py:227 #: netbox/circuits/forms/filtersets.py:162 #: netbox/circuits/forms/filtersets.py:362 netbox/core/forms/filtersets.py:38 -#: netbox/core/forms/filtersets.py:85 netbox/core/tables/data.py:23 +#: netbox/core/forms/filtersets.py:85 netbox/core/tables/data.py:24 #: netbox/core/tables/jobs.py:28 netbox/core/tables/tasks.py:90 #: netbox/dcim/forms/bulk_edit.py:116 netbox/dcim/forms/bulk_edit.py:191 #: netbox/dcim/forms/bulk_edit.py:367 netbox/dcim/forms/bulk_edit.py:480 @@ -777,8 +777,8 @@ msgstr "" #: netbox/dcim/forms/filtersets.py:1036 netbox/dcim/forms/filtersets.py:1137 #: netbox/dcim/forms/filtersets.py:1248 netbox/dcim/forms/filtersets.py:1404 #: netbox/dcim/forms/filtersets.py:1655 netbox/dcim/tables/devices.py:158 -#: netbox/dcim/tables/devices.py:532 netbox/dcim/tables/devices.py:863 -#: netbox/dcim/tables/devices.py:997 netbox/dcim/tables/devices.py:1108 +#: netbox/dcim/tables/devices.py:537 netbox/dcim/tables/devices.py:868 +#: netbox/dcim/tables/devices.py:1002 netbox/dcim/tables/devices.py:1113 #: netbox/dcim/tables/modules.py:104 netbox/dcim/tables/power.py:74 #: netbox/dcim/tables/racks.py:129 netbox/dcim/tables/racks.py:233 #: netbox/dcim/tables/sites.py:96 netbox/dcim/tables/sites.py:155 @@ -1099,7 +1099,7 @@ msgstr "" #: netbox/dcim/forms/filtersets.py:1632 netbox/dcim/forms/model_forms.py:264 #: netbox/dcim/forms/model_forms.py:1224 netbox/dcim/forms/model_forms.py:1693 #: netbox/dcim/forms/object_import.py:182 netbox/dcim/tables/devices.py:187 -#: netbox/dcim/tables/devices.py:855 netbox/dcim/tables/devices.py:981 +#: netbox/dcim/tables/devices.py:860 netbox/dcim/tables/devices.py:986 #: netbox/dcim/tables/devicetypes.py:311 netbox/dcim/tables/racks.py:132 #: netbox/extras/filtersets.py:689 netbox/ipam/forms/bulk_edit.py:245 #: netbox/ipam/forms/bulk_edit.py:295 netbox/ipam/forms/bulk_edit.py:348 @@ -1221,7 +1221,7 @@ msgstr "" #: netbox/dcim/forms/bulk_import.py:1289 netbox/dcim/forms/model_forms.py:1298 #: netbox/dcim/forms/model_forms.py:1567 netbox/dcim/forms/model_forms.py:1734 #: netbox/dcim/forms/model_forms.py:1769 netbox/dcim/forms/model_forms.py:1899 -#: netbox/dcim/tables/connections.py:65 netbox/dcim/tables/devices.py:1154 +#: netbox/dcim/tables/connections.py:65 netbox/dcim/tables/devices.py:1159 #: netbox/ipam/forms/bulk_import.py:324 netbox/ipam/forms/model_forms.py:291 #: netbox/ipam/forms/model_forms.py:300 netbox/ipam/tables/fhrp.py:64 #: netbox/ipam/tables/ip.py:330 netbox/ipam/tables/vlans.py:147 @@ -1711,19 +1711,19 @@ msgstr "" #: netbox/circuits/tables/providers.py:18 #: netbox/circuits/tables/providers.py:67 #: netbox/circuits/tables/providers.py:97 -#: netbox/circuits/tables/virtual_circuits.py:18 netbox/core/tables/data.py:16 -#: netbox/core/tables/jobs.py:16 netbox/core/tables/plugins.py:53 +#: netbox/circuits/tables/virtual_circuits.py:18 netbox/core/tables/data.py:17 +#: netbox/core/tables/jobs.py:16 netbox/core/tables/plugins.py:45 #: netbox/core/tables/tasks.py:12 netbox/core/tables/tasks.py:117 #: netbox/dcim/forms/filtersets.py:65 netbox/dcim/forms/object_create.py:43 #: netbox/dcim/tables/devices.py:63 netbox/dcim/tables/devices.py:107 -#: netbox/dcim/tables/devices.py:153 netbox/dcim/tables/devices.py:307 -#: netbox/dcim/tables/devices.py:410 netbox/dcim/tables/devices.py:451 -#: netbox/dcim/tables/devices.py:499 netbox/dcim/tables/devices.py:553 -#: netbox/dcim/tables/devices.py:576 netbox/dcim/tables/devices.py:696 -#: netbox/dcim/tables/devices.py:779 netbox/dcim/tables/devices.py:825 -#: netbox/dcim/tables/devices.py:887 netbox/dcim/tables/devices.py:956 -#: netbox/dcim/tables/devices.py:1021 netbox/dcim/tables/devices.py:1040 -#: netbox/dcim/tables/devices.py:1069 netbox/dcim/tables/devices.py:1099 +#: netbox/dcim/tables/devices.py:153 netbox/dcim/tables/devices.py:312 +#: netbox/dcim/tables/devices.py:415 netbox/dcim/tables/devices.py:456 +#: netbox/dcim/tables/devices.py:504 netbox/dcim/tables/devices.py:558 +#: netbox/dcim/tables/devices.py:581 netbox/dcim/tables/devices.py:701 +#: netbox/dcim/tables/devices.py:784 netbox/dcim/tables/devices.py:830 +#: netbox/dcim/tables/devices.py:892 netbox/dcim/tables/devices.py:961 +#: netbox/dcim/tables/devices.py:1026 netbox/dcim/tables/devices.py:1045 +#: netbox/dcim/tables/devices.py:1074 netbox/dcim/tables/devices.py:1104 #: netbox/dcim/tables/devicetypes.py:31 netbox/dcim/tables/devicetypes.py:227 #: netbox/dcim/tables/modules.py:17 netbox/dcim/tables/power.py:22 #: netbox/dcim/tables/power.py:62 netbox/dcim/tables/racks.py:24 @@ -1873,7 +1873,7 @@ msgstr "" #: netbox/circuits/tables/providers.py:80 #: netbox/circuits/tables/providers.py:105 #: netbox/circuits/tables/virtual_circuits.py:67 -#: netbox/dcim/tables/devices.py:1082 netbox/dcim/tables/devicetypes.py:97 +#: netbox/dcim/tables/devices.py:1087 netbox/dcim/tables/devicetypes.py:97 #: netbox/dcim/tables/modules.py:27 netbox/dcim/tables/modules.py:68 #: netbox/dcim/tables/modules.py:107 netbox/dcim/tables/power.py:39 #: netbox/dcim/tables/power.py:96 netbox/dcim/tables/racks.py:88 @@ -1976,12 +1976,12 @@ msgstr "" #: netbox/dcim/forms/model_forms.py:1850 netbox/dcim/forms/model_forms.py:1923 #: netbox/dcim/forms/object_create.py:260 netbox/dcim/tables/connections.py:22 #: netbox/dcim/tables/connections.py:41 netbox/dcim/tables/connections.py:60 -#: netbox/dcim/tables/devices.py:303 netbox/dcim/tables/devices.py:388 -#: netbox/dcim/tables/devices.py:429 netbox/dcim/tables/devices.py:471 -#: netbox/dcim/tables/devices.py:521 netbox/dcim/tables/devices.py:633 -#: netbox/dcim/tables/devices.py:745 netbox/dcim/tables/devices.py:801 -#: netbox/dcim/tables/devices.py:847 netbox/dcim/tables/devices.py:906 -#: netbox/dcim/tables/devices.py:974 netbox/dcim/tables/devices.py:1103 +#: netbox/dcim/tables/devices.py:308 netbox/dcim/tables/devices.py:393 +#: netbox/dcim/tables/devices.py:434 netbox/dcim/tables/devices.py:476 +#: netbox/dcim/tables/devices.py:526 netbox/dcim/tables/devices.py:638 +#: netbox/dcim/tables/devices.py:750 netbox/dcim/tables/devices.py:806 +#: netbox/dcim/tables/devices.py:852 netbox/dcim/tables/devices.py:911 +#: netbox/dcim/tables/devices.py:979 netbox/dcim/tables/devices.py:1108 #: netbox/dcim/tables/modules.py:87 netbox/extras/forms/filtersets.py:386 #: netbox/ipam/forms/bulk_import.py:310 netbox/ipam/forms/filtersets.py:626 #: netbox/ipam/forms/model_forms.py:334 netbox/ipam/tables/vlans.py:158 @@ -2144,7 +2144,7 @@ msgstr "" msgid "30 days" msgstr "" -#: netbox/core/choices.py:103 netbox/core/tables/plugins.py:75 +#: netbox/core/choices.py:103 netbox/core/tables/plugins.py:68 #: netbox/templates/generic/object.html:61 msgid "Updated" msgstr "" @@ -2194,7 +2194,7 @@ msgstr "" msgid "Error" msgstr "" -#: netbox/core/data_backends.py:32 netbox/core/tables/plugins.py:61 +#: netbox/core/data_backends.py:32 netbox/core/tables/plugins.py:53 #: netbox/templates/core/plugin.html:88 #: netbox/templates/dcim/interface.html:273 msgid "Local" @@ -2255,9 +2255,9 @@ msgid "User name" msgstr "" #: netbox/core/forms/bulk_edit.py:26 netbox/core/forms/filtersets.py:43 -#: netbox/core/tables/data.py:26 netbox/dcim/choices.py:1678 +#: netbox/core/tables/data.py:27 netbox/dcim/choices.py:1678 #: netbox/dcim/forms/bulk_edit.py:1201 netbox/dcim/forms/bulk_edit.py:1482 -#: netbox/dcim/forms/filtersets.py:1458 netbox/dcim/tables/devices.py:581 +#: netbox/dcim/forms/filtersets.py:1458 netbox/dcim/tables/devices.py:586 #: netbox/dcim/tables/devicetypes.py:231 netbox/extras/forms/bulk_edit.py:127 #: netbox/extras/forms/bulk_edit.py:195 netbox/extras/forms/bulk_edit.py:223 #: netbox/extras/forms/bulk_edit.py:282 netbox/extras/forms/filtersets.py:147 @@ -2280,7 +2280,7 @@ msgid "Enabled" msgstr "" #: netbox/core/forms/bulk_edit.py:36 netbox/core/forms/filtersets.py:50 -#: netbox/core/tables/data.py:29 netbox/templates/core/datasource.html:50 +#: netbox/core/tables/data.py:30 netbox/templates/core/datasource.html:50 msgid "Sync interval" msgstr "" @@ -2409,7 +2409,7 @@ msgstr "" msgid "Action" msgstr "" -#: netbox/core/forms/model_forms.py:55 netbox/core/tables/data.py:52 +#: netbox/core/forms/model_forms.py:55 netbox/core/tables/data.py:56 #: netbox/templates/core/datafile.html:21 #: netbox/templates/extras/report/base.html:33 #: netbox/templates/extras/script/base.html:32 @@ -2845,19 +2845,19 @@ msgstr "" msgid "Is Active" msgstr "" -#: netbox/core/tables/data.py:32 +#: netbox/core/tables/data.py:33 msgid "Last Synced" msgstr "" -#: netbox/core/tables/data.py:35 netbox/templates/core/datasource.html:118 +#: netbox/core/tables/data.py:36 netbox/templates/core/datasource.html:118 msgid "Files" msgstr "" -#: netbox/core/tables/data.py:56 netbox/templates/core/datafile.html:25 +#: netbox/core/tables/data.py:60 netbox/templates/core/datafile.html:25 msgid "Path" msgstr "" -#: netbox/core/tables/data.py:60 +#: netbox/core/tables/data.py:64 #: netbox/templates/extras/inc/result_pending.html:7 msgid "Last updated" msgstr "" @@ -2889,45 +2889,45 @@ msgstr "" msgid "No log entries" msgstr "" -#: netbox/core/tables/plugins.py:23 netbox/templates/vpn/ipsecprofile.html:44 +#: netbox/core/tables/plugins.py:15 netbox/templates/vpn/ipsecprofile.html:44 #: netbox/vpn/forms/bulk_edit.py:141 netbox/vpn/forms/bulk_import.py:172 #: netbox/vpn/tables/crypto.py:61 msgid "Version" msgstr "" -#: netbox/core/tables/plugins.py:28 netbox/templates/core/datafile.html:32 +#: netbox/core/tables/plugins.py:20 netbox/templates/core/datafile.html:32 msgid "Last Updated" msgstr "" -#: netbox/core/tables/plugins.py:32 +#: netbox/core/tables/plugins.py:24 msgid "Minimum NetBox Version" msgstr "" -#: netbox/core/tables/plugins.py:36 +#: netbox/core/tables/plugins.py:28 msgid "Maximum NetBox Version" msgstr "" -#: netbox/core/tables/plugins.py:40 netbox/core/tables/plugins.py:86 +#: netbox/core/tables/plugins.py:32 netbox/core/tables/plugins.py:79 msgid "No plugin data found" msgstr "" -#: netbox/core/tables/plugins.py:57 netbox/templates/core/plugin.html:62 +#: netbox/core/tables/plugins.py:49 netbox/templates/core/plugin.html:62 msgid "Author" msgstr "" -#: netbox/core/tables/plugins.py:69 netbox/templates/core/plugin.html:84 +#: netbox/core/tables/plugins.py:62 netbox/templates/core/plugin.html:84 msgid "Certified" msgstr "" -#: netbox/core/tables/plugins.py:72 +#: netbox/core/tables/plugins.py:65 msgid "Published" msgstr "" -#: netbox/core/tables/plugins.py:78 +#: netbox/core/tables/plugins.py:71 msgid "Installed Version" msgstr "" -#: netbox/core/tables/plugins.py:82 +#: netbox/core/tables/plugins.py:75 msgid "Latest Version" msgstr "" @@ -2991,7 +2991,7 @@ msgstr "" msgid "No workers found" msgstr "" -#: netbox/core/utils.py:84 netbox/core/utils.py:150 netbox/core/views.py:421 +#: netbox/core/utils.py:84 netbox/core/utils.py:150 netbox/core/views.py:427 #, python-brace-format msgid "Job {job_id} not found" msgstr "" @@ -3001,55 +3001,55 @@ msgstr "" msgid "Job {id} not found." msgstr "" -#: netbox/core/views.py:86 +#: netbox/core/views.py:92 #, python-brace-format msgid "Queued job #{id} to sync {datasource}" msgstr "" -#: netbox/core/views.py:189 netbox/templates/extras/htmx/script_result.html:43 +#: netbox/core/views.py:195 netbox/templates/extras/htmx/script_result.html:43 msgid "Log" msgstr "" -#: netbox/core/views.py:357 +#: netbox/core/views.py:363 #, python-brace-format msgid "Restored configuration revision #{id}" msgstr "" -#: netbox/core/views.py:460 +#: netbox/core/views.py:466 #, python-brace-format msgid "Job {id} has been deleted." msgstr "" -#: netbox/core/views.py:462 +#: netbox/core/views.py:468 #, python-brace-format msgid "Error deleting job {id}: {error}" msgstr "" -#: netbox/core/views.py:471 +#: netbox/core/views.py:477 #, python-brace-format msgid "Job {id} has been re-enqueued." msgstr "" -#: netbox/core/views.py:480 +#: netbox/core/views.py:486 #, python-brace-format msgid "Job {id} has been enqueued." msgstr "" -#: netbox/core/views.py:489 +#: netbox/core/views.py:495 #, python-brace-format msgid "Job {id} has been stopped." msgstr "" -#: netbox/core/views.py:491 +#: netbox/core/views.py:497 #, python-brace-format msgid "Failed to stop job {id}" msgstr "" -#: netbox/core/views.py:645 +#: netbox/core/views.py:651 msgid "Plugins catalog could not be loaded" msgstr "" -#: netbox/core/views.py:681 +#: netbox/core/views.py:687 #, python-brace-format msgid "Plugin {name} not found" msgstr "" @@ -3179,8 +3179,8 @@ msgstr "" #: netbox/dcim/forms/model_forms.py:540 netbox/dcim/forms/model_forms.py:1216 #: netbox/dcim/forms/model_forms.py:1685 netbox/dcim/forms/object_import.py:177 #: netbox/dcim/tables/devices.py:67 netbox/dcim/tables/devices.py:111 -#: netbox/dcim/tables/devices.py:704 netbox/dcim/tables/devices.py:914 -#: netbox/dcim/tables/devices.py:1001 netbox/dcim/tables/devices.py:1160 +#: netbox/dcim/tables/devices.py:709 netbox/dcim/tables/devices.py:919 +#: netbox/dcim/tables/devices.py:1006 netbox/dcim/tables/devices.py:1165 #: netbox/dcim/tables/sites.py:28 netbox/dcim/tables/sites.py:62 #: netbox/dcim/tables/sites.py:147 netbox/ipam/forms/bulk_import.py:568 #: netbox/ipam/forms/model_forms.py:777 netbox/ipam/tables/fhrp.py:59 @@ -3325,7 +3325,7 @@ msgstr "" #: netbox/dcim/choices.py:1052 netbox/dcim/forms/bulk_edit.py:1495 #: netbox/dcim/forms/bulk_import.py:922 netbox/dcim/forms/model_forms.py:1108 -#: netbox/dcim/tables/devices.py:708 netbox/templates/dcim/interface.html:112 +#: netbox/dcim/tables/devices.py:713 netbox/templates/dcim/interface.html:112 #: netbox/templates/virtualization/vminterface.html:43 #: netbox/virtualization/forms/bulk_edit.py:194 #: netbox/virtualization/forms/bulk_import.py:164 @@ -3841,7 +3841,7 @@ msgid "Virtual Chassis (ID)" msgstr "" #: netbox/dcim/filtersets.py:1624 netbox/dcim/forms/filtersets.py:111 -#: netbox/dcim/forms/object_create.py:430 netbox/dcim/tables/devices.py:224 +#: netbox/dcim/forms/object_create.py:430 netbox/dcim/tables/devices.py:229 #: netbox/netbox/navigation/menu.py:79 netbox/templates/dcim/device.html:31 #: netbox/templates/dcim/device.html:126 #: netbox/templates/dcim/device_edit.html:95 @@ -3905,7 +3905,7 @@ msgstr "" #: netbox/dcim/forms/bulk_import.py:973 netbox/dcim/forms/filtersets.py:1526 #: netbox/dcim/forms/model_forms.py:1545 #: netbox/dcim/models/device_components.py:795 -#: netbox/dcim/tables/devices.py:662 netbox/ipam/filtersets.py:335 +#: netbox/dcim/tables/devices.py:667 netbox/ipam/filtersets.py:335 #: netbox/ipam/filtersets.py:346 netbox/ipam/filtersets.py:478 #: netbox/ipam/filtersets.py:579 netbox/ipam/filtersets.py:590 #: netbox/ipam/forms/bulk_edit.py:226 netbox/ipam/forms/bulk_edit.py:282 @@ -3946,7 +3946,7 @@ msgid "L2VPN (ID)" msgstr "" #: netbox/dcim/filtersets.py:1879 netbox/dcim/forms/filtersets.py:1531 -#: netbox/dcim/tables/devices.py:598 netbox/ipam/filtersets.py:1042 +#: netbox/dcim/tables/devices.py:603 netbox/ipam/filtersets.py:1042 #: netbox/ipam/forms/filtersets.py:592 netbox/ipam/tables/vlans.py:115 #: netbox/templates/dcim/interface.html:99 netbox/templates/ipam/vlan.html:82 #: netbox/templates/vpn/l2vpntermination.html:12 @@ -4004,8 +4004,8 @@ msgstr "" msgid "LAG interface (ID)" msgstr "" -#: netbox/dcim/filtersets.py:1966 netbox/dcim/tables/devices.py:620 -#: netbox/dcim/tables/devices.py:1149 netbox/templates/dcim/interface.html:131 +#: netbox/dcim/filtersets.py:1966 netbox/dcim/tables/devices.py:625 +#: netbox/dcim/tables/devices.py:1154 netbox/templates/dcim/interface.html:131 #: netbox/templates/dcim/macaddress.html:11 #: netbox/templates/dcim/macaddress.html:14 #: netbox/templates/virtualization/vminterface.html:79 @@ -4037,7 +4037,7 @@ msgstr "" msgid "Wireless LAN" msgstr "" -#: netbox/dcim/filtersets.py:2020 netbox/dcim/tables/devices.py:649 +#: netbox/dcim/filtersets.py:2020 netbox/dcim/tables/devices.py:654 msgid "Wireless link" msgstr "" @@ -4102,7 +4102,7 @@ msgstr "" #: netbox/dcim/forms/model_forms.py:601 netbox/dcim/forms/model_forms.py:660 #: netbox/dcim/forms/object_create.py:208 #: netbox/dcim/forms/object_create.py:357 netbox/dcim/tables/devices.py:183 -#: netbox/dcim/tables/devices.py:755 netbox/dcim/tables/devicetypes.py:253 +#: netbox/dcim/tables/devices.py:760 netbox/dcim/tables/devicetypes.py:253 #: netbox/templates/dcim/device.html:49 netbox/templates/dcim/device.html:137 #: netbox/templates/dcim/modulebay.html:38 #: netbox/templates/dcim/virtualchassis.html:56 @@ -4149,7 +4149,7 @@ msgstr "" #: netbox/dcim/forms/model_forms.py:545 netbox/dcim/forms/model_forms.py:1229 #: netbox/dcim/forms/model_forms.py:1698 netbox/dcim/forms/object_import.py:188 #: netbox/dcim/tables/devices.py:115 netbox/dcim/tables/devices.py:190 -#: netbox/dcim/tables/devices.py:984 netbox/dcim/tables/devicetypes.py:85 +#: netbox/dcim/tables/devices.py:989 netbox/dcim/tables/devicetypes.py:85 #: netbox/dcim/tables/devicetypes.py:315 netbox/dcim/tables/modules.py:49 #: netbox/dcim/tables/modules.py:95 netbox/dcim/tables/racks.py:58 #: netbox/dcim/tables/racks.py:135 netbox/templates/dcim/devicetype.html:14 @@ -4437,7 +4437,7 @@ msgstr "" #: netbox/dcim/forms/bulk_edit.py:761 netbox/dcim/forms/bulk_import.py:571 #: netbox/dcim/forms/filtersets.py:826 netbox/dcim/forms/model_forms.py:563 -#: netbox/dcim/forms/model_forms.py:627 netbox/dcim/tables/devices.py:200 +#: netbox/dcim/forms/model_forms.py:627 netbox/dcim/tables/devices.py:205 #: netbox/extras/filtersets.py:700 netbox/templates/dcim/device.html:192 #: netbox/templates/dcim/platform.html:26 #: netbox/templates/virtualization/virtualmachine.html:27 @@ -4451,7 +4451,7 @@ msgstr "" #: netbox/dcim/forms/bulk_edit.py:791 netbox/dcim/forms/bulk_import.py:590 #: netbox/dcim/forms/filtersets.py:758 netbox/dcim/forms/filtersets.py:928 -#: netbox/dcim/forms/model_forms.py:636 netbox/dcim/tables/devices.py:220 +#: netbox/dcim/forms/model_forms.py:636 netbox/dcim/tables/devices.py:225 #: netbox/extras/filtersets.py:733 netbox/extras/forms/filtersets.py:387 #: netbox/ipam/forms/filtersets.py:439 netbox/ipam/forms/filtersets.py:472 #: netbox/templates/dcim/device.html:245 @@ -4610,7 +4610,7 @@ msgid "Wireless role" msgstr "" #: netbox/dcim/forms/bulk_edit.py:1367 netbox/dcim/forms/model_forms.py:783 -#: netbox/dcim/forms/model_forms.py:1380 netbox/dcim/tables/devices.py:330 +#: netbox/dcim/forms/model_forms.py:1380 netbox/dcim/tables/devices.py:335 #: netbox/templates/dcim/consoleport.html:24 #: netbox/templates/dcim/consoleserverport.html:24 #: netbox/templates/dcim/frontport.html:24 @@ -4623,7 +4623,7 @@ msgstr "" msgid "Module" msgstr "" -#: netbox/dcim/forms/bulk_edit.py:1509 netbox/dcim/tables/devices.py:713 +#: netbox/dcim/forms/bulk_edit.py:1509 netbox/dcim/tables/devices.py:718 #: netbox/templates/dcim/interface.html:116 msgid "LAG" msgstr "" @@ -4635,7 +4635,7 @@ msgstr "" #: netbox/dcim/forms/bulk_edit.py:1520 netbox/dcim/forms/bulk_import.py:793 #: netbox/dcim/forms/bulk_import.py:819 netbox/dcim/forms/filtersets.py:1330 #: netbox/dcim/forms/filtersets.py:1355 netbox/dcim/forms/filtersets.py:1446 -#: netbox/dcim/tables/devices.py:646 +#: netbox/dcim/tables/devices.py:651 #: netbox/templates/circuits/inc/circuit_termination_fields.html:62 #: netbox/templates/dcim/consoleport.html:40 #: netbox/templates/dcim/consoleserverport.html:40 @@ -4664,14 +4664,14 @@ msgid "VLAN group" msgstr "" #: netbox/dcim/forms/bulk_edit.py:1566 netbox/dcim/forms/model_forms.py:1517 -#: netbox/dcim/tables/devices.py:607 +#: netbox/dcim/tables/devices.py:612 #: netbox/virtualization/forms/bulk_edit.py:230 #: netbox/virtualization/forms/model_forms.py:340 msgid "Untagged VLAN" msgstr "" #: netbox/dcim/forms/bulk_edit.py:1575 netbox/dcim/forms/model_forms.py:1526 -#: netbox/dcim/tables/devices.py:613 +#: netbox/dcim/tables/devices.py:618 #: netbox/virtualization/forms/bulk_edit.py:238 #: netbox/virtualization/forms/model_forms.py:349 msgid "Tagged VLANs" @@ -4695,7 +4695,7 @@ msgid "Wireless LAN group" msgstr "" #: netbox/dcim/forms/bulk_edit.py:1618 netbox/dcim/forms/model_forms.py:1503 -#: netbox/dcim/tables/devices.py:655 netbox/netbox/navigation/menu.py:153 +#: netbox/dcim/tables/devices.py:660 netbox/netbox/navigation/menu.py:153 #: netbox/templates/dcim/interface.html:337 #: netbox/wireless/tables/wirelesslan.py:28 msgid "Wireless LANs" @@ -5089,7 +5089,7 @@ msgstr "" msgid "Physical medium classification" msgstr "" -#: netbox/dcim/forms/bulk_import.py:1110 netbox/dcim/tables/devices.py:868 +#: netbox/dcim/forms/bulk_import.py:1110 netbox/dcim/tables/devices.py:873 msgid "Installed device" msgstr "" @@ -5250,7 +5250,7 @@ msgid "" msgstr "" #: netbox/dcim/forms/bulk_import.py:1542 netbox/dcim/forms/model_forms.py:900 -#: netbox/dcim/tables/devices.py:1073 netbox/templates/dcim/device.html:138 +#: netbox/dcim/tables/devices.py:1078 netbox/templates/dcim/device.html:138 #: netbox/templates/dcim/virtualchassis.html:17 #: netbox/templates/dcim/virtualchassis.html:57 msgid "Master" @@ -5354,7 +5354,7 @@ msgstr "" msgid "Power Feed" msgstr "" -#: netbox/dcim/forms/filtersets.py:138 netbox/dcim/tables/devices.py:312 +#: netbox/dcim/forms/filtersets.py:138 netbox/dcim/tables/devices.py:317 msgid "Device Status" msgstr "" @@ -5432,8 +5432,8 @@ msgstr "" #: netbox/dcim/forms/filtersets.py:1322 netbox/dcim/forms/filtersets.py:1347 #: netbox/dcim/forms/filtersets.py:1371 netbox/dcim/forms/filtersets.py:1391 -#: netbox/dcim/forms/filtersets.py:1424 netbox/dcim/tables/devices.py:381 -#: netbox/dcim/tables/devices.py:677 +#: netbox/dcim/forms/filtersets.py:1424 netbox/dcim/tables/devices.py:386 +#: netbox/dcim/tables/devices.py:682 #: netbox/templates/circuits/inc/circuit_termination_fields.html:16 #: netbox/templates/dcim/consoleport.html:55 #: netbox/templates/dcim/consoleserverport.html:55 @@ -5485,7 +5485,7 @@ msgid "Transmit power (dBm)" msgstr "" #: netbox/dcim/forms/filtersets.py:1544 netbox/dcim/forms/filtersets.py:1569 -#: netbox/dcim/tables/devices.py:344 netbox/templates/dcim/cable.html:12 +#: netbox/dcim/tables/devices.py:349 netbox/templates/dcim/cable.html:12 #: netbox/templates/dcim/cable_trace.html:46 #: netbox/templates/dcim/frontport.html:77 #: netbox/templates/dcim/htmx/cable_edit.html:53 @@ -5495,7 +5495,7 @@ msgstr "" msgid "Cable" msgstr "" -#: netbox/dcim/forms/filtersets.py:1648 netbox/dcim/tables/devices.py:993 +#: netbox/dcim/forms/filtersets.py:1648 netbox/dcim/tables/devices.py:998 msgid "Discovered" msgstr "" @@ -5671,7 +5671,7 @@ msgid "Front Port" msgstr "" #: netbox/dcim/forms/model_forms.py:1302 netbox/dcim/forms/model_forms.py:1773 -#: netbox/dcim/tables/devices.py:758 +#: netbox/dcim/tables/devices.py:763 #: netbox/templates/circuits/inc/circuit_termination_fields.html:54 #: netbox/templates/dcim/consoleport.html:79 #: netbox/templates/dcim/consoleserverport.html:79 @@ -5684,7 +5684,7 @@ msgid "Rear Port" msgstr "" #: netbox/dcim/forms/model_forms.py:1303 netbox/dcim/forms/model_forms.py:1774 -#: netbox/dcim/tables/connections.py:46 netbox/dcim/tables/devices.py:528 +#: netbox/dcim/tables/connections.py:46 netbox/dcim/tables/devices.py:533 #: netbox/templates/dcim/poweroutlet.html:58 #: netbox/templates/dcim/powerport.html:17 msgid "Power Port" @@ -5788,7 +5788,7 @@ msgid "" msgstr "" #: netbox/dcim/forms/object_create.py:114 -#: netbox/dcim/forms/object_create.py:274 netbox/dcim/tables/devices.py:270 +#: netbox/dcim/forms/object_create.py:274 netbox/dcim/tables/devices.py:275 msgid "Rear ports" msgstr "" @@ -5811,7 +5811,7 @@ msgid "" "selected number of rear port positions ({rearport_count})." msgstr "" -#: netbox/dcim/forms/object_create.py:413 netbox/dcim/tables/devices.py:1079 +#: netbox/dcim/forms/object_create.py:413 netbox/dcim/tables/devices.py:1084 #: netbox/ipam/tables/fhrp.py:31 netbox/templates/dcim/virtualchassis.html:43 #: netbox/templates/dcim/virtualchassis_edit.html:51 #: netbox/templates/ipam/fhrpgroup.html:38 @@ -6290,7 +6290,7 @@ msgid "tagged VLANs" msgstr "" #: netbox/dcim/models/device_components.py:604 -#: netbox/dcim/tables/devices.py:616 netbox/ipam/forms/bulk_edit.py:521 +#: netbox/dcim/tables/devices.py:621 netbox/ipam/forms/bulk_edit.py:521 #: netbox/ipam/forms/bulk_import.py:514 netbox/ipam/forms/filtersets.py:587 #: netbox/ipam/forms/model_forms.py:701 netbox/ipam/tables/vlans.py:108 #: netbox/templates/dcim/interface.html:86 netbox/templates/ipam/vlan.html:77 @@ -7440,7 +7440,7 @@ msgstr "" msgid "VMs" msgstr "" -#: netbox/dcim/tables/devices.py:119 netbox/dcim/tables/devices.py:234 +#: netbox/dcim/tables/devices.py:119 netbox/dcim/tables/devices.py:239 #: netbox/extras/forms/model_forms.py:743 netbox/templates/dcim/device.html:118 #: netbox/templates/dcim/devicerole.html:48 #: netbox/templates/dcim/platform.html:45 @@ -7452,7 +7452,11 @@ msgstr "" msgid "Config Template" msgstr "" -#: netbox/dcim/tables/devices.py:205 netbox/dcim/tables/devices.py:1113 +#: netbox/dcim/tables/devices.py:200 netbox/dcim/tables/devicetypes.py:103 +msgid "U Height" +msgstr "" + +#: netbox/dcim/tables/devices.py:210 netbox/dcim/tables/devices.py:1118 #: netbox/ipam/forms/bulk_import.py:587 netbox/ipam/forms/model_forms.py:317 #: netbox/ipam/forms/model_forms.py:330 netbox/ipam/tables/ip.py:314 #: netbox/ipam/tables/ip.py:381 netbox/ipam/tables/ip.py:391 @@ -7461,50 +7465,50 @@ msgstr "" msgid "IP Address" msgstr "" -#: netbox/dcim/tables/devices.py:209 netbox/dcim/tables/devices.py:1117 +#: netbox/dcim/tables/devices.py:214 netbox/dcim/tables/devices.py:1122 #: netbox/virtualization/tables/virtualmachines.py:56 msgid "IPv4 Address" msgstr "" -#: netbox/dcim/tables/devices.py:213 netbox/dcim/tables/devices.py:1121 +#: netbox/dcim/tables/devices.py:218 netbox/dcim/tables/devices.py:1126 #: netbox/virtualization/tables/virtualmachines.py:60 msgid "IPv6 Address" msgstr "" -#: netbox/dcim/tables/devices.py:228 +#: netbox/dcim/tables/devices.py:233 msgid "VC Position" msgstr "" -#: netbox/dcim/tables/devices.py:231 +#: netbox/dcim/tables/devices.py:236 msgid "VC Priority" msgstr "" -#: netbox/dcim/tables/devices.py:238 netbox/templates/dcim/device_edit.html:40 +#: netbox/dcim/tables/devices.py:243 netbox/templates/dcim/device_edit.html:40 #: netbox/templates/dcim/devicebay_populate.html:16 msgid "Parent Device" msgstr "" -#: netbox/dcim/tables/devices.py:243 +#: netbox/dcim/tables/devices.py:248 msgid "Position (Device Bay)" msgstr "" -#: netbox/dcim/tables/devices.py:252 +#: netbox/dcim/tables/devices.py:257 msgid "Console ports" msgstr "" -#: netbox/dcim/tables/devices.py:255 +#: netbox/dcim/tables/devices.py:260 msgid "Console server ports" msgstr "" -#: netbox/dcim/tables/devices.py:258 +#: netbox/dcim/tables/devices.py:263 msgid "Power ports" msgstr "" -#: netbox/dcim/tables/devices.py:261 +#: netbox/dcim/tables/devices.py:266 msgid "Power outlets" msgstr "" -#: netbox/dcim/tables/devices.py:264 netbox/dcim/tables/devices.py:1126 +#: netbox/dcim/tables/devices.py:269 netbox/dcim/tables/devices.py:1131 #: netbox/dcim/tables/devicetypes.py:133 netbox/dcim/views.py:1207 #: netbox/dcim/views.py:1518 netbox/dcim/views.py:2305 #: netbox/netbox/navigation/menu.py:95 netbox/netbox/navigation/menu.py:259 @@ -7522,28 +7526,28 @@ msgstr "" msgid "Interfaces" msgstr "" -#: netbox/dcim/tables/devices.py:267 +#: netbox/dcim/tables/devices.py:272 msgid "Front ports" msgstr "" -#: netbox/dcim/tables/devices.py:273 +#: netbox/dcim/tables/devices.py:278 msgid "Device bays" msgstr "" -#: netbox/dcim/tables/devices.py:276 +#: netbox/dcim/tables/devices.py:281 msgid "Module bays" msgstr "" -#: netbox/dcim/tables/devices.py:279 +#: netbox/dcim/tables/devices.py:284 msgid "Inventory items" msgstr "" -#: netbox/dcim/tables/devices.py:322 netbox/dcim/tables/modules.py:91 +#: netbox/dcim/tables/devices.py:327 netbox/dcim/tables/modules.py:91 #: netbox/templates/dcim/module.html:65 netbox/templates/dcim/modulebay.html:17 msgid "Module Bay" msgstr "" -#: netbox/dcim/tables/devices.py:335 netbox/dcim/tables/devicetypes.py:52 +#: netbox/dcim/tables/devices.py:340 netbox/dcim/tables/devicetypes.py:52 #: netbox/dcim/tables/devicetypes.py:148 netbox/dcim/views.py:1282 #: netbox/dcim/views.py:2391 netbox/netbox/navigation/menu.py:104 #: netbox/templates/dcim/buttons/bulk_add_components.html:66 @@ -7554,27 +7558,27 @@ msgstr "" msgid "Inventory Items" msgstr "" -#: netbox/dcim/tables/devices.py:350 +#: netbox/dcim/tables/devices.py:355 msgid "Cable Color" msgstr "" -#: netbox/dcim/tables/devices.py:356 +#: netbox/dcim/tables/devices.py:361 msgid "Link Peers" msgstr "" -#: netbox/dcim/tables/devices.py:359 +#: netbox/dcim/tables/devices.py:364 msgid "Mark Connected" msgstr "" -#: netbox/dcim/tables/devices.py:478 +#: netbox/dcim/tables/devices.py:483 msgid "Maximum draw (W)" msgstr "" -#: netbox/dcim/tables/devices.py:481 +#: netbox/dcim/tables/devices.py:486 msgid "Allocated draw (W)" msgstr "" -#: netbox/dcim/tables/devices.py:586 netbox/ipam/forms/model_forms.py:794 +#: netbox/dcim/tables/devices.py:591 netbox/ipam/forms/model_forms.py:794 #: netbox/ipam/tables/fhrp.py:28 netbox/ipam/views.py:683 #: netbox/ipam/views.py:784 netbox/netbox/navigation/menu.py:165 #: netbox/netbox/navigation/menu.py:167 @@ -7586,12 +7590,12 @@ msgstr "" msgid "IP Addresses" msgstr "" -#: netbox/dcim/tables/devices.py:592 netbox/netbox/navigation/menu.py:211 +#: netbox/dcim/tables/devices.py:597 netbox/netbox/navigation/menu.py:211 #: netbox/templates/ipam/inc/panels/fhrp_groups.html:6 msgid "FHRP Groups" msgstr "" -#: netbox/dcim/tables/devices.py:604 netbox/templates/dcim/interface.html:95 +#: netbox/dcim/tables/devices.py:609 netbox/templates/dcim/interface.html:95 #: netbox/templates/virtualization/vminterface.html:65 #: netbox/templates/vpn/tunnel.html:18 #: netbox/templates/vpn/tunneltermination.html:13 @@ -7602,41 +7606,41 @@ msgstr "" msgid "Tunnel" msgstr "" -#: netbox/dcim/tables/devices.py:640 netbox/dcim/tables/devicetypes.py:234 +#: netbox/dcim/tables/devices.py:645 netbox/dcim/tables/devicetypes.py:234 #: netbox/templates/dcim/interface.html:65 msgid "Management Only" msgstr "" -#: netbox/dcim/tables/devices.py:659 +#: netbox/dcim/tables/devices.py:664 msgid "VDCs" msgstr "" -#: netbox/dcim/tables/devices.py:666 netbox/templates/dcim/interface.html:163 +#: netbox/dcim/tables/devices.py:671 netbox/templates/dcim/interface.html:163 msgid "Virtual Circuit" msgstr "" -#: netbox/dcim/tables/devices.py:918 netbox/templates/dcim/modulebay.html:53 +#: netbox/dcim/tables/devices.py:923 netbox/templates/dcim/modulebay.html:53 msgid "Installed Module" msgstr "" -#: netbox/dcim/tables/devices.py:921 +#: netbox/dcim/tables/devices.py:926 msgid "Module Serial" msgstr "" -#: netbox/dcim/tables/devices.py:925 +#: netbox/dcim/tables/devices.py:930 msgid "Module Asset Tag" msgstr "" -#: netbox/dcim/tables/devices.py:934 +#: netbox/dcim/tables/devices.py:939 msgid "Module Status" msgstr "" -#: netbox/dcim/tables/devices.py:988 netbox/dcim/tables/devicetypes.py:319 +#: netbox/dcim/tables/devices.py:993 netbox/dcim/tables/devicetypes.py:319 #: netbox/templates/dcim/inventoryitem.html:44 msgid "Component" msgstr "" -#: netbox/dcim/tables/devices.py:1046 +#: netbox/dcim/tables/devices.py:1051 msgid "Items" msgstr "" @@ -7671,10 +7675,6 @@ msgstr "" msgid "Full Depth" msgstr "" -#: netbox/dcim/tables/devicetypes.py:103 -msgid "U Height" -msgstr "" - #: netbox/dcim/tables/devicetypes.py:118 netbox/dcim/tables/modules.py:65 #: netbox/dcim/tables/racks.py:93 msgid "Instances" From 873372f61eb04e6148ce431285962c67cfce1b42 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 9 Sep 2025 12:56:08 -0400 Subject: [PATCH 13/40] Closes #20241: Record A & B terminations on cable changelog records (#20246) --- netbox/dcim/models/cables.py | 165 +++++++++++++++++++++--------- netbox/utilities/testing/api.py | 8 +- netbox/utilities/testing/views.py | 4 +- 3 files changed, 122 insertions(+), 55 deletions(-) diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py index 69e07ed9432..89c9a99b419 100644 --- a/netbox/dcim/models/cables.py +++ b/netbox/dcim/models/cables.py @@ -18,6 +18,7 @@ from utilities.conversion import to_meters from utilities.exceptions import AbortRequest from utilities.fields import ColorField, GenericArrayForeignKey from utilities.querysets import RestrictedQuerySet +from utilities.serialization import deserialize_object, serialize_object from wireless.models import WirelessLink from .device_components import FrontPort, RearPort, PathEndpoint @@ -119,43 +120,61 @@ class Cable(PrimaryModel): pk = self.pk or self._pk return self.label or f'#{pk}' - @property - def a_terminations(self): - if hasattr(self, '_a_terminations'): - return self._a_terminations + def get_status_color(self): + return LinkStatusChoices.colors.get(self.status) + def _get_x_terminations(self, side): + """ + Return the terminating objects for the given cable end (A or B). + """ + if side not in (CableEndChoices.SIDE_A, CableEndChoices.SIDE_B): + raise ValueError(f"Unknown cable side: {side}") + attr = f'_{side.lower()}_terminations' + + if hasattr(self, attr): + return getattr(self, attr) if not self.pk: return [] - - # Query self.terminations.all() to leverage cached results return [ - ct.termination for ct in self.terminations.all() if ct.cable_end == CableEndChoices.SIDE_A + # Query self.terminations.all() to leverage cached results + ct.termination for ct in self.terminations.all() if ct.cable_end == side ] + def _set_x_terminations(self, side, value): + """ + Set the terminating objects for the given cable end (A or B). + """ + if side not in (CableEndChoices.SIDE_A, CableEndChoices.SIDE_B): + raise ValueError(f"Unknown cable side: {side}") + _attr = f'_{side.lower()}_terminations' + + # If the provided value is a list of CableTermination IDs, resolve them + # to their corresponding termination objects. + if all(isinstance(item, int) for item in value): + value = [ + ct.termination for ct in CableTermination.objects.filter(pk__in=value).prefetch_related('termination') + ] + + if not self.pk or getattr(self, _attr, []) != list(value): + self._terminations_modified = True + + setattr(self, _attr, value) + + @property + def a_terminations(self): + return self._get_x_terminations(CableEndChoices.SIDE_A) + @a_terminations.setter def a_terminations(self, value): - if not self.pk or self.a_terminations != list(value): - self._terminations_modified = True - self._a_terminations = value + self._set_x_terminations(CableEndChoices.SIDE_A, value) @property def b_terminations(self): - if hasattr(self, '_b_terminations'): - return self._b_terminations - - if not self.pk: - return [] - - # Query self.terminations.all() to leverage cached results - return [ - ct.termination for ct in self.terminations.all() if ct.cable_end == CableEndChoices.SIDE_B - ] + return self._get_x_terminations(CableEndChoices.SIDE_B) @b_terminations.setter def b_terminations(self, value): - if not self.pk or self.b_terminations != list(value): - self._terminations_modified = True - self._b_terminations = value + self._set_x_terminations(CableEndChoices.SIDE_B, value) @property def color_name(self): @@ -208,7 +227,7 @@ class Cable(PrimaryModel): for termination in self.b_terminations: CableTermination(cable=self, cable_end='B', termination=termination).clean() - def save(self, *args, **kwargs): + def save(self, *args, force_insert=False, force_update=False, using=None, update_fields=None): _created = self.pk is None # Store the given length (if any) in meters for use in database ordering @@ -221,39 +240,87 @@ class Cable(PrimaryModel): if self.length is None: self.length_unit = None - super().save(*args, **kwargs) + # If this is a new Cable, save it before attempting to create its CableTerminations + if self._state.adding: + super().save(*args, force_insert=True, using=using, update_fields=update_fields) + # Update the private PK used in __str__() + self._pk = self.pk - # Update the private pk used in __str__ in case this is a new object (i.e. just got its pk) - self._pk = self.pk - - # Retrieve existing A/B terminations for the Cable - a_terminations = {ct.termination: ct for ct in self.terminations.filter(cable_end='A')} - b_terminations = {ct.termination: ct for ct in self.terminations.filter(cable_end='B')} - - # Delete stale CableTerminations if self._terminations_modified: - for termination, ct in a_terminations.items(): - if termination.pk and termination not in self.a_terminations: - ct.delete() - for termination, ct in b_terminations.items(): - if termination.pk and termination not in self.b_terminations: - ct.delete() + self.update_terminations() + + super().save(*args, force_update=True, using=using, update_fields=update_fields) - # Save new CableTerminations (if any) - if self._terminations_modified: - for termination in self.a_terminations: - if not termination.pk or termination not in a_terminations: - CableTermination(cable=self, cable_end='A', termination=termination).save() - for termination in self.b_terminations: - if not termination.pk or termination not in b_terminations: - CableTermination(cable=self, cable_end='B', termination=termination).save() try: trace_paths.send(Cable, instance=self, created=_created) except UnsupportedCablePath as e: raise AbortRequest(e) - def get_status_color(self): - return LinkStatusChoices.colors.get(self.status) + def serialize_object(self, exclude=None): + data = serialize_object(self, exclude=exclude or []) + + # Add A & B terminations to the serialized data + a_terminations, b_terminations = self.get_terminations() + data['a_terminations'] = sorted([ct.pk for ct in a_terminations.values()]) + data['b_terminations'] = sorted([ct.pk for ct in b_terminations.values()]) + + return data + + @classmethod + def deserialize_object(cls, data, pk=None): + a_terminations = data.pop('a_terminations', []) + b_terminations = data.pop('b_terminations', []) + + instance = deserialize_object(cls, data, pk=pk) + + # Assign A & B termination objects to the Cable instance + queryset = CableTermination.objects.prefetch_related('termination') + instance.a_terminations = [ + ct.termination for ct in queryset.filter(pk__in=a_terminations) + ] + instance.b_terminations = [ + ct.termination for ct in queryset.filter(pk__in=b_terminations) + ] + + return instance + + def get_terminations(self): + """ + Return two dictionaries mapping A & B side terminating objects to their corresponding CableTerminations + for this Cable. + """ + a_terminations = {} + b_terminations = {} + + for ct in CableTermination.objects.filter(cable=self).prefetch_related('termination'): + if ct.cable_end == CableEndChoices.SIDE_A: + a_terminations[ct.termination] = ct + else: + b_terminations[ct.termination] = ct + + return a_terminations, b_terminations + + def update_terminations(self): + """ + Create/delete CableTerminations for this Cable to reflect its current state. + """ + a_terminations, b_terminations = self.get_terminations() + + # Delete any stale CableTerminations + for termination, ct in a_terminations.items(): + if termination.pk and termination not in self.a_terminations: + ct.delete() + for termination, ct in b_terminations.items(): + if termination.pk and termination not in self.b_terminations: + ct.delete() + + # Save any new CableTerminations + for termination in self.a_terminations: + if not termination.pk or termination not in a_terminations: + CableTermination(cable=self, cable_end='A', termination=termination).save() + for termination in self.b_terminations: + if not termination.pk or termination not in b_terminations: + CableTermination(cable=self, cable_end='B', termination=termination).save() class CableTermination(ChangeLoggedModel): diff --git a/netbox/utilities/testing/api.py b/netbox/utilities/testing/api.py index 8df8f44389a..1fe8813679e 100644 --- a/netbox/utilities/testing/api.py +++ b/netbox/utilities/testing/api.py @@ -247,9 +247,9 @@ class APIViewTestCases: if issubclass(self.model, ChangeLoggingMixin): objectchange = ObjectChange.objects.get( changed_object_type=ContentType.objects.get_for_model(instance), - changed_object_id=instance.pk + changed_object_id=instance.pk, + action=ObjectChangeActionChoices.ACTION_CREATE, ) - self.assertEqual(objectchange.action, ObjectChangeActionChoices.ACTION_CREATE) self.assertEqual(objectchange.message, data['changelog_message']) def test_bulk_create_objects(self): @@ -298,11 +298,11 @@ class APIViewTestCases: ] objectchanges = ObjectChange.objects.filter( changed_object_type=ContentType.objects.get_for_model(self.model), - changed_object_id__in=id_list + changed_object_id__in=id_list, + action=ObjectChangeActionChoices.ACTION_CREATE, ) self.assertEqual(len(objectchanges), len(self.create_data)) for oc in objectchanges: - self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_CREATE) self.assertEqual(oc.message, changelog_message) class UpdateObjectViewTestCase(APITestCase): diff --git a/netbox/utilities/testing/views.py b/netbox/utilities/testing/views.py index da8a870984f..99a6dd43aea 100644 --- a/netbox/utilities/testing/views.py +++ b/netbox/utilities/testing/views.py @@ -655,11 +655,11 @@ class ViewTestCases: self.assertIsNotNone(request_id, "Unable to determine request ID from response") objectchanges = ObjectChange.objects.filter( changed_object_type=ContentType.objects.get_for_model(self.model), - request_id=request_id + request_id=request_id, + action=ObjectChangeActionChoices.ACTION_CREATE, ) self.assertEqual(len(objectchanges), len(self.csv_data) - 1) for oc in objectchanges: - self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_CREATE) self.assertEqual(oc.message, data['changelog_message']) @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) From 1034f738af5eb034806d83f435908463973027b3 Mon Sep 17 00:00:00 2001 From: Aaron <56231533+aq5747@users.noreply.github.com> Date: Tue, 9 Sep 2025 15:33:11 -0400 Subject: [PATCH 14/40] Fixes #20217: Fix '0 VLANs available' in the VLANs table in VLAN Groups (#20261) * Fixes #20217: hide 0 VLANs available message in VLAN groups * Simplified fix to improve readability --- netbox/ipam/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/ipam/utils.py b/netbox/ipam/utils.py index 8fe40592e30..790ac6503d0 100644 --- a/netbox/ipam/utils.py +++ b/netbox/ipam/utils.py @@ -164,7 +164,7 @@ def available_vlans_from_range(vlans, vlan_group, vid_range): prev_vid = vlan.vid # Annotate any remaining available VLANs - if prev_vid < max_vid: + if prev_vid < max_vid - 1: new_vlans.append({ 'vid': prev_vid + 1, 'vlan_group': vlan_group, From 0e627d4d9b0da028134f86a6d9f71dbf3e5ade8e Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 05:02:17 +0000 Subject: [PATCH 15/40] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 66 ++++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index 1da1c788a50..083399192d6 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: 2025-09-09 05:02+0000\n" +"POT-Creation-Date: 2025-09-10 05:02+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -1434,7 +1434,7 @@ msgstr "" msgid "Group Assignment" msgstr "" -#: netbox/circuits/models/base.py:18 netbox/dcim/models/cables.py:69 +#: netbox/circuits/models/base.py:18 netbox/dcim/models/cables.py:70 #: netbox/dcim/models/device_component_templates.py:531 #: netbox/dcim/models/device_component_templates.py:631 #: netbox/dcim/models/device_components.py:516 @@ -1466,7 +1466,7 @@ msgstr "" #: netbox/circuits/models/circuits.py:66 #: netbox/circuits/models/virtual_circuits.py:59 netbox/core/models/data.py:52 -#: netbox/core/models/jobs.py:95 netbox/dcim/models/cables.py:51 +#: netbox/core/models/jobs.py:95 netbox/dcim/models/cables.py:52 #: netbox/dcim/models/device_components.py:487 #: netbox/dcim/models/device_components.py:1328 #: netbox/dcim/models/devices.py:580 netbox/dcim/models/devices.py:1188 @@ -2585,7 +2585,7 @@ msgstr "" msgid "Config revision #{id}" msgstr "" -#: netbox/core/models/data.py:44 netbox/dcim/models/cables.py:44 +#: netbox/core/models/data.py:44 netbox/dcim/models/cables.py:45 #: netbox/dcim/models/device_component_templates.py:199 #: netbox/dcim/models/device_component_templates.py:234 #: netbox/dcim/models/device_component_templates.py:270 @@ -2669,7 +2669,7 @@ msgstr "" msgid "last updated" msgstr "" -#: netbox/core/models/data.py:283 netbox/dcim/models/cables.py:451 +#: netbox/core/models/data.py:283 netbox/dcim/models/cables.py:518 msgid "path" msgstr "" @@ -5836,124 +5836,124 @@ msgstr "" msgid "A position must be specified for the first VC member." msgstr "" -#: netbox/dcim/models/cables.py:64 +#: netbox/dcim/models/cables.py:65 #: netbox/dcim/models/device_component_templates.py:51 #: netbox/dcim/models/device_components.py:57 #: netbox/extras/models/customfields.py:113 msgid "label" msgstr "" -#: netbox/dcim/models/cables.py:73 +#: netbox/dcim/models/cables.py:74 msgid "length" msgstr "" -#: netbox/dcim/models/cables.py:80 +#: netbox/dcim/models/cables.py:81 msgid "length unit" msgstr "" -#: netbox/dcim/models/cables.py:98 +#: netbox/dcim/models/cables.py:99 msgid "cable" msgstr "" -#: netbox/dcim/models/cables.py:99 +#: netbox/dcim/models/cables.py:100 msgid "cables" msgstr "" -#: netbox/dcim/models/cables.py:174 +#: netbox/dcim/models/cables.py:193 msgid "Must specify a unit when setting a cable length" msgstr "" -#: netbox/dcim/models/cables.py:177 +#: netbox/dcim/models/cables.py:196 msgid "Must define A and B terminations when creating a new cable." msgstr "" -#: netbox/dcim/models/cables.py:184 +#: netbox/dcim/models/cables.py:203 msgid "Cannot connect different termination types to same end of cable." msgstr "" -#: netbox/dcim/models/cables.py:192 +#: netbox/dcim/models/cables.py:211 #, python-brace-format msgid "Incompatible termination types: {type_a} and {type_b}" msgstr "" -#: netbox/dcim/models/cables.py:202 +#: netbox/dcim/models/cables.py:221 msgid "A and B terminations cannot connect to the same object." msgstr "" -#: netbox/dcim/models/cables.py:271 netbox/ipam/models/asns.py:38 +#: netbox/dcim/models/cables.py:338 netbox/ipam/models/asns.py:38 msgid "end" msgstr "" -#: netbox/dcim/models/cables.py:320 +#: netbox/dcim/models/cables.py:387 msgid "cable termination" msgstr "" -#: netbox/dcim/models/cables.py:321 +#: netbox/dcim/models/cables.py:388 msgid "cable terminations" msgstr "" -#: netbox/dcim/models/cables.py:340 +#: netbox/dcim/models/cables.py:407 #, python-brace-format msgid "" "Duplicate termination found for {app_label}.{model} {termination_id}: cable " "{cable_pk}" msgstr "" -#: netbox/dcim/models/cables.py:350 +#: netbox/dcim/models/cables.py:417 #, python-brace-format msgid "Cables cannot be terminated to {type_display} interfaces" msgstr "" -#: netbox/dcim/models/cables.py:357 +#: netbox/dcim/models/cables.py:424 msgid "Circuit terminations attached to a provider network may not be cabled." msgstr "" -#: netbox/dcim/models/cables.py:455 netbox/extras/models/configs.py:99 +#: netbox/dcim/models/cables.py:522 netbox/extras/models/configs.py:99 msgid "is active" msgstr "" -#: netbox/dcim/models/cables.py:459 +#: netbox/dcim/models/cables.py:526 msgid "is complete" msgstr "" -#: netbox/dcim/models/cables.py:463 +#: netbox/dcim/models/cables.py:530 msgid "is split" msgstr "" -#: netbox/dcim/models/cables.py:471 +#: netbox/dcim/models/cables.py:538 msgid "cable path" msgstr "" -#: netbox/dcim/models/cables.py:472 +#: netbox/dcim/models/cables.py:539 msgid "cable paths" msgstr "" -#: netbox/dcim/models/cables.py:547 +#: netbox/dcim/models/cables.py:614 msgid "All originating terminations must be attached to the same link" msgstr "" -#: netbox/dcim/models/cables.py:559 +#: netbox/dcim/models/cables.py:626 msgid "All mid-span terminations must have the same termination type" msgstr "" -#: netbox/dcim/models/cables.py:564 +#: netbox/dcim/models/cables.py:631 msgid "All mid-span terminations must have the same parent object" msgstr "" -#: netbox/dcim/models/cables.py:588 +#: netbox/dcim/models/cables.py:655 msgid "All links must be cable or wireless" msgstr "" -#: netbox/dcim/models/cables.py:590 +#: netbox/dcim/models/cables.py:657 msgid "All links must match first link type" msgstr "" -#: netbox/dcim/models/cables.py:673 +#: netbox/dcim/models/cables.py:740 msgid "" "All positions counts within the path on opposite ends of links must match" msgstr "" -#: netbox/dcim/models/cables.py:682 +#: netbox/dcim/models/cables.py:749 msgid "Remote termination position filter is missing" msgstr "" From a99e21afd665ebba88a67b41ad8047dcc1c04b7f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 10 Sep 2025 12:33:36 -0400 Subject: [PATCH 16/40] Fixes #20290: Fix ordering of migrations to support upgrading from v3.7 --- netbox/users/migrations/0005_alter_user_table.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netbox/users/migrations/0005_alter_user_table.py b/netbox/users/migrations/0005_alter_user_table.py index 173a7ab4dc8..d87a8abe905 100644 --- a/netbox/users/migrations/0005_alter_user_table.py +++ b/netbox/users/migrations/0005_alter_user_table.py @@ -24,8 +24,9 @@ def update_content_types(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ + ('core', '0018_concrete_objecttype'), + ('extras', '0117_move_objectchange'), ('users', '0002_squashed_0004'), - ('extras', '0113_customfield_rename_object_type'), ] operations = [ From bf7356473c8dd8eb08c202e3f5f78cb42c686484 Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Wed, 10 Sep 2025 19:00:22 +0200 Subject: [PATCH 17/40] fix(extras): Inherit ConfigContext from ancestors locations (#20291) --- netbox/extras/querysets.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/netbox/extras/querysets.py b/netbox/extras/querysets.py index 8d6628a834f..cb7c1b0aa37 100644 --- a/netbox/extras/querysets.py +++ b/netbox/extras/querysets.py @@ -22,9 +22,10 @@ class ConfigContextQuerySet(RestrictedQuerySet): aggregate_data: If True, use the JSONBAgg aggregate function to return only the list of JSON data objects """ - # Device type and location assignment is relevant only for Devices + # Device type and location assignment are relevant only for Devices device_type = getattr(obj, 'device_type', None) location = getattr(obj, 'location', None) + locations = location.get_ancestors(include_self=True) if location else [] # Get assigned cluster, group, and type (if any) cluster = getattr(obj, 'cluster', None) @@ -49,7 +50,7 @@ class ConfigContextQuerySet(RestrictedQuerySet): Q(regions__in=regions) | Q(regions=None), Q(site_groups__in=sitegroups) | Q(site_groups=None), Q(sites=obj.site) | Q(sites=None), - Q(locations=location) | Q(locations=None), + Q(locations__in=locations) | Q(locations=None), Q(device_types=device_type) | Q(device_types=None), Q(roles__in=device_roles) | Q(roles=None), Q(platforms=obj.platform) | Q(platforms=None), @@ -124,7 +125,15 @@ class ConfigContextModelQuerySet(RestrictedQuerySet): # Apply Location & DeviceType filters only for VirtualMachines if self.model._meta.model_name == 'device': - base_query.add((Q(locations=OuterRef('location')) | Q(locations=None)), Q.AND) + base_query.add( + (Q( + locations__tree_id=OuterRef('location__tree_id'), + locations__level__lte=OuterRef('location__level'), + locations__lft__lte=OuterRef('location__lft'), + locations__rght__gte=OuterRef('location__rght'), + ) | Q(locations=None)), + Q.AND + ) base_query.add((Q(device_types=OuterRef('device_type')) | Q(device_types=None)), Q.AND) elif self.model._meta.model_name == 'virtualmachine': base_query.add(Q(locations=None), Q.AND) From 6f01da90b438bae9b9b809ac1b870d548d0aef65 Mon Sep 17 00:00:00 2001 From: Jason Novinger Date: Thu, 11 Sep 2025 08:48:14 -0600 Subject: [PATCH 18/40] Closes #20206: Clarifies django-storages configuration from env vars --- docs/configuration/system.md | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/docs/configuration/system.md b/docs/configuration/system.md index 89e7d8d8eb5..8c5bc6d171b 100644 --- a/docs/configuration/system.md +++ b/docs/configuration/system.md @@ -257,6 +257,46 @@ The specific configuration settings for each storage backend can be found in the !!! note Any keys defined in the `STORAGES` configuration parameter replace those in the default configuration. It is only necessary to define keys within the `STORAGES` for the specific backend(s) you wish to configure. +### Environment Variables and Third-Party Libraries + +NetBox uses an explicit Python configuration approach rather than automatic environment variable detection. While this provides clear configuration management and version control capabilities, it affects how some third-party libraries like `django-storages` function within NetBox's context. + +Many Django libraries (including `django-storages`) expect to automatically detect environment variables like `AWS_STORAGE_BUCKET_NAME` or `AWS_S3_ACCESS_KEY_ID`. However, NetBox's configuration processing prevents this automatic detection from working as documented in some of these libraries. + +When using third-party libraries that rely on environment variable detection, you may need to explicitly read environment variables in your NetBox `configuration.py`: + +```python +import os + +STORAGES = { + 'default': { + 'BACKEND': 'storages.backends.s3.S3Storage', + 'OPTIONS': { + 'bucket_name': os.environ.get('AWS_STORAGE_BUCKET_NAME'), + 'access_key': os.environ.get('AWS_S3_ACCESS_KEY_ID'), + 'secret_key': os.environ.get('AWS_S3_SECRET_ACCESS_KEY'), + 'endpoint_url': os.environ.get('AWS_S3_ENDPOINT_URL'), + 'location': 'media/', + } + }, + 'staticfiles': { + 'BACKEND': 'storages.backends.s3.S3Storage', + 'OPTIONS': { + 'bucket_name': os.environ.get('AWS_STORAGE_BUCKET_NAME'), + 'access_key': os.environ.get('AWS_S3_ACCESS_KEY_ID'), + 'secret_key': os.environ.get('AWS_S3_SECRET_ACCESS_KEY'), + 'endpoint_url': os.environ.get('AWS_S3_ENDPOINT_URL'), + 'location': 'static/', + } + }, +} +``` + +This approach works because the environment variables are resolved during NetBox's configuration processing, before the third-party library attempts its own environment variable detection. + +!!! warning "Common Gotcha" + Simply setting environment variables like `AWS_STORAGE_BUCKET_NAME` without explicitly reading them in your configuration will not work. The variables must be read using `os.environ.get()` within your `configuration.py` file. + --- ## TIME_ZONE From c57d9f9a372a9ba00d683d6ed0c3d0ba469fc713 Mon Sep 17 00:00:00 2001 From: Jason Novinger Date: Thu, 11 Sep 2025 08:51:50 -0600 Subject: [PATCH 19/40] Fix 'dim' type --> 'dcim' --- docs/configuration/data-validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/data-validation.md b/docs/configuration/data-validation.md index 1b8263de355..9988f6e0b65 100644 --- a/docs/configuration/data-validation.md +++ b/docs/configuration/data-validation.md @@ -17,7 +17,7 @@ CUSTOM_VALIDATORS = { }, "my_plugin.validators.Validator1" ], - "dim.device": [ + "dcim.device": [ "my_plugin.validators.Validator1" ] } From be4db9a899d8a460c444d306a55b0f13bc821186 Mon Sep 17 00:00:00 2001 From: Elliott Balsley <3991046+llamafilm@users.noreply.github.com> Date: Thu, 11 Sep 2025 08:43:26 -0700 Subject: [PATCH 20/40] format script results timestamp (#20307) --- netbox/extras/tables/tables.py | 5 +++-- netbox/extras/views.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/netbox/extras/tables/tables.py b/netbox/extras/tables/tables.py index 5c1a63d265e..e89d06c4041 100644 --- a/netbox/extras/tables/tables.py +++ b/netbox/extras/tables/tables.py @@ -725,8 +725,9 @@ class ScriptResultsTable(BaseTable): index = tables.Column( verbose_name=_('Line') ) - time = tables.Column( - verbose_name=_('Time') + time = columns.DateTimeColumn( + verbose_name=_('Time'), + timespec='seconds' ) status = tables.TemplateColumn( template_code="""{% load log_levels %}{% log_level record.status %}""", diff --git a/netbox/extras/views.py b/netbox/extras/views.py index c76afbd15fb..32d19674b86 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -1,3 +1,4 @@ +from datetime import datetime from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.contenttypes.models import ContentType @@ -1547,7 +1548,6 @@ class ScriptResultView(TableMixin, generic.ObjectView): except KeyError: log_threshold = LOG_LEVEL_RANK[LogLevelChoices.LOG_INFO] if job.data: - if 'log' in job.data: if 'tests' in job.data: tests = job.data['tests'] @@ -1558,7 +1558,7 @@ class ScriptResultView(TableMixin, generic.ObjectView): index += 1 result = { 'index': index, - 'time': log.get('time'), + 'time': datetime.fromisoformat(log.get('time')), 'status': log.get('status'), 'message': log.get('message'), 'object': log.get('obj'), From d1e40281f33a4280b006158fb055cbdd01271b3d Mon Sep 17 00:00:00 2001 From: Jason Novinger Date: Thu, 11 Sep 2025 15:46:04 +0000 Subject: [PATCH 21/40] Fixes #20242: Conditionally log request.id in EventRule triggered script (#20322) --- netbox/extras/jobs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/extras/jobs.py b/netbox/extras/jobs.py index 8a039c7c8a5..c270833b1e5 100644 --- a/netbox/extras/jobs.py +++ b/netbox/extras/jobs.py @@ -106,7 +106,7 @@ class ScriptJob(JobRunner): # Add the current request as a property of the script script.request = request - self.logger.debug(f"Request ID: {request.id}") + self.logger.debug(f"Request ID: {request.id if request else None}") # Execute the script. If commit is True, wrap it with the event_tracking context manager to ensure we process # change logging, event rules, etc. From cd122a7dde242b3710be1478cacaa7b855e87256 Mon Sep 17 00:00:00 2001 From: Jason Novinger Date: Thu, 11 Sep 2025 10:00:22 -0600 Subject: [PATCH 22/40] Address PR feedback --- docs/configuration/system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/system.md b/docs/configuration/system.md index 8c5bc6d171b..8a73f629f7a 100644 --- a/docs/configuration/system.md +++ b/docs/configuration/system.md @@ -294,7 +294,7 @@ STORAGES = { This approach works because the environment variables are resolved during NetBox's configuration processing, before the third-party library attempts its own environment variable detection. -!!! warning "Common Gotcha" +!!! warning "Configuration Behavior" Simply setting environment variables like `AWS_STORAGE_BUCKET_NAME` without explicitly reading them in your configuration will not work. The variables must be read using `os.environ.get()` within your `configuration.py` file. --- From 53d1b1aa50c37249c2d3ab80c5b1b918b5922bea Mon Sep 17 00:00:00 2001 From: Jason Novinger Date: Thu, 11 Sep 2025 16:47:23 +0000 Subject: [PATCH 23/40] Closes #19944: Add multi-scenario CSV import testing support with cleanup (#20302) * Closes #19944: Add multi-scenario CSV import testing support with cleanup Enhanced BulkImportObjectsViewTestCase to support multiple CSV import scenarios via dictionary format, where each scenario runs as a separate subtest with automatic cleanup. This enables testing different import configurations (e.g., with/without optional fields) in a single test run with clear output showing which scenario is being tested. Introduces cleanupSubTest() context manager that uses database savepoints to automatically roll back changes between subtests, providing test isolation similar to separate test methods. This allows subtests to create/modify objects without affecting subsequent subtests in the same test method. Added post_import_callback parameter to bulk import tests, allowing child classes to inject custom assertions that run before database cleanup. This solves the inheritance problem where child classes need to verify imported data but the parent's cleanup would roll back the data before assertions could run. The callback approach is cleaner than conditional cleanup parameters - it makes the execution timing explicit and maintains test isolation while still allowing extensibility. * Fixup ModuleTypeTestCase bulk import test to work with callback mechamisn * Update CableTestCase to use expanded CSV scenario testing * Remove unneeded permission cleanup Co-authored-by: Jeremy Stretch * Consolidate scenario name retrieval into method --------- Co-authored-by: Jeremy Stretch --- netbox/dcim/tests/test_views.py | 67 ++++++++++---- netbox/utilities/testing/base.py | 16 ++++ netbox/utilities/testing/views.py | 145 ++++++++++++++++++++++-------- 3 files changed, 173 insertions(+), 55 deletions(-) diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index b23f7e16d79..2e6d5ebc58c 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -1078,14 +1078,14 @@ class ModuleTypeTestCase(ViewTestCases.PrimaryObjectViewTestCase): 'dcim.add_modulebaytemplate', ) + def verify_module_type_profile(scenario_name): + # TODO: remove extra regression asserts once parent test supports testing all import fields + fan_module_type = ModuleType.objects.get(part_number='generic-fan') + fan_module_type_profile = ModuleTypeProfile.objects.get(name='Fan') + assert fan_module_type.profile == fan_module_type_profile + # run base test - super().test_bulk_import_objects_with_permission() - - # TODO: remove extra regression asserts once parent test supports testing all import fields - fan_module_type = ModuleType.objects.get(part_number='generic-fan') - fan_module_type_profile = ModuleTypeProfile.objects.get(name='Fan') - - assert fan_module_type.profile == fan_module_type_profile + super().test_bulk_import_objects_with_permission(post_import_callback=verify_module_type_profile) @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[]) def test_bulk_import_objects_with_constrained_permission(self): @@ -3290,8 +3290,10 @@ class CableTestCase( Device(name='Device 1', site=sites[0], device_type=devicetype, role=role), Device(name='Device 2', site=sites[0], device_type=devicetype, role=role), Device(name='Device 3', site=sites[0], device_type=devicetype, role=role), + Device(name='Device 4', site=sites[0], device_type=devicetype, role=role), # Create 'Device 1' assigned to 'Site 2' (allowed since the site is different) Device(name='Device 1', site=sites[1], device_type=devicetype, role=role), + Device(name='Device 5', site=sites[1], device_type=devicetype, role=role), ) Device.objects.bulk_create(devices) @@ -3300,22 +3302,36 @@ class CableTestCase( vc.save() interfaces = ( + # Device 1, Site 1 Interface(device=devices[0], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), Interface(device=devices[0], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), Interface(device=devices[0], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + # Device 2, Site 1 Interface(device=devices[1], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), Interface(device=devices[1], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), Interface(device=devices[1], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + # Device 3, Site 1 Interface(device=devices[2], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), Interface(device=devices[2], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), Interface(device=devices[2], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + # Device 3, Site 1 Interface(device=devices[3], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), Interface(device=devices[3], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), Interface(device=devices[3], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + # Device 1, Site 2 + Interface(device=devices[4], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[4], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[4], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + + # Device 1, Site 2 + Interface(device=devices[5], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[5], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[5], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[1], name='Device 2 Interface', type=InterfaceTypeChoices.TYPE_1GE_FIXED), Interface(device=devices[2], name='Device 3 Interface', type=InterfaceTypeChoices.TYPE_1GE_FIXED), - Interface(device=devices[3], name='Interface 4', type=InterfaceTypeChoices.TYPE_1GE_FIXED), - Interface(device=devices[3], name='Interface 5', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[4], name='Interface 4', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[4], name='Interface 5', type=InterfaceTypeChoices.TYPE_1GE_FIXED), ) Interface.objects.bulk_create(interfaces) @@ -3342,16 +3358,29 @@ class CableTestCase( 'tags': [t.pk for t in tags], } - # Ensure that CSV bulk import supports assigning terminations from parent devices that share - # the same device name, provided those devices belong to different sites. - cls.csv_data = ( - "side_a_site,side_a_device,side_a_type,side_a_name,side_b_site,side_b_device,side_b_type,side_b_name", - "Site 1,Device 3,dcim.interface,Interface 1,Site 2,Device 1,dcim.interface,Interface 1", - "Site 1,Device 3,dcim.interface,Interface 2,Site 2,Device 1,dcim.interface,Interface 2", - "Site 1,Device 3,dcim.interface,Interface 3,Site 2,Device 1,dcim.interface,Interface 3", - "Site 1,Device 1,dcim.interface,Device 2 Interface,Site 2,Device 1,dcim.interface,Interface 4", - "Site 1,Device 1,dcim.interface,Device 3 Interface,Site 2,Device 1,dcim.interface,Interface 5", - ) + cls.csv_data = { + 'default': ( + "side_a_device,side_a_type,side_a_name,side_b_device,side_b_type,side_b_name", + "Device 4,dcim.interface,Interface 1,Device 5,dcim.interface,Interface 1", + "Device 3,dcim.interface,Interface 2,Device 4,dcim.interface,Interface 2", + "Device 3,dcim.interface,Interface 3,Device 4,dcim.interface,Interface 3", + + # The following is no longer possible in this scenario, because there are multiple + # devices named "Device 1" across multiple sites. See the "site-filtering" scenario + # below for how to specify a site for non-unique device names. + # "Device 1,dcim.interface,Device 3 Interface,Device 4,dcim.interface,Interface 5", + ), + 'site-filtering': ( + # Ensure that CSV bulk import supports assigning terminations from parent devices + # that share the same device name, provided those devices belong to different sites. + "side_a_site,side_a_device,side_a_type,side_a_name,side_b_site,side_b_device,side_b_type,side_b_name", + "Site 1,Device 3,dcim.interface,Interface 1,Site 2,Device 1,dcim.interface,Interface 1", + "Site 1,Device 3,dcim.interface,Interface 2,Site 2,Device 1,dcim.interface,Interface 2", + "Site 1,Device 3,dcim.interface,Interface 3,Site 2,Device 1,dcim.interface,Interface 3", + "Site 1,Device 1,dcim.interface,Device 2 Interface,Site 2,Device 1,dcim.interface,Interface 4", + "Site 1,Device 1,dcim.interface,Device 3 Interface,Site 2,Device 1,dcim.interface,Interface 5", + ) + } cls.csv_update_data = ( "id,label,color", diff --git a/netbox/utilities/testing/base.py b/netbox/utilities/testing/base.py index 6d17fa1ecd7..1a0c3f46ba2 100644 --- a/netbox/utilities/testing/base.py +++ b/netbox/utilities/testing/base.py @@ -1,9 +1,11 @@ import json +from contextlib import contextmanager from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.fields import ArrayField, RangeField from django.core.exceptions import FieldDoesNotExist +from django.db import transaction from django.db.models import ManyToManyField, ManyToManyRel, JSONField from django.forms.models import model_to_dict from django.test import Client, TestCase as _TestCase @@ -36,6 +38,20 @@ class TestCase(_TestCase): self.client = Client() self.client.force_login(self.user) + @contextmanager + def cleanupSubTest(self, **params): + """ + Context manager that wraps subTest with automatic cleanup. + All database changes within the context will be rolled back. + """ + sid = transaction.savepoint() + + try: + with self.subTest(**params): + yield + finally: + transaction.savepoint_rollback(sid) + # # Permissions management # diff --git a/netbox/utilities/testing/views.py b/netbox/utilities/testing/views.py index 99a6dd43aea..f00b21d0835 100644 --- a/netbox/utilities/testing/views.py +++ b/netbox/utilities/testing/views.py @@ -152,7 +152,6 @@ class ViewTestCases: @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[]) def test_create_object_with_permission(self): - # Assign unconstrained permission obj_perm = ObjectPermission( name='Test permission', @@ -586,19 +585,59 @@ class ViewTestCases: response = self.client.post(**request) self.assertHttpStatus(response, 302) self.assertEqual(initial_count + self.bulk_create_count, self._get_queryset().count()) - for instance in self._get_queryset().order_by('-pk')[:self.bulk_create_count]: + for instance in self._get_queryset().order_by('-pk')[: self.bulk_create_count]: self.assertInstanceEqual(instance, self.bulk_create_data, exclude=self.validation_excluded_fields) class BulkImportObjectsViewTestCase(ModelViewTestCase): """ Create multiple instances from imported data. - :csv_data: A list of CSV-formatted lines (starting with the headers) to be used for bulk object import. + :csv_data: CSV data for bulk import testing. Supports two formats: + + 1. Tuple/list format (backwards compatible): + csv_data = ( + "name,slug,description", + "Object 1,object-1,First object", + "Object 2,object-2,Second object", + ) + + 2. Dictionary format for multiple scenarios: + csv_data = { + 'default': ( + "name,slug,description", + "Object 1,object-1,First object", + ), + 'with_optional_fields': ( + "name,slug,description,comments", + "Object 2,object-2,Second object,With comments", + ) + } + + When using dictionary format, test_bulk_import_objects_with_permission() + runs each scenario as a separate subtest with clear output: + + test_bulk_import_objects_with_permission (scenario=default) ... ok + test_bulk_import_objects_with_permission (scenario=with_optional_fields) ... ok """ + csv_data = () - def _get_csv_data(self): - return '\n'.join(self.csv_data) + def get_scenarios(self): + return self.csv_data.keys() if isinstance(self.csv_data, dict) else ['default'] + + def _get_csv_data(self, scenario_name='default'): + """ + Get CSV data for testing. Supports both tuple/list and dictionary formats. + """ + if isinstance(self.csv_data, dict): + if scenario_name not in self.csv_data: + available = ', '.join(self.csv_data.keys()) + raise ValueError(f"Scenario '{scenario_name}' not found in csv_data. Available: {available}") + return '\n'.join(self.csv_data[scenario_name]) + elif isinstance(self.csv_data, (tuple, list)): + return '\n'.join(self.csv_data) + else: + raise TypeError(f'csv_data must be a tuple, list, or dictionary, got {type(self.csv_data)}') def _get_update_csv_data(self): return self.csv_update_data, '\n'.join(self.csv_update_data) @@ -620,10 +659,36 @@ class ViewTestCases: self.assertHttpStatus(response, 403) @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[]) - def test_bulk_import_objects_with_permission(self): + def test_bulk_import_objects_with_permission(self, post_import_callback=None): + # Assign model-level permission once for all scenarios + obj_perm = ObjectPermission(name='Test permission', actions=['add']) + obj_perm.save() + obj_perm.users.add(self.user) + obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model)) + + # Try GET with model-level permission (only once) + self.assertHttpStatus(self.client.get(self._get_url('bulk_import')), 200) + + # Test each scenario + for scenario_name in self.get_scenarios(): + with self.cleanupSubTest(scenario=scenario_name): + self._test_bulk_import_with_permission_scenario(scenario_name) + + if post_import_callback: + post_import_callback(scenario_name) + + def _test_bulk_import_with_permission_scenario(self, scenario_name): + """ + Helper method to test a single bulk import scenario. + """ initial_count = self._get_queryset().count() + + # Get CSV data for this scenario + scenario_data = self._get_csv_data(scenario_name) + expected_new_objects = len(scenario_data.splitlines()) - 1 + data = { - 'data': self._get_csv_data(), + 'data': scenario_data, 'format': ImportFormatChoices.CSV, 'csv_delimiter': CSVDelimiterChoices.AUTO, } @@ -632,33 +697,24 @@ class ViewTestCases: if issubclass(self.model, ChangeLoggingMixin): data['changelog_message'] = get_random_string(10) - # Assign model-level permission - obj_perm = ObjectPermission( - name='Test permission', - actions=['add'] - ) - obj_perm.save() - obj_perm.users.add(self.user) - obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model)) - - # Try GET with model-level permission - self.assertHttpStatus(self.client.get(self._get_url('bulk_import')), 200) - # Test POST with permission response = self.client.post(self._get_url('bulk_import'), data) self.assertHttpStatus(response, 302) - self.assertEqual(self._get_queryset().count(), initial_count + len(self.csv_data) - 1) + + # Verify object count increase + self.assertEqual(self._get_queryset().count(), initial_count + expected_new_objects) # Verify ObjectChange creation if issubclass(self.model, ChangeLoggingMixin): request_id = response.headers.get('X-Request-ID') - self.assertIsNotNone(request_id, "Unable to determine request ID from response") + self.assertIsNotNone(request_id, 'Unable to determine request ID from response') objectchanges = ObjectChange.objects.filter( changed_object_type=ContentType.objects.get_for_model(self.model), request_id=request_id, action=ObjectChangeActionChoices.ACTION_CREATE, ) - self.assertEqual(len(objectchanges), len(self.csv_data) - 1) + self.assertEqual(len(objectchanges), expected_new_objects) + for oc in objectchanges: self.assertEqual(oc.message, data['changelog_message']) @@ -701,35 +757,52 @@ class ViewTestCases: self.assertEqual(value, value) @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[]) - def test_bulk_import_objects_with_constrained_permission(self): - initial_count = self._get_queryset().count() - data = { - 'data': self._get_csv_data(), - 'format': ImportFormatChoices.CSV, - 'csv_delimiter': CSVDelimiterChoices.AUTO, - } - - # Assign constrained permission + def test_bulk_import_objects_with_constrained_permission(self, post_import_callback=None): + # Assign constrained permission (deny all initially) obj_perm = ObjectPermission( name='Test permission', constraints={'pk': 0}, # Dummy permission to deny all - actions=['add'] + actions=['add'], ) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model)) - # Attempt to import non-permitted objects + # Test each scenario with constrained permissions + for scenario_name in self.get_scenarios(): + with self.cleanupSubTest(scenario=scenario_name): + self._test_bulk_import_constrained_scenario(scenario_name, obj_perm) + + if post_import_callback: + post_import_callback(scenario_name) + + def _test_bulk_import_constrained_scenario(self, scenario_name, obj_perm): + """ + Helper method to test a single bulk import scenario with constrained permissions. + """ + initial_count = self._get_queryset().count() + + # Get CSV data for this scenario + scenario_data = self._get_csv_data(scenario_name) + expected_new_objects = len(scenario_data.splitlines()) - 1 + + data = { + 'data': scenario_data, + 'format': ImportFormatChoices.CSV, + 'csv_delimiter': CSVDelimiterChoices.AUTO, + } + + # Attempt to import non-permitted objects (should fail) self.assertHttpStatus(self.client.post(self._get_url('bulk_import'), data), 200) self.assertEqual(self._get_queryset().count(), initial_count) - # Update permission constraints + # Update permission constraints to allow all obj_perm.constraints = {'pk__gt': 0} # Dummy permission to allow all obj_perm.save() - # Import permitted objects + # Import permitted objects (should succeed) self.assertHttpStatus(self.client.post(self._get_url('bulk_import'), data), 302) - self.assertEqual(self._get_queryset().count(), initial_count + len(self.csv_data) - 1) + self.assertEqual(self._get_queryset().count(), initial_count + expected_new_objects) class BulkEditObjectsViewTestCase(ModelViewTestCase): """ From 77376524f96f78d8d077b12ac51913aca5b112bf Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 11 Sep 2025 16:28:07 -0400 Subject: [PATCH 24/40] Fixes #20329: Fix InconsistentMigrationHistory exception when upgrading from v4.3 (#20330) Reverts "Fixes #20290: Fix ordering of migrations to support upgrading from v3.7" --- netbox/users/migrations/0005_alter_user_table.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/netbox/users/migrations/0005_alter_user_table.py b/netbox/users/migrations/0005_alter_user_table.py index d87a8abe905..173a7ab4dc8 100644 --- a/netbox/users/migrations/0005_alter_user_table.py +++ b/netbox/users/migrations/0005_alter_user_table.py @@ -24,9 +24,8 @@ def update_content_types(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('core', '0018_concrete_objecttype'), - ('extras', '0117_move_objectchange'), ('users', '0002_squashed_0004'), + ('extras', '0113_customfield_rename_object_type'), ] operations = [ From cf0ef9226866223be6b54e791ea157ef849a340f Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 12 Sep 2025 05:02:16 +0000 Subject: [PATCH 25/40] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 36 ++++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index 083399192d6..252de386d31 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: 2025-09-10 05:02+0000\n" +"POT-Creation-Date: 2025-09-12 05:02+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -2389,7 +2389,7 @@ msgstr "" #: netbox/core/forms/filtersets.py:140 netbox/core/tables/change_logging.py:15 #: netbox/core/tables/jobs.py:69 netbox/extras/tables/tables.py:729 -#: netbox/extras/tables/tables.py:783 +#: netbox/extras/tables/tables.py:784 #: netbox/templates/core/objectchange.html:32 msgid "Time" msgstr "" @@ -2606,7 +2606,7 @@ msgid "type" msgstr "" #: netbox/core/models/data.py:49 netbox/extras/choices.py:37 -#: netbox/extras/models/models.py:176 netbox/extras/tables/tables.py:793 +#: netbox/extras/models/models.py:176 netbox/extras/tables/tables.py:794 #: netbox/templates/core/datasource.html:62 #: netbox/templates/core/plugin.html:66 msgid "URL" @@ -2820,7 +2820,7 @@ msgstr "" #: netbox/extras/tables/tables.py:347 netbox/extras/tables/tables.py:366 #: netbox/extras/tables/tables.py:398 netbox/extras/tables/tables.py:478 #: netbox/extras/tables/tables.py:539 netbox/extras/tables/tables.py:696 -#: netbox/extras/tables/tables.py:736 netbox/extras/tables/tables.py:790 +#: netbox/extras/tables/tables.py:737 netbox/extras/tables/tables.py:791 #: netbox/netbox/tables/tables.py:276 #: netbox/templates/core/objectchange.html:58 #: netbox/templates/extras/eventrule.html:78 @@ -2835,7 +2835,7 @@ msgid "Request ID" msgstr "" #: netbox/core/tables/change_logging.py:45 netbox/core/tables/jobs.py:76 -#: netbox/extras/tables/tables.py:739 netbox/extras/tables/tables.py:796 +#: netbox/extras/tables/tables.py:740 netbox/extras/tables/tables.py:797 #: netbox/templates/core/objectchange.html:68 msgid "Message" msgstr "" @@ -2864,7 +2864,7 @@ msgstr "" #: netbox/core/tables/jobs.py:12 netbox/core/tables/tasks.py:77 #: netbox/dcim/tables/devicetypes.py:169 netbox/extras/tables/tables.py:232 -#: netbox/extras/tables/tables.py:529 netbox/extras/tables/tables.py:761 +#: netbox/extras/tables/tables.py:529 netbox/extras/tables/tables.py:762 #: netbox/netbox/tables/tables.py:222 #: netbox/templates/dcim/virtualchassis_edit.html:56 #: netbox/utilities/forms/forms.py:118 @@ -2880,8 +2880,8 @@ msgstr "" msgid "Log Entries" msgstr "" -#: netbox/core/tables/jobs.py:73 netbox/extras/tables/tables.py:733 -#: netbox/extras/tables/tables.py:787 +#: netbox/core/tables/jobs.py:73 netbox/extras/tables/tables.py:734 +#: netbox/extras/tables/tables.py:788 msgid "Level" msgstr "" @@ -9818,11 +9818,11 @@ msgstr "" msgid "Comments (Short)" msgstr "" -#: netbox/extras/tables/tables.py:726 netbox/extras/tables/tables.py:777 +#: netbox/extras/tables/tables.py:726 netbox/extras/tables/tables.py:778 msgid "Line" msgstr "" -#: netbox/extras/tables/tables.py:780 +#: netbox/extras/tables/tables.py:781 msgid "Method" msgstr "" @@ -9863,32 +9863,32 @@ msgstr "" msgid "Invalid attribute \"{name}\" for {model}" msgstr "" -#: netbox/extras/views.py:1080 +#: netbox/extras/views.py:1081 #, python-brace-format msgid "An error occurred while rendering the template: {error}" msgstr "" -#: netbox/extras/views.py:1242 +#: netbox/extras/views.py:1243 msgid "Your dashboard has been reset." msgstr "" -#: netbox/extras/views.py:1288 +#: netbox/extras/views.py:1289 msgid "Added widget: " msgstr "" -#: netbox/extras/views.py:1329 +#: netbox/extras/views.py:1330 msgid "Updated widget: " msgstr "" -#: netbox/extras/views.py:1365 +#: netbox/extras/views.py:1366 msgid "Deleted widget: " msgstr "" -#: netbox/extras/views.py:1367 +#: netbox/extras/views.py:1368 msgid "Error deleting widget: " msgstr "" -#: netbox/extras/views.py:1472 +#: netbox/extras/views.py:1473 msgid "Unable to run script: RQ worker process not running." msgstr "" @@ -16172,7 +16172,7 @@ msgstr "" msgid "Selected" msgstr "" -#: netbox/utilities/testing/views.py:668 +#: netbox/utilities/testing/views.py:724 msgid "The test must define csv_update_data." msgstr "" From 37644eed3f370777cfa40ce852e86e64d5470974 Mon Sep 17 00:00:00 2001 From: Jo Date: Fri, 12 Sep 2025 08:22:16 +0200 Subject: [PATCH 26/40] Extended plugin development documentation regarding bulk edit/delete buttons in tables --- docs/plugins/development/tables.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/plugins/development/tables.md b/docs/plugins/development/tables.md index c5115884938..ea36b204de4 100644 --- a/docs/plugins/development/tables.md +++ b/docs/plugins/development/tables.md @@ -47,6 +47,11 @@ table.configure(request) This will automatically apply any user-specific preferences for the table. (If using a generic view provided by NetBox, table configuration is handled automatically.) + +### Bulk Edit and Delete Actions + +Bulk edit and delete buttons are automatically added to the table, if there is an appropriate view registered to the `${modelname}_bulk_edit` or `${modelname}_bulk_delete` path name. + ## Columns The table column classes listed below are supported for use in plugins. These classes can be imported from `netbox.tables.columns`. From 4b17faae52c1a210576c5ceaa2acfbb8e830061b Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 12 Sep 2025 09:33:49 -0400 Subject: [PATCH 27/40] Bump Django to v5.2.6 (#20340) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 26925a6ce98..5be453add5c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ colorama==0.4.6 -Django==5.2.5 +Django==5.2.6 django-cors-headers==4.7.0 django-debug-toolbar==5.2.0 django-filter==25.1 From 103939ad3c05bf50fd46ce99a910813c7732ced0 Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Fri, 12 Sep 2025 15:53:08 +0200 Subject: [PATCH 28/40] Fixes #20197: Correct validation for virtual chassis parent interface (#20337) --- netbox/dcim/models/device_components.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index f1e460d779f..9e1d7b61e9b 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -872,14 +872,14 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd "The selected parent interface ({interface}) belongs to a different device ({device})" ).format(interface=self.parent, device=self.parent.device) }) - elif self.parent.device.virtual_chassis != self.parent.virtual_chassis: + elif self.parent.device.virtual_chassis != self.device.virtual_chassis: raise ValidationError({ 'parent': _( "The selected parent interface ({interface}) belongs to {device}, which is not part of " "virtual chassis {virtual_chassis}." ).format( interface=self.parent, - device=self.parent_device, + device=self.parent.device, virtual_chassis=self.device.virtual_chassis ) }) @@ -890,7 +890,7 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd if self.pk and self.bridge_id == self.pk: raise ValidationError({'bridge': _("An interface cannot be bridged to itself.")}) - # A bridged interface belong to the same device or virtual chassis + # A bridged interface belongs to the same device or virtual chassis if self.bridge and self.bridge.device != self.device: if self.device.virtual_chassis is None: raise ValidationError({ From 2d6b3d19e708331bba178bb9fe595519cdd22b1a Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Sat, 13 Sep 2025 00:41:49 +0200 Subject: [PATCH 29/40] Fixes #20236: Improve file naming and upload handling (#20315) --- netbox/extras/models/models.py | 6 +- netbox/extras/tests/test_models.py | 92 +++++++++++++++++-- netbox/extras/tests/test_utils.py | 143 ++++++++++++++++++++++++++++- netbox/extras/utils.py | 54 ++++++++--- 4 files changed, 273 insertions(+), 22 deletions(-) diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index be4c44d63af..7361d087dec 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -1,6 +1,6 @@ import json -import os import urllib.parse +from pathlib import Path from django.conf import settings from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation @@ -728,7 +728,9 @@ class ImageAttachment(ChangeLoggedModel): @property def filename(self): - return os.path.basename(self.image.name).split('_', 2)[2] + base_name = Path(self.image.name).name + prefix = f"{self.object_type.model}_{self.object_id}_" + return base_name.removeprefix(prefix) @property def html_tag(self): diff --git a/netbox/extras/tests/test_models.py b/netbox/extras/tests/test_models.py index 341920a81a3..905ceff8823 100644 --- a/netbox/extras/tests/test_models.py +++ b/netbox/extras/tests/test_models.py @@ -1,17 +1,95 @@ import tempfile from pathlib import Path +from django.contrib.contenttypes.models import ContentType +from django.core.files.uploadedfile import SimpleUploadedFile from django.forms import ValidationError from django.test import tag, TestCase from core.models import DataSource, ObjectType from dcim.models import Device, DeviceRole, DeviceType, Location, Manufacturer, Platform, Region, Site, SiteGroup -from extras.models import ConfigContext, ConfigContextProfile, ConfigTemplate, Tag +from extras.models import ConfigContext, ConfigContextProfile, ConfigTemplate, ImageAttachment, Tag from tenancy.models import Tenant, TenantGroup from utilities.exceptions import AbortRequest from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine +class ImageAttachmentTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.ct_rack = ContentType.objects.get(app_label='dcim', model='rack') + cls.image_content = b'' + + def _stub_image_attachment(self, object_id, image_filename, name=None): + """ + Creates an instance of ImageAttachment with the provided object_id and image_name. + + This method prepares a stubbed image attachment to test functionalities that + require an ImageAttachment object. + The function initializes the attachment with a specified file name and + pre-defined image content. + """ + ia = ImageAttachment( + object_type=self.ct_rack, + object_id=object_id, + name=name, + image=SimpleUploadedFile( + name=image_filename, + content=self.image_content, + content_type='image/jpeg', + ), + ) + return ia + + def test_filename_strips_expected_prefix(self): + """ + Tests that the filename of the image attachment is stripped of the expected + prefix. + """ + ia = self._stub_image_attachment(12, 'image-attachments/rack_12_My_File.png') + self.assertEqual(ia.filename, 'My_File.png') + + def test_filename_legacy_nested_path_returns_basename(self): + """ + Tests if the filename of a legacy-nested path correctly returns only the basename. + """ + # e.g. "image-attachments/rack_12_5/31/23.jpg" -> "23.jpg" + ia = self._stub_image_attachment(12, 'image-attachments/rack_12_5/31/23.jpg') + self.assertEqual(ia.filename, '23.jpg') + + def test_filename_no_prefix_returns_basename(self): + """ + Tests that the filename property correctly returns the basename for an image + attachment that has no leading prefix in its path. + """ + ia = self._stub_image_attachment(42, 'image-attachments/just_name.webp') + self.assertEqual(ia.filename, 'just_name.webp') + + def test_mismatched_prefix_is_not_stripped(self): + """ + Tests that a mismatched prefix in the filename is not stripped. + """ + # Prefix does not match object_id -> leave as-is (basename only) + ia = self._stub_image_attachment(12, 'image-attachments/rack_13_other.png') + self.assertEqual('rack_13_other.png', ia.filename) + + def test_str_uses_name_when_present(self): + """ + Tests that the `str` representation of the object uses the + `name` attribute when provided. + """ + ia = self._stub_image_attachment(12, 'image-attachments/rack_12_file.png', name='Human title') + self.assertEqual('Human title', str(ia)) + + def test_str_falls_back_to_filename(self): + """ + Tests that the `str` representation of the object falls back to + the filename if the name attribute is not set. + """ + ia = self._stub_image_attachment(12, 'image-attachments/rack_12_file.png', name='') + self.assertEqual('file.png', str(ia)) + + class TagTest(TestCase): def test_default_ordering_weight_then_name_is_set(self): @@ -445,7 +523,7 @@ class ConfigContextTest(TestCase): vm1 = VirtualMachine.objects.create(name="VM 1", site=site, role=vm_role) vm2 = VirtualMachine.objects.create(name="VM 2", cluster=cluster, role=vm_role) - # Check that their individually-rendered config contexts are identical + # Check that their individually rendered config contexts are identical self.assertEqual( vm1.get_config_context(), vm2.get_config_context() @@ -462,7 +540,7 @@ class ConfigContextTest(TestCase): """ Tagged items use a generic relationship, which results in duplicate rows being returned when queried. This is combated by appending distinct() to the config context querysets. This test creates a config - context assigned to two tags and ensures objects related by those same two tags result in only a single + context assigned to two tags and ensures objects related to those same two tags result in only a single config context record being returned. See https://github.com/netbox-community/netbox/issues/5314 @@ -495,14 +573,14 @@ class ConfigContextTest(TestCase): self.assertEqual(ConfigContext.objects.get_for_object(device).count(), 1) self.assertEqual(device.get_config_context(), annotated_queryset[0].get_config_context()) - def test_multiple_tags_return_distinct_objects_with_seperate_config_contexts(self): + def test_multiple_tags_return_distinct_objects_with_separate_config_contexts(self): """ Tagged items use a generic relationship, which results in duplicate rows being returned when queried. - This is combatted by by appending distinct() to the config context querysets. This test creates a config - context assigned to two tags and ensures objects related by those same two tags result in only a single + This is combated by appending distinct() to the config context querysets. This test creates a config + context assigned to two tags and ensures objects related to those same two tags result in only a single config context record being returned. - This test case is seperate from the above in that it deals with multiple config context objects in play. + This test case is separate from the above in that it deals with multiple config context objects in play. See https://github.com/netbox-community/netbox/issues/5387 """ diff --git a/netbox/extras/tests/test_utils.py b/netbox/extras/tests/test_utils.py index b851acab83f..ec0102887af 100644 --- a/netbox/extras/tests/test_utils.py +++ b/netbox/extras/tests/test_utils.py @@ -1,7 +1,10 @@ +from types import SimpleNamespace + +from django.contrib.contenttypes.models import ContentType from django.test import TestCase from extras.models import ExportTemplate -from extras.utils import filename_from_model +from extras.utils import filename_from_model, image_upload from tenancy.models import ContactGroup, TenantGroup from wireless.models import WirelessLANGroup @@ -17,3 +20,141 @@ class FilenameFromModelTests(TestCase): for model, expected in cases: self.assertEqual(filename_from_model(model), expected) + + +class ImageUploadTests(TestCase): + @classmethod + def setUpTestData(cls): + # We only need a ContentType with model="rack" for the prefix; + # this doesn't require creating a Rack object. + cls.ct_rack = ContentType.objects.get(app_label='dcim', model='rack') + + def _stub_instance(self, object_id=12, name=None): + """ + Creates a minimal stub for use with the `image_upload()` function. + + This method generates an instance of `SimpleNamespace` containing a set + of attributes required to simulate the expected input for the + `image_upload()` method. + It is designed to simplify testing or processing by providing a + lightweight representation of an object. + """ + return SimpleNamespace(object_type=self.ct_rack, object_id=object_id, name=name) + + def _second_segment(self, path: str): + """ + Extracts and returns the portion of the input string after the + first '/' character. + """ + return path.split('/', 1)[1] + + def test_windows_fake_path_and_extension_lowercased(self): + """ + Tests handling of a Windows file path with a fake directory and extension. + """ + inst = self._stub_instance(name=None) + path = image_upload(inst, r'C:\fake_path\MyPhoto.JPG') + # Base directory and single-level path + seg2 = self._second_segment(path) + self.assertTrue(path.startswith('image-attachments/rack_12_')) + self.assertNotIn('/', seg2, 'should not create nested directories') + # Extension from the uploaded file, lowercased + self.assertTrue(seg2.endswith('.jpg')) + + def test_name_with_slashes_is_flattened_no_subdirectories(self): + """ + Tests that a name with slashes is flattened and does not + create subdirectories. + """ + inst = self._stub_instance(name='5/31/23') + path = image_upload(inst, 'image.png') + seg2 = self._second_segment(path) + self.assertTrue(seg2.startswith('rack_12_')) + self.assertNotIn('/', seg2) + self.assertNotIn('\\', seg2) + self.assertTrue(seg2.endswith('.png')) + + def test_name_with_backslashes_is_flattened_no_subdirectories(self): + """ + Tests that a name including backslashes is correctly flattened + into a single directory name without creating subdirectories. + """ + inst = self._stub_instance(name=r'5\31\23') + path = image_upload(inst, 'image_name.png') + + seg2 = self._second_segment(path) + self.assertTrue(seg2.startswith('rack_12_')) + self.assertNotIn('/', seg2) + self.assertNotIn('\\', seg2) + self.assertTrue(seg2.endswith('.png')) + + def test_prefix_format_is_as_expected(self): + """ + Tests the output path format generated by the `image_upload` function. + """ + inst = self._stub_instance(object_id=99, name='label') + path = image_upload(inst, 'a.webp') + # The second segment must begin with "rack_99_" + seg2 = self._second_segment(path) + self.assertTrue(seg2.startswith('rack_99_')) + self.assertTrue(seg2.endswith('.webp')) + + def test_unsupported_file_extension(self): + """ + Test that when the file extension is not allowed, the extension + is omitted. + """ + inst = self._stub_instance(name='test') + path = image_upload(inst, 'document.txt') + + seg2 = self._second_segment(path) + self.assertTrue(seg2.startswith('rack_12_test')) + self.assertFalse(seg2.endswith('.txt')) + # When not allowed, no extension should be appended + self.assertNotRegex(seg2, r'\.txt$') + + def test_instance_name_with_whitespace_and_special_chars(self): + """ + Test that an instance name with leading/trailing whitespace and + special characters is sanitized properly. + """ + # Suppose the instance name has surrounding whitespace and + # extra slashes. + inst = self._stub_instance(name=' my/complex\\name ') + path = image_upload(inst, 'irrelevant.png') + + # The output should be flattened and sanitized. + # We expect the name to be transformed into a valid filename without + # path separators. + seg2 = self._second_segment(path) + self.assertNotIn(' ', seg2) + self.assertNotIn('/', seg2) + self.assertNotIn('\\', seg2) + self.assertTrue(seg2.endswith('.png')) + + def test_separator_variants_with_subTest(self): + """ + Tests that both forward slash and backslash in file paths are + handled consistently by the `image_upload` function and + processed into a sanitized uniform format. + """ + for name in ['2025/09/12', r'2025\09\12']: + with self.subTest(name=name): + inst = self._stub_instance(name=name) + path = image_upload(inst, 'x.jpeg') + seg2 = self._second_segment(path) + self.assertTrue(seg2.startswith('rack_12_')) + self.assertNotIn('/', seg2) + self.assertNotIn('\\', seg2) + self.assertTrue(seg2.endswith('.jpeg') or seg2.endswith('.jpg')) + + def test_fallback_on_suspicious_file_operation(self): + """ + Test that when default_storage.get_valid_name() raises a + SuspiciousFileOperation, the fallback default is used. + """ + inst = self._stub_instance(name=' ') + path = image_upload(inst, 'sample.png') + # Expect the fallback name 'unnamed' to be used. + self.assertIn('unnamed', path) + self.assertTrue(path.startswith('image-attachments/rack_12_')) diff --git a/netbox/extras/utils.py b/netbox/extras/utils.py index c9f554d224b..761f4affb7d 100644 --- a/netbox/extras/utils.py +++ b/netbox/extras/utils.py @@ -1,15 +1,20 @@ import importlib +from pathlib import Path -from django.core.exceptions import ImproperlyConfigured +from django.core.exceptions import ImproperlyConfigured, SuspiciousFileOperation +from django.core.files.storage import default_storage +from django.core.files.utils import validate_file_name from django.db import models from django.db.models import Q from taggit.managers import _TaggableManager from netbox.context import current_request + from .validators import CustomValidator __all__ = ( 'SharedObjectViewMixin', + 'filename_from_model', 'image_upload', 'is_report', 'is_script', @@ -35,13 +40,13 @@ class SharedObjectViewMixin: def filename_from_model(model: models.Model) -> str: - """Standardises how we generate filenames from model class for exports""" + """Standardizes how we generate filenames from model class for exports""" base = model._meta.verbose_name_plural.lower().replace(' ', '_') return f'netbox_{base}' def filename_from_object(context: dict) -> str: - """Standardises how we generate filenames from model class for exports""" + """Standardizes how we generate filenames from model class for exports""" if 'device' in context: base = f"{context['device'].name or 'config'}" elif 'virtualmachine' in context: @@ -64,17 +69,42 @@ def is_taggable(obj): def image_upload(instance, filename): """ Return a path for uploading image attachments. + + - Normalizes browser paths (e.g., C:\\fake_path\\photo.jpg) + - Uses the instance.name if provided (sanitized to a *basename*, no ext) + - Prefixes with a machine-friendly identifier + + Note: Relies on Django's default_storage utility. """ - path = 'image-attachments/' + upload_dir = 'image-attachments' + default_filename = 'unnamed' + allowed_img_extensions = ('bmp', 'gif', 'jpeg', 'jpg', 'png', 'webp') - # Rename the file to the provided name, if any. Attempt to preserve the file extension. - extension = filename.rsplit('.')[-1].lower() - if instance.name and extension in ['bmp', 'gif', 'jpeg', 'jpg', 'png', 'webp']: - filename = '.'.join([instance.name, extension]) - elif instance.name: - filename = instance.name + # Normalize Windows paths and create a Path object. + normalized_filename = str(filename).replace('\\', '/') + file_path = Path(normalized_filename) - return '{}{}_{}_{}'.format(path, instance.object_type.name, instance.object_id, filename) + # Extract the extension from the uploaded file. + ext = file_path.suffix.lower().lstrip('.') + + # Use the instance-provided name if available; otherwise use the file stem. + # Rely on Django's get_valid_filename to perform sanitization. + stem = (instance.name or file_path.stem).strip() + try: + safe_stem = default_storage.get_valid_name(stem) + except SuspiciousFileOperation: + safe_stem = default_filename + + # Append the uploaded extension only if it's an allowed image type + final_name = f"{safe_stem}.{ext}" if ext in allowed_img_extensions else safe_stem + + # Create a machine-friendly prefix from the instance + prefix = f"{instance.object_type.model}_{instance.object_id}" + name_with_path = f"{upload_dir}/{prefix}_{final_name}" + + # Validate the generated relative path (blocks absolute/traversal) + validate_file_name(name_with_path, allow_relative_path=True) + return name_with_path def is_script(obj): @@ -107,7 +137,7 @@ def run_validators(instance, validators): request = current_request.get() for validator in validators: - # Loading a validator class by dotted path + # Loading a validator class by a dotted path if type(validator) is str: module, cls = validator.rsplit('.', 1) validator = getattr(importlib.import_module(module), cls)() From 2a99aadc5db11e7ab5e626b593197bd8f361888a Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 13 Sep 2025 05:03:28 +0000 Subject: [PATCH 30/40] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index 252de386d31..82c22480841 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: 2025-09-12 05:02+0000\n" +"POT-Creation-Date: 2025-09-13 05:03+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -2550,7 +2550,7 @@ msgstr "" #: netbox/core/models/config.py:18 netbox/core/models/data.py:269 #: netbox/core/models/files.py:30 netbox/core/models/jobs.py:60 -#: netbox/extras/models/models.py:837 netbox/extras/models/notifications.py:39 +#: netbox/extras/models/models.py:839 netbox/extras/models/notifications.py:39 #: netbox/extras/models/notifications.py:195 #: netbox/netbox/models/features.py:61 netbox/users/models/tokens.py:32 msgid "created" @@ -6951,7 +6951,7 @@ msgid "Numeric identifier unique to the parent device" msgstr "" #: netbox/dcim/models/devices.py:1222 netbox/extras/models/customfields.py:231 -#: netbox/extras/models/models.py:111 netbox/extras/models/models.py:798 +#: netbox/extras/models/models.py:111 netbox/extras/models/models.py:800 #: netbox/netbox/models/__init__.py:120 netbox/netbox/models/__init__.py:155 msgid "comments" msgstr "" @@ -9518,32 +9518,32 @@ msgstr "" msgid "Image attachments cannot be assigned to this object type ({type})." msgstr "" -#: netbox/extras/models/models.py:792 +#: netbox/extras/models/models.py:794 msgid "kind" msgstr "" -#: netbox/extras/models/models.py:806 +#: netbox/extras/models/models.py:808 msgid "journal entry" msgstr "" -#: netbox/extras/models/models.py:807 +#: netbox/extras/models/models.py:809 msgid "journal entries" msgstr "" -#: netbox/extras/models/models.py:825 +#: netbox/extras/models/models.py:827 #, python-brace-format msgid "Journaling is not supported for this object type ({type})." msgstr "" -#: netbox/extras/models/models.py:867 +#: netbox/extras/models/models.py:869 msgid "bookmark" msgstr "" -#: netbox/extras/models/models.py:868 +#: netbox/extras/models/models.py:870 msgid "bookmarks" msgstr "" -#: netbox/extras/models/models.py:884 +#: netbox/extras/models/models.py:886 #, python-brace-format msgid "Bookmarks cannot be assigned to this object type ({type})." msgstr "" From 2dac09cea0b4517fc9d49678fafecfb0a8623eed Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Sat, 13 Sep 2025 20:11:13 +0200 Subject: [PATCH 31/40] Closes #20341: Drop legacy django_admin_log table (#20349) --- .../0012_drop_django_admin_log_table.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 netbox/users/migrations/0012_drop_django_admin_log_table.py diff --git a/netbox/users/migrations/0012_drop_django_admin_log_table.py b/netbox/users/migrations/0012_drop_django_admin_log_table.py new file mode 100644 index 00000000000..512d4c7b392 --- /dev/null +++ b/netbox/users/migrations/0012_drop_django_admin_log_table.py @@ -0,0 +1,22 @@ +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ('users', '0011_concrete_objecttype'), + ] + + operations = [ + # Django admin UI was removed in NetBox v4.0 + # Older installations may still have the old `django_admin_log` table in place + # Drop the obsolete table if it exists. This is a no-op on fresh or already-clean DBs. + migrations.RunSQL( + sql='DROP TABLE IF EXISTS "django_admin_log";', + reverse_sql=migrations.RunSQL.noop, + ), + # Clean up a potential leftover sequence in older DBs + migrations.RunSQL( + sql='DROP SEQUENCE IF EXISTS "django_admin_log_id_seq";', + reverse_sql=migrations.RunSQL.noop, + ), + ] From fb004bb94e4330027578d6ad51e424a9bc8ecfa8 Mon Sep 17 00:00:00 2001 From: Jason Novinger Date: Mon, 15 Sep 2025 17:04:56 +0000 Subject: [PATCH 32/40] #20327: Device queries now faster when including ConfigContexts (#20346) * Fixes #20327: Device queries are now faster when including ConfidContexts Move .distinct() from main queryset to tag subquery to eliminate performance bottleneck when querying devices with config contexts. The .distinct() call on the main device queryset was causing PostgreSQL to sort all devices before pagination, resulting in 15x slower API responses for large installations (10k+ devices, 100+ config contexts). Moving .distinct() to the tag subquery eliminates duplicates at their source (GenericForeignKey tag relationships) while preserving the fix for issues #5314 and #5387 without impacting overall query performance. * Add performance regression test for config context annotation The test verifies that: - Main device queries do not use expensive DISTINCT operations - Tag subqueries properly use DISTINCT to prevent duplicates from issue #5387 This ensures the optimization from issue #20327 (moving .distinct() from maintaining query to tag subquery) cannot be accidentally reverted while maintaining the correctness guarantees for issues #5314 and #5387. * Address PR feedback, clean up new regression test The new regression test now avoids casting the query to a string and inspecting the string, which was brittle at best. The new approach asserts directly against `queryset.distinct` for the main query and then finds the subquery that we expect to have distinct set and verifies that is in fact the case. I also realized that the use of `connection.query_log` was problematic, in that it didn't seem to return any queries as expected. This meant that the test was actually not making any assertions since none of the code inside of the for loop over `device_queries` ever ran. --- netbox/extras/querysets.py | 4 +- netbox/extras/tests/test_models.py | 84 +++++++++++++++++++++++------- 2 files changed, 66 insertions(+), 22 deletions(-) diff --git a/netbox/extras/querysets.py b/netbox/extras/querysets.py index cb7c1b0aa37..ee2afed4c9f 100644 --- a/netbox/extras/querysets.py +++ b/netbox/extras/querysets.py @@ -93,7 +93,7 @@ class ConfigContextModelQuerySet(RestrictedQuerySet): _data=EmptyGroupByJSONBAgg('data', ordering=['weight', 'name']) ).values("_data").order_by() ) - ).distinct() + ) def _get_config_context_filters(self): # Construct the set of Q objects for the specific object types @@ -117,7 +117,7 @@ class ConfigContextModelQuerySet(RestrictedQuerySet): ).values_list( 'tag_id', flat=True - ) + ).distinct() ) ) | Q(tags=None), is_active=True, diff --git a/netbox/extras/tests/test_models.py b/netbox/extras/tests/test_models.py index 905ceff8823..7b2e58646af 100644 --- a/netbox/extras/tests/test_models.py +++ b/netbox/extras/tests/test_models.py @@ -8,7 +8,7 @@ from django.test import tag, TestCase from core.models import DataSource, ObjectType from dcim.models import Device, DeviceRole, DeviceType, Location, Manufacturer, Platform, Region, Site, SiteGroup -from extras.models import ConfigContext, ConfigContextProfile, ConfigTemplate, ImageAttachment, Tag +from extras.models import ConfigContext, ConfigContextProfile, ConfigTemplate, ImageAttachment, Tag, TaggedItem from tenancy.models import Tenant, TenantGroup from utilities.exceptions import AbortRequest from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine @@ -536,6 +536,34 @@ class ConfigContextTest(TestCase): vms[1].get_config_context() ) + def test_valid_local_context_data(self): + device = Device.objects.first() + device.local_context_data = None + device.clean() + + device.local_context_data = {"foo": "bar"} + device.clean() + + def test_invalid_local_context_data(self): + device = Device.objects.first() + + device.local_context_data = "" + with self.assertRaises(ValidationError): + device.clean() + + device.local_context_data = 0 + with self.assertRaises(ValidationError): + device.clean() + + device.local_context_data = False + with self.assertRaises(ValidationError): + device.clean() + + device.local_context_data = 'foo' + with self.assertRaises(ValidationError): + device.clean() + + @tag('regression') def test_multiple_tags_return_distinct_objects(self): """ Tagged items use a generic relationship, which results in duplicate rows being returned when queried. @@ -573,6 +601,7 @@ class ConfigContextTest(TestCase): self.assertEqual(ConfigContext.objects.get_for_object(device).count(), 1) self.assertEqual(device.get_config_context(), annotated_queryset[0].get_config_context()) + @tag('regression') def test_multiple_tags_return_distinct_objects_with_separate_config_contexts(self): """ Tagged items use a generic relationship, which results in duplicate rows being returned when queried. @@ -621,32 +650,47 @@ class ConfigContextTest(TestCase): self.assertEqual(ConfigContext.objects.get_for_object(device).count(), 2) self.assertEqual(device.get_config_context(), annotated_queryset[0].get_config_context()) - def test_valid_local_context_data(self): + @tag('performance', 'regression') + def test_config_context_annotation_query_optimization(self): + """ + Regression test for issue #20327: Ensure config context annotation + doesn't use expensive DISTINCT on main query. + + Verifies that DISTINCT is only used in tag subquery where needed, + not on the main device query which is expensive for large datasets. + """ device = Device.objects.first() - device.local_context_data = None - device.clean() + queryset = Device.objects.filter(pk=device.pk).annotate_config_context_data() - device.local_context_data = {"foo": "bar"} - device.clean() + # Main device query should NOT use DISTINCT + self.assertFalse(queryset.query.distinct) - def test_invalid_local_context_data(self): - device = Device.objects.first() + # Check that tag subqueries DO use DISTINCT by inspecting the annotation + config_annotation = queryset.query.annotations.get('config_context_data') + self.assertIsNotNone(config_annotation) - device.local_context_data = "" - with self.assertRaises(ValidationError): - device.clean() + def find_tag_subqueries(where_node): + """Find subqueries in WHERE clause that relate to tag filtering""" + subqueries = [] - device.local_context_data = 0 - with self.assertRaises(ValidationError): - device.clean() + def traverse(node): + if hasattr(node, 'children'): + for child in node.children: + try: + if child.rhs.query.model is TaggedItem: + subqueries.append(child.rhs.query) + except AttributeError: + traverse(child) + traverse(where_node) + return subqueries - device.local_context_data = False - with self.assertRaises(ValidationError): - device.clean() + # Find subqueries in the WHERE clause that should have DISTINCT + tag_subqueries = find_tag_subqueries(config_annotation.query.where) + distinct_subqueries = [sq for sq in tag_subqueries if sq.distinct] - device.local_context_data = 'foo' - with self.assertRaises(ValidationError): - device.clean() + # Verify we found at least one DISTINCT subquery for tags + self.assertEqual(len(distinct_subqueries), 1) + self.assertTrue(distinct_subqueries[0].distinct) class ConfigTemplateTest(TestCase): From 31644b4ce66a437cd4de2d1a40c5b4c2d9177775 Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Sun, 14 Sep 2025 19:23:27 +0200 Subject: [PATCH 33/40] fix(ipam): Remove FHRP IP prefix constraint Remove `FHRPGroupAssignmentForm.__init__` logic that tied group choices to the interface IP prefix. Add `group_id` to the `q` filter to enable matching by group ID. Fixes #19262 --- netbox/ipam/filtersets.py | 1 + netbox/ipam/forms/model_forms.py | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py index 7f8cd2f04fc..cf4f99d3276 100644 --- a/netbox/ipam/filtersets.py +++ b/netbox/ipam/filtersets.py @@ -804,6 +804,7 @@ class FHRPGroupFilterSet(NetBoxModelFilterSet): return queryset return queryset.filter( Q(description__icontains=value) | + Q(group_id__contains=value) | Q(name__icontains=value) ) diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index 1b4a3d596b5..399198c52b0 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -580,13 +580,6 @@ class FHRPGroupAssignmentForm(forms.ModelForm): model = FHRPGroupAssignment fields = ('group', 'priority') - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - ipaddresses = self.instance.interface.ip_addresses.all() - for ipaddress in ipaddresses: - self.fields['group'].widget.add_query_param('related_ip', ipaddress.pk) - def clean_group(self): group = self.cleaned_data['group'] From 684106031a312ddcc2f2cc7c7b037cd172f213ba Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Mon, 15 Sep 2025 16:27:05 +0200 Subject: [PATCH 34/40] feat(dcim): Improve CableTypeChoices structure and grouping Refactors `CableTypeChoices` by reorganizing cable types into more specific subcategories. Enhances clarity with distinct groups such as Copper (Twisted Pair, Twinax, Coaxial) and Fiber (Multi Mode, Single Mode, Other). Closes #19865 --- netbox/dcim/choices.py | 67 +++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index d44048d58f8..33ac11e1dbf 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -1497,8 +1497,9 @@ class PortTypeChoices(ChoiceSet): # Cables/links # -class CableTypeChoices(ChoiceSet): +class CableTypeChoices(ChoiceSet): + # Copper - Twisted Pair (UTP/STP) TYPE_CAT3 = 'cat3' TYPE_CAT5 = 'cat5' TYPE_CAT5E = 'cat5e' @@ -1507,26 +1508,41 @@ class CableTypeChoices(ChoiceSet): TYPE_CAT7 = 'cat7' TYPE_CAT7A = 'cat7a' TYPE_CAT8 = 'cat8' + TYPE_MRJ21_TRUNK = 'mrj21-trunk' + + # Copper - Twinax (DAC) TYPE_DAC_ACTIVE = 'dac-active' TYPE_DAC_PASSIVE = 'dac-passive' - TYPE_MRJ21_TRUNK = 'mrj21-trunk' + + # Copper - Coaxial TYPE_COAXIAL = 'coaxial' + + # Fiber Optic - Multimode TYPE_MMF = 'mmf' TYPE_MMF_OM1 = 'mmf-om1' TYPE_MMF_OM2 = 'mmf-om2' TYPE_MMF_OM3 = 'mmf-om3' TYPE_MMF_OM4 = 'mmf-om4' TYPE_MMF_OM5 = 'mmf-om5' + + # Fiber Optic - Single-mode TYPE_SMF = 'smf' TYPE_SMF_OS1 = 'smf-os1' TYPE_SMF_OS2 = 'smf-os2' + + # Fiber Optic - Other TYPE_AOC = 'aoc' + + # Power TYPE_POWER = 'power' + + # USB TYPE_USB = 'usb' CHOICES = ( ( - _('Copper'), ( + _('Copper - Twisted Pair (UTP/STP)'), + ( (TYPE_CAT3, 'CAT3'), (TYPE_CAT5, 'CAT5'), (TYPE_CAT5E, 'CAT5e'), @@ -1535,28 +1551,57 @@ class CableTypeChoices(ChoiceSet): (TYPE_CAT7, 'CAT7'), (TYPE_CAT7A, 'CAT7a'), (TYPE_CAT8, 'CAT8'), + (TYPE_MRJ21_TRUNK, 'MRJ21 Trunk'), + ), + ), + ( + _('Copper - Twinax (DAC)'), + ( (TYPE_DAC_ACTIVE, 'Direct Attach Copper (Active)'), (TYPE_DAC_PASSIVE, 'Direct Attach Copper (Passive)'), - (TYPE_MRJ21_TRUNK, 'MRJ21 Trunk'), + ), + ), + ( + _('Copper - Coaxial'), + ( (TYPE_COAXIAL, 'Coaxial'), ), ), ( - _('Fiber'), ( + _('Fiber - Multimode'), + ( (TYPE_MMF, 'Multimode Fiber'), (TYPE_MMF_OM1, 'Multimode Fiber (OM1)'), (TYPE_MMF_OM2, 'Multimode Fiber (OM2)'), (TYPE_MMF_OM3, 'Multimode Fiber (OM3)'), (TYPE_MMF_OM4, 'Multimode Fiber (OM4)'), (TYPE_MMF_OM5, 'Multimode Fiber (OM5)'), - (TYPE_SMF, 'Singlemode Fiber'), - (TYPE_SMF_OS1, 'Singlemode Fiber (OS1)'), - (TYPE_SMF_OS2, 'Singlemode Fiber (OS2)'), - (TYPE_AOC, 'Active Optical Cabling (AOC)'), ), ), - (TYPE_USB, _('USB')), - (TYPE_POWER, _('Power')), + ( + _('Fiber - Single-mode'), + ( + (TYPE_SMF, 'Single-mode Fiber'), + (TYPE_SMF_OS1, 'Single-mode Fiber (OS1)'), + (TYPE_SMF_OS2, 'Single-mode Fiber (OS2)'), + ), + ), + ( + _('Fiber - Other'), + ((TYPE_AOC, 'Active Optical Cabling (AOC)'),), + ), + ( + _('Power'), + ( + (TYPE_POWER, 'Power'), + ), + ), + ( + _('USB'), + ( + (TYPE_USB, 'USB'), + ), + ), ) From 34b111bdc47253601994577886e28b5b7bf22549 Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Sun, 14 Sep 2025 17:07:02 +0200 Subject: [PATCH 35/40] feat(users): Add support for cloning ObjectPermission objects Introduces cloning functionality for ObjectPermission objects using the CloningMixin. Updates the constraints field handling, adds JSONField, and introduces logic to process initial data for cloned objects. Fixes #15492 --- netbox/users/forms/model_forms.py | 46 ++++++++++++++++++++++-------- netbox/users/models/permissions.py | 7 ++++- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/netbox/users/forms/model_forms.py b/netbox/users/forms/model_forms.py index 505104c034f..3e8c853a56f 100644 --- a/netbox/users/forms/model_forms.py +++ b/netbox/users/forms/model_forms.py @@ -1,3 +1,5 @@ +import json + from django import forms from django.conf import settings from django.contrib.auth import password_validation @@ -13,7 +15,11 @@ from netbox.preferences import PREFERENCES from users.constants import * from users.models import * from utilities.data import flatten_dict -from utilities.forms.fields import ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField +from utilities.forms.fields import ( + ContentTypeMultipleChoiceField, + DynamicModelMultipleChoiceField, + JSONField, +) from utilities.forms.rendering import FieldSet from utilities.forms.widgets import DateTimePicker, SplitMultiSelectWidget from utilities.permissions import qs_filter_from_constraints @@ -316,13 +322,22 @@ class ObjectPermissionForm(forms.ModelForm): required=False, queryset=Group.objects.all() ) + constraints = JSONField( + required=False, + label=_('Constraints'), + help_text=_( + 'JSON expression of a queryset filter that will return only permitted objects. Leave null ' + 'to match all objects of this type. A list of multiple objects will result in a logical OR ' + 'operation.' + ), + ) fieldsets = ( FieldSet('name', 'description', 'enabled'), FieldSet('can_view', 'can_add', 'can_change', 'can_delete', 'actions', name=_('Actions')), FieldSet('object_types', name=_('Objects')), FieldSet('groups', 'users', name=_('Assignment')), - FieldSet('constraints', name=_('Constraints')) + FieldSet('constraints', name=_('Constraints')), ) class Meta: @@ -330,13 +345,6 @@ class ObjectPermissionForm(forms.ModelForm): fields = [ 'name', 'description', 'enabled', 'object_types', 'users', 'groups', 'constraints', 'actions', ] - help_texts = { - 'constraints': _( - 'JSON expression of a queryset filter that will return only permitted objects. Leave null ' - 'to match all objects of this type. A list of multiple objects will result in a logical OR ' - 'operation.' - ) - } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -344,18 +352,32 @@ class ObjectPermissionForm(forms.ModelForm): # Make the actions field optional since the form uses it only for non-CRUD actions self.fields['actions'].required = False - # Populate assigned users and groups + # Prepare the appropriate fields when editing an existing ObjectPermission if self.instance.pk: + # Populate assigned users and groups self.fields['groups'].initial = self.instance.groups.values_list('id', flat=True) self.fields['users'].initial = self.instance.users.values_list('id', flat=True) - # Check the appropriate checkboxes when editing an existing ObjectPermission - if self.instance.pk: + # Check the appropriate checkboxes when editing an existing ObjectPermission for action in ['view', 'add', 'change', 'delete']: if action in self.instance.actions: self.fields[f'can_{action}'].initial = True self.instance.actions.remove(action) + # Populate initial data for a new ObjectPermission + elif self.initial: + # Handle cloned objects - actions come from initial data (URL parameters) + if 'actions' in self.initial: + if cloned_actions := self.initial['actions']: + for action in ['view', 'add', 'change', 'delete']: + if action in cloned_actions: + self.fields[f'can_{action}'].initial = True + self.initial['actions'].remove(action) + # Convert data delivered via initial data to JSON data + if 'constraints' in self.initial: + if type(self.initial['constraints']) is str: + self.initial['constraints'] = json.loads(self.initial['constraints']) + def clean(self): super().clean() diff --git a/netbox/users/models/permissions.py b/netbox/users/models/permissions.py index 3ae8ff4c156..3e41d4356ad 100644 --- a/netbox/users/models/permissions.py +++ b/netbox/users/models/permissions.py @@ -3,6 +3,7 @@ from django.db import models from django.urls import reverse from django.utils.translation import gettext_lazy as _ +from netbox.models.features import CloningMixin from utilities.querysets import RestrictedQuerySet __all__ = ( @@ -10,7 +11,7 @@ __all__ = ( ) -class ObjectPermission(models.Model): +class ObjectPermission(CloningMixin, models.Model): """ A mapping of view, add, change, and/or delete permission for users and/or groups to an arbitrary set of objects identified by ORM query parameters. @@ -43,6 +44,10 @@ class ObjectPermission(models.Model): help_text=_("Queryset filter matching the applicable objects of the selected type(s)") ) + clone_fields = ( + 'description', 'enabled', 'object_types', 'actions', 'constraints', + ) + objects = RestrictedQuerySet.as_manager() class Meta: From c2aa87a4c9bc7b99207a1a145b5b582034d76123 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 15 Sep 2025 14:39:05 -0400 Subject: [PATCH 36/40] Closes #20321: Add PHY interface types for pluggable transceivers (#20343) --- netbox/dcim/choices.py | 257 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 238 insertions(+), 19 deletions(-) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 33ac11e1dbf..d44fb22b231 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -889,22 +889,118 @@ class InterfaceTypeChoices(ChoiceSet): TYPE_BRIDGE = 'bridge' TYPE_LAG = 'lag' - # Ethernet + # FastEthernet TYPE_100ME_FX = '100base-fx' TYPE_100ME_LFX = '100base-lfx' - TYPE_100ME_FIXED = '100base-tx' + TYPE_100ME_FIXED = '100base-tx' # TODO: Rename to _TX TYPE_100ME_T1 = '100base-t1' + + # GigabitEthernet + TYPE_1GE_BX10_D = '1000base-bx10-d' + TYPE_1GE_BX10_U = '1000base-bx10-u' + TYPE_1GE_CWDM = '1000base-cwdm' + TYPE_1GE_CX = '1000base-cx' + TYPE_1GE_DWDM = '1000base-dwdm' + TYPE_1GE_EX = '1000base-ex' + TYPE_1GE_SX_FIXED = '1000base-sx' # TODO: Drop _FIXED suffix + TYPE_1GE_LSX = '1000base-lsx' + TYPE_1GE_LX_FIXED = '1000base-lx' # TODO: Drop _FIXED suffix + TYPE_1GE_LX10 = '1000base-lx10' + TYPE_1GE_FIXED = '1000base-t' # TODO: Rename to _T + TYPE_1GE_TX_FIXED = '1000base-tx' # TODO: Drop _FIXED suffix + TYPE_1GE_ZX = '1000base-zx' + + # 2.5/5 Gbps Ethernet + TYPE_2GE_FIXED = '2.5gbase-t' # TODO: Rename to _T + TYPE_5GE_FIXED = '5gbase-t' # TODO: Rename to _T + + # 10 Gbps Ethernet + TYPE_10GE_BR_D = '10gbase-br-d' + TYPE_10GE_BR_U = '10gbase-br-u' + TYPE_10GE_CX4 = '10gbase-cx4' + TYPE_10GE_ER = '10gbase-er' + TYPE_10GE_LR = '10gbase-lr' + TYPE_10GE_LRM = '10gbase-lrm' + TYPE_10GE_LX4 = '10gbase-lx4' + TYPE_10GE_SR = '10gbase-sr' + TYPE_10GE_FIXED = '10gbase-t' + TYPE_10GE_ZR = '10gbase-zr' + + # 25 Gbps Ethernet + TYPE_25GE_CR = '25gbase-cr' + TYPE_25GE_ER = '25gbase-er' + TYPE_25GE_LR = '25gbase-lr' + TYPE_25GE_SR = '25gbase-sr' + TYPE_25GE_T = '25gbase-t' + + # 40 Gbps Ethernet + TYPE_40GE_CR4 = '40gbase-cr4' + TYPE_40GE_ER4 = '40gbase-er4' + TYPE_40GE_FR4 = '40gbase-fr4' + TYPE_40GE_LR4 = '40gbase-lr4' + TYPE_40GE_SR4 = '40gbase-sr4' + + # 50 Gbps Ethernet + TYPE_50GE_CR = '50gbase-cr' + TYPE_50GE_ER = '50gbase-er' + TYPE_50GE_FR = '50gbase-fr' + TYPE_50GE_LR = '50gbase-lr' + TYPE_50GE_SR = '50gbase-sr' + + # 100 Gbps Ethernet + TYPE_100GE_CR1 = '100gbase-cr1' + TYPE_100GE_CR2 = '100gbase-cr2' + TYPE_100GE_CR4 = '100gbase-cr4' + TYPE_100GE_CR10 = '100gbase-cr10' + TYPE_100GE_CWDM4 = '100gbase-cwdm4' + TYPE_100GE_DR = '100gbase-dr' + TYPE_100GE_FR1 = '100gbase-fr1' + TYPE_100GE_ER4 = '100gbase-er4' + TYPE_100GE_LR1 = '100gbase-lr1' + TYPE_100GE_LR4 = '100gbase-lr4' + TYPE_100GE_SR1 = '100gbase-sr1' + TYPE_100GE_SR1_2 = '100gbase-sr1.2' + TYPE_100GE_SR2 = '100gbase-sr2' + TYPE_100GE_SR4 = '100gbase-sr4' + TYPE_100GE_SR10 = '100gbase-sr10' + TYPE_100GE_ZR = '100gbase-zr' + + # 200 Gbps Ethernet + TYPE_200GE_CR2 = '200gbase-cr2' + TYPE_200GE_CR4 = '200gbase-cr4' + TYPE_200GE_SR2 = '200gbase-sr2' + TYPE_200GE_SR4 = '200gbase-sr4' + TYPE_200GE_DR4 = '200gbase-dr4' + TYPE_200GE_FR4 = '200gbase-fr4' + TYPE_200GE_LR4 = '200gbase-lr4' + TYPE_200GE_ER4 = '200gbase-er4' + TYPE_200GE_VR2 = '200gbase-vr2' + + # 400 Gbps Ethernet + TYPE_400GE_CR4 = '400gbase-cr4' + TYPE_400GE_DR4 = '400gbase-dr4' + TYPE_400GE_ER8 = '400gbase-er8' + TYPE_400GE_FR4 = '400gbase-fr4' + TYPE_400GE_FR8 = '400gbase-fr8' + TYPE_400GE_LR4 = '400gbase-lr4' + TYPE_400GE_LR8 = '400gbase-lr8' + TYPE_400GE_SR4 = '400gbase-sr4' + TYPE_400GE_SR4_2 = '400gbase-sr4_2' + TYPE_400GE_SR8 = '400gbase-sr8' + TYPE_400GE_SR16 = '400gbase-sr16' + TYPE_400GE_VR4 = '400gbase-vr4' + TYPE_400GE_ZR = '400gbase-zr' + + # 800 Gbps Ethernet + TYPE_800GE_CR8 = '800gbase-cr8' + TYPE_800GE_DR8 = '800gbase-dr8' + TYPE_800GE_SR8 = '800gbase-sr8' + TYPE_800GE_VR8 = '800gbase-vr8' + + # Ethernet (modular) TYPE_100ME_SFP = '100base-x-sfp' - TYPE_1GE_FIXED = '1000base-t' - TYPE_1GE_SX_FIXED = '1000base-sx' - TYPE_1GE_LX_FIXED = '1000base-lx' - TYPE_1GE_TX_FIXED = '1000base-tx' TYPE_1GE_GBIC = '1000base-x-gbic' TYPE_1GE_SFP = '1000base-x-sfp' - TYPE_2GE_FIXED = '2.5gbase-t' - TYPE_5GE_FIXED = '5gbase-t' - TYPE_10GE_FIXED = '10gbase-t' - TYPE_10GE_CX4 = '10gbase-cx4' TYPE_10GE_SFP_PLUS = '10gbase-x-sfpp' TYPE_10GE_XFP = '10gbase-x-xfp' TYPE_10GE_XENPAK = '10gbase-x-xenpak' @@ -935,7 +1031,7 @@ class InterfaceTypeChoices(ChoiceSet): TYPE_800GE_QSFP_DD = '800gbase-x-qsfpdd' TYPE_800GE_OSFP = '800gbase-x-osfp' - # Ethernet Backplane + # Backplane Ethernet TYPE_1GE_KX = '1000base-kx' TYPE_2GE_KX = '2.5gbase-kx' TYPE_5GE_KR = '5gbase-kr' @@ -1054,24 +1150,147 @@ class InterfaceTypeChoices(ChoiceSet): ), ), ( - _('Ethernet (fixed)'), + _('FastEthernet (100 Mbps)'), ( - (TYPE_100ME_FX, '100BASE-FX (10/100ME FIBER)'), - (TYPE_100ME_LFX, '100BASE-LFX (10/100ME FIBER)'), + (TYPE_100ME_FX, '100BASE-FX (10/100ME)'), + (TYPE_100ME_LFX, '100BASE-LFX (10/100ME)'), (TYPE_100ME_FIXED, '100BASE-TX (10/100ME)'), - (TYPE_100ME_T1, '100BASE-T1 (10/100ME Single Pair)'), - (TYPE_1GE_FIXED, '1000BASE-T (1GE)'), + (TYPE_100ME_T1, '100BASE-T1 (10/100ME)'), + ), + ), + ( + _('GigabitEthernet (1 Gbps)'), + ( + (TYPE_1GE_BX10_D, '1000BASE-BX10-D (1GE BiDi Down)'), + (TYPE_1GE_BX10_U, '1000BASE-BX10-U (1GE BiDi Up)'), + (TYPE_1GE_CX, '1000BASE-CX (1GE DAC)'), + (TYPE_1GE_CWDM, '1000BASE-CWDM (1GE)'), + (TYPE_1GE_DWDM, '1000BASE-DWDM (1GE)'), + (TYPE_1GE_EX, '1000BASE-EX (1GE)'), (TYPE_1GE_SX_FIXED, '1000BASE-SX (1GE)'), + (TYPE_1GE_LSX, '1000BASE-LSX (1GE)'), (TYPE_1GE_LX_FIXED, '1000BASE-LX (1GE)'), + (TYPE_1GE_LX10, '1000BASE-LX10/LH (1GE)'), + (TYPE_1GE_FIXED, '1000BASE-T (1GE)'), (TYPE_1GE_TX_FIXED, '1000BASE-TX (1GE)'), + (TYPE_1GE_ZX, '1000BASE-ZX (1GE)'), + ), + ), + ( + _('2.5/5 Gbps Ethernet'), + ( (TYPE_2GE_FIXED, '2.5GBASE-T (2.5GE)'), (TYPE_5GE_FIXED, '5GBASE-T (5GE)'), + ), + ), + ( + _('10 Gbps Ethernet'), + ( + (TYPE_10GE_BR_D, '10GBASE-DR-D (10GE BiDi Down)'), + (TYPE_10GE_BR_U, '10GBASE-DR-U (10GE BiDi Up)'), + (TYPE_10GE_CX4, '10GBASE-CX4 (10GE DAC)'), + (TYPE_10GE_ER, '10GBASE-ER (10GE)'), + (TYPE_10GE_LR, '10GBASE-LR (10GE)'), + (TYPE_10GE_LRM, '10GBASE-LRM (10GE)'), + (TYPE_10GE_LX4, '10GBASE-LX4 (10GE)'), + (TYPE_10GE_SR, '10GBASE-SR (10GE)'), (TYPE_10GE_FIXED, '10GBASE-T (10GE)'), - (TYPE_10GE_CX4, '10GBASE-CX4 (10GE)'), + (TYPE_10GE_ZR, '10GBASE-ZR (10GE)'), ) ), ( - _('Ethernet (modular)'), + _('25 Gbps Ethernet'), + ( + (TYPE_25GE_CR, '25GBASE-CR (25GE DAC)'), + (TYPE_25GE_ER, '25GBASE-ER (25GE)'), + (TYPE_25GE_LR, '25GBASE-LR (25GE)'), + (TYPE_25GE_SR, '25GBASE-SR (25GE)'), + (TYPE_25GE_T, '25GBASE-T (25GE)'), + ) + ), + ( + _('40 Gbps Ethernet'), + ( + (TYPE_40GE_CR4, '40GBASE-CR4 (40GE DAC)'), + (TYPE_40GE_ER4, '40GBASE-ER4 (40GE)'), + (TYPE_40GE_FR4, '40GBASE-FR4 (40GE)'), + (TYPE_40GE_LR4, '40GBASE-LR4 (40GE)'), + (TYPE_40GE_SR4, '40GBASE-SR4 (40GE)'), + ) + ), + ( + _('50 Gbps Ethernet'), + ( + (TYPE_50GE_CR, '50GBASE-CR (50GE DAC)'), + (TYPE_50GE_ER, '50GBASE-ER (50GE)'), + (TYPE_50GE_FR, '50GBASE-FR (50GE)'), + (TYPE_50GE_LR, '50GBASE-LR (50GE)'), + (TYPE_50GE_SR, '50GBASE-SR (50GE)'), + ) + ), + ( + _('100 Gbps Ethernet'), + ( + (TYPE_100GE_CR1, '100GBASE-CR1 (100GE DAC)'), + (TYPE_100GE_CR2, '100GBASE-CR2 (100GE DAC)'), + (TYPE_100GE_CR4, '100GBASE-CR4 (100GE DAC)'), + (TYPE_100GE_CR10, '100GBASE-CR10 (100GE DAC)'), + (TYPE_100GE_DR, '100GBASE-DR (100GE)'), + (TYPE_100GE_ER4, '100GBASE-ER4 (100GE)'), + (TYPE_100GE_FR1, '100GBASE-FR1 (100GE)'), + (TYPE_100GE_LR1, '100GBASE-LR1 (100GE)'), + (TYPE_100GE_LR4, '100GBASE-LR4 (100GE)'), + (TYPE_100GE_SR1, '100GBASE-SR1 (100GE)'), + (TYPE_100GE_SR1_2, '100GBASE-SR1.2 (100GE BiDi)'), + (TYPE_100GE_SR2, '100GBASE-SR2 (100GE)'), + (TYPE_100GE_SR4, '100GBASE-SR4 (100GE)'), + (TYPE_100GE_SR10, '100GBASE-SR10 (100GE)'), + (TYPE_100GE_ZR, '100GBASE-ZR (100GE)'), + ) + ), + ( + _('200 Gbps Ethernet'), + ( + (TYPE_200GE_CR2, '200GBASE-CR2 (200GE)'), + (TYPE_200GE_CR4, '200GBASE-CR4 (200GE)'), + (TYPE_200GE_SR2, '200GBASE-SR2 (200GE)'), + (TYPE_200GE_SR4, '200GBASE-SR4 (200GE)'), + (TYPE_200GE_DR4, '200GBASE-DR4 (200GE)'), + (TYPE_200GE_ER4, '200GBASE-ER4 (200GE)'), + (TYPE_200GE_FR4, '200GBASE-FR4 (200GE)'), + (TYPE_200GE_LR4, '200GBASE-LR4 (200GE)'), + (TYPE_200GE_VR2, '200GBASE-VR2 (200GE)'), + ) + ), + ( + _('400 Gbps Ethernet'), + ( + (TYPE_400GE_CR4, '400GBASE-CR4 (400GE)'), + (TYPE_400GE_DR4, '400GBASE-DR4 (400GE)'), + (TYPE_400GE_ER8, '400GBASE-ER8 (400GE)'), + (TYPE_400GE_FR4, '400GBASE-FR4 (400GE)'), + (TYPE_400GE_FR8, '400GBASE-FR8 (400GE)'), + (TYPE_400GE_LR4, '400GBASE-LR4 (400GE)'), + (TYPE_400GE_LR8, '400GBASE-LR8 (400GE)'), + (TYPE_400GE_SR4, '400GBASE-SR4 (400GE)'), + (TYPE_400GE_SR4_2, '400GBASE-SR4.2 (400GE BiDi)'), + (TYPE_400GE_SR8, '400GBASE-SR8 (400GE)'), + (TYPE_400GE_SR16, '400GBASE-SR16 (400GE)'), + (TYPE_400GE_VR4, '400GBASE-VR4 (400GE)'), + (TYPE_400GE_ZR, '400GBASE-ZR (400GE)'), + ) + ), + ( + _('800 Gbps Ethernet'), + ( + (TYPE_800GE_CR8, '800GBASE-CR8 (800GE)'), + (TYPE_800GE_DR8, '800GBASE-DR8 (800GE)'), + (TYPE_800GE_SR8, '800GBASE-SR8 (800GE)'), + (TYPE_800GE_VR8, '800GBASE-VR8 (800GE)'), + ) + ), + ( + _('Pluggable transceivers'), ( (TYPE_100ME_SFP, 'SFP (100ME)'), (TYPE_1GE_GBIC, 'GBIC (1GE)'), @@ -1108,7 +1327,7 @@ class InterfaceTypeChoices(ChoiceSet): ) ), ( - _('Ethernet (backplane)'), + _('Backplane Ethernet'), ( (TYPE_1GE_KX, '1000BASE-KX (1GE)'), (TYPE_2GE_KX, '2.5GBASE-KX (2.5GE)'), From 85689b25de276e7d688e7931269e6bd758fee022 Mon Sep 17 00:00:00 2001 From: Robin Schneider Date: Mon, 15 Sep 2025 20:52:46 +0200 Subject: [PATCH 37/40] feat: add Wi-Fi Alliance generation labels to Interface type texts (#20348) * feat: add Wi-Fi Alliance generation labels to Interface type texts Closes: #20347 * Shorten labels for WiGig choices --------- Co-authored-by: Jeremy Stretch --- netbox/dcim/choices.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index d44fb22b231..328b09a6cf7 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -1347,12 +1347,12 @@ class InterfaceTypeChoices(ChoiceSet): ( (TYPE_80211A, 'IEEE 802.11a'), (TYPE_80211G, 'IEEE 802.11b/g'), - (TYPE_80211N, 'IEEE 802.11n'), - (TYPE_80211AC, 'IEEE 802.11ac'), - (TYPE_80211AD, 'IEEE 802.11ad'), - (TYPE_80211AX, 'IEEE 802.11ax'), - (TYPE_80211AY, 'IEEE 802.11ay'), - (TYPE_80211BE, 'IEEE 802.11be'), + (TYPE_80211N, 'IEEE 802.11n (Wi-Fi 4)'), + (TYPE_80211AC, 'IEEE 802.11ac (Wi-Fi 5)'), + (TYPE_80211AD, 'IEEE 802.11ad (WiGig)'), + (TYPE_80211AX, 'IEEE 802.11ax (Wi-Fi 6)'), + (TYPE_80211AY, 'IEEE 802.11ay (WiGig)'), + (TYPE_80211BE, 'IEEE 802.11be (Wi-Fi 7)'), (TYPE_802151, 'IEEE 802.15.1 (Bluetooth)'), (TYPE_802154, 'IEEE 802.15.4 (LR-WPAN)'), (TYPE_OTHER_WIRELESS, 'Other (Wireless)'), From 5bfbca9a83cb85284e55e43d46e35d897ad795a8 Mon Sep 17 00:00:00 2001 From: Jason Novinger Date: Mon, 15 Sep 2025 23:49:43 +0000 Subject: [PATCH 38/40] Fixes #20298: Add placeholder for failed image thumbnail generation (#20359) --- netbox/templates/extras/object_imageattachments.html | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/netbox/templates/extras/object_imageattachments.html b/netbox/templates/extras/object_imageattachments.html index 9c3c137a9ac..981927d455a 100644 --- a/netbox/templates/extras/object_imageattachments.html +++ b/netbox/templates/extras/object_imageattachments.html @@ -27,6 +27,16 @@ alt="{{ object.description|default:object.name }}" /> + {% empty %} + +
+
+ +
{% trans "Thumbnail cannot be generated" %}
+
{% trans "Click to view original" %}
+
+
+
{% endthumbnail %}
{{ object }} From 81401b9e1742de4c19fc8511f8ffd5fc08e9b92d Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 05:02:30 +0000 Subject: [PATCH 39/40] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 392 +++++++++++-------- 1 file changed, 230 insertions(+), 162 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index 82c22480841..719c7e3d05c 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: 2025-09-13 05:03+0000\n" +"POT-Creation-Date: 2025-09-16 05:02+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -20,7 +20,7 @@ msgstr "" #: netbox/account/tables.py:27 netbox/templates/account/token.html:22 #: netbox/templates/users/token.html:17 netbox/users/forms/bulk_import.py:39 -#: netbox/users/forms/model_forms.py:113 +#: netbox/users/forms/model_forms.py:119 msgid "Key" msgstr "" @@ -57,7 +57,7 @@ msgstr "" #: netbox/account/tables.py:45 netbox/templates/account/token.html:55 #: netbox/templates/users/token.html:47 netbox/users/forms/bulk_edit.py:122 -#: netbox/users/forms/model_forms.py:125 +#: netbox/users/forms/model_forms.py:131 msgid "Allowed IPs" msgstr "" @@ -84,9 +84,9 @@ msgstr "" #: netbox/circuits/choices.py:21 netbox/dcim/choices.py:20 #: netbox/dcim/choices.py:102 netbox/dcim/choices.py:204 -#: netbox/dcim/choices.py:257 netbox/dcim/choices.py:1571 -#: netbox/dcim/choices.py:1629 netbox/dcim/choices.py:1696 -#: netbox/dcim/choices.py:1718 netbox/virtualization/choices.py:20 +#: netbox/dcim/choices.py:257 netbox/dcim/choices.py:1835 +#: netbox/dcim/choices.py:1893 netbox/dcim/choices.py:1960 +#: netbox/dcim/choices.py:1982 netbox/virtualization/choices.py:20 #: netbox/virtualization/choices.py:46 netbox/vpn/choices.py:18 #: netbox/vpn/choices.py:281 msgid "Planned" @@ -100,8 +100,8 @@ msgstr "" #: netbox/core/tables/tasks.py:23 netbox/dcim/choices.py:22 #: netbox/dcim/choices.py:103 netbox/dcim/choices.py:155 #: netbox/dcim/choices.py:203 netbox/dcim/choices.py:256 -#: netbox/dcim/choices.py:1628 netbox/dcim/choices.py:1695 -#: netbox/dcim/choices.py:1717 netbox/extras/tables/tables.py:598 +#: netbox/dcim/choices.py:1892 netbox/dcim/choices.py:1959 +#: netbox/dcim/choices.py:1981 netbox/extras/tables/tables.py:598 #: netbox/ipam/choices.py:31 netbox/ipam/choices.py:49 #: netbox/ipam/choices.py:69 netbox/ipam/choices.py:154 #: netbox/templates/extras/configcontext.html:29 @@ -113,8 +113,8 @@ msgid "Active" msgstr "" #: netbox/circuits/choices.py:24 netbox/dcim/choices.py:202 -#: netbox/dcim/choices.py:255 netbox/dcim/choices.py:1627 -#: netbox/dcim/choices.py:1697 netbox/dcim/choices.py:1716 +#: netbox/dcim/choices.py:255 netbox/dcim/choices.py:1891 +#: netbox/dcim/choices.py:1961 netbox/dcim/choices.py:1980 #: netbox/virtualization/choices.py:24 netbox/virtualization/choices.py:44 msgid "Offline" msgstr "" @@ -127,7 +127,7 @@ msgstr "" msgid "Decommissioned" msgstr "" -#: netbox/circuits/choices.py:90 netbox/dcim/choices.py:1640 +#: netbox/circuits/choices.py:90 netbox/dcim/choices.py:1904 #: netbox/templates/dcim/interface.html:135 #: netbox/templates/virtualization/vminterface.html:83 #: netbox/tenancy/choices.py:17 @@ -167,7 +167,7 @@ msgstr "" #: netbox/dcim/filtersets.py:467 netbox/dcim/filtersets.py:1108 #: netbox/dcim/filtersets.py:1430 netbox/dcim/filtersets.py:1528 #: netbox/dcim/filtersets.py:2221 netbox/dcim/filtersets.py:2464 -#: netbox/dcim/filtersets.py:2522 netbox/ipam/filtersets.py:954 +#: netbox/dcim/filtersets.py:2522 netbox/ipam/filtersets.py:955 #: netbox/virtualization/filtersets.py:139 netbox/vpn/filtersets.py:361 msgid "Region (ID)" msgstr "" @@ -180,7 +180,7 @@ msgstr "" #: netbox/dcim/filtersets.py:1437 netbox/dcim/filtersets.py:1535 #: netbox/dcim/filtersets.py:2228 netbox/dcim/filtersets.py:2471 #: netbox/dcim/filtersets.py:2529 netbox/extras/filtersets.py:646 -#: netbox/ipam/filtersets.py:961 netbox/virtualization/filtersets.py:146 +#: netbox/ipam/filtersets.py:962 netbox/virtualization/filtersets.py:146 #: netbox/vpn/filtersets.py:356 msgid "Region (slug)" msgstr "" @@ -192,7 +192,7 @@ msgstr "" #: netbox/dcim/filtersets.py:1121 netbox/dcim/filtersets.py:1443 #: netbox/dcim/filtersets.py:1541 netbox/dcim/filtersets.py:2234 #: netbox/dcim/filtersets.py:2477 netbox/dcim/filtersets.py:2535 -#: netbox/ipam/filtersets.py:239 netbox/ipam/filtersets.py:967 +#: netbox/ipam/filtersets.py:239 netbox/ipam/filtersets.py:968 #: netbox/virtualization/filtersets.py:152 msgid "Site group (ID)" msgstr "" @@ -205,7 +205,7 @@ msgstr "" #: netbox/dcim/filtersets.py:1548 netbox/dcim/filtersets.py:2241 #: netbox/dcim/filtersets.py:2484 netbox/dcim/filtersets.py:2542 #: netbox/extras/filtersets.py:652 netbox/ipam/filtersets.py:246 -#: netbox/ipam/filtersets.py:974 netbox/virtualization/filtersets.py:159 +#: netbox/ipam/filtersets.py:975 netbox/virtualization/filtersets.py:159 msgid "Site group (slug)" msgstr "" @@ -233,7 +233,7 @@ msgstr "" #: netbox/extras/filtersets.py:662 netbox/ipam/forms/bulk_edit.py:479 #: netbox/ipam/forms/bulk_import.py:475 netbox/ipam/forms/filtersets.py:161 #: netbox/ipam/forms/filtersets.py:236 netbox/ipam/forms/filtersets.py:457 -#: netbox/ipam/forms/filtersets.py:552 netbox/ipam/forms/model_forms.py:680 +#: netbox/ipam/forms/filtersets.py:552 netbox/ipam/forms/model_forms.py:673 #: netbox/ipam/tables/vlans.py:89 netbox/ipam/tables/vlans.py:199 #: netbox/templates/dcim/device.html:22 #: netbox/templates/dcim/inc/cable_termination.html:8 @@ -262,7 +262,7 @@ msgstr "" #: netbox/circuits/filtersets.py:315 netbox/dcim/base_filtersets.py:53 #: netbox/dcim/filtersets.py:245 netbox/dcim/filtersets.py:366 #: netbox/dcim/filtersets.py:461 netbox/extras/filtersets.py:668 -#: netbox/ipam/filtersets.py:257 netbox/ipam/filtersets.py:984 +#: netbox/ipam/filtersets.py:257 netbox/ipam/filtersets.py:985 #: netbox/virtualization/filtersets.py:169 netbox/vpn/filtersets.py:366 msgid "Site (slug)" msgstr "" @@ -321,7 +321,7 @@ msgstr "" #: netbox/dcim/filtersets.py:1132 netbox/dcim/filtersets.py:1455 #: netbox/dcim/filtersets.py:1553 netbox/dcim/filtersets.py:2246 #: netbox/dcim/filtersets.py:2488 netbox/dcim/filtersets.py:2547 -#: netbox/ipam/filtersets.py:251 netbox/ipam/filtersets.py:978 +#: netbox/ipam/filtersets.py:251 netbox/ipam/filtersets.py:979 #: netbox/virtualization/filtersets.py:163 netbox/vpn/filtersets.py:371 msgid "Site (ID)" msgstr "" @@ -812,7 +812,7 @@ msgstr "" #: netbox/templates/vpn/l2vpn.html:26 netbox/templates/vpn/tunnel.html:25 #: netbox/templates/wireless/wirelesslan.html:22 #: netbox/templates/wireless/wirelesslink.html:17 -#: netbox/users/forms/filtersets.py:32 netbox/users/forms/model_forms.py:195 +#: netbox/users/forms/filtersets.py:32 netbox/users/forms/model_forms.py:201 #: netbox/virtualization/forms/bulk_edit.py:71 #: netbox/virtualization/forms/bulk_edit.py:100 #: netbox/virtualization/forms/bulk_import.py:55 @@ -1003,7 +1003,7 @@ msgstr "" #: netbox/ipam/forms/model_forms.py:85 netbox/ipam/forms/model_forms.py:120 #: netbox/ipam/forms/model_forms.py:142 netbox/ipam/forms/model_forms.py:167 #: netbox/ipam/forms/model_forms.py:234 netbox/ipam/forms/model_forms.py:272 -#: netbox/ipam/forms/model_forms.py:331 netbox/ipam/forms/model_forms.py:632 +#: netbox/ipam/forms/model_forms.py:331 netbox/ipam/forms/model_forms.py:625 #: netbox/netbox/navigation/menu.py:24 #: netbox/templates/dcim/device_edit.html:87 #: netbox/templates/dcim/htmx/cable_edit.html:75 @@ -1109,7 +1109,7 @@ msgstr "" #: netbox/ipam/forms/filtersets.py:305 netbox/ipam/forms/filtersets.py:384 #: netbox/ipam/forms/filtersets.py:572 netbox/ipam/forms/model_forms.py:195 #: netbox/ipam/forms/model_forms.py:221 netbox/ipam/forms/model_forms.py:260 -#: netbox/ipam/forms/model_forms.py:695 netbox/ipam/tables/ip.py:210 +#: netbox/ipam/forms/model_forms.py:688 netbox/ipam/tables/ip.py:210 #: netbox/ipam/tables/ip.py:269 netbox/ipam/tables/ip.py:325 #: netbox/ipam/tables/vlans.py:101 netbox/ipam/tables/vlans.py:213 #: netbox/templates/circuits/virtualcircuittermination.html:42 @@ -1377,7 +1377,7 @@ msgstr "" #: netbox/templates/extras/configcontext.html:36 #: netbox/templates/ipam/ipaddress.html:59 #: netbox/templates/ipam/vlan_edit.html:42 -#: netbox/tenancy/forms/filtersets.py:87 netbox/users/forms/model_forms.py:324 +#: netbox/tenancy/forms/filtersets.py:87 netbox/users/forms/model_forms.py:339 msgid "Assignment" msgstr "" @@ -1386,7 +1386,7 @@ msgstr "" #: netbox/circuits/tables/circuits.py:191 netbox/dcim/forms/bulk_edit.py:127 #: netbox/dcim/forms/bulk_import.py:103 netbox/dcim/forms/model_forms.py:126 #: netbox/dcim/tables/sites.py:103 netbox/extras/forms/filtersets.py:572 -#: netbox/ipam/filtersets.py:994 netbox/ipam/forms/bulk_edit.py:488 +#: netbox/ipam/filtersets.py:995 netbox/ipam/forms/bulk_edit.py:488 #: netbox/ipam/forms/bulk_import.py:482 netbox/ipam/forms/model_forms.py:571 #: netbox/ipam/tables/fhrp.py:67 netbox/ipam/tables/vlans.py:93 #: netbox/ipam/tables/vlans.py:204 @@ -1579,7 +1579,7 @@ msgstr "" #: netbox/extras/models/notifications.py:131 netbox/extras/models/tags.py:33 #: netbox/ipam/models/vlans.py:373 netbox/netbox/models/__init__.py:115 #: netbox/netbox/models/__init__.py:150 netbox/netbox/models/__init__.py:200 -#: netbox/users/models/permissions.py:23 netbox/users/models/tokens.py:57 +#: netbox/users/models/permissions.py:24 netbox/users/models/tokens.py:57 #: netbox/users/models/users.py:33 #: netbox/virtualization/models/virtualmachines.py:281 msgid "description" @@ -1620,7 +1620,7 @@ msgstr "" #: netbox/ipam/models/vrfs.py:75 netbox/netbox/models/__init__.py:142 #: netbox/netbox/models/__init__.py:190 netbox/tenancy/models/contacts.py:56 #: netbox/tenancy/models/tenants.py:19 netbox/tenancy/models/tenants.py:42 -#: netbox/users/models/permissions.py:19 netbox/users/models/users.py:28 +#: netbox/users/models/permissions.py:20 netbox/users/models/users.py:28 #: netbox/virtualization/models/clusters.py:52 #: netbox/virtualization/models/virtualmachines.py:71 #: netbox/virtualization/models/virtualmachines.py:276 @@ -2084,7 +2084,7 @@ msgstr "" #: netbox/core/choices.py:22 netbox/core/choices.py:59 #: netbox/core/constants.py:21 netbox/core/tables/tasks.py:35 #: netbox/dcim/choices.py:206 netbox/dcim/choices.py:259 -#: netbox/dcim/choices.py:1630 netbox/dcim/choices.py:1720 +#: netbox/dcim/choices.py:1894 netbox/dcim/choices.py:1984 #: netbox/virtualization/choices.py:48 msgid "Failed" msgstr "" @@ -2212,7 +2212,7 @@ msgstr "" #: netbox/core/data_backends.py:56 netbox/templates/account/base.html:23 #: netbox/templates/account/password.html:12 -#: netbox/users/forms/model_forms.py:171 +#: netbox/users/forms/model_forms.py:177 msgid "Password" msgstr "" @@ -2255,7 +2255,7 @@ msgid "User name" msgstr "" #: netbox/core/forms/bulk_edit.py:26 netbox/core/forms/filtersets.py:43 -#: netbox/core/tables/data.py:27 netbox/dcim/choices.py:1678 +#: netbox/core/tables/data.py:27 netbox/dcim/choices.py:1942 #: netbox/dcim/forms/bulk_edit.py:1201 netbox/dcim/forms/bulk_edit.py:1482 #: netbox/dcim/forms/filtersets.py:1458 netbox/dcim/tables/devices.py:586 #: netbox/dcim/tables/devicetypes.py:231 netbox/extras/forms/bulk_edit.py:127 @@ -2382,7 +2382,7 @@ msgstr "" #: netbox/templates/users/user.html:4 netbox/templates/users/user.html:12 #: netbox/users/filtersets.py:107 netbox/users/filtersets.py:174 #: netbox/users/forms/filtersets.py:84 netbox/users/forms/filtersets.py:125 -#: netbox/users/forms/model_forms.py:156 netbox/users/forms/model_forms.py:193 +#: netbox/users/forms/model_forms.py:162 netbox/users/forms/model_forms.py:199 #: netbox/users/tables.py:19 msgid "User" msgstr "" @@ -2443,7 +2443,7 @@ msgstr "" msgid "Rack Elevations" msgstr "" -#: netbox/core/forms/model_forms.py:160 netbox/dcim/choices.py:1559 +#: netbox/core/forms/model_forms.py:160 netbox/dcim/choices.py:1813 #: netbox/dcim/forms/bulk_edit.py:1044 netbox/dcim/forms/bulk_edit.py:1436 #: netbox/dcim/forms/bulk_edit.py:1457 netbox/dcim/tables/racks.py:161 #: netbox/netbox/navigation/menu.py:313 netbox/netbox/navigation/menu.py:317 @@ -2486,7 +2486,7 @@ msgstr "" #: netbox/core/forms/model_forms.py:170 netbox/dcim/forms/filtersets.py:762 #: netbox/templates/core/inc/config_data.html:127 -#: netbox/users/forms/model_forms.py:65 +#: netbox/users/forms/model_forms.py:71 msgid "Miscellaneous" msgstr "" @@ -2617,7 +2617,7 @@ msgstr "" #: netbox/dcim/models/device_components.py:548 #: netbox/extras/models/models.py:74 netbox/extras/models/models.py:313 #: netbox/extras/models/models.py:494 netbox/extras/models/models.py:573 -#: netbox/users/models/permissions.py:28 +#: netbox/users/models/permissions.py:29 msgid "enabled" msgstr "" @@ -3081,8 +3081,8 @@ msgid "Staging" msgstr "" #: netbox/dcim/choices.py:23 netbox/dcim/choices.py:208 -#: netbox/dcim/choices.py:260 netbox/dcim/choices.py:1572 -#: netbox/dcim/choices.py:1721 netbox/virtualization/choices.py:23 +#: netbox/dcim/choices.py:260 netbox/dcim/choices.py:1836 +#: netbox/dcim/choices.py:1985 netbox/virtualization/choices.py:23 #: netbox/virtualization/choices.py:49 netbox/vpn/choices.py:282 msgid "Decommissioning" msgstr "" @@ -3147,7 +3147,7 @@ msgstr "" msgid "Millimeters" msgstr "" -#: netbox/dcim/choices.py:115 netbox/dcim/choices.py:1594 +#: netbox/dcim/choices.py:115 netbox/dcim/choices.py:1858 msgid "Inches" msgstr "" @@ -3183,7 +3183,7 @@ msgstr "" #: netbox/dcim/tables/devices.py:1006 netbox/dcim/tables/devices.py:1165 #: netbox/dcim/tables/sites.py:28 netbox/dcim/tables/sites.py:62 #: netbox/dcim/tables/sites.py:147 netbox/ipam/forms/bulk_import.py:568 -#: netbox/ipam/forms/model_forms.py:777 netbox/ipam/tables/fhrp.py:59 +#: netbox/ipam/forms/model_forms.py:770 netbox/ipam/tables/fhrp.py:59 #: netbox/ipam/tables/ip.py:336 netbox/ipam/tables/services.py:44 #: netbox/templates/dcim/devicerole.html:34 #: netbox/templates/dcim/interface.html:108 @@ -3231,7 +3231,7 @@ msgid "Rear" msgstr "" #: netbox/dcim/choices.py:205 netbox/dcim/choices.py:258 -#: netbox/dcim/choices.py:1719 netbox/virtualization/choices.py:47 +#: netbox/dcim/choices.py:1983 netbox/virtualization/choices.py:47 msgid "Staged" msgstr "" @@ -3264,7 +3264,7 @@ msgid "Top to bottom" msgstr "" #: netbox/dcim/choices.py:235 netbox/dcim/choices.py:280 -#: netbox/dcim/choices.py:1338 +#: netbox/dcim/choices.py:1557 msgid "Passive" msgstr "" @@ -3293,8 +3293,8 @@ msgid "Proprietary" msgstr "" #: netbox/dcim/choices.py:604 netbox/dcim/choices.py:849 -#: netbox/dcim/choices.py:1250 netbox/dcim/choices.py:1252 -#: netbox/dcim/choices.py:1488 netbox/dcim/choices.py:1490 +#: netbox/dcim/choices.py:1469 netbox/dcim/choices.py:1471 +#: netbox/dcim/choices.py:1707 netbox/dcim/choices.py:1709 #: netbox/netbox/navigation/menu.py:209 msgid "Other" msgstr "" @@ -3307,11 +3307,11 @@ msgstr "" msgid "Physical" msgstr "" -#: netbox/dcim/choices.py:880 netbox/dcim/choices.py:1051 +#: netbox/dcim/choices.py:880 netbox/dcim/choices.py:1147 msgid "Virtual" msgstr "" -#: netbox/dcim/choices.py:881 netbox/dcim/choices.py:1127 +#: netbox/dcim/choices.py:881 netbox/dcim/choices.py:1346 #: netbox/dcim/forms/bulk_edit.py:1642 netbox/dcim/forms/filtersets.py:1418 #: netbox/dcim/forms/model_forms.py:1126 netbox/dcim/forms/model_forms.py:1579 #: netbox/netbox/navigation/menu.py:147 netbox/netbox/navigation/menu.py:151 @@ -3319,11 +3319,11 @@ msgstr "" msgid "Wireless" msgstr "" -#: netbox/dcim/choices.py:1049 +#: netbox/dcim/choices.py:1145 msgid "Virtual interfaces" msgstr "" -#: netbox/dcim/choices.py:1052 netbox/dcim/forms/bulk_edit.py:1495 +#: netbox/dcim/choices.py:1148 netbox/dcim/forms/bulk_edit.py:1495 #: netbox/dcim/forms/bulk_import.py:922 netbox/dcim/forms/model_forms.py:1108 #: netbox/dcim/tables/devices.py:713 netbox/templates/dcim/interface.html:112 #: netbox/templates/virtualization/vminterface.html:43 @@ -3333,27 +3333,67 @@ msgstr "" msgid "Bridge" msgstr "" -#: netbox/dcim/choices.py:1053 +#: netbox/dcim/choices.py:1149 msgid "Link Aggregation Group (LAG)" msgstr "" -#: netbox/dcim/choices.py:1057 -msgid "Ethernet (fixed)" +#: netbox/dcim/choices.py:1153 +msgid "FastEthernet (100 Mbps)" msgstr "" -#: netbox/dcim/choices.py:1074 -msgid "Ethernet (modular)" +#: netbox/dcim/choices.py:1162 +msgid "GigabitEthernet (1 Gbps)" msgstr "" -#: netbox/dcim/choices.py:1111 -msgid "Ethernet (backplane)" +#: netbox/dcim/choices.py:1180 +msgid "2.5/5 Gbps Ethernet" msgstr "" -#: netbox/dcim/choices.py:1143 +#: netbox/dcim/choices.py:1187 +msgid "10 Gbps Ethernet" +msgstr "" + +#: netbox/dcim/choices.py:1202 +msgid "25 Gbps Ethernet" +msgstr "" + +#: netbox/dcim/choices.py:1212 +msgid "40 Gbps Ethernet" +msgstr "" + +#: netbox/dcim/choices.py:1222 +msgid "50 Gbps Ethernet" +msgstr "" + +#: netbox/dcim/choices.py:1232 +msgid "100 Gbps Ethernet" +msgstr "" + +#: netbox/dcim/choices.py:1252 +msgid "200 Gbps Ethernet" +msgstr "" + +#: netbox/dcim/choices.py:1266 +msgid "400 Gbps Ethernet" +msgstr "" + +#: netbox/dcim/choices.py:1284 +msgid "800 Gbps Ethernet" +msgstr "" + +#: netbox/dcim/choices.py:1293 +msgid "Pluggable transceivers" +msgstr "" + +#: netbox/dcim/choices.py:1330 +msgid "Backplane Ethernet" +msgstr "" + +#: netbox/dcim/choices.py:1362 msgid "Cellular" msgstr "" -#: netbox/dcim/choices.py:1195 netbox/dcim/forms/filtersets.py:385 +#: netbox/dcim/choices.py:1414 netbox/dcim/forms/filtersets.py:385 #: netbox/dcim/forms/filtersets.py:839 netbox/dcim/forms/filtersets.py:1041 #: netbox/dcim/forms/filtersets.py:1640 #: netbox/templates/dcim/inventoryitem.html:56 @@ -3361,127 +3401,147 @@ msgstr "" msgid "Serial" msgstr "" -#: netbox/dcim/choices.py:1210 +#: netbox/dcim/choices.py:1429 msgid "Coaxial" msgstr "" -#: netbox/dcim/choices.py:1231 +#: netbox/dcim/choices.py:1450 msgid "Stacking" msgstr "" -#: netbox/dcim/choices.py:1283 +#: netbox/dcim/choices.py:1502 msgid "Half" msgstr "" -#: netbox/dcim/choices.py:1284 +#: netbox/dcim/choices.py:1503 msgid "Full" msgstr "" -#: netbox/dcim/choices.py:1285 netbox/netbox/preferences.py:42 +#: netbox/dcim/choices.py:1504 netbox/netbox/preferences.py:42 #: netbox/wireless/choices.py:480 msgid "Auto" msgstr "" -#: netbox/dcim/choices.py:1297 +#: netbox/dcim/choices.py:1516 msgid "Access" msgstr "" -#: netbox/dcim/choices.py:1298 netbox/ipam/tables/vlans.py:150 +#: netbox/dcim/choices.py:1517 netbox/ipam/tables/vlans.py:150 #: netbox/ipam/tables/vlans.py:195 #: netbox/templates/dcim/inc/interface_vlans_table.html:7 msgid "Tagged" msgstr "" -#: netbox/dcim/choices.py:1299 +#: netbox/dcim/choices.py:1518 msgid "Tagged (All)" msgstr "" -#: netbox/dcim/choices.py:1300 netbox/templates/ipam/vlan_edit.html:26 +#: netbox/dcim/choices.py:1519 netbox/templates/ipam/vlan_edit.html:26 msgid "Q-in-Q (802.1ad)" msgstr "" -#: netbox/dcim/choices.py:1329 +#: netbox/dcim/choices.py:1548 msgid "IEEE Standard" msgstr "" -#: netbox/dcim/choices.py:1340 +#: netbox/dcim/choices.py:1559 msgid "Passive 24V (2-pair)" msgstr "" -#: netbox/dcim/choices.py:1341 +#: netbox/dcim/choices.py:1560 msgid "Passive 24V (4-pair)" msgstr "" -#: netbox/dcim/choices.py:1342 +#: netbox/dcim/choices.py:1561 msgid "Passive 48V (2-pair)" msgstr "" -#: netbox/dcim/choices.py:1343 +#: netbox/dcim/choices.py:1562 msgid "Passive 48V (4-pair)" msgstr "" -#: netbox/dcim/choices.py:1416 netbox/dcim/choices.py:1529 +#: netbox/dcim/choices.py:1635 msgid "Copper" msgstr "" -#: netbox/dcim/choices.py:1439 +#: netbox/dcim/choices.py:1658 msgid "Fiber Optic" msgstr "" -#: netbox/dcim/choices.py:1475 netbox/dcim/choices.py:1558 +#: netbox/dcim/choices.py:1694 netbox/dcim/choices.py:1819 msgid "USB" msgstr "" -#: netbox/dcim/choices.py:1545 -msgid "Fiber" +#: netbox/dcim/choices.py:1763 +msgid "Copper - Twisted Pair (UTP/STP)" msgstr "" -#: netbox/dcim/choices.py:1570 netbox/dcim/forms/filtersets.py:1305 +#: netbox/dcim/choices.py:1777 +msgid "Copper - Twinax (DAC)" +msgstr "" + +#: netbox/dcim/choices.py:1784 +msgid "Copper - Coaxial" +msgstr "" + +#: netbox/dcim/choices.py:1790 +msgid "Fiber - Multimode" +msgstr "" + +#: netbox/dcim/choices.py:1801 +msgid "Fiber - Single-mode" +msgstr "" + +#: netbox/dcim/choices.py:1809 +msgid "Fiber - Other" +msgstr "" + +#: netbox/dcim/choices.py:1834 netbox/dcim/forms/filtersets.py:1305 msgid "Connected" msgstr "" -#: netbox/dcim/choices.py:1589 netbox/netbox/choices.py:177 +#: netbox/dcim/choices.py:1853 netbox/netbox/choices.py:177 msgid "Kilometers" msgstr "" -#: netbox/dcim/choices.py:1590 netbox/netbox/choices.py:178 +#: netbox/dcim/choices.py:1854 netbox/netbox/choices.py:178 #: netbox/templates/dcim/cable_trace.html:65 msgid "Meters" msgstr "" -#: netbox/dcim/choices.py:1591 +#: netbox/dcim/choices.py:1855 msgid "Centimeters" msgstr "" -#: netbox/dcim/choices.py:1592 netbox/netbox/choices.py:179 +#: netbox/dcim/choices.py:1856 netbox/netbox/choices.py:179 msgid "Miles" msgstr "" -#: netbox/dcim/choices.py:1593 netbox/netbox/choices.py:180 +#: netbox/dcim/choices.py:1857 netbox/netbox/choices.py:180 #: netbox/templates/dcim/cable_trace.html:66 msgid "Feet" msgstr "" -#: netbox/dcim/choices.py:1641 +#: netbox/dcim/choices.py:1905 msgid "Redundant" msgstr "" -#: netbox/dcim/choices.py:1662 +#: netbox/dcim/choices.py:1926 msgid "Single phase" msgstr "" -#: netbox/dcim/choices.py:1663 +#: netbox/dcim/choices.py:1927 msgid "Three-phase" msgstr "" -#: netbox/dcim/choices.py:1679 netbox/extras/choices.py:53 +#: netbox/dcim/choices.py:1943 netbox/extras/choices.py:53 #: netbox/netbox/preferences.py:32 netbox/netbox/preferences.py:71 #: netbox/templates/extras/customfield.html:78 netbox/vpn/choices.py:20 #: netbox/wireless/choices.py:27 msgid "Disabled" msgstr "" -#: netbox/dcim/choices.py:1680 +#: netbox/dcim/choices.py:1944 msgid "Faulty" msgstr "" @@ -3512,7 +3572,7 @@ msgid "Parent site group (slug)" msgstr "" #: netbox/dcim/filtersets.py:167 netbox/extras/filtersets.py:422 -#: netbox/ipam/filtersets.py:836 netbox/ipam/filtersets.py:988 +#: netbox/ipam/filtersets.py:837 netbox/ipam/filtersets.py:989 msgid "Group (ID)" msgstr "" @@ -3559,14 +3619,14 @@ msgstr "" #: netbox/dcim/filtersets.py:414 netbox/dcim/filtersets.py:928 #: netbox/dcim/filtersets.py:1077 netbox/dcim/filtersets.py:2164 #: netbox/ipam/filtersets.py:376 netbox/ipam/filtersets.py:488 -#: netbox/ipam/filtersets.py:998 netbox/virtualization/filtersets.py:177 +#: netbox/ipam/filtersets.py:999 netbox/virtualization/filtersets.py:177 msgid "Role (ID)" msgstr "" #: netbox/dcim/filtersets.py:420 netbox/dcim/filtersets.py:934 #: netbox/dcim/filtersets.py:1084 netbox/dcim/filtersets.py:2170 #: netbox/extras/filtersets.py:695 netbox/ipam/filtersets.py:382 -#: netbox/ipam/filtersets.py:494 netbox/ipam/filtersets.py:1004 +#: netbox/ipam/filtersets.py:494 netbox/ipam/filtersets.py:1005 #: netbox/virtualization/filtersets.py:184 msgid "Role (slug)" msgstr "" @@ -3812,14 +3872,14 @@ msgstr "" #: netbox/dcim/filtersets.py:1487 netbox/dcim/filtersets.py:1585 #: netbox/dcim/filtersets.py:1775 netbox/ipam/filtersets.py:606 -#: netbox/ipam/filtersets.py:846 netbox/ipam/filtersets.py:1174 +#: netbox/ipam/filtersets.py:847 netbox/ipam/filtersets.py:1175 #: netbox/virtualization/filtersets.py:127 netbox/vpn/filtersets.py:382 msgid "Device (ID)" msgstr "" #: netbox/dcim/filtersets.py:1493 netbox/dcim/filtersets.py:1591 #: netbox/dcim/filtersets.py:1770 netbox/ipam/filtersets.py:601 -#: netbox/ipam/filtersets.py:841 netbox/ipam/filtersets.py:1169 +#: netbox/ipam/filtersets.py:842 netbox/ipam/filtersets.py:1170 #: netbox/vpn/filtersets.py:377 msgid "Device (name)" msgstr "" @@ -3859,13 +3919,13 @@ msgid "Cable (ID)" msgstr "" #: netbox/dcim/filtersets.py:1780 netbox/ipam/filtersets.py:611 -#: netbox/ipam/filtersets.py:851 netbox/ipam/filtersets.py:1179 +#: netbox/ipam/filtersets.py:852 netbox/ipam/filtersets.py:1180 #: netbox/vpn/filtersets.py:388 msgid "Virtual machine (name)" msgstr "" #: netbox/dcim/filtersets.py:1785 netbox/ipam/filtersets.py:616 -#: netbox/ipam/filtersets.py:856 netbox/ipam/filtersets.py:1184 +#: netbox/ipam/filtersets.py:857 netbox/ipam/filtersets.py:1185 #: netbox/virtualization/filtersets.py:253 #: netbox/virtualization/filtersets.py:304 netbox/vpn/filtersets.py:393 msgid "Virtual machine (ID)" @@ -3940,13 +4000,13 @@ msgstr "" msgid "VRF (RD)" msgstr "" -#: netbox/dcim/filtersets.py:1873 netbox/ipam/filtersets.py:1036 +#: netbox/dcim/filtersets.py:1873 netbox/ipam/filtersets.py:1037 #: netbox/vpn/filtersets.py:345 msgid "L2VPN (ID)" msgstr "" #: netbox/dcim/filtersets.py:1879 netbox/dcim/forms/filtersets.py:1531 -#: netbox/dcim/tables/devices.py:603 netbox/ipam/filtersets.py:1042 +#: netbox/dcim/tables/devices.py:603 netbox/ipam/filtersets.py:1043 #: netbox/ipam/forms/filtersets.py:592 netbox/ipam/tables/vlans.py:115 #: netbox/templates/dcim/interface.html:99 netbox/templates/ipam/vlan.html:82 #: netbox/templates/vpn/l2vpntermination.html:12 @@ -3957,14 +4017,14 @@ msgstr "" msgid "L2VPN" msgstr "" -#: netbox/dcim/filtersets.py:1884 netbox/ipam/filtersets.py:1117 +#: netbox/dcim/filtersets.py:1884 netbox/ipam/filtersets.py:1118 msgid "VLAN Translation Policy (ID)" msgstr "" #: netbox/dcim/filtersets.py:1890 netbox/dcim/forms/filtersets.py:1497 #: netbox/dcim/forms/model_forms.py:1562 #: netbox/dcim/models/device_components.py:611 -#: netbox/ipam/forms/filtersets.py:511 netbox/ipam/forms/model_forms.py:721 +#: netbox/ipam/forms/filtersets.py:511 netbox/ipam/forms/model_forms.py:714 #: netbox/templates/ipam/vlantranslationpolicy.html:11 #: netbox/virtualization/forms/bulk_edit.py:248 #: netbox/virtualization/forms/filtersets.py:251 @@ -5156,7 +5216,7 @@ msgstr "" msgid "Parent VM of assigned interface (if any)" msgstr "" -#: netbox/dcim/forms/bulk_import.py:1293 netbox/ipam/filtersets.py:1047 +#: netbox/dcim/forms/bulk_import.py:1293 netbox/ipam/filtersets.py:1048 #: netbox/ipam/forms/bulk_import.py:328 msgid "Assigned interface" msgstr "" @@ -5513,15 +5573,15 @@ msgid "A virtual chassis member already exists in position {vc_position}." msgstr "" #: netbox/dcim/forms/mixins.py:27 netbox/dcim/forms/mixins.py:88 -#: netbox/ipam/forms/bulk_edit.py:425 netbox/ipam/forms/model_forms.py:618 +#: netbox/ipam/forms/bulk_edit.py:425 netbox/ipam/forms/model_forms.py:611 msgid "Scope type" msgstr "" #: netbox/dcim/forms/mixins.py:30 netbox/dcim/forms/mixins.py:91 #: netbox/ipam/forms/bulk_edit.py:270 netbox/ipam/forms/bulk_edit.py:428 #: netbox/ipam/forms/bulk_edit.py:447 netbox/ipam/forms/filtersets.py:181 -#: netbox/ipam/forms/model_forms.py:232 netbox/ipam/forms/model_forms.py:621 -#: netbox/ipam/forms/model_forms.py:631 netbox/ipam/tables/ip.py:195 +#: netbox/ipam/forms/model_forms.py:232 netbox/ipam/forms/model_forms.py:614 +#: netbox/ipam/forms/model_forms.py:624 netbox/ipam/tables/ip.py:195 #: netbox/ipam/tables/vlans.py:40 netbox/templates/ipam/prefix.html:48 #: netbox/templates/ipam/vlangroup.html:38 #: netbox/templates/virtualization/cluster.html:42 @@ -6292,7 +6352,7 @@ msgstr "" #: netbox/dcim/models/device_components.py:604 #: netbox/dcim/tables/devices.py:621 netbox/ipam/forms/bulk_edit.py:521 #: netbox/ipam/forms/bulk_import.py:514 netbox/ipam/forms/filtersets.py:587 -#: netbox/ipam/forms/model_forms.py:701 netbox/ipam/tables/vlans.py:108 +#: netbox/ipam/forms/model_forms.py:694 netbox/ipam/tables/vlans.py:108 #: netbox/templates/dcim/interface.html:86 netbox/templates/ipam/vlan.html:77 #: netbox/templates/virtualization/vminterface.html:60 msgid "Q-in-Q SVLAN" @@ -7578,7 +7638,7 @@ msgstr "" msgid "Allocated draw (W)" msgstr "" -#: netbox/dcim/tables/devices.py:591 netbox/ipam/forms/model_forms.py:794 +#: netbox/dcim/tables/devices.py:591 netbox/ipam/forms/model_forms.py:787 #: netbox/ipam/tables/fhrp.py:28 netbox/ipam/views.py:683 #: netbox/ipam/views.py:784 netbox/netbox/navigation/menu.py:165 #: netbox/netbox/navigation/menu.py:167 @@ -8399,7 +8459,7 @@ msgstr "" #: netbox/extras/forms/model_forms.py:256 #: netbox/extras/forms/model_forms.py:299 #: netbox/extras/forms/model_forms.py:452 -#: netbox/extras/forms/model_forms.py:569 netbox/users/forms/model_forms.py:284 +#: netbox/extras/forms/model_forms.py:569 netbox/users/forms/model_forms.py:290 msgid "Object types" msgstr "" @@ -8495,8 +8555,8 @@ msgstr "" #: netbox/extras/forms/bulk_import.py:285 #: netbox/extras/forms/model_forms.py:400 netbox/netbox/navigation/menu.py:414 #: netbox/templates/extras/notificationgroup.html:41 -#: netbox/templates/users/group.html:29 netbox/users/forms/model_forms.py:237 -#: netbox/users/forms/model_forms.py:249 netbox/users/forms/model_forms.py:310 +#: netbox/templates/users/group.html:29 netbox/users/forms/model_forms.py:243 +#: netbox/users/forms/model_forms.py:255 netbox/users/forms/model_forms.py:316 #: netbox/users/tables.py:102 msgid "Users" msgstr "" @@ -8512,8 +8572,8 @@ msgstr "" #: netbox/templates/tenancy/contact.html:21 #: netbox/tenancy/forms/bulk_edit.py:144 netbox/tenancy/forms/filtersets.py:78 #: netbox/tenancy/forms/model_forms.py:99 netbox/tenancy/tables/contacts.py:68 -#: netbox/users/forms/model_forms.py:182 netbox/users/forms/model_forms.py:194 -#: netbox/users/forms/model_forms.py:315 netbox/users/tables.py:35 +#: netbox/users/forms/model_forms.py:188 netbox/users/forms/model_forms.py:200 +#: netbox/users/forms/model_forms.py:321 netbox/users/tables.py:35 #: netbox/users/tables.py:106 msgid "Groups" msgstr "" @@ -10097,51 +10157,51 @@ msgstr "" msgid "NAT inside IP address (ID)" msgstr "" -#: netbox/ipam/filtersets.py:1027 +#: netbox/ipam/filtersets.py:1028 msgid "Q-in-Q SVLAN (ID)" msgstr "" -#: netbox/ipam/filtersets.py:1031 +#: netbox/ipam/filtersets.py:1032 msgid "Q-in-Q SVLAN number (1-4094)" msgstr "" -#: netbox/ipam/filtersets.py:1052 +#: netbox/ipam/filtersets.py:1053 msgid "Assigned VM interface" msgstr "" -#: netbox/ipam/filtersets.py:1123 +#: netbox/ipam/filtersets.py:1124 msgid "VLAN Translation Policy (name)" msgstr "" -#: netbox/ipam/filtersets.py:1189 +#: netbox/ipam/filtersets.py:1190 msgid "FHRP Group (name)" msgstr "" -#: netbox/ipam/filtersets.py:1194 +#: netbox/ipam/filtersets.py:1195 msgid "FHRP Group (ID)" msgstr "" -#: netbox/ipam/filtersets.py:1199 +#: netbox/ipam/filtersets.py:1200 msgid "IP address (ID)" msgstr "" -#: netbox/ipam/filtersets.py:1205 netbox/ipam/models/ip.py:816 +#: netbox/ipam/filtersets.py:1206 netbox/ipam/models/ip.py:816 msgid "IP address" msgstr "" -#: netbox/ipam/filtersets.py:1257 +#: netbox/ipam/filtersets.py:1258 msgid "Primary IPv4 (ID)" msgstr "" -#: netbox/ipam/filtersets.py:1263 +#: netbox/ipam/filtersets.py:1264 msgid "Primary IPv4 (address)" msgstr "" -#: netbox/ipam/filtersets.py:1268 +#: netbox/ipam/filtersets.py:1269 msgid "Primary IPv6 (ID)" msgstr "" -#: netbox/ipam/filtersets.py:1274 +#: netbox/ipam/filtersets.py:1275 msgid "Primary IPv6 (address)" msgstr "" @@ -10202,7 +10262,7 @@ msgid "Date added" msgstr "" #: netbox/ipam/forms/bulk_edit.py:213 netbox/ipam/forms/filtersets.py:266 -#: netbox/ipam/forms/model_forms.py:629 netbox/ipam/forms/model_forms.py:677 +#: netbox/ipam/forms/model_forms.py:622 netbox/ipam/forms/model_forms.py:670 #: netbox/ipam/tables/ip.py:202 netbox/templates/ipam/vlan_edit.html:49 #: netbox/templates/ipam/vlangroup.html:27 msgid "VLAN Group" @@ -10312,14 +10372,14 @@ msgid "Site & Group" msgstr "" #: netbox/ipam/forms/bulk_edit.py:557 netbox/ipam/forms/bulk_import.py:538 -#: netbox/ipam/forms/model_forms.py:733 netbox/ipam/tables/vlans.py:258 +#: netbox/ipam/forms/model_forms.py:726 netbox/ipam/tables/vlans.py:258 #: netbox/templates/ipam/vlantranslationrule.html:14 #: netbox/vpn/forms/model_forms.py:322 netbox/vpn/forms/model_forms.py:359 msgid "Policy" msgstr "" -#: netbox/ipam/forms/bulk_edit.py:578 netbox/ipam/forms/model_forms.py:751 -#: netbox/ipam/forms/model_forms.py:784 netbox/ipam/tables/services.py:19 +#: netbox/ipam/forms/bulk_edit.py:578 netbox/ipam/forms/model_forms.py:744 +#: netbox/ipam/forms/model_forms.py:777 netbox/ipam/tables/services.py:19 #: netbox/ipam/tables/services.py:49 netbox/templates/ipam/service.html:38 #: netbox/templates/ipam/servicetemplate.html:23 msgid "Ports" @@ -10594,66 +10654,66 @@ msgstr "" msgid "Virtual IP Address" msgstr "" -#: netbox/ipam/forms/model_forms.py:603 +#: netbox/ipam/forms/model_forms.py:596 msgid "Assignment already exists" msgstr "" -#: netbox/ipam/forms/model_forms.py:612 netbox/templates/ipam/vlangroup.html:42 +#: netbox/ipam/forms/model_forms.py:605 netbox/templates/ipam/vlangroup.html:42 msgid "VLAN IDs" msgstr "" -#: netbox/ipam/forms/model_forms.py:630 +#: netbox/ipam/forms/model_forms.py:623 msgid "Child VLANs" msgstr "" -#: netbox/ipam/forms/model_forms.py:688 +#: netbox/ipam/forms/model_forms.py:681 msgid "" "The direct assignment of VLANs to a site is deprecated and will be removed " "in a future release. Users are encouraged to utilize VLAN groups for this " "purpose." msgstr "" -#: netbox/ipam/forms/model_forms.py:739 +#: netbox/ipam/forms/model_forms.py:732 #: netbox/templates/ipam/vlantranslationrule.html:11 msgid "VLAN Translation Rule" msgstr "" -#: netbox/ipam/forms/model_forms.py:756 netbox/ipam/forms/model_forms.py:789 +#: netbox/ipam/forms/model_forms.py:749 netbox/ipam/forms/model_forms.py:782 msgid "" "Comma-separated list of one or more port numbers. A range may be specified " "using a hyphen." msgstr "" -#: netbox/ipam/forms/model_forms.py:761 +#: netbox/ipam/forms/model_forms.py:754 #: netbox/templates/ipam/servicetemplate.html:12 msgid "Application Service Template" msgstr "" -#: netbox/ipam/forms/model_forms.py:774 +#: netbox/ipam/forms/model_forms.py:767 msgid "Parent type" msgstr "" -#: netbox/ipam/forms/model_forms.py:801 +#: netbox/ipam/forms/model_forms.py:794 msgid "Port(s)" msgstr "" -#: netbox/ipam/forms/model_forms.py:802 netbox/ipam/forms/model_forms.py:868 +#: netbox/ipam/forms/model_forms.py:795 netbox/ipam/forms/model_forms.py:861 msgid "Application Service" msgstr "" -#: netbox/ipam/forms/model_forms.py:856 +#: netbox/ipam/forms/model_forms.py:849 msgid "Application Service template" msgstr "" -#: netbox/ipam/forms/model_forms.py:865 +#: netbox/ipam/forms/model_forms.py:858 msgid "From Template" msgstr "" -#: netbox/ipam/forms/model_forms.py:866 +#: netbox/ipam/forms/model_forms.py:859 msgid "Custom" msgstr "" -#: netbox/ipam/forms/model_forms.py:898 +#: netbox/ipam/forms/model_forms.py:891 msgid "" "Must specify name, protocol, and port(s) if not using an application service " "template." @@ -12077,9 +12137,9 @@ msgstr "" msgid "API Tokens" msgstr "" -#: netbox/netbox/navigation/menu.py:461 netbox/users/forms/model_forms.py:188 -#: netbox/users/forms/model_forms.py:196 netbox/users/forms/model_forms.py:243 -#: netbox/users/forms/model_forms.py:250 +#: netbox/netbox/navigation/menu.py:461 netbox/users/forms/model_forms.py:194 +#: netbox/users/forms/model_forms.py:202 netbox/users/forms/model_forms.py:249 +#: netbox/users/forms/model_forms.py:256 msgid "Permissions" msgstr "" @@ -14002,7 +14062,7 @@ msgstr "" #: netbox/templates/dcim/virtualchassis_add_member.html:27 #: netbox/templates/generic/object_edit.html:78 #: netbox/templates/users/objectpermission.html:31 -#: netbox/users/forms/filtersets.py:67 netbox/users/forms/model_forms.py:322 +#: netbox/users/forms/filtersets.py:67 netbox/users/forms/model_forms.py:337 msgid "Actions" msgstr "" @@ -14365,7 +14425,15 @@ msgstr "" msgid "Attach an Image" msgstr "" -#: netbox/templates/extras/object_imageattachments.html:39 +#: netbox/templates/extras/object_imageattachments.html:35 +msgid "Thumbnail cannot be generated" +msgstr "" + +#: netbox/templates/extras/object_imageattachments.html:36 +msgid "Click to view original" +msgstr "" + +#: netbox/templates/extras/object_imageattachments.html:49 #, python-format msgid "" "\n" @@ -15133,7 +15201,7 @@ msgid "View" msgstr "" #: netbox/templates/users/objectpermission.html:52 -#: netbox/users/forms/model_forms.py:325 +#: netbox/users/forms/model_forms.py:327 netbox/users/forms/model_forms.py:340 msgid "Constraints" msgstr "" @@ -15609,85 +15677,85 @@ msgstr "" msgid "Can Delete" msgstr "" -#: netbox/users/forms/model_forms.py:63 +#: netbox/users/forms/model_forms.py:69 msgid "User Interface" msgstr "" -#: netbox/users/forms/model_forms.py:115 +#: netbox/users/forms/model_forms.py:121 msgid "" "Keys must be at least 40 characters in length. Be sure to record " "your key prior to submitting this form, as it may no longer be " "accessible once the token has been created." msgstr "" -#: netbox/users/forms/model_forms.py:127 +#: netbox/users/forms/model_forms.py:133 msgid "" "Allowed IPv4/IPv6 networks from where the token can be used. Leave blank for " "no restrictions. Example: 10.1.1.0/24,192.168.10.16/32,2001:" "db8:1::/64" msgstr "" -#: netbox/users/forms/model_forms.py:176 +#: netbox/users/forms/model_forms.py:182 msgid "Confirm password" msgstr "" -#: netbox/users/forms/model_forms.py:179 +#: netbox/users/forms/model_forms.py:185 msgid "Enter the same password as before, for verification." msgstr "" -#: netbox/users/forms/model_forms.py:228 +#: netbox/users/forms/model_forms.py:234 msgid "Passwords do not match! Please check your input and try again." msgstr "" -#: netbox/users/forms/model_forms.py:289 +#: netbox/users/forms/model_forms.py:295 msgid "Select the types of objects to which the permission will appy." msgstr "" -#: netbox/users/forms/model_forms.py:304 +#: netbox/users/forms/model_forms.py:310 msgid "Additional actions" msgstr "" -#: netbox/users/forms/model_forms.py:307 +#: netbox/users/forms/model_forms.py:313 msgid "Actions granted in addition to those listed above" msgstr "" -#: netbox/users/forms/model_forms.py:323 -msgid "Objects" -msgstr "" - -#: netbox/users/forms/model_forms.py:335 +#: netbox/users/forms/model_forms.py:329 msgid "" "JSON expression of a queryset filter that will return only permitted " "objects. Leave null to match all objects of this type. A list of multiple " "objects will result in a logical OR operation." msgstr "" -#: netbox/users/forms/model_forms.py:374 +#: netbox/users/forms/model_forms.py:338 +msgid "Objects" +msgstr "" + +#: netbox/users/forms/model_forms.py:396 msgid "At least one action must be selected." msgstr "" -#: netbox/users/forms/model_forms.py:392 +#: netbox/users/forms/model_forms.py:414 #, python-brace-format msgid "Invalid filter for {model}: {error}" msgstr "" -#: netbox/users/models/permissions.py:37 +#: netbox/users/models/permissions.py:38 msgid "The list of actions granted by this permission" msgstr "" -#: netbox/users/models/permissions.py:42 +#: netbox/users/models/permissions.py:43 msgid "constraints" msgstr "" -#: netbox/users/models/permissions.py:43 +#: netbox/users/models/permissions.py:44 msgid "Queryset filter matching the applicable objects of the selected type(s)" msgstr "" -#: netbox/users/models/permissions.py:50 +#: netbox/users/models/permissions.py:55 msgid "permission" msgstr "" -#: netbox/users/models/permissions.py:51 netbox/users/models/users.py:47 +#: netbox/users/models/permissions.py:56 netbox/users/models/users.py:47 msgid "permissions" msgstr "" From 8fa1abd371d44bcc645671bfda15c1bb5d464669 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 16 Sep 2025 11:56:50 -0400 Subject: [PATCH 40/40] Release v4.4.1 (#20366) * Release v4.4.1 * Revert django-mptt to v0.17.0 --- .../ISSUE_TEMPLATE/01-feature_request.yaml | 2 +- .github/ISSUE_TEMPLATE/02-bug_report.yaml | 2 +- base_requirements.txt | 3 +- contrib/generated_schema.json | 77 +- contrib/openapi.json | 1070 +++- docs/release-notes/version-4.4.md | 42 + netbox/project-static/dist/netbox.js | 8 +- netbox/project-static/dist/netbox.js.map | 6 +- netbox/project-static/package.json | 6 +- netbox/project-static/yarn.lock | 24 +- netbox/release.yaml | 4 +- netbox/translations/cs/LC_MESSAGES/django.mo | Bin 248396 -> 252606 bytes netbox/translations/cs/LC_MESSAGES/django.po | 5194 ++++++++-------- netbox/translations/da/LC_MESSAGES/django.mo | Bin 240771 -> 244808 bytes netbox/translations/da/LC_MESSAGES/django.po | 5194 ++++++++-------- netbox/translations/de/LC_MESSAGES/django.mo | Bin 253564 -> 257696 bytes netbox/translations/de/LC_MESSAGES/django.po | 5205 +++++++++-------- netbox/translations/es/LC_MESSAGES/django.mo | Bin 255342 -> 259613 bytes netbox/translations/es/LC_MESSAGES/django.po | 5198 ++++++++-------- netbox/translations/fr/LC_MESSAGES/django.mo | Bin 257334 -> 261578 bytes netbox/translations/fr/LC_MESSAGES/django.po | 5204 ++++++++-------- netbox/translations/it/LC_MESSAGES/django.mo | Bin 253430 -> 257630 bytes netbox/translations/it/LC_MESSAGES/django.po | 5200 ++++++++-------- netbox/translations/ja/LC_MESSAGES/django.mo | Bin 271826 -> 276797 bytes netbox/translations/ja/LC_MESSAGES/django.po | 5188 ++++++++-------- netbox/translations/nl/LC_MESSAGES/django.mo | Bin 248963 -> 253154 bytes netbox/translations/nl/LC_MESSAGES/django.po | 5202 ++++++++-------- netbox/translations/pl/LC_MESSAGES/django.mo | Bin 251174 -> 255396 bytes netbox/translations/pl/LC_MESSAGES/django.po | 5199 ++++++++-------- netbox/translations/pt/LC_MESSAGES/django.mo | Bin 251355 -> 255571 bytes netbox/translations/pt/LC_MESSAGES/django.po | 5201 ++++++++-------- netbox/translations/ru/LC_MESSAGES/django.mo | Bin 322816 -> 328108 bytes netbox/translations/ru/LC_MESSAGES/django.po | 5201 ++++++++-------- netbox/translations/tr/LC_MESSAGES/django.mo | Bin 244764 -> 248857 bytes netbox/translations/tr/LC_MESSAGES/django.po | 5196 ++++++++-------- netbox/translations/uk/LC_MESSAGES/django.mo | Bin 322443 -> 327676 bytes netbox/translations/uk/LC_MESSAGES/django.po | 5196 ++++++++-------- netbox/translations/zh/LC_MESSAGES/django.mo | Bin 226529 -> 230387 bytes netbox/translations/zh/LC_MESSAGES/django.po | 5187 ++++++++-------- pyproject.toml | 2 +- requirements.txt | 20 +- 41 files changed, 39209 insertions(+), 34822 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/01-feature_request.yaml b/.github/ISSUE_TEMPLATE/01-feature_request.yaml index 763733c0958..eda44e63e18 100644 --- a/.github/ISSUE_TEMPLATE/01-feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/01-feature_request.yaml @@ -15,7 +15,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v4.4.0 + placeholder: v4.4.1 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/02-bug_report.yaml b/.github/ISSUE_TEMPLATE/02-bug_report.yaml index ee0643ff44c..4e62aa84e13 100644 --- a/.github/ISSUE_TEMPLATE/02-bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/02-bug_report.yaml @@ -27,7 +27,7 @@ body: attributes: label: NetBox Version description: What version of NetBox are you currently running? - placeholder: v4.4.0 + placeholder: v4.4.1 validations: required: true - type: dropdown diff --git a/base_requirements.txt b/base_requirements.txt index fd20eae0921..9bf8680a5e9 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -139,8 +139,7 @@ requests # rq # https://github.com/rq/rq/blob/master/CHANGES.md -# RQ v2.5 drops support for Redis < 5.0 -rq==2.4.1 +rq # Django app for social-auth-core # https://github.com/python-social-auth/social-app-django/blob/master/CHANGELOG.md diff --git a/contrib/generated_schema.json b/contrib/generated_schema.json index 9c115cf14ca..473e75cffa2 100644 --- a/contrib/generated_schema.json +++ b/contrib/generated_schema.json @@ -330,14 +330,87 @@ "100base-lfx", "100base-tx", "100base-t1", - "1000base-t", + "1000base-bx10-d", + "1000base-bx10-u", + "1000base-cx", + "1000base-cwdm", + "1000base-dwdm", + "1000base-ex", "1000base-sx", + "1000base-lsx", "1000base-lx", + "1000base-lx10", + "1000base-t", "1000base-tx", + "1000base-zx", "2.5gbase-t", "5gbase-t", - "10gbase-t", + "10gbase-br-d", + "10gbase-br-u", "10gbase-cx4", + "10gbase-er", + "10gbase-lr", + "10gbase-lrm", + "10gbase-lx4", + "10gbase-sr", + "10gbase-t", + "10gbase-zr", + "25gbase-cr", + "25gbase-er", + "25gbase-lr", + "25gbase-sr", + "25gbase-t", + "40gbase-cr4", + "40gbase-er4", + "40gbase-fr4", + "40gbase-lr4", + "40gbase-sr4", + "50gbase-cr", + "50gbase-er", + "50gbase-fr", + "50gbase-lr", + "50gbase-sr", + "100gbase-cr1", + "100gbase-cr2", + "100gbase-cr4", + "100gbase-cr10", + "100gbase-dr", + "100gbase-er4", + "100gbase-fr1", + "100gbase-lr1", + "100gbase-lr4", + "100gbase-sr1", + "100gbase-sr1.2", + "100gbase-sr2", + "100gbase-sr4", + "100gbase-sr10", + "100gbase-zr", + "200gbase-cr2", + "200gbase-cr4", + "200gbase-sr2", + "200gbase-sr4", + "200gbase-dr4", + "200gbase-er4", + "200gbase-fr4", + "200gbase-lr4", + "200gbase-vr2", + "400gbase-cr4", + "400gbase-dr4", + "400gbase-er8", + "400gbase-fr4", + "400gbase-fr8", + "400gbase-lr4", + "400gbase-lr8", + "400gbase-sr4", + "400gbase-sr4_2", + "400gbase-sr8", + "400gbase-sr16", + "400gbase-vr4", + "400gbase-zr", + "800gbase-cr8", + "800gbase-dr8", + "800gbase-sr8", + "800gbase-vr8", "100base-x-sfp", "1000base-x-gbic", "1000base-x-sfp", diff --git a/contrib/openapi.json b/contrib/openapi.json index 09733b4c209..ddbdff987e3 100644 --- a/contrib/openapi.json +++ b/contrib/openapi.json @@ -23531,7 +23531,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "7b11d524b2b1a7ef", + "x-spec-enum-id": "c731f2793fceac04", "nullable": true } }, @@ -23552,7 +23552,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "7b11d524b2b1a7ef", + "x-spec-enum-id": "c731f2793fceac04", "nullable": true } }, @@ -23566,7 +23566,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "7b11d524b2b1a7ef", + "x-spec-enum-id": "c731f2793fceac04", "nullable": true } }, @@ -23580,7 +23580,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "7b11d524b2b1a7ef", + "x-spec-enum-id": "c731f2793fceac04", "nullable": true } }, @@ -23594,7 +23594,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "7b11d524b2b1a7ef", + "x-spec-enum-id": "c731f2793fceac04", "nullable": true } }, @@ -23608,7 +23608,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "7b11d524b2b1a7ef", + "x-spec-enum-id": "c731f2793fceac04", "nullable": true } }, @@ -23622,7 +23622,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "7b11d524b2b1a7ef", + "x-spec-enum-id": "c731f2793fceac04", "nullable": true } }, @@ -23636,7 +23636,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "7b11d524b2b1a7ef", + "x-spec-enum-id": "c731f2793fceac04", "nullable": true } }, @@ -23650,7 +23650,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "7b11d524b2b1a7ef", + "x-spec-enum-id": "c731f2793fceac04", "nullable": true } }, @@ -23664,7 +23664,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "7b11d524b2b1a7ef", + "x-spec-enum-id": "c731f2793fceac04", "nullable": true } }, @@ -23678,7 +23678,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "7b11d524b2b1a7ef", + "x-spec-enum-id": "c731f2793fceac04", "nullable": true } }, @@ -23692,7 +23692,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "7b11d524b2b1a7ef", + "x-spec-enum-id": "c731f2793fceac04", "nullable": true } }, @@ -47839,7 +47839,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -47859,7 +47859,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -47872,7 +47872,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -47885,7 +47885,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -47898,7 +47898,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -47911,7 +47911,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -47924,7 +47924,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -47937,7 +47937,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -47950,7 +47950,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -47963,7 +47963,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -47976,7 +47976,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -47989,7 +47989,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -51766,7 +51766,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -51786,7 +51786,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -51799,7 +51799,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -51812,7 +51812,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -51825,7 +51825,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -51838,7 +51838,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -51851,7 +51851,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -51864,7 +51864,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -51877,7 +51877,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -51890,7 +51890,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -51903,7 +51903,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -51916,7 +51916,7 @@ "type": "array", "items": { "type": "string", - "x-spec-enum-id": "8c4c5e112f77a383" + "x-spec-enum-id": "efa24ff9c7a39c40" } }, "explode": true, @@ -112805,8 +112805,8 @@ "schema": { "type": "array", "items": { - "type": "integer", - "format": "int32" + "type": "number", + "format": "double" } }, "explode": true, @@ -112825,8 +112825,8 @@ "schema": { "type": "array", "items": { - "type": "integer", - "format": "int32" + "type": "number", + "format": "double" } }, "explode": true, @@ -112838,8 +112838,8 @@ "schema": { "type": "array", "items": { - "type": "integer", - "format": "int32" + "type": "number", + "format": "double" } }, "explode": true, @@ -112851,8 +112851,8 @@ "schema": { "type": "array", "items": { - "type": "integer", - "format": "int32" + "type": "number", + "format": "double" } }, "explode": true, @@ -112864,8 +112864,8 @@ "schema": { "type": "array", "items": { - "type": "integer", - "format": "int32" + "type": "number", + "format": "double" } }, "explode": true, @@ -112877,8 +112877,8 @@ "schema": { "type": "array", "items": { - "type": "integer", - "format": "int32" + "type": "number", + "format": "double" } }, "explode": true, @@ -112890,8 +112890,8 @@ "schema": { "type": "array", "items": { - "type": "integer", - "format": "int32" + "type": "number", + "format": "double" } }, "explode": true, @@ -112910,8 +112910,8 @@ "schema": { "type": "array", "items": { - "type": "integer", - "format": "int32" + "type": "number", + "format": "double" } }, "explode": true, @@ -112923,8 +112923,8 @@ "schema": { "type": "array", "items": { - "type": "integer", - "format": "int32" + "type": "number", + "format": "double" } }, "explode": true, @@ -112936,8 +112936,8 @@ "schema": { "type": "array", "items": { - "type": "integer", - "format": "int32" + "type": "number", + "format": "double" } }, "explode": true, @@ -112949,8 +112949,8 @@ "schema": { "type": "array", "items": { - "type": "integer", - "format": "int32" + "type": "number", + "format": "double" } }, "explode": true, @@ -112962,8 +112962,8 @@ "schema": { "type": "array", "items": { - "type": "integer", - "format": "int32" + "type": "number", + "format": "double" } }, "explode": true, @@ -204674,9 +204674,9 @@ "cat7", "cat7a", "cat8", + "mrj21-trunk", "dac-active", "dac-passive", - "mrj21-trunk", "coaxial", "mmf", "mmf-om1", @@ -204688,14 +204688,14 @@ "smf-os1", "smf-os2", "aoc", - "usb", "power", + "usb", "", null ], "type": "string", - "description": "* `cat3` - CAT3\n* `cat5` - CAT5\n* `cat5e` - CAT5e\n* `cat6` - CAT6\n* `cat6a` - CAT6a\n* `cat7` - CAT7\n* `cat7a` - CAT7a\n* `cat8` - CAT8\n* `dac-active` - Direct Attach Copper (Active)\n* `dac-passive` - Direct Attach Copper (Passive)\n* `mrj21-trunk` - MRJ21 Trunk\n* `coaxial` - Coaxial\n* `mmf` - Multimode Fiber\n* `mmf-om1` - Multimode Fiber (OM1)\n* `mmf-om2` - Multimode Fiber (OM2)\n* `mmf-om3` - Multimode Fiber (OM3)\n* `mmf-om4` - Multimode Fiber (OM4)\n* `mmf-om5` - Multimode Fiber (OM5)\n* `smf` - Singlemode Fiber\n* `smf-os1` - Singlemode Fiber (OS1)\n* `smf-os2` - Singlemode Fiber (OS2)\n* `aoc` - Active Optical Cabling (AOC)\n* `usb` - USB\n* `power` - Power", - "x-spec-enum-id": "7b11d524b2b1a7ef", + "description": "* `cat3` - CAT3\n* `cat5` - CAT5\n* `cat5e` - CAT5e\n* `cat6` - CAT6\n* `cat6a` - CAT6a\n* `cat7` - CAT7\n* `cat7a` - CAT7a\n* `cat8` - CAT8\n* `mrj21-trunk` - MRJ21 Trunk\n* `dac-active` - Direct Attach Copper (Active)\n* `dac-passive` - Direct Attach Copper (Passive)\n* `coaxial` - Coaxial\n* `mmf` - Multimode Fiber\n* `mmf-om1` - Multimode Fiber (OM1)\n* `mmf-om2` - Multimode Fiber (OM2)\n* `mmf-om3` - Multimode Fiber (OM3)\n* `mmf-om4` - Multimode Fiber (OM4)\n* `mmf-om5` - Multimode Fiber (OM5)\n* `smf` - Single-mode Fiber\n* `smf-os1` - Single-mode Fiber (OS1)\n* `smf-os2` - Single-mode Fiber (OS2)\n* `aoc` - Active Optical Cabling (AOC)\n* `power` - Power\n* `usb` - USB", + "x-spec-enum-id": "c731f2793fceac04", "nullable": true }, "a_terminations": { @@ -204844,9 +204844,9 @@ "cat7", "cat7a", "cat8", + "mrj21-trunk", "dac-active", "dac-passive", - "mrj21-trunk", "coaxial", "mmf", "mmf-om1", @@ -204858,14 +204858,14 @@ "smf-os1", "smf-os2", "aoc", - "usb", "power", + "usb", "", null ], "type": "string", - "description": "* `cat3` - CAT3\n* `cat5` - CAT5\n* `cat5e` - CAT5e\n* `cat6` - CAT6\n* `cat6a` - CAT6a\n* `cat7` - CAT7\n* `cat7a` - CAT7a\n* `cat8` - CAT8\n* `dac-active` - Direct Attach Copper (Active)\n* `dac-passive` - Direct Attach Copper (Passive)\n* `mrj21-trunk` - MRJ21 Trunk\n* `coaxial` - Coaxial\n* `mmf` - Multimode Fiber\n* `mmf-om1` - Multimode Fiber (OM1)\n* `mmf-om2` - Multimode Fiber (OM2)\n* `mmf-om3` - Multimode Fiber (OM3)\n* `mmf-om4` - Multimode Fiber (OM4)\n* `mmf-om5` - Multimode Fiber (OM5)\n* `smf` - Singlemode Fiber\n* `smf-os1` - Singlemode Fiber (OS1)\n* `smf-os2` - Singlemode Fiber (OS2)\n* `aoc` - Active Optical Cabling (AOC)\n* `usb` - USB\n* `power` - Power", - "x-spec-enum-id": "7b11d524b2b1a7ef", + "description": "* `cat3` - CAT3\n* `cat5` - CAT5\n* `cat5e` - CAT5e\n* `cat6` - CAT6\n* `cat6a` - CAT6a\n* `cat7` - CAT7\n* `cat7a` - CAT7a\n* `cat8` - CAT8\n* `mrj21-trunk` - MRJ21 Trunk\n* `dac-active` - Direct Attach Copper (Active)\n* `dac-passive` - Direct Attach Copper (Passive)\n* `coaxial` - Coaxial\n* `mmf` - Multimode Fiber\n* `mmf-om1` - Multimode Fiber (OM1)\n* `mmf-om2` - Multimode Fiber (OM2)\n* `mmf-om3` - Multimode Fiber (OM3)\n* `mmf-om4` - Multimode Fiber (OM4)\n* `mmf-om5` - Multimode Fiber (OM5)\n* `smf` - Single-mode Fiber\n* `smf-os1` - Single-mode Fiber (OS1)\n* `smf-os2` - Single-mode Fiber (OS2)\n* `aoc` - Active Optical Cabling (AOC)\n* `power` - Power\n* `usb` - USB", + "x-spec-enum-id": "c731f2793fceac04", "nullable": true }, "a_terminations": { @@ -209051,19 +209051,23 @@ "description": "Fields with higher weights appear lower in a form." }, "validation_minimum": { - "type": "integer", - "maximum": 9223372036854775807, - "minimum": -9223372036854775808, - "format": "int64", + "type": "number", + "format": "double", + "maximum": 1000000000000, + "minimum": -1000000000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, "nullable": true, "title": "Minimum value", "description": "Minimum allowed value (for numeric fields)" }, "validation_maximum": { - "type": "integer", - "maximum": 9223372036854775807, - "minimum": -9223372036854775808, - "format": "int64", + "type": "number", + "format": "double", + "maximum": 1000000000000, + "minimum": -1000000000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, "nullable": true, "title": "Maximum value", "description": "Maximum allowed value (for numeric fields)" @@ -209367,19 +209371,23 @@ "description": "Fields with higher weights appear lower in a form." }, "validation_minimum": { - "type": "integer", - "maximum": 9223372036854775807, - "minimum": -9223372036854775808, - "format": "int64", + "type": "number", + "format": "double", + "maximum": 1000000000000, + "minimum": -1000000000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, "nullable": true, "title": "Minimum value", "description": "Minimum allowed value (for numeric fields)" }, "validation_maximum": { - "type": "integer", - "maximum": 9223372036854775807, - "minimum": -9223372036854775808, - "format": "int64", + "type": "number", + "format": "double", + "maximum": 1000000000000, + "minimum": -1000000000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, "nullable": true, "title": "Maximum value", "description": "Maximum allowed value (for numeric fields)" @@ -215311,14 +215319,87 @@ "100base-lfx", "100base-tx", "100base-t1", - "1000base-t", + "1000base-bx10-d", + "1000base-bx10-u", + "1000base-cx", + "1000base-cwdm", + "1000base-dwdm", + "1000base-ex", "1000base-sx", + "1000base-lsx", "1000base-lx", + "1000base-lx10", + "1000base-t", "1000base-tx", + "1000base-zx", "2.5gbase-t", "5gbase-t", - "10gbase-t", + "10gbase-br-d", + "10gbase-br-u", "10gbase-cx4", + "10gbase-er", + "10gbase-lr", + "10gbase-lrm", + "10gbase-lx4", + "10gbase-sr", + "10gbase-t", + "10gbase-zr", + "25gbase-cr", + "25gbase-er", + "25gbase-lr", + "25gbase-sr", + "25gbase-t", + "40gbase-cr4", + "40gbase-er4", + "40gbase-fr4", + "40gbase-lr4", + "40gbase-sr4", + "50gbase-cr", + "50gbase-er", + "50gbase-fr", + "50gbase-lr", + "50gbase-sr", + "100gbase-cr1", + "100gbase-cr2", + "100gbase-cr4", + "100gbase-cr10", + "100gbase-dr", + "100gbase-er4", + "100gbase-fr1", + "100gbase-lr1", + "100gbase-lr4", + "100gbase-sr1", + "100gbase-sr1.2", + "100gbase-sr2", + "100gbase-sr4", + "100gbase-sr10", + "100gbase-zr", + "200gbase-cr2", + "200gbase-cr4", + "200gbase-sr2", + "200gbase-sr4", + "200gbase-dr4", + "200gbase-er4", + "200gbase-fr4", + "200gbase-lr4", + "200gbase-vr2", + "400gbase-cr4", + "400gbase-dr4", + "400gbase-er8", + "400gbase-fr4", + "400gbase-fr8", + "400gbase-lr4", + "400gbase-lr8", + "400gbase-sr4", + "400gbase-sr4_2", + "400gbase-sr8", + "400gbase-sr16", + "400gbase-vr4", + "400gbase-zr", + "800gbase-cr8", + "800gbase-dr8", + "800gbase-sr8", + "800gbase-vr8", "100base-x-sfp", "1000base-x-gbic", "1000base-x-sfp", @@ -215438,8 +215519,8 @@ "other" ], "type": "string", - "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME FIBER)\n* `100base-lfx` - 100BASE-LFX (10/100ME FIBER)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME Single Pair)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n\n* `ieee802.11ac` - IEEE 802.11ac\n* `ieee802.11ad` - IEEE 802.11ad\n* `ieee802.11ax` - IEEE 802.11ax\n* `ieee802.11ay` - IEEE 802.11ay\n* `ieee802.11be` - IEEE 802.11be\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", - "x-spec-enum-id": "8c4c5e112f77a383" + "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-DR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-DR-U (10GE BiDi Up)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", + "x-spec-enum-id": "efa24ff9c7a39c40" }, "label": { "type": "string", @@ -215447,18 +215528,91 @@ "Virtual", "Bridge", "Link Aggregation Group (LAG)", - "100BASE-FX (10/100ME FIBER)", - "100BASE-LFX (10/100ME FIBER)", + "100BASE-FX (10/100ME)", + "100BASE-LFX (10/100ME)", "100BASE-TX (10/100ME)", - "100BASE-T1 (10/100ME Single Pair)", - "1000BASE-T (1GE)", + "100BASE-T1 (10/100ME)", + "1000BASE-BX10-D (1GE BiDi Down)", + "1000BASE-BX10-U (1GE BiDi Up)", + "1000BASE-CX (1GE DAC)", + "1000BASE-CWDM (1GE)", + "1000BASE-DWDM (1GE)", + "1000BASE-EX (1GE)", "1000BASE-SX (1GE)", + "1000BASE-LSX (1GE)", "1000BASE-LX (1GE)", + "1000BASE-LX10/LH (1GE)", + "1000BASE-T (1GE)", "1000BASE-TX (1GE)", + "1000BASE-ZX (1GE)", "2.5GBASE-T (2.5GE)", "5GBASE-T (5GE)", + "10GBASE-DR-D (10GE BiDi Down)", + "10GBASE-DR-U (10GE BiDi Up)", + "10GBASE-CX4 (10GE DAC)", + "10GBASE-ER (10GE)", + "10GBASE-LR (10GE)", + "10GBASE-LRM (10GE)", + "10GBASE-LX4 (10GE)", + "10GBASE-SR (10GE)", "10GBASE-T (10GE)", - "10GBASE-CX4 (10GE)", + "10GBASE-ZR (10GE)", + "25GBASE-CR (25GE DAC)", + "25GBASE-ER (25GE)", + "25GBASE-LR (25GE)", + "25GBASE-SR (25GE)", + "25GBASE-T (25GE)", + "40GBASE-CR4 (40GE DAC)", + "40GBASE-ER4 (40GE)", + "40GBASE-FR4 (40GE)", + "40GBASE-LR4 (40GE)", + "40GBASE-SR4 (40GE)", + "50GBASE-CR (50GE DAC)", + "50GBASE-ER (50GE)", + "50GBASE-FR (50GE)", + "50GBASE-LR (50GE)", + "50GBASE-SR (50GE)", + "100GBASE-CR1 (100GE DAC)", + "100GBASE-CR2 (100GE DAC)", + "100GBASE-CR4 (100GE DAC)", + "100GBASE-CR10 (100GE DAC)", + "100GBASE-DR (100GE)", + "100GBASE-ER4 (100GE)", + "100GBASE-FR1 (100GE)", + "100GBASE-LR1 (100GE)", + "100GBASE-LR4 (100GE)", + "100GBASE-SR1 (100GE)", + "100GBASE-SR1.2 (100GE BiDi)", + "100GBASE-SR2 (100GE)", + "100GBASE-SR4 (100GE)", + "100GBASE-SR10 (100GE)", + "100GBASE-ZR (100GE)", + "200GBASE-CR2 (200GE)", + "200GBASE-CR4 (200GE)", + "200GBASE-SR2 (200GE)", + "200GBASE-SR4 (200GE)", + "200GBASE-DR4 (200GE)", + "200GBASE-ER4 (200GE)", + "200GBASE-FR4 (200GE)", + "200GBASE-LR4 (200GE)", + "200GBASE-VR2 (200GE)", + "400GBASE-CR4 (400GE)", + "400GBASE-DR4 (400GE)", + "400GBASE-ER8 (400GE)", + "400GBASE-FR4 (400GE)", + "400GBASE-FR8 (400GE)", + "400GBASE-LR4 (400GE)", + "400GBASE-LR8 (400GE)", + "400GBASE-SR4 (400GE)", + "400GBASE-SR4.2 (400GE BiDi)", + "400GBASE-SR8 (400GE)", + "400GBASE-SR16 (400GE)", + "400GBASE-VR4 (400GE)", + "400GBASE-ZR (400GE)", + "800GBASE-CR8 (800GE)", + "800GBASE-DR8 (800GE)", + "800GBASE-SR8 (800GE)", + "800GBASE-VR8 (800GE)", "SFP (100ME)", "GBIC (1GE)", "SFP (1GE)", @@ -215504,12 +215658,12 @@ "100GBASE-KR4 (100GE)", "IEEE 802.11a", "IEEE 802.11b/g", - "IEEE 802.11n", - "IEEE 802.11ac", - "IEEE 802.11ad", - "IEEE 802.11ax", - "IEEE 802.11ay", - "IEEE 802.11be", + "IEEE 802.11n (Wi-Fi 4)", + "IEEE 802.11ac (Wi-Fi 5)", + "IEEE 802.11ad (WiGig)", + "IEEE 802.11ax (Wi-Fi 6)", + "IEEE 802.11ay (WiGig)", + "IEEE 802.11be (Wi-Fi 7)", "IEEE 802.15.1 (Bluetooth)", "IEEE 802.15.4 (LR-WPAN)", "Other (Wireless)", @@ -216447,14 +216601,87 @@ "100base-lfx", "100base-tx", "100base-t1", - "1000base-t", + "1000base-bx10-d", + "1000base-bx10-u", + "1000base-cx", + "1000base-cwdm", + "1000base-dwdm", + "1000base-ex", "1000base-sx", + "1000base-lsx", "1000base-lx", + "1000base-lx10", + "1000base-t", "1000base-tx", + "1000base-zx", "2.5gbase-t", "5gbase-t", - "10gbase-t", + "10gbase-br-d", + "10gbase-br-u", "10gbase-cx4", + "10gbase-er", + "10gbase-lr", + "10gbase-lrm", + "10gbase-lx4", + "10gbase-sr", + "10gbase-t", + "10gbase-zr", + "25gbase-cr", + "25gbase-er", + "25gbase-lr", + "25gbase-sr", + "25gbase-t", + "40gbase-cr4", + "40gbase-er4", + "40gbase-fr4", + "40gbase-lr4", + "40gbase-sr4", + "50gbase-cr", + "50gbase-er", + "50gbase-fr", + "50gbase-lr", + "50gbase-sr", + "100gbase-cr1", + "100gbase-cr2", + "100gbase-cr4", + "100gbase-cr10", + "100gbase-dr", + "100gbase-er4", + "100gbase-fr1", + "100gbase-lr1", + "100gbase-lr4", + "100gbase-sr1", + "100gbase-sr1.2", + "100gbase-sr2", + "100gbase-sr4", + "100gbase-sr10", + "100gbase-zr", + "200gbase-cr2", + "200gbase-cr4", + "200gbase-sr2", + "200gbase-sr4", + "200gbase-dr4", + "200gbase-er4", + "200gbase-fr4", + "200gbase-lr4", + "200gbase-vr2", + "400gbase-cr4", + "400gbase-dr4", + "400gbase-er8", + "400gbase-fr4", + "400gbase-fr8", + "400gbase-lr4", + "400gbase-lr8", + "400gbase-sr4", + "400gbase-sr4_2", + "400gbase-sr8", + "400gbase-sr16", + "400gbase-vr4", + "400gbase-zr", + "800gbase-cr8", + "800gbase-dr8", + "800gbase-sr8", + "800gbase-vr8", "100base-x-sfp", "1000base-x-gbic", "1000base-x-sfp", @@ -216574,8 +216801,8 @@ "other" ], "type": "string", - "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME FIBER)\n* `100base-lfx` - 100BASE-LFX (10/100ME FIBER)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME Single Pair)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n\n* `ieee802.11ac` - IEEE 802.11ac\n* `ieee802.11ad` - IEEE 802.11ad\n* `ieee802.11ax` - IEEE 802.11ax\n* `ieee802.11ay` - IEEE 802.11ay\n* `ieee802.11be` - IEEE 802.11be\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", - "x-spec-enum-id": "8c4c5e112f77a383" + "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-DR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-DR-U (10GE BiDi Up)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", + "x-spec-enum-id": "efa24ff9c7a39c40" }, "enabled": { "type": "boolean" @@ -217093,14 +217320,87 @@ "100base-lfx", "100base-tx", "100base-t1", - "1000base-t", + "1000base-bx10-d", + "1000base-bx10-u", + "1000base-cx", + "1000base-cwdm", + "1000base-dwdm", + "1000base-ex", "1000base-sx", + "1000base-lsx", "1000base-lx", + "1000base-lx10", + "1000base-t", "1000base-tx", + "1000base-zx", "2.5gbase-t", "5gbase-t", - "10gbase-t", + "10gbase-br-d", + "10gbase-br-u", "10gbase-cx4", + "10gbase-er", + "10gbase-lr", + "10gbase-lrm", + "10gbase-lx4", + "10gbase-sr", + "10gbase-t", + "10gbase-zr", + "25gbase-cr", + "25gbase-er", + "25gbase-lr", + "25gbase-sr", + "25gbase-t", + "40gbase-cr4", + "40gbase-er4", + "40gbase-fr4", + "40gbase-lr4", + "40gbase-sr4", + "50gbase-cr", + "50gbase-er", + "50gbase-fr", + "50gbase-lr", + "50gbase-sr", + "100gbase-cr1", + "100gbase-cr2", + "100gbase-cr4", + "100gbase-cr10", + "100gbase-dr", + "100gbase-er4", + "100gbase-fr1", + "100gbase-lr1", + "100gbase-lr4", + "100gbase-sr1", + "100gbase-sr1.2", + "100gbase-sr2", + "100gbase-sr4", + "100gbase-sr10", + "100gbase-zr", + "200gbase-cr2", + "200gbase-cr4", + "200gbase-sr2", + "200gbase-sr4", + "200gbase-dr4", + "200gbase-er4", + "200gbase-fr4", + "200gbase-lr4", + "200gbase-vr2", + "400gbase-cr4", + "400gbase-dr4", + "400gbase-er8", + "400gbase-fr4", + "400gbase-fr8", + "400gbase-lr4", + "400gbase-lr8", + "400gbase-sr4", + "400gbase-sr4_2", + "400gbase-sr8", + "400gbase-sr16", + "400gbase-vr4", + "400gbase-zr", + "800gbase-cr8", + "800gbase-dr8", + "800gbase-sr8", + "800gbase-vr8", "100base-x-sfp", "1000base-x-gbic", "1000base-x-sfp", @@ -217220,8 +217520,8 @@ "other" ], "type": "string", - "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME FIBER)\n* `100base-lfx` - 100BASE-LFX (10/100ME FIBER)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME Single Pair)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n\n* `ieee802.11ac` - IEEE 802.11ac\n* `ieee802.11ad` - IEEE 802.11ad\n* `ieee802.11ax` - IEEE 802.11ax\n* `ieee802.11ay` - IEEE 802.11ay\n* `ieee802.11be` - IEEE 802.11be\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", - "x-spec-enum-id": "8c4c5e112f77a383" + "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-DR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-DR-U (10GE BiDi Up)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", + "x-spec-enum-id": "efa24ff9c7a39c40" }, "label": { "type": "string", @@ -217229,18 +217529,91 @@ "Virtual", "Bridge", "Link Aggregation Group (LAG)", - "100BASE-FX (10/100ME FIBER)", - "100BASE-LFX (10/100ME FIBER)", + "100BASE-FX (10/100ME)", + "100BASE-LFX (10/100ME)", "100BASE-TX (10/100ME)", - "100BASE-T1 (10/100ME Single Pair)", - "1000BASE-T (1GE)", + "100BASE-T1 (10/100ME)", + "1000BASE-BX10-D (1GE BiDi Down)", + "1000BASE-BX10-U (1GE BiDi Up)", + "1000BASE-CX (1GE DAC)", + "1000BASE-CWDM (1GE)", + "1000BASE-DWDM (1GE)", + "1000BASE-EX (1GE)", "1000BASE-SX (1GE)", + "1000BASE-LSX (1GE)", "1000BASE-LX (1GE)", + "1000BASE-LX10/LH (1GE)", + "1000BASE-T (1GE)", "1000BASE-TX (1GE)", + "1000BASE-ZX (1GE)", "2.5GBASE-T (2.5GE)", "5GBASE-T (5GE)", + "10GBASE-DR-D (10GE BiDi Down)", + "10GBASE-DR-U (10GE BiDi Up)", + "10GBASE-CX4 (10GE DAC)", + "10GBASE-ER (10GE)", + "10GBASE-LR (10GE)", + "10GBASE-LRM (10GE)", + "10GBASE-LX4 (10GE)", + "10GBASE-SR (10GE)", "10GBASE-T (10GE)", - "10GBASE-CX4 (10GE)", + "10GBASE-ZR (10GE)", + "25GBASE-CR (25GE DAC)", + "25GBASE-ER (25GE)", + "25GBASE-LR (25GE)", + "25GBASE-SR (25GE)", + "25GBASE-T (25GE)", + "40GBASE-CR4 (40GE DAC)", + "40GBASE-ER4 (40GE)", + "40GBASE-FR4 (40GE)", + "40GBASE-LR4 (40GE)", + "40GBASE-SR4 (40GE)", + "50GBASE-CR (50GE DAC)", + "50GBASE-ER (50GE)", + "50GBASE-FR (50GE)", + "50GBASE-LR (50GE)", + "50GBASE-SR (50GE)", + "100GBASE-CR1 (100GE DAC)", + "100GBASE-CR2 (100GE DAC)", + "100GBASE-CR4 (100GE DAC)", + "100GBASE-CR10 (100GE DAC)", + "100GBASE-DR (100GE)", + "100GBASE-ER4 (100GE)", + "100GBASE-FR1 (100GE)", + "100GBASE-LR1 (100GE)", + "100GBASE-LR4 (100GE)", + "100GBASE-SR1 (100GE)", + "100GBASE-SR1.2 (100GE BiDi)", + "100GBASE-SR2 (100GE)", + "100GBASE-SR4 (100GE)", + "100GBASE-SR10 (100GE)", + "100GBASE-ZR (100GE)", + "200GBASE-CR2 (200GE)", + "200GBASE-CR4 (200GE)", + "200GBASE-SR2 (200GE)", + "200GBASE-SR4 (200GE)", + "200GBASE-DR4 (200GE)", + "200GBASE-ER4 (200GE)", + "200GBASE-FR4 (200GE)", + "200GBASE-LR4 (200GE)", + "200GBASE-VR2 (200GE)", + "400GBASE-CR4 (400GE)", + "400GBASE-DR4 (400GE)", + "400GBASE-ER8 (400GE)", + "400GBASE-FR4 (400GE)", + "400GBASE-FR8 (400GE)", + "400GBASE-LR4 (400GE)", + "400GBASE-LR8 (400GE)", + "400GBASE-SR4 (400GE)", + "400GBASE-SR4.2 (400GE BiDi)", + "400GBASE-SR8 (400GE)", + "400GBASE-SR16 (400GE)", + "400GBASE-VR4 (400GE)", + "400GBASE-ZR (400GE)", + "800GBASE-CR8 (800GE)", + "800GBASE-DR8 (800GE)", + "800GBASE-SR8 (800GE)", + "800GBASE-VR8 (800GE)", "SFP (100ME)", "GBIC (1GE)", "SFP (1GE)", @@ -217286,12 +217659,12 @@ "100GBASE-KR4 (100GE)", "IEEE 802.11a", "IEEE 802.11b/g", - "IEEE 802.11n", - "IEEE 802.11ac", - "IEEE 802.11ad", - "IEEE 802.11ax", - "IEEE 802.11ay", - "IEEE 802.11be", + "IEEE 802.11n (Wi-Fi 4)", + "IEEE 802.11ac (Wi-Fi 5)", + "IEEE 802.11ad (WiGig)", + "IEEE 802.11ax (Wi-Fi 6)", + "IEEE 802.11ay (WiGig)", + "IEEE 802.11be (Wi-Fi 7)", "IEEE 802.15.1 (Bluetooth)", "IEEE 802.15.4 (LR-WPAN)", "Other (Wireless)", @@ -217544,14 +217917,87 @@ "100base-lfx", "100base-tx", "100base-t1", - "1000base-t", + "1000base-bx10-d", + "1000base-bx10-u", + "1000base-cx", + "1000base-cwdm", + "1000base-dwdm", + "1000base-ex", "1000base-sx", + "1000base-lsx", "1000base-lx", + "1000base-lx10", + "1000base-t", "1000base-tx", + "1000base-zx", "2.5gbase-t", "5gbase-t", - "10gbase-t", + "10gbase-br-d", + "10gbase-br-u", "10gbase-cx4", + "10gbase-er", + "10gbase-lr", + "10gbase-lrm", + "10gbase-lx4", + "10gbase-sr", + "10gbase-t", + "10gbase-zr", + "25gbase-cr", + "25gbase-er", + "25gbase-lr", + "25gbase-sr", + "25gbase-t", + "40gbase-cr4", + "40gbase-er4", + "40gbase-fr4", + "40gbase-lr4", + "40gbase-sr4", + "50gbase-cr", + "50gbase-er", + "50gbase-fr", + "50gbase-lr", + "50gbase-sr", + "100gbase-cr1", + "100gbase-cr2", + "100gbase-cr4", + "100gbase-cr10", + "100gbase-dr", + "100gbase-er4", + "100gbase-fr1", + "100gbase-lr1", + "100gbase-lr4", + "100gbase-sr1", + "100gbase-sr1.2", + "100gbase-sr2", + "100gbase-sr4", + "100gbase-sr10", + "100gbase-zr", + "200gbase-cr2", + "200gbase-cr4", + "200gbase-sr2", + "200gbase-sr4", + "200gbase-dr4", + "200gbase-er4", + "200gbase-fr4", + "200gbase-lr4", + "200gbase-vr2", + "400gbase-cr4", + "400gbase-dr4", + "400gbase-er8", + "400gbase-fr4", + "400gbase-fr8", + "400gbase-lr4", + "400gbase-lr8", + "400gbase-sr4", + "400gbase-sr4_2", + "400gbase-sr8", + "400gbase-sr16", + "400gbase-vr4", + "400gbase-zr", + "800gbase-cr8", + "800gbase-dr8", + "800gbase-sr8", + "800gbase-vr8", "100base-x-sfp", "1000base-x-gbic", "1000base-x-sfp", @@ -217671,8 +218117,8 @@ "other" ], "type": "string", - "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME FIBER)\n* `100base-lfx` - 100BASE-LFX (10/100ME FIBER)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME Single Pair)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n\n* `ieee802.11ac` - IEEE 802.11ac\n* `ieee802.11ad` - IEEE 802.11ad\n* `ieee802.11ax` - IEEE 802.11ax\n* `ieee802.11ay` - IEEE 802.11ay\n* `ieee802.11be` - IEEE 802.11be\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", - "x-spec-enum-id": "8c4c5e112f77a383" + "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-DR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-DR-U (10GE BiDi Up)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", + "x-spec-enum-id": "efa24ff9c7a39c40" }, "enabled": { "type": "boolean" @@ -228705,9 +229151,9 @@ "cat7", "cat7a", "cat8", + "mrj21-trunk", "dac-active", "dac-passive", - "mrj21-trunk", "coaxial", "mmf", "mmf-om1", @@ -228719,14 +229165,14 @@ "smf-os1", "smf-os2", "aoc", - "usb", "power", + "usb", "", null ], "type": "string", - "description": "* `cat3` - CAT3\n* `cat5` - CAT5\n* `cat5e` - CAT5e\n* `cat6` - CAT6\n* `cat6a` - CAT6a\n* `cat7` - CAT7\n* `cat7a` - CAT7a\n* `cat8` - CAT8\n* `dac-active` - Direct Attach Copper (Active)\n* `dac-passive` - Direct Attach Copper (Passive)\n* `mrj21-trunk` - MRJ21 Trunk\n* `coaxial` - Coaxial\n* `mmf` - Multimode Fiber\n* `mmf-om1` - Multimode Fiber (OM1)\n* `mmf-om2` - Multimode Fiber (OM2)\n* `mmf-om3` - Multimode Fiber (OM3)\n* `mmf-om4` - Multimode Fiber (OM4)\n* `mmf-om5` - Multimode Fiber (OM5)\n* `smf` - Singlemode Fiber\n* `smf-os1` - Singlemode Fiber (OS1)\n* `smf-os2` - Singlemode Fiber (OS2)\n* `aoc` - Active Optical Cabling (AOC)\n* `usb` - USB\n* `power` - Power", - "x-spec-enum-id": "7b11d524b2b1a7ef", + "description": "* `cat3` - CAT3\n* `cat5` - CAT5\n* `cat5e` - CAT5e\n* `cat6` - CAT6\n* `cat6a` - CAT6a\n* `cat7` - CAT7\n* `cat7a` - CAT7a\n* `cat8` - CAT8\n* `mrj21-trunk` - MRJ21 Trunk\n* `dac-active` - Direct Attach Copper (Active)\n* `dac-passive` - Direct Attach Copper (Passive)\n* `coaxial` - Coaxial\n* `mmf` - Multimode Fiber\n* `mmf-om1` - Multimode Fiber (OM1)\n* `mmf-om2` - Multimode Fiber (OM2)\n* `mmf-om3` - Multimode Fiber (OM3)\n* `mmf-om4` - Multimode Fiber (OM4)\n* `mmf-om5` - Multimode Fiber (OM5)\n* `smf` - Single-mode Fiber\n* `smf-os1` - Single-mode Fiber (OS1)\n* `smf-os2` - Single-mode Fiber (OS2)\n* `aoc` - Active Optical Cabling (AOC)\n* `power` - Power\n* `usb` - USB", + "x-spec-enum-id": "c731f2793fceac04", "nullable": true }, "a_terminations": { @@ -229727,19 +230173,23 @@ "description": "Fields with higher weights appear lower in a form." }, "validation_minimum": { - "type": "integer", - "maximum": 9223372036854775807, - "minimum": -9223372036854775808, - "format": "int64", + "type": "number", + "format": "double", + "maximum": 1000000000000, + "minimum": -1000000000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, "nullable": true, "title": "Minimum value", "description": "Minimum allowed value (for numeric fields)" }, "validation_maximum": { - "type": "integer", - "maximum": 9223372036854775807, - "minimum": -9223372036854775808, - "format": "int64", + "type": "number", + "format": "double", + "maximum": 1000000000000, + "minimum": -1000000000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, "nullable": true, "title": "Maximum value", "description": "Maximum allowed value (for numeric fields)" @@ -231357,14 +231807,87 @@ "100base-lfx", "100base-tx", "100base-t1", - "1000base-t", + "1000base-bx10-d", + "1000base-bx10-u", + "1000base-cx", + "1000base-cwdm", + "1000base-dwdm", + "1000base-ex", "1000base-sx", + "1000base-lsx", "1000base-lx", + "1000base-lx10", + "1000base-t", "1000base-tx", + "1000base-zx", "2.5gbase-t", "5gbase-t", - "10gbase-t", + "10gbase-br-d", + "10gbase-br-u", "10gbase-cx4", + "10gbase-er", + "10gbase-lr", + "10gbase-lrm", + "10gbase-lx4", + "10gbase-sr", + "10gbase-t", + "10gbase-zr", + "25gbase-cr", + "25gbase-er", + "25gbase-lr", + "25gbase-sr", + "25gbase-t", + "40gbase-cr4", + "40gbase-er4", + "40gbase-fr4", + "40gbase-lr4", + "40gbase-sr4", + "50gbase-cr", + "50gbase-er", + "50gbase-fr", + "50gbase-lr", + "50gbase-sr", + "100gbase-cr1", + "100gbase-cr2", + "100gbase-cr4", + "100gbase-cr10", + "100gbase-dr", + "100gbase-er4", + "100gbase-fr1", + "100gbase-lr1", + "100gbase-lr4", + "100gbase-sr1", + "100gbase-sr1.2", + "100gbase-sr2", + "100gbase-sr4", + "100gbase-sr10", + "100gbase-zr", + "200gbase-cr2", + "200gbase-cr4", + "200gbase-sr2", + "200gbase-sr4", + "200gbase-dr4", + "200gbase-er4", + "200gbase-fr4", + "200gbase-lr4", + "200gbase-vr2", + "400gbase-cr4", + "400gbase-dr4", + "400gbase-er8", + "400gbase-fr4", + "400gbase-fr8", + "400gbase-lr4", + "400gbase-lr8", + "400gbase-sr4", + "400gbase-sr4_2", + "400gbase-sr8", + "400gbase-sr16", + "400gbase-vr4", + "400gbase-zr", + "800gbase-cr8", + "800gbase-dr8", + "800gbase-sr8", + "800gbase-vr8", "100base-x-sfp", "1000base-x-gbic", "1000base-x-sfp", @@ -231484,8 +232007,8 @@ "other" ], "type": "string", - "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME FIBER)\n* `100base-lfx` - 100BASE-LFX (10/100ME FIBER)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME Single Pair)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n\n* `ieee802.11ac` - IEEE 802.11ac\n* `ieee802.11ad` - IEEE 802.11ad\n* `ieee802.11ax` - IEEE 802.11ax\n* `ieee802.11ay` - IEEE 802.11ay\n* `ieee802.11be` - IEEE 802.11be\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", - "x-spec-enum-id": "8c4c5e112f77a383" + "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-DR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-DR-U (10GE BiDi Up)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", + "x-spec-enum-id": "efa24ff9c7a39c40" }, "enabled": { "type": "boolean" @@ -232002,14 +232525,87 @@ "100base-lfx", "100base-tx", "100base-t1", - "1000base-t", + "1000base-bx10-d", + "1000base-bx10-u", + "1000base-cx", + "1000base-cwdm", + "1000base-dwdm", + "1000base-ex", "1000base-sx", + "1000base-lsx", "1000base-lx", + "1000base-lx10", + "1000base-t", "1000base-tx", + "1000base-zx", "2.5gbase-t", "5gbase-t", - "10gbase-t", + "10gbase-br-d", + "10gbase-br-u", "10gbase-cx4", + "10gbase-er", + "10gbase-lr", + "10gbase-lrm", + "10gbase-lx4", + "10gbase-sr", + "10gbase-t", + "10gbase-zr", + "25gbase-cr", + "25gbase-er", + "25gbase-lr", + "25gbase-sr", + "25gbase-t", + "40gbase-cr4", + "40gbase-er4", + "40gbase-fr4", + "40gbase-lr4", + "40gbase-sr4", + "50gbase-cr", + "50gbase-er", + "50gbase-fr", + "50gbase-lr", + "50gbase-sr", + "100gbase-cr1", + "100gbase-cr2", + "100gbase-cr4", + "100gbase-cr10", + "100gbase-dr", + "100gbase-er4", + "100gbase-fr1", + "100gbase-lr1", + "100gbase-lr4", + "100gbase-sr1", + "100gbase-sr1.2", + "100gbase-sr2", + "100gbase-sr4", + "100gbase-sr10", + "100gbase-zr", + "200gbase-cr2", + "200gbase-cr4", + "200gbase-sr2", + "200gbase-sr4", + "200gbase-dr4", + "200gbase-er4", + "200gbase-fr4", + "200gbase-lr4", + "200gbase-vr2", + "400gbase-cr4", + "400gbase-dr4", + "400gbase-er8", + "400gbase-fr4", + "400gbase-fr8", + "400gbase-lr4", + "400gbase-lr8", + "400gbase-sr4", + "400gbase-sr4_2", + "400gbase-sr8", + "400gbase-sr16", + "400gbase-vr4", + "400gbase-zr", + "800gbase-cr8", + "800gbase-dr8", + "800gbase-sr8", + "800gbase-vr8", "100base-x-sfp", "1000base-x-gbic", "1000base-x-sfp", @@ -232129,8 +232725,8 @@ "other" ], "type": "string", - "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME FIBER)\n* `100base-lfx` - 100BASE-LFX (10/100ME FIBER)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME Single Pair)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n\n* `ieee802.11ac` - IEEE 802.11ac\n* `ieee802.11ad` - IEEE 802.11ad\n* `ieee802.11ax` - IEEE 802.11ax\n* `ieee802.11ay` - IEEE 802.11ay\n* `ieee802.11be` - IEEE 802.11be\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", - "x-spec-enum-id": "8c4c5e112f77a383" + "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-DR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-DR-U (10GE BiDi Up)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", + "x-spec-enum-id": "efa24ff9c7a39c40" }, "enabled": { "type": "boolean" @@ -248783,9 +249379,9 @@ "cat7", "cat7a", "cat8", + "mrj21-trunk", "dac-active", "dac-passive", - "mrj21-trunk", "coaxial", "mmf", "mmf-om1", @@ -248797,14 +249393,14 @@ "smf-os1", "smf-os2", "aoc", - "usb", "power", + "usb", "", null ], "type": "string", - "description": "* `cat3` - CAT3\n* `cat5` - CAT5\n* `cat5e` - CAT5e\n* `cat6` - CAT6\n* `cat6a` - CAT6a\n* `cat7` - CAT7\n* `cat7a` - CAT7a\n* `cat8` - CAT8\n* `dac-active` - Direct Attach Copper (Active)\n* `dac-passive` - Direct Attach Copper (Passive)\n* `mrj21-trunk` - MRJ21 Trunk\n* `coaxial` - Coaxial\n* `mmf` - Multimode Fiber\n* `mmf-om1` - Multimode Fiber (OM1)\n* `mmf-om2` - Multimode Fiber (OM2)\n* `mmf-om3` - Multimode Fiber (OM3)\n* `mmf-om4` - Multimode Fiber (OM4)\n* `mmf-om5` - Multimode Fiber (OM5)\n* `smf` - Singlemode Fiber\n* `smf-os1` - Singlemode Fiber (OS1)\n* `smf-os2` - Singlemode Fiber (OS2)\n* `aoc` - Active Optical Cabling (AOC)\n* `usb` - USB\n* `power` - Power", - "x-spec-enum-id": "7b11d524b2b1a7ef", + "description": "* `cat3` - CAT3\n* `cat5` - CAT5\n* `cat5e` - CAT5e\n* `cat6` - CAT6\n* `cat6a` - CAT6a\n* `cat7` - CAT7\n* `cat7a` - CAT7a\n* `cat8` - CAT8\n* `mrj21-trunk` - MRJ21 Trunk\n* `dac-active` - Direct Attach Copper (Active)\n* `dac-passive` - Direct Attach Copper (Passive)\n* `coaxial` - Coaxial\n* `mmf` - Multimode Fiber\n* `mmf-om1` - Multimode Fiber (OM1)\n* `mmf-om2` - Multimode Fiber (OM2)\n* `mmf-om3` - Multimode Fiber (OM3)\n* `mmf-om4` - Multimode Fiber (OM4)\n* `mmf-om5` - Multimode Fiber (OM5)\n* `smf` - Single-mode Fiber\n* `smf-os1` - Single-mode Fiber (OS1)\n* `smf-os2` - Single-mode Fiber (OS2)\n* `aoc` - Active Optical Cabling (AOC)\n* `power` - Power\n* `usb` - USB", + "x-spec-enum-id": "c731f2793fceac04", "nullable": true }, "a_terminations": { @@ -249846,19 +250442,23 @@ "description": "Fields with higher weights appear lower in a form." }, "validation_minimum": { - "type": "integer", - "maximum": 9223372036854775807, - "minimum": -9223372036854775808, - "format": "int64", + "type": "number", + "format": "double", + "maximum": 1000000000000, + "minimum": -1000000000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, "nullable": true, "title": "Minimum value", "description": "Minimum allowed value (for numeric fields)" }, "validation_maximum": { - "type": "integer", - "maximum": 9223372036854775807, - "minimum": -9223372036854775808, - "format": "int64", + "type": "number", + "format": "double", + "maximum": 1000000000000, + "minimum": -1000000000000, + "exclusiveMaximum": true, + "exclusiveMinimum": true, "nullable": true, "title": "Maximum value", "description": "Maximum allowed value (for numeric fields)" @@ -251544,14 +252144,87 @@ "100base-lfx", "100base-tx", "100base-t1", - "1000base-t", + "1000base-bx10-d", + "1000base-bx10-u", + "1000base-cx", + "1000base-cwdm", + "1000base-dwdm", + "1000base-ex", "1000base-sx", + "1000base-lsx", "1000base-lx", + "1000base-lx10", + "1000base-t", "1000base-tx", + "1000base-zx", "2.5gbase-t", "5gbase-t", - "10gbase-t", + "10gbase-br-d", + "10gbase-br-u", "10gbase-cx4", + "10gbase-er", + "10gbase-lr", + "10gbase-lrm", + "10gbase-lx4", + "10gbase-sr", + "10gbase-t", + "10gbase-zr", + "25gbase-cr", + "25gbase-er", + "25gbase-lr", + "25gbase-sr", + "25gbase-t", + "40gbase-cr4", + "40gbase-er4", + "40gbase-fr4", + "40gbase-lr4", + "40gbase-sr4", + "50gbase-cr", + "50gbase-er", + "50gbase-fr", + "50gbase-lr", + "50gbase-sr", + "100gbase-cr1", + "100gbase-cr2", + "100gbase-cr4", + "100gbase-cr10", + "100gbase-dr", + "100gbase-er4", + "100gbase-fr1", + "100gbase-lr1", + "100gbase-lr4", + "100gbase-sr1", + "100gbase-sr1.2", + "100gbase-sr2", + "100gbase-sr4", + "100gbase-sr10", + "100gbase-zr", + "200gbase-cr2", + "200gbase-cr4", + "200gbase-sr2", + "200gbase-sr4", + "200gbase-dr4", + "200gbase-er4", + "200gbase-fr4", + "200gbase-lr4", + "200gbase-vr2", + "400gbase-cr4", + "400gbase-dr4", + "400gbase-er8", + "400gbase-fr4", + "400gbase-fr8", + "400gbase-lr4", + "400gbase-lr8", + "400gbase-sr4", + "400gbase-sr4_2", + "400gbase-sr8", + "400gbase-sr16", + "400gbase-vr4", + "400gbase-zr", + "800gbase-cr8", + "800gbase-dr8", + "800gbase-sr8", + "800gbase-vr8", "100base-x-sfp", "1000base-x-gbic", "1000base-x-sfp", @@ -251671,8 +252344,8 @@ "other" ], "type": "string", - "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME FIBER)\n* `100base-lfx` - 100BASE-LFX (10/100ME FIBER)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME Single Pair)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n\n* `ieee802.11ac` - IEEE 802.11ac\n* `ieee802.11ad` - IEEE 802.11ad\n* `ieee802.11ax` - IEEE 802.11ax\n* `ieee802.11ay` - IEEE 802.11ay\n* `ieee802.11be` - IEEE 802.11be\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", - "x-spec-enum-id": "8c4c5e112f77a383" + "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-DR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-DR-U (10GE BiDi Up)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", + "x-spec-enum-id": "efa24ff9c7a39c40" }, "enabled": { "type": "boolean" @@ -252194,14 +252867,87 @@ "100base-lfx", "100base-tx", "100base-t1", - "1000base-t", + "1000base-bx10-d", + "1000base-bx10-u", + "1000base-cx", + "1000base-cwdm", + "1000base-dwdm", + "1000base-ex", "1000base-sx", + "1000base-lsx", "1000base-lx", + "1000base-lx10", + "1000base-t", "1000base-tx", + "1000base-zx", "2.5gbase-t", "5gbase-t", - "10gbase-t", + "10gbase-br-d", + "10gbase-br-u", "10gbase-cx4", + "10gbase-er", + "10gbase-lr", + "10gbase-lrm", + "10gbase-lx4", + "10gbase-sr", + "10gbase-t", + "10gbase-zr", + "25gbase-cr", + "25gbase-er", + "25gbase-lr", + "25gbase-sr", + "25gbase-t", + "40gbase-cr4", + "40gbase-er4", + "40gbase-fr4", + "40gbase-lr4", + "40gbase-sr4", + "50gbase-cr", + "50gbase-er", + "50gbase-fr", + "50gbase-lr", + "50gbase-sr", + "100gbase-cr1", + "100gbase-cr2", + "100gbase-cr4", + "100gbase-cr10", + "100gbase-dr", + "100gbase-er4", + "100gbase-fr1", + "100gbase-lr1", + "100gbase-lr4", + "100gbase-sr1", + "100gbase-sr1.2", + "100gbase-sr2", + "100gbase-sr4", + "100gbase-sr10", + "100gbase-zr", + "200gbase-cr2", + "200gbase-cr4", + "200gbase-sr2", + "200gbase-sr4", + "200gbase-dr4", + "200gbase-er4", + "200gbase-fr4", + "200gbase-lr4", + "200gbase-vr2", + "400gbase-cr4", + "400gbase-dr4", + "400gbase-er8", + "400gbase-fr4", + "400gbase-fr8", + "400gbase-lr4", + "400gbase-lr8", + "400gbase-sr4", + "400gbase-sr4_2", + "400gbase-sr8", + "400gbase-sr16", + "400gbase-vr4", + "400gbase-zr", + "800gbase-cr8", + "800gbase-dr8", + "800gbase-sr8", + "800gbase-vr8", "100base-x-sfp", "1000base-x-gbic", "1000base-x-sfp", @@ -252321,8 +253067,8 @@ "other" ], "type": "string", - "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME FIBER)\n* `100base-lfx` - 100BASE-LFX (10/100ME FIBER)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME Single Pair)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n\n* `ieee802.11ac` - IEEE 802.11ac\n* `ieee802.11ad` - IEEE 802.11ad\n* `ieee802.11ax` - IEEE 802.11ax\n* `ieee802.11ay` - IEEE 802.11ay\n* `ieee802.11be` - IEEE 802.11be\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", - "x-spec-enum-id": "8c4c5e112f77a383" + "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-DR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-DR-U (10GE BiDi Up)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other", + "x-spec-enum-id": "efa24ff9c7a39c40" }, "enabled": { "type": "boolean" diff --git a/docs/release-notes/version-4.4.md b/docs/release-notes/version-4.4.md index 7138c277c48..0c95e9fcf1d 100644 --- a/docs/release-notes/version-4.4.md +++ b/docs/release-notes/version-4.4.md @@ -1,5 +1,47 @@ # NetBox v4.4 +## v4.4.1 (2025-09-16) + +### Enhancements + +* [#15492](https://github.com/netbox-community/netbox/issues/15492) - Enable cloning of permissions +* [#16381](https://github.com/netbox-community/netbox/issues/16381) - Display script result timestamps in system timezone +* [#19262](https://github.com/netbox-community/netbox/issues/19262) - No longer restrict FHRP group assignment by assigned IP address +* [#19408](https://github.com/netbox-community/netbox/issues/19408) - Support export templates for circuit terminations and virtual circuit terminations +* [#19428](https://github.com/netbox-community/netbox/issues/19428) - Add an optional U height field to the devices table +* [#19547](https://github.com/netbox-community/netbox/issues/19547) - Add individual "sync" buttons in data sources table +* [#19865](https://github.com/netbox-community/netbox/issues/19865) - Reorganize cable type groupings +* [#20222](https://github.com/netbox-community/netbox/issues/20222) - Enable the `HttpOnly` flag for CSRF cookie +* [#20237](https://github.com/netbox-community/netbox/issues/20237) - Include VPN tunnel groups in global search results +* [#20241](https://github.com/netbox-community/netbox/issues/20241) - Record A & B terminations in cable changelog data +* [#20277](https://github.com/netbox-community/netbox/issues/20277) - Add support for attribute assignment to `deserialize_object()` utility +* [#20321](https://github.com/netbox-community/netbox/issues/20321) - Add physical media types for transceiver interfaces +* [#20347](https://github.com/netbox-community/netbox/issues/20347) - Add Wi-Fi Alliance aliases to 802.11 interface types + +### Bug Fixes + +* [#19729](https://github.com/netbox-community/netbox/issues/19729) - Restore `kind` filter for interfaces in GraphQL API +* [#19744](https://github.com/netbox-community/netbox/issues/19744) - Plugins list should be orderable by "active" column +* [#19851](https://github.com/netbox-community/netbox/issues/19851) - Fix `ValueError` complaining of missing `scope` when bulk importing wireless LANs +* [#19896](https://github.com/netbox-community/netbox/issues/19896) - Min/max values for decimal custom fields should accept decimal values +* [#20197](https://github.com/netbox-community/netbox/issues/20197) - Correct validation for virtual chassis parent interface +* [#20215](https://github.com/netbox-community/netbox/issues/20215) - All GraphQL filters for config contexts should be optional +* [#20217](https://github.com/netbox-community/netbox/issues/20217) - Remove "0 VLANs available" row at end of VLAN range table +* [#20221](https://github.com/netbox-community/netbox/issues/20221) - JSON fields should not coerce empty dictionaries to null +* [#20227](https://github.com/netbox-community/netbox/issues/20227) - Ensure consistent padding of Markdown content +* [#20234](https://github.com/netbox-community/netbox/issues/20234) - Fix "add" button link for prerequisite object warning in UI +* [#20236](https://github.com/netbox-community/netbox/issues/20236) - Strip invalid characters from uploaded image file names +* [#20238](https://github.com/netbox-community/netbox/issues/20238) - Fix support for outside IP assignment during bulk import of tunnel terminations +* [#20242](https://github.com/netbox-community/netbox/issues/20242) - Avoid `AttributeError` exception on background jobs with no request ID +* [#20252](https://github.com/netbox-community/netbox/issues/20252) - Remove generic AddObject from ObjectChildrenView to prevent duplicate "add" buttons +* [#20264](https://github.com/netbox-community/netbox/issues/20264) - Fix rendering of default icon in plugins list +* [#20272](https://github.com/netbox-community/netbox/issues/20272) - ConfigContexts assigned to ancestor locations should apply to device/VM +* [#20282](https://github.com/netbox-community/netbox/issues/20282) - Fix styling of prerequisite objects warning +* [#20298](https://github.com/netbox-community/netbox/issues/20298) - Display a placeholder when an image thumbnail fails to load +* [#20327](https://github.com/netbox-community/netbox/issues/20327) - Avoid calling `distinct()` on device/VM queryset when fetching config context data + +--- + ## v4.4.0 (2025-09-02) ### New Features diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index 1f5e199d8e7..b6f1b04c32e 100644 --- a/netbox/project-static/dist/netbox.js +++ b/netbox/project-static/dist/netbox.js @@ -1,11 +1,11 @@ -"use strict";(()=>{var tu=Object.create;var Mi=Object.defineProperty,nu=Object.defineProperties,iu=Object.getOwnPropertyDescriptor,ru=Object.getOwnPropertyDescriptors,ou=Object.getOwnPropertyNames,ps=Object.getOwnPropertySymbols,su=Object.getPrototypeOf,ms=Object.prototype.hasOwnProperty,au=Object.prototype.propertyIsEnumerable;var qr=(n,e,t)=>e in n?Mi(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)&&qr(n,t,e[t]);if(ps)for(var t of ps(e))au.call(e,t)&&qr(n,t,e[t]);return n},ae=(n,e)=>nu(n,ru(e));var lu=(n,e)=>()=>(e||n((e={exports:{}}).exports,e),e.exports),gs=(n,e)=>{for(var t in e)Mi(n,t,{get:e[t],enumerable:!0})},cu=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of ou(e))!ms.call(n,r)&&r!==t&&Mi(n,r,{get:()=>e[r],enumerable:!(i=iu(e,r))||i.enumerable});return n};var uu=(n,e,t)=>(t=n!=null?tu(su(n)):{},cu(e||!n||!n.__esModule?Mi(t,"default",{value:n,enumerable:!0}):t,n));var ee=(n,e,t)=>qr(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 gc=lu((vi,ts)=>{(function(e,t){typeof vi=="object"&&typeof ts=="object"?ts.exports=t():typeof define=="function"&&define.amd?define([],t):typeof vi=="object"?vi.ClipboardJS=t():e.ClipboardJS=t()})(vi,function(){return function(){var n={686:function(i,r,o){"use strict";o.d(r,{default:function(){return Re}});var s=o(279),a=o.n(s),l=o(370),c=o.n(l),u=o(817),d=o.n(u);function p(W){try{return document.execCommand(W)}catch(M){return!1}}var y=function(M){var D=d()(M);return p("cut"),D},m=y;function v(W){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 $=window.pageYOffset||document.documentElement.scrollTop;return D.style.top="".concat($,"px"),D.setAttribute("readonly",""),D.value=W,D}var w=function(M,D){var $=v(M);D.container.appendChild($);var V=d()($);return p("copy"),$.remove(),V},T=function(M){var D=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},$="";return typeof M=="string"?$=w(M,D):M instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(M==null?void 0:M.type)?$=w(M.value,D):($=d()(M),p("copy")),$},_=T;function S(W){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?S=function(D){return typeof D}:S=function(D){return D&&typeof Symbol=="function"&&D.constructor===Symbol&&D!==Symbol.prototype?"symbol":typeof D},S(W)}var A=function(){var M=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},D=M.action,$=D===void 0?"copy":D,V=M.container,q=M.target,U=M.text;if($!=="copy"&&$!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(q!==void 0)if(q&&S(q)==="object"&&q.nodeType===1){if($==="copy"&&q.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if($==="cut"&&(q.hasAttribute("readonly")||q.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(U)return _(U,{container:V});if(q)return $==="cut"?m(q):_(q,{container:V})},K=A;function z(W){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?z=function(D){return typeof D}:z=function(D){return D&&typeof Symbol=="function"&&D.constructor===Symbol&&D!==Symbol.prototype?"symbol":typeof D},z(W)}function L(W,M){if(!(W instanceof M))throw new TypeError("Cannot call a class as a function")}function H(W,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=z(V.container)==="object"?V.container:document.body}},{key:"listenClick",value:function(V){var q=this;this.listener=c()(V,"click",function(U){return q.onClick(U)})}},{key:"onClick",value:function(V){var q=V.delegateTarget||V.currentTarget,U=this.action(q)||"copy",Z=K({action:U,container:this.container,target:this.target(q),text:this.text(q)});this.emit(Z?"success":"error",{action:U,text:Z,trigger:q,clearSelection:function(){q&&q.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(V){return ne("action",V)}},{key:"defaultTarget",value:function(V){var q=ne("target",V);if(q)return document.querySelector(q)}},{key:"defaultText",value:function(V){return ne("text",V)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(V){var q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return _(V,q)}},{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"],q=typeof V=="string"?[V]:V,U=!!document.queryCommandSupported;return q.forEach(function(Z){U=U&&!!document.queryCommandSupported(Z)}),U}}]),D}(a()),Re=qe},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 v=c.apply(this,arguments);return u.addEventListener(p,v,m),{destroy:function(){u.removeEventListener(p,v,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(v){return a(v,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(v){v.addEventListener(y,m)}),{destroy:function(){Array.prototype.forEach.call(p,function(v){v.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:()=>hn,arrow:()=>Ni,auto:()=>jn,basePlacements:()=>lt,beforeMain:()=>bs,beforeRead:()=>vs,beforeWrite:()=>xs,bottom:()=>ge,clippingParents:()=>Ur,computeStyles:()=>mn,createPopper:()=>Jn,createPopperBase:()=>Is,createPopperLite:()=>Rs,detectOverflow:()=>ke,end:()=>_t,eventListeners:()=>gn,flip:()=>Pi,hide:()=>Fi,left:()=>he,main:()=>_s,modifierPhases:()=>Gr,offset:()=>Bi,placements:()=>qn,popper:()=>$t,popperGenerator:()=>Gt,popperOffsets:()=>En,preventOverflow:()=>$i,read:()=>ys,reference:()=>Yr,right:()=>me,start:()=>rt,top:()=>de,variationPlacements:()=>ki,viewport:()=>Wn,write:()=>Ts});var de="top",ge="bottom",me="right",he="left",jn="auto",lt=[de,ge,me,he],rt="start",_t="end",Ur="clippingParents",Wn="viewport",$t="popper",Yr="reference",ki=lt.reduce(function(n,e){return n.concat([e+"-"+rt,e+"-"+_t])},[]),qn=[].concat(lt,[jn]).reduce(function(n,e){return n.concat([e,e+"-"+rt,e+"-"+_t])},[]),vs="beforeRead",ys="read",Es="afterRead",bs="beforeMain",_s="main",ws="afterMain",xs="beforeWrite",Ts="write",Cs="afterWrite",Gr=[vs,ys,Es,bs,_s,ws,xs,Ts,Cs];function we(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 Ue(n){var e=ce(n).Element;return n instanceof e||n instanceof Element}function be(n){var e=ce(n).HTMLElement;return n instanceof e||n instanceof HTMLElement}function fn(n){if(typeof ShadowRoot=="undefined")return!1;var e=ce(n).ShadowRoot;return n instanceof e||n instanceof ShadowRoot}function du(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];!be(o)||!we(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 fu(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},{});!be(r)||!we(r)||(Object.assign(r.style,a),Object.keys(o).forEach(function(l){r.removeAttribute(l)}))})}}var hn={name:"applyStyles",enabled:!0,phase:"write",fn:du,effect:fu,requires:["computeStyles"]};function xe(n){return n.split("-")[0]}var Ze=Math.max,Vt=Math.min,ct=Math.round;function pn(){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 Un(){return!/^((?!chrome|android).)*safari/i.test(pn())}function Ye(n,e,t){e===void 0&&(e=!1),t===void 0&&(t=!1);var i=n.getBoundingClientRect(),r=1,o=1;e&&be(n)&&(r=n.offsetWidth>0&&ct(i.width)/n.offsetWidth||1,o=n.offsetHeight>0&&ct(i.height)/n.offsetHeight||1);var s=Ue(n)?ce(n):window,a=s.visualViewport,l=!Un()&&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 zt(n){var e=Ye(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 Yn(n,e){var t=e.getRootNode&&e.getRootNode();if(n.contains(e))return!0;if(t&&fn(t)){var i=e;do{if(i&&n.isSameNode(i))return!0;i=i.parentNode||i.host}while(i)}return!1}function Me(n){return ce(n).getComputedStyle(n)}function Kr(n){return["table","td","th"].indexOf(we(n))>=0}function Ae(n){return((Ue(n)?n.ownerDocument:n.document)||window.document).documentElement}function ut(n){return we(n)==="html"?n:n.assignedSlot||n.parentNode||(fn(n)?n.host:null)||Ae(n)}function As(n){return!be(n)||Me(n).position==="fixed"?null:n.offsetParent}function hu(n){var e=/firefox/i.test(pn()),t=/Trident/i.test(pn());if(t&&be(n)){var i=Me(n);if(i.position==="fixed")return null}var r=ut(n);for(fn(r)&&(r=r.host);be(r)&&["html","body"].indexOf(we(r))<0;){var o=Me(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=As(n);t&&Kr(t)&&Me(t).position==="static";)t=As(t);return t&&(we(t)==="html"||we(t)==="body"&&Me(t).position==="static")?e:t||hu(n)||e}function jt(n){return["top","bottom"].indexOf(n)>=0?"x":"y"}function Wt(n,e,t){return Ze(n,Vt(e,t))}function Ss(n,e,t){var i=Wt(n,e,t);return i>t?t:i}function Gn(){return{top:0,right:0,bottom:0,left:0}}function Kn(n){return Object.assign({},Gn(),n)}function Xn(n,e){return e.reduce(function(t,i){return t[i]=n,t},{})}var pu=function(e,t){return e=typeof e=="function"?e(Object.assign({},t.rects,{placement:t.placement})):e,Kn(typeof e!="number"?e:Xn(e,lt))};function mu(n){var e,t=n.state,i=n.name,r=n.options,o=t.elements.arrow,s=t.modifiersData.popperOffsets,a=xe(t.placement),l=jt(a),c=[he,me].indexOf(a)>=0,u=c?"height":"width";if(!(!o||!s)){var d=pu(r.padding,t),p=zt(o),y=l==="y"?de:he,m=l==="y"?ge:me,v=t.rects.reference[u]+t.rects.reference[l]-s[l]-t.rects.popper[u],w=s[l]-t.rects.reference[l],T=et(o),_=T?l==="y"?T.clientHeight||0:T.clientWidth||0:0,S=v/2-w/2,A=d[y],K=_-p[u]-d[m],z=_/2-p[u]/2+S,L=Wt(A,z,K),H=l;t.modifiersData[i]=(e={},e[H]=L,e.centerOffset=L-z,e)}}function gu(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)||Yn(e.elements.popper,r)&&(e.elements.arrow=r))}var Ni={name:"arrow",enabled:!0,phase:"main",fn:mu,effect:gu,requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Ge(n){return n.split("-")[1]}var vu={top:"auto",right:"auto",bottom:"auto",left:"auto"};function yu(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,v=m===void 0?0:m,w=typeof u=="function"?u({x:y,y:v}):{x:y,y:v};y=w.x,v=w.y;var T=s.hasOwnProperty("x"),_=s.hasOwnProperty("y"),S=he,A=de,K=window;if(c){var z=et(t),L="clientHeight",H="clientWidth";if(z===ce(t)&&(z=Ae(t),Me(z).position!=="static"&&a==="absolute"&&(L="scrollHeight",H="scrollWidth")),z=z,r===de||(r===he||r===me)&&o===_t){A=ge;var N=d&&z===K&&K.visualViewport?K.visualViewport.height:z[L];v-=N-i.height,v*=l?1:-1}if(r===he||(r===de||r===ge)&&o===_t){S=me;var Y=d&&z===K&&K.visualViewport?K.visualViewport.width:z[H];y-=Y-i.width,y*=l?1:-1}}var B=Object.assign({position:a},c&&vu),ie=u===!0?yu({x:y,y:v},ce(t)):{x:y,y:v};if(y=ie.x,v=ie.y,l){var J;return Object.assign({},B,(J={},J[A]=_?"0":"",J[S]=T?"0":"",J.transform=(K.devicePixelRatio||1)<=1?"translate("+y+"px, "+v+"px)":"translate3d("+y+"px, "+v+"px, 0)",J))}return Object.assign({},B,(e={},e[A]=_?v+"px":"",e[S]=T?y+"px":"",e.transform="",e))}function Eu(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:xe(e.placement),variation:Ge(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 mn={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:Eu,data:{}};var Ii={passive:!0};function bu(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,Ii)}),a&&l.addEventListener("resize",t.update,Ii),function(){o&&c.forEach(function(u){u.removeEventListener("scroll",t.update,Ii)}),a&&l.removeEventListener("resize",t.update,Ii)}}var gn={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:bu,data:{}};var _u={left:"right",right:"left",bottom:"top",top:"bottom"};function vn(n){return n.replace(/left|right|bottom|top/g,function(e){return _u[e]})}var wu={start:"end",end:"start"};function Ri(n){return n.replace(/start|end/g,function(e){return wu[e]})}function qt(n){var e=ce(n),t=e.pageXOffset,i=e.pageYOffset;return{scrollLeft:t,scrollTop:i}}function Ut(n){return Ye(Ae(n)).left+qt(n).scrollLeft}function Xr(n,e){var t=ce(n),i=Ae(n),r=t.visualViewport,o=i.clientWidth,s=i.clientHeight,a=0,l=0;if(r){o=r.width,s=r.height;var c=Un();(c||!c&&e==="fixed")&&(a=r.offsetLeft,l=r.offsetTop)}return{width:o,height:s,x:a+Ut(n),y:l}}function Qr(n){var e,t=Ae(n),i=qt(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+Ut(n),l=-i.scrollTop;return Me(r||t).direction==="rtl"&&(a+=Ze(t.clientWidth,r?r.clientWidth:0)-o),{width:o,height:s,x:a,y:l}}function Yt(n){var e=Me(n),t=e.overflow,i=e.overflowX,r=e.overflowY;return/auto|scroll|overlay|hidden/.test(t+r+i)}function Hi(n){return["html","body","#document"].indexOf(we(n))>=0?n.ownerDocument.body:be(n)&&Yt(n)?n:Hi(ut(n))}function wt(n,e){var t;e===void 0&&(e=[]);var i=Hi(n),r=i===((t=n.ownerDocument)==null?void 0:t.body),o=ce(i),s=r?[o].concat(o.visualViewport||[],Yt(i)?i:[]):i,a=e.concat(s);return r?a:a.concat(wt(ut(s)))}function yn(n){return Object.assign({},n,{left:n.x,top:n.y,right:n.x+n.width,bottom:n.y+n.height})}function xu(n,e){var t=Ye(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===Wn?yn(Xr(n,t)):Ue(e)?xu(e,t):yn(Qr(Ae(n)))}function Tu(n){var e=wt(ut(n)),t=["absolute","fixed"].indexOf(Me(n).position)>=0,i=t&&be(n)?et(n):n;return Ue(i)?e.filter(function(r){return Ue(r)&&Yn(r,i)&&we(r)!=="body"}):[]}function Jr(n,e,t,i){var r=e==="clippingParents"?Tu(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=Vt(u.right,l.right),l.bottom=Vt(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 Qn(n){var e=n.reference,t=n.element,i=n.placement,r=i?xe(i):null,o=i?Ge(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 he:l={x:e.x-t.width,y:a};break;default:l={x:e.x,y:e.y}}var c=r?jt(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 _t: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?Ur:a,c=t.rootBoundary,u=c===void 0?Wn:c,d=t.elementContext,p=d===void 0?$t:d,y=t.altBoundary,m=y===void 0?!1:y,v=t.padding,w=v===void 0?0:v,T=Kn(typeof w!="number"?w:Xn(w,lt)),_=p===$t?Yr:$t,S=n.rects.popper,A=n.elements[m?_:p],K=Jr(Ue(A)?A:A.contextElement||Ae(n.elements.popper),l,u,s),z=Ye(n.elements.reference),L=Qn({reference:z,element:S,strategy:"absolute",placement:r}),H=yn(Object.assign({},S,L)),N=p===$t?H:z,Y={top:K.top-N.top+T.top,bottom:N.bottom-K.bottom+T.bottom,left:K.left-N.left+T.left,right:N.right-K.right+T.right},B=n.modifiersData.offset;if(p===$t&&B){var ie=B[r];Object.keys(Y).forEach(function(J){var Te=[me,ge].indexOf(J)>=0?1:-1,Ce=[de,ge].indexOf(J)>=0?"y":"x";Y[J]+=ie[Ce]*Te})}return Y}function Zr(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?qn:l,u=Ge(i),d=u?a?ki:ki.filter(function(m){return Ge(m)===u}):lt,p=d.filter(function(m){return c.indexOf(m)>=0});p.length===0&&(p=d);var y=p.reduce(function(m,v){return m[v]=ke(n,{placement:v,boundary:r,rootBoundary:o,padding:s})[xe(v)],m},{});return Object.keys(y).sort(function(m,v){return y[m]-y[v]})}function Cu(n){if(xe(n)===jn)return[];var e=vn(n);return[Ri(n),e,Ri(e)]}function Au(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,v=t.allowedAutoPlacements,w=e.options.placement,T=xe(w),_=T===w,S=l||(_||!m?[vn(w)]:Cu(w)),A=[w].concat(S).reduce(function(V,q){return V.concat(xe(q)===jn?Zr(e,{placement:q,boundary:u,rootBoundary:d,padding:c,flipVariations:m,allowedAutoPlacements:v}):q)},[]),K=e.rects.reference,z=e.rects.popper,L=new Map,H=!0,N=A[0],Y=0;Y=0,Ce=Te?"width":"height",se=ke(e,{placement:B,boundary:u,rootBoundary:d,altBoundary:p,padding:c}),ne=Te?J?me:he:J?ge:de;K[Ce]>z[Ce]&&(ne=vn(ne));var qe=vn(ne),Re=[];if(o&&Re.push(se[ie]<=0),a&&Re.push(se[ne]<=0,se[qe]<=0),Re.every(function(V){return V})){N=B,H=!1;break}L.set(B,Re)}if(H)for(var W=m?3:1,M=function(q){var U=A.find(function(Z){var oe=L.get(Z);if(oe)return oe.slice(0,q).every(function(bt){return bt})});if(U)return N=U,"break"},D=W;D>0;D--){var $=M(D);if($==="break")break}e.placement!==N&&(e.modifiersData[i]._skip=!0,e.placement=N,e.reset=!0)}}var Pi={name:"flip",enabled:!0,phase:"main",fn:Au,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,he].some(function(e){return n[e]>=0})}function Su(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 Fi={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:Su};function Du(n,e,t){var i=xe(n),r=[he,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,[he,me].indexOf(i)>=0?{x:a,y:s}:{x:s,y:a}}function Ou(n){var e=n.state,t=n.options,i=n.name,r=t.offset,o=r===void 0?[0,0]:r,s=qn.reduce(function(u,d){return u[d]=Du(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 Bi={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:Ou};function Lu(n){var e=n.state,t=n.name;e.modifiersData[t]=Qn({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})}var En={name:"popperOffsets",enabled:!0,phase:"read",fn:Lu,data:{}};function eo(n){return n==="x"?"y":"x"}function Mu(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,v=m===void 0?0:m,w=ke(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:u}),T=xe(e.placement),_=Ge(e.placement),S=!_,A=jt(T),K=eo(A),z=e.modifiersData.popperOffsets,L=e.rects.reference,H=e.rects.popper,N=typeof v=="function"?v(Object.assign({},e.rects,{placement:e.placement})):v,Y=typeof N=="number"?{mainAxis:N,altAxis:N}:Object.assign({mainAxis:0,altAxis:0},N),B=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,ie={x:0,y:0};if(z){if(o){var J,Te=A==="y"?de:he,Ce=A==="y"?ge:me,se=A==="y"?"height":"width",ne=z[A],qe=ne+w[Te],Re=ne-w[Ce],W=y?-H[se]/2:0,M=_===rt?L[se]:H[se],D=_===rt?-H[se]:-L[se],$=e.elements.arrow,V=y&&$?zt($):{width:0,height:0},q=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:Gn(),U=q[Te],Z=q[Ce],oe=Wt(0,L[se],V[se]),bt=S?L[se]/2-W-oe-U-Y.mainAxis:M-oe-U-Y.mainAxis,Br=S?-L[se]/2+W+oe+Z+Y.mainAxis:D+oe+Z+Y.mainAxis,on=e.elements.arrow&&et(e.elements.arrow),sn=on?A==="y"?on.clientTop||0:on.clientLeft||0:0,Ti=(J=B==null?void 0:B[A])!=null?J:0,$r=ne+bt-Ti-sn,Ci=ne+Br-Ti,Ai=Wt(y?Vt(qe,$r):qe,ne,y?Ze(Re,Ci):Re);z[A]=Ai,ie[A]=Ai-ne}if(a){var Bn,Si=A==="x"?de:he,an=A==="x"?ge:me,ot=z[K],ln=K==="y"?"height":"width",$n=ot+w[Si],cn=ot-w[an],un=[de,he].indexOf(T)!==-1,Bt=(Bn=B==null?void 0:B[K])!=null?Bn:0,Di=un?$n:ot-L[ln]-H[ln]-Bt+Y.altAxis,Vn=un?ot+L[ln]+H[ln]-Bt-Y.altAxis:cn,Oi=y&&un?Ss(Di,ot,Vn):Wt(y?Di:$n,ot,y?Vn:cn);z[K]=Oi,ie[K]=Oi-ot}e.modifiersData[i]=ie}}var $i={name:"preventOverflow",enabled:!0,phase:"main",fn:Mu,requiresIfExists:["offset"]};function to(n){return{scrollLeft:n.scrollLeft,scrollTop:n.scrollTop}}function no(n){return n===ce(n)||!be(n)?qt(n):to(n)}function ku(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 io(n,e,t){t===void 0&&(t=!1);var i=be(e),r=be(e)&&ku(e),o=Ae(e),s=Ye(n,r,t),a={scrollLeft:0,scrollTop:0},l={x:0,y:0};return(i||!i&&!t)&&((we(e)!=="body"||Yt(o))&&(a=no(e)),be(e)?(l=Ye(e,!0),l.x+=e.clientLeft,l.y+=e.clientTop):o&&(l.x=Ut(o))),{x:s.left+a.scrollLeft-l.x,y:s.top+a.scrollTop-l.y,width:s.width,height:s.height}}function Nu(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 ro(n){var e=Nu(n);return Gr.reduce(function(t,i){return t.concat(e.filter(function(r){return r.phase===i}))},[])}function oo(n){var e;return function(){return e||(e=new Promise(function(t){Promise.resolve().then(function(){e=void 0,t(n())})})),e}}function so(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 ks={placement:"bottom",modifiers:[],strategy:"absolute"};function Ns(){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),Fu=n=>n==null?`${n}`:Object.prototype.toString.call(n).match(/\s([a-z]+)/i)[1].toLowerCase(),Bu=n=>{do n+=Math.floor(Math.random()*Hu);while(document.getElementById(n));return n},$u=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))*Pu)},da=n=>{n.dispatchEvent(new Event(To))},dt=n=>!n||typeof n!="object"?!1:(typeof n.jquery!="undefined"&&(n=n[0]),typeof n.nodeType!="undefined"),Tt=n=>dt(n)?n.jquery?n[0]:n:typeof n=="string"&&n.length>0?document.querySelector(ua(n)):null,An=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},Ct=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},Xi=()=>{},ii=n=>{n.offsetHeight},ha=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,lo=[],Vu=n=>{document.readyState==="loading"?(lo.length||document.addEventListener("DOMContentLoaded",()=>{for(let e of lo)e()}),lo.push(n)):n()},Ke=()=>document.documentElement.dir==="rtl",Qe=n=>{Vu(()=>{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)}})},He=(n,e=[],t=n)=>typeof n=="function"?n.call(...e):t,pa=(n,e,t=!0)=>{if(!t){He(n);return}let r=$u(e)+5,o=!1,s=({target:a})=>{a===e&&(o=!0,e.removeEventListener(To,s),He(n))};e.addEventListener(To,s),setTimeout(()=>{o||da(e)},r)},Do=(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))])},zu=/[^.]*(?=\..*)\.|.*/,ju=/\..*/,Wu=/::\d+$/,co={},Hs=1,ma={mouseenter:"mouseover",mouseleave:"mouseout"},qu=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}::${Hs++}`||n.uidEvent||Hs++}function va(n){let e=ga(n);return n.uidEvent=e,co[e]=co[e]||{},co[e]}function Uu(n,e){return function t(i){return Oo(i,{delegateTarget:n}),t.oneOff&&x.off(n,i.type,e),e.apply(n,[i])}}function Yu(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 Oo(r,{delegateTarget:s}),i.oneOff&&x.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 qu.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(v){if(!v.relatedTarget||v.relatedTarget!==v.delegateTarget&&!v.delegateTarget.contains(v.relatedTarget))return m.call(this,v)})(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(zu,"")),p=o?Yu(n,t,s):Uu(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 Co(n,e,t,i,r){let o=ya(e[t],i,r);o&&(n.removeEventListener(t,o,!!r),delete e[t][o.uidEvent])}function Gu(n,e,t,i){let r=e[t]||{};for(let[o,s]of Object.entries(r))o.includes(i)&&Co(n,e,t,s.callable,s.delegationSelector)}function ba(n){return n=n.replace(ju,""),ma[n]||n}var x={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;Co(n,l,s,o,r?t:null);return}if(u)for(let d of Object.keys(l))Gu(n,l,d,e.slice(1));for(let[d,p]of Object.entries(c)){let y=d.replace(Wu,"");(!a||e.includes(y))&&Co(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=Oo(new Event(e,{bubbles:a,cancelable:!0}),t);return c&&u.preventDefault(),l&&n.dispatchEvent(u),u.defaultPrevented&&s&&s.preventDefault(),u}};function Oo(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 uo(n){return n.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}var ft={setDataAttribute(n,e,t){n.setAttribute(`data-bs-${uo(e)}`,t)},removeDataAttribute(n,e){n.removeAttribute(`data-bs-${uo(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-${uo(e)}`))}},Qt=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":Fu(o);if(!new RegExp(r).test(s))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${i}" provided type "${s}" but expected type "${r}".`)}}},Ku="5.3.8",je=class extends Qt{constructor(e,t){super(),e=Tt(e),e&&(this._element=e,this._config=this._getConfig(t),ao.set(this._element,this.constructor.DATA_KEY,this))}dispose(){ao.remove(this._element,this.constructor.DATA_KEY),x.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 ao.get(Tt(e),this.DATA_KEY)}static getOrCreateInstance(e,t={}){return this.getInstance(e)||new this(e,typeof t=="object"?t:null)}static get VERSION(){return Ku}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(e){return`${e}${this.EVENT_KEY}`}},fo=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},j={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=>!Ct(t)&&An(t))},getSelectorFromElement(n){let e=fo(n);return e&&j.findOne(e)?e:null},getElementFromSelector(n){let e=fo(n);return e?j.findOne(e):null},getMultipleElementsFromSelector(n){let e=fo(n);return e?j.find(e):[]}},rr=(n,e="hide")=>{let t=`click.dismiss${n.EVENT_KEY}`,i=n.NAME;x.on(document,t,`[data-bs-dismiss="${i}"]`,function(r){if(["A","AREA"].includes(this.tagName)&&r.preventDefault(),Ct(this))return;let o=j.getElementFromSelector(this)||this.closest(`.${i}`);n.getOrCreateInstance(o)[e]()})},Xu="alert",Qu="bs.alert",_a=`.${Qu}`,Ju=`close${_a}`,Zu=`closed${_a}`,ed="fade",td="show",Qi=class n extends je{static get NAME(){return Xu}close(){if(x.trigger(this._element,Ju).defaultPrevented)return;this._element.classList.remove(td);let t=this._element.classList.contains(ed);this._queueCallback(()=>this._destroyElement(),this._element,t)}_destroyElement(){this._element.remove(),x.trigger(this._element,Zu),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)}})}};rr(Qi,"close");Qe(Qi);var nd="button",id="bs.button",rd=`.${id}`,od=".data-api",sd="active",Bs='[data-bs-toggle="button"]',ad=`click${rd}${od}`,Ji=class n extends je{static get NAME(){return nd}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle(sd))}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this);e==="toggle"&&t[e]()})}};x.on(document,ad,Bs,n=>{n.preventDefault();let e=n.target.closest(Bs);Ji.getOrCreateInstance(e).toggle()});Qe(Ji);var ld="swipe",Sn=".bs.swipe",cd=`touchstart${Sn}`,ud=`touchmove${Sn}`,dd=`touchend${Sn}`,fd=`pointerdown${Sn}`,hd=`pointerup${Sn}`,pd="touch",md="pen",gd="pointer-event",vd=40,yd={endCallback:null,leftCallback:null,rightCallback:null},Ed={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"},Zi=class n extends Qt{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 yd}static get DefaultType(){return Ed}static get NAME(){return ld}dispose(){x.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(),He(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<=vd)return;let t=e/this._deltaX;this._deltaX=0,t&&He(t>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(x.on(this._element,fd,e=>this._start(e)),x.on(this._element,hd,e=>this._end(e)),this._element.classList.add(gd)):(x.on(this._element,cd,e=>this._start(e)),x.on(this._element,ud,e=>this._move(e)),x.on(this._element,dd,e=>this._end(e)))}_eventIsPointerPenTouch(e){return this._supportPointerEvents&&(e.pointerType===md||e.pointerType===pd)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}},bd="carousel",_d="bs.carousel",Ot=`.${_d}`,wa=".data-api",wd="ArrowLeft",xd="ArrowRight",Td=500,Zn="next",bn="prev",wn="left",Gi="right",Cd=`slide${Ot}`,ho=`slid${Ot}`,Ad=`keydown${Ot}`,Sd=`mouseenter${Ot}`,Dd=`mouseleave${Ot}`,Od=`dragstart${Ot}`,Ld=`load${Ot}${wa}`,Md=`click${Ot}${wa}`,xa="carousel",zi="active",kd="slide",Nd="carousel-item-end",Id="carousel-item-start",Rd="carousel-item-next",Hd="carousel-item-prev",Ta=".active",Ca=".carousel-item",Pd=Ta+Ca,Fd=".carousel-item img",Bd=".carousel-indicators",$d="[data-bs-slide], [data-bs-slide-to]",Vd='[data-bs-ride="carousel"]',zd={[wd]:Gi,[xd]:wn},jd={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},Wd={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"},ti=class n extends je{constructor(e,t){super(e,t),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=j.findOne(Bd,this._element),this._addEventListeners(),this._config.ride===xa&&this.cycle()}static get Default(){return jd}static get DefaultType(){return Wd}static get NAME(){return bd}next(){this._slide(Zn)}nextWhenVisible(){!document.hidden&&An(this._element)&&this.next()}prev(){this._slide(bn)}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){x.one(this._element,ho,()=>this.cycle());return}this.cycle()}}to(e){let t=this._getItems();if(e>t.length-1||e<0)return;if(this._isSliding){x.one(this._element,ho,()=>this.to(e));return}let i=this._getItemIndex(this._getActive());if(i===e)return;let r=e>i?Zn:bn;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&&x.on(this._element,Ad,e=>this._keydown(e)),this._config.pause==="hover"&&(x.on(this._element,Sd,()=>this.pause()),x.on(this._element,Dd,()=>this._maybeEnableCycle())),this._config.touch&&Zi.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(let i of j.find(Fd,this._element))x.on(i,Od,r=>r.preventDefault());let t={leftCallback:()=>this._slide(this._directionToOrder(wn)),rightCallback:()=>this._slide(this._directionToOrder(Gi)),endCallback:()=>{this._config.pause==="hover"&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout(()=>this._maybeEnableCycle(),Td+this._config.interval))}};this._swipeHelper=new Zi(this._element,t)}_keydown(e){if(/input|textarea/i.test(e.target.tagName))return;let t=zd[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=j.findOne(Ta,this._indicatorsElement);t.classList.remove(zi),t.removeAttribute("aria-current");let i=j.findOne(`[data-bs-slide-to="${e}"]`,this._indicatorsElement);i&&(i.classList.add(zi),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===Zn,o=t||Do(this._getItems(),i,r,this._config.wrap);if(o===i)return;let s=this._getItemIndex(o),a=y=>x.trigger(this._element,y,{relatedTarget:o,direction:this._orderToDirection(e),from:this._getItemIndex(i),to:s});if(a(Cd).defaultPrevented||!i||!o)return;let c=!!this._interval;this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(s),this._activeElement=o;let u=r?Id:Nd,d=r?Rd:Hd;o.classList.add(d),ii(o),i.classList.add(u),o.classList.add(u);let p=()=>{o.classList.remove(u,d),o.classList.add(zi),i.classList.remove(zi,d,u),this._isSliding=!1,a(ho)};this._queueCallback(p,i,this._isAnimated()),c&&this.cycle()}_isAnimated(){return this._element.classList.contains(kd)}_getActive(){return j.findOne(Pd,this._element)}_getItems(){return j.find(Ca,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(e){return Ke()?e===wn?bn:Zn:e===wn?Zn:bn}_orderToDirection(e){return Ke()?e===bn?wn:Gi:e===bn?Gi:wn}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]()}})}};x.on(document,Md,$d,function(n){let e=j.getElementFromSelector(this);if(!e||!e.classList.contains(xa))return;n.preventDefault();let t=ti.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()});x.on(window,Ld,()=>{let n=j.find(Vd);for(let e of n)ti.getOrCreateInstance(e)});Qe(ti);var qd="collapse",Ud="bs.collapse",ri=`.${Ud}`,Yd=".data-api",Gd=`show${ri}`,Kd=`shown${ri}`,Xd=`hide${ri}`,Qd=`hidden${ri}`,Jd=`click${ri}${Yd}`,po="show",Tn="collapse",ji="collapsing",Zd="collapsed",ef=`:scope .${Tn} .${Tn}`,tf="collapse-horizontal",nf="width",rf="height",of=".collapse.show, .collapse.collapsing",Ao='[data-bs-toggle="collapse"]',sf={parent:null,toggle:!0},af={parent:"(null|element)",toggle:"boolean"},At=class n extends je{constructor(e,t){super(e,t),this._isTransitioning=!1,this._triggerArray=[];let i=j.find(Ao);for(let r of i){let o=j.getSelectorFromElement(r),s=j.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 sf}static get DefaultType(){return af}static get NAME(){return qd}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let e=[];if(this._config.parent&&(e=this._getFirstLevelChildren(of).filter(a=>a!==this._element).map(a=>n.getOrCreateInstance(a,{toggle:!1}))),e.length&&e[0]._isTransitioning||x.trigger(this._element,Gd).defaultPrevented)return;for(let a of e)a.hide();let i=this._getDimension();this._element.classList.remove(Tn),this._element.classList.add(ji),this._element.style[i]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;let r=()=>{this._isTransitioning=!1,this._element.classList.remove(ji),this._element.classList.add(Tn,po),this._element.style[i]="",x.trigger(this._element,Kd)},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()||x.trigger(this._element,Xd).defaultPrevented)return;let t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,ii(this._element),this._element.classList.add(ji),this._element.classList.remove(Tn,po);for(let r of this._triggerArray){let o=j.getElementFromSelector(r);o&&!this._isShown(o)&&this._addAriaAndCollapsedClass([r],!1)}this._isTransitioning=!0;let i=()=>{this._isTransitioning=!1,this._element.classList.remove(ji),this._element.classList.add(Tn),x.trigger(this._element,Qd)};this._element.style[t]="",this._queueCallback(i,this._element,!0)}_isShown(e=this._element){return e.classList.contains(po)}_configAfterMerge(e){return e.toggle=!!e.toggle,e.parent=Tt(e.parent),e}_getDimension(){return this._element.classList.contains(tf)?nf:rf}_initializeChildren(){if(!this._config.parent)return;let e=this._getFirstLevelChildren(Ao);for(let t of e){let i=j.getElementFromSelector(t);i&&this._addAriaAndCollapsedClass([t],this._isShown(i))}}_getFirstLevelChildren(e){let t=j.find(ef,this._config.parent);return j.find(e,this._config.parent).filter(i=>!t.includes(i))}_addAriaAndCollapsedClass(e,t){if(e.length)for(let i of e)i.classList.toggle(Zd,!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]()}})}};x.on(document,Jd,Ao,function(n){(n.target.tagName==="A"||n.delegateTarget&&n.delegateTarget.tagName==="A")&&n.preventDefault();for(let e of j.getMultipleElementsFromSelector(this))At.getOrCreateInstance(e,{toggle:!1}).toggle()});Qe(At);var $s="dropdown",lf="bs.dropdown",Zt=`.${lf}`,Lo=".data-api",cf="Escape",Vs="Tab",uf="ArrowUp",zs="ArrowDown",df=2,ff=`hide${Zt}`,hf=`hidden${Zt}`,pf=`show${Zt}`,mf=`shown${Zt}`,Aa=`click${Zt}${Lo}`,Sa=`keydown${Zt}${Lo}`,gf=`keyup${Zt}${Lo}`,xn="show",vf="dropup",yf="dropend",Ef="dropstart",bf="dropup-center",_f="dropdown-center",Kt='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',wf=`${Kt}.${xn}`,Ki=".dropdown-menu",xf=".navbar",Tf=".navbar-nav",Cf=".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",Af=Ke()?"top-end":"top-start",Sf=Ke()?"top-start":"top-end",Df=Ke()?"bottom-end":"bottom-start",Of=Ke()?"bottom-start":"bottom-end",Lf=Ke()?"left-start":"right-start",Mf=Ke()?"right-start":"left-start",kf="top",Nf="bottom",If={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},Rf={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"},St=class n extends je{constructor(e,t){super(e,t),this._popper=null,this._parent=this._element.parentNode,this._menu=j.next(this._element,Ki)[0]||j.prev(this._element,Ki)[0]||j.findOne(Ki,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return If}static get DefaultType(){return Rf}static get NAME(){return $s}toggle(){return this._isShown()?this.hide():this.show()}show(){if(Ct(this._element)||this._isShown())return;let e={relatedTarget:this._element};if(!x.trigger(this._element,pf,e).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(Tf))for(let i of[].concat(...document.body.children))x.on(i,"mouseover",Xi);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(xn),this._element.classList.add(xn),x.trigger(this._element,mf,e)}}hide(){if(Ct(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(!x.trigger(this._element,ff,e).defaultPrevented){if("ontouchstart"in document.documentElement)for(let i of[].concat(...document.body.children))x.off(i,"mouseover",Xi);this._popper&&this._popper.destroy(),this._menu.classList.remove(xn),this._element.classList.remove(xn),this._element.setAttribute("aria-expanded","false"),ft.removeDataAttribute(this._menu,"popper"),x.trigger(this._element,hf,e)}}_getConfig(e){if(e=super._getConfig(e),typeof e.reference=="object"&&!dt(e.reference)&&typeof e.reference.getBoundingClientRect!="function")throw new TypeError(`${$s.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return e}_createPopper(){if(typeof Vi=="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=Tt(this._config.reference):typeof this._config.reference=="object"&&(e=this._config.reference);let t=this._getPopperConfig();this._popper=Jn(e,this._menu,t)}_isShown(){return this._menu.classList.contains(xn)}_getPlacement(){let e=this._parent;if(e.classList.contains(yf))return Lf;if(e.classList.contains(Ef))return Mf;if(e.classList.contains(bf))return kf;if(e.classList.contains(_f))return Nf;let t=getComputedStyle(this._menu).getPropertyValue("--bs-position").trim()==="end";return e.classList.contains(vf)?t?Sf:Af:t?Of:Df}_detectNavbar(){return this._element.closest(xf)!==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),He(this._config.popperConfig,[void 0,e]))}_selectMenuItem({key:e,target:t}){let i=j.find(Cf,this._menu).filter(r=>An(r));i.length&&Do(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===df||e.type==="keyup"&&e.key!==Vs)return;let t=j.find(wf);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===cf,r=[uf,zs].includes(e.key);if(!r&&!i||t&&!i)return;e.preventDefault();let o=this.matches(Kt)?this:j.prev(this,Kt)[0]||j.next(this,Kt)[0]||j.findOne(Kt,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())}};x.on(document,Sa,Kt,St.dataApiKeydownHandler);x.on(document,Sa,Ki,St.dataApiKeydownHandler);x.on(document,Aa,St.clearMenus);x.on(document,gf,St.clearMenus);x.on(document,Aa,Kt,function(n){n.preventDefault(),St.getOrCreateInstance(this).toggle()});Qe(St);var Da="backdrop",Hf="fade",js="show",Ws=`mousedown.bs.${Da}`,Pf={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Ff={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"},er=class extends Qt{constructor(e){super(),this._config=this._getConfig(e),this._isAppended=!1,this._element=null}static get Default(){return Pf}static get DefaultType(){return Ff}static get NAME(){return Da}show(e){if(!this._config.isVisible){He(e);return}this._append();let t=this._getElement();this._config.isAnimated&&ii(t),t.classList.add(js),this._emulateAnimation(()=>{He(e)})}hide(e){if(!this._config.isVisible){He(e);return}this._getElement().classList.remove(js),this._emulateAnimation(()=>{this.dispose(),He(e)})}dispose(){this._isAppended&&(x.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(Hf),this._element=e}return this._element}_configAfterMerge(e){return e.rootElement=Tt(e.rootElement),e}_append(){if(this._isAppended)return;let e=this._getElement();this._config.rootElement.append(e),x.on(e,Ws,()=>{He(this._config.clickCallback)}),this._isAppended=!0}_emulateAnimation(e){pa(e,this._getElement(),this._config.isAnimated)}},Bf="focustrap",$f="bs.focustrap",tr=`.${$f}`,Vf=`focusin${tr}`,zf=`keydown.tab${tr}`,jf="Tab",Wf="forward",qs="backward",qf={autofocus:!0,trapElement:null},Uf={autofocus:"boolean",trapElement:"element"},nr=class extends Qt{constructor(e){super(),this._config=this._getConfig(e),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return qf}static get DefaultType(){return Uf}static get NAME(){return Bf}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),x.off(document,tr),x.on(document,Vf,e=>this._handleFocusin(e)),x.on(document,zf,e=>this._handleKeydown(e)),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,x.off(document,tr))}_handleFocusin(e){let{trapElement:t}=this._config;if(e.target===document||e.target===t||t.contains(e.target))return;let i=j.focusableChildren(t);i.length===0?t.focus():this._lastTabNavDirection===qs?i[i.length-1].focus():i[0].focus()}_handleKeydown(e){e.key===jf&&(this._lastTabNavDirection=e.shiftKey?qs:Wf)}},Us=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",Ys=".sticky-top",Wi="padding-right",Gs="margin-right",ni=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,Wi,t=>t+e),this._setElementAttributes(Us,Wi,t=>t+e),this._setElementAttributes(Ys,Gs,t=>t-e)}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,Wi),this._resetElementAttributes(Us,Wi),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 j.find(e,this._element))t(i)}},Yf="modal",Gf="bs.modal",Xe=`.${Gf}`,Kf=".data-api",Xf="Escape",Qf=`hide${Xe}`,Jf=`hidePrevented${Xe}`,Oa=`hidden${Xe}`,La=`show${Xe}`,Zf=`shown${Xe}`,eh=`resize${Xe}`,th=`click.dismiss${Xe}`,nh=`mousedown.dismiss${Xe}`,ih=`keydown.dismiss${Xe}`,rh=`click${Xe}${Kf}`,Ks="modal-open",oh="fade",Xs="show",mo="modal-static",sh=".modal.show",ah=".modal-dialog",lh=".modal-body",ch='[data-bs-toggle="modal"]',uh={backdrop:!0,focus:!0,keyboard:!0},dh={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"},tt=class n extends je{constructor(e,t){super(e,t),this._dialog=j.findOne(ah,this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new ni,this._addEventListeners()}static get Default(){return uh}static get DefaultType(){return dh}static get NAME(){return Yf}toggle(e){return this._isShown?this.hide():this.show(e)}show(e){this._isShown||this._isTransitioning||x.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||x.trigger(this._element,Qf).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Xs),this._queueCallback(()=>this._hideModal(),this._element,this._isAnimated()))}dispose(){x.off(window,Xe),x.off(this._dialog,Xe),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new er({isVisible:!!this._config.backdrop,isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new nr({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=j.findOne(lh,this._dialog);t&&(t.scrollTop=0),ii(this._element),this._element.classList.add(Xs);let i=()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,x.trigger(this._element,Zf,{relatedTarget:e})};this._queueCallback(i,this._dialog,this._isAnimated())}_addEventListeners(){x.on(this._element,ih,e=>{if(e.key===Xf){if(this._config.keyboard){this.hide();return}this._triggerBackdropTransition()}}),x.on(window,eh,()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()}),x.on(this._element,nh,e=>{x.one(this._element,th,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(),x.trigger(this._element,Oa)})}_isAnimated(){return this._element.classList.contains(oh)}_triggerBackdropTransition(){if(x.trigger(this._element,Jf).defaultPrevented)return;let t=this._element.scrollHeight>document.documentElement.clientHeight,i=this._element.style.overflowY;i==="hidden"||this._element.classList.contains(mo)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(mo),this._queueCallback(()=>{this._element.classList.remove(mo),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=Ke()?"paddingLeft":"paddingRight";this._element.style[r]=`${t}px`}if(!i&&e){let r=Ke()?"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)}})}};x.on(document,rh,ch,function(n){let e=j.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&n.preventDefault(),x.one(e,La,r=>{r.defaultPrevented||x.one(e,Oa,()=>{An(this)&&this.focus()})});let t=j.findOne(sh);t&&tt.getInstance(t).hide(),tt.getOrCreateInstance(e).toggle(this)});rr(tt);Qe(tt);var fh="offcanvas",hh="bs.offcanvas",mt=`.${hh}`,Ma=".data-api",ph=`load${mt}${Ma}`,mh="Escape",Qs="show",Js="showing",Zs="hiding",gh="offcanvas-backdrop",ka=".offcanvas.show",vh=`show${mt}`,yh=`shown${mt}`,Eh=`hide${mt}`,ea=`hidePrevented${mt}`,Na=`hidden${mt}`,bh=`resize${mt}`,_h=`click${mt}${Ma}`,wh=`keydown.dismiss${mt}`,xh='[data-bs-toggle="offcanvas"]',Th={backdrop:!0,keyboard:!0,scroll:!1},Ch={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"},Dt=class n extends je{constructor(e,t){super(e,t),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return Th}static get DefaultType(){return Ch}static get NAME(){return fh}toggle(e){return this._isShown?this.hide():this.show(e)}show(e){if(this._isShown||x.trigger(this._element,vh,{relatedTarget:e}).defaultPrevented)return;this._isShown=!0,this._backdrop.show(),this._config.scroll||new ni().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),x.trigger(this._element,yh,{relatedTarget:e})};this._queueCallback(i,this._element,!0)}hide(){if(!this._isShown||x.trigger(this._element,Eh).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 ni().reset(),x.trigger(this._element,Na)};this._queueCallback(t,this._element,!0)}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){let e=()=>{if(this._config.backdrop==="static"){x.trigger(this._element,ea);return}this.hide()},t=!!this._config.backdrop;return new er({className:gh,isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?e:null})}_initializeFocusTrap(){return new nr({trapElement:this._element})}_addEventListeners(){x.on(this._element,wh,e=>{if(e.key===mh){if(this._config.keyboard){this.hide();return}x.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)}})}};x.on(document,_h,xh,function(n){let e=j.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&n.preventDefault(),Ct(this))return;x.one(e,Na,()=>{An(this)&&this.focus()});let t=j.findOne(ka);t&&t!==e&&Dt.getInstance(t).hide(),Dt.getOrCreateInstance(e).toggle(this)});x.on(window,ph,()=>{for(let n of j.find(ka))Dt.getOrCreateInstance(n).show()});x.on(window,bh,()=>{for(let n of j.find("[aria-modal][class*=show][class*=offcanvas-]"))getComputedStyle(n).position!=="fixed"&&Dt.getOrCreateInstance(n).hide()});rr(Dt);Qe(Dt);var Ah=/^aria-[\w-]*$/i,Ia={"*":["class","dir","id","lang","role",Ah],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:[]},Sh=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Dh=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Oh=(n,e)=>{let t=n.nodeName.toLowerCase();return e.includes(t)?Sh.has(t)?!!Dh.test(n.nodeValue):!0:e.filter(i=>i instanceof RegExp).some(i=>i.test(t))};function Lh(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)Oh(u,c)||s.removeAttribute(u.nodeName)}return r.body.innerHTML}var Mh="TemplateFactory",kh={allowList:Ia,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},Nh={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Ih={entry:"(string|element|function|null)",selector:"(string|element)"},So=class extends Qt{constructor(e){super(),this._config=this._getConfig(e)}static get Default(){return kh}static get DefaultType(){return Nh}static get NAME(){return Mh}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},Ih)}_setContent(e,t,i){let r=j.findOne(i,e);if(r){if(t=this._resolvePossibleFunction(t),!t){r.remove();return}if(dt(t)){this._putElementInTemplate(Tt(t),r);return}if(this._config.html){r.innerHTML=this._maybeSanitize(t);return}r.textContent=t}}_maybeSanitize(e){return this._config.sanitize?Lh(e,this._config.allowList,this._config.sanitizeFn):e}_resolvePossibleFunction(e){return He(e,[void 0,this])}_putElementInTemplate(e,t){if(this._config.html){t.innerHTML="",t.append(e);return}t.textContent=e.textContent}},Rh="tooltip",Hh=new Set(["sanitize","allowList","sanitizeFn"]),go="fade",Ph="modal",qi="show",Fh=".tooltip-inner",ta=`.${Ph}`,na="hide.bs.modal",ei="hover",vo="focus",yo="click",Bh="manual",$h="hide",Vh="hidden",zh="show",jh="shown",Wh="inserted",qh="click",Uh="focusin",Yh="focusout",Gh="mouseenter",Kh="mouseleave",Xh={AUTO:"auto",TOP:"top",RIGHT:Ke()?"left":"right",BOTTOM:"bottom",LEFT:Ke()?"right":"left"},Qh={allowList:Ia,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"},Jh={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 je{constructor(e,t){if(typeof Vi=="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 Qh}static get DefaultType(){return Jh}static get NAME(){return Rh}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),x.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=x.trigger(this._element,this.constructor.eventName(zh)),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),x.trigger(this._element,this.constructor.eventName(Wh))),this._popper=this._createPopper(r),r.classList.add(qi),"ontouchstart"in document.documentElement)for(let a of[].concat(...document.body.children))x.on(a,"mouseover",Xi);let s=()=>{x.trigger(this._element,this.constructor.eventName(jh)),this._isHovered===!1&&this._leave(),this._isHovered=!1};this._queueCallback(s,this.tip,this._isAnimated())}hide(){if(!this._isShown()||x.trigger(this._element,this.constructor.eventName($h)).defaultPrevented)return;if(this._getTipElement().classList.remove(qi),"ontouchstart"in document.documentElement)for(let r of[].concat(...document.body.children))x.off(r,"mouseover",Xi);this._activeTrigger[yo]=!1,this._activeTrigger[vo]=!1,this._activeTrigger[ei]=!1,this._isHovered=null;let i=()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),x.trigger(this._element,this.constructor.eventName(Vh)))};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(go,qi),t.classList.add(`bs-${this.constructor.NAME}-auto`);let i=Bu(this.constructor.NAME).toString();return t.setAttribute("id",i),this._isAnimated()&&t.classList.add(go),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{[Fh]: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(go)}_isShown(){return this.tip&&this.tip.classList.contains(qi)}_createPopper(e){let t=He(this._config.placement,[this,e,this._element]),i=Xh[t.toUpperCase()];return Jn(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 He(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),He(this._config.popperConfig,[void 0,t]))}_setListeners(){let e=this._config.trigger.split(" ");for(let t of e)if(t==="click")x.on(this._element,this.constructor.eventName(qh),this._config.selector,i=>{let r=this._initializeOnDelegatedTarget(i);r._activeTrigger[yo]=!(r._isShown()&&r._activeTrigger[yo]),r.toggle()});else if(t!==Bh){let i=t===ei?this.constructor.eventName(Gh):this.constructor.eventName(Uh),r=t===ei?this.constructor.eventName(Kh):this.constructor.eventName(Yh);x.on(this._element,i,this._config.selector,o=>{let s=this._initializeOnDelegatedTarget(o);s._activeTrigger[o.type==="focusin"?vo:ei]=!0,s._enter()}),x.on(this._element,r,this._config.selector,o=>{let s=this._initializeOnDelegatedTarget(o);s._activeTrigger[o.type==="focusout"?vo:ei]=s._element.contains(o.relatedTarget),s._leave()})}this._hideModalHandler=()=>{this._element&&this.hide()},x.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))Hh.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:Tt(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]()}})}};Qe(ht);var Zh="popover",ep=".popover-header",tp=".popover-body",np=ae(O({},ht.Default),{content:"",offset:[0,8],placement:"right",template:'',trigger:"click"}),ip=ae(O({},ht.DefaultType),{content:"(null|string|element|function)"}),Cn=class n extends ht{static get Default(){return np}static get DefaultType(){return ip}static get NAME(){return Zh}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{[ep]:this._getTitle(),[tp]:this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}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]()}})}};Qe(Cn);var rp="scrollspy",op="bs.scrollspy",Mo=`.${op}`,sp=".data-api",ap=`activate${Mo}`,ia=`click${Mo}`,lp=`load${Mo}${sp}`,cp="dropdown-item",_n="active",up='[data-bs-spy="scroll"]',Eo="[href]",dp=".nav, .list-group",ra=".nav-link",fp=".nav-item",hp=".list-group-item",pp=`${ra}, ${fp} > ${ra}, ${hp}`,mp=".dropdown",gp=".dropdown-toggle",vp={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},yp={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"},ir=class n extends je{constructor(e,t){super(e,t),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement=getComputedStyle(this._element).overflowY==="visible"?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return vp}static get DefaultType(){return yp}static get NAME(){return rp}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(let e of this._observableSections.values())this._observer.observe(e)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(e){return e.target=Tt(e.target)||document.body,e.rootMargin=e.offset?`${e.offset}px 0px -30%`:e.rootMargin,typeof e.threshold=="string"&&(e.threshold=e.threshold.split(",").map(t=>Number.parseFloat(t))),e}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(x.off(this._config.target,ia),x.on(this._config.target,ia,Eo,e=>{let t=this._observableSections.get(e.target.hash);if(t){e.preventDefault();let i=this._rootElement||window,r=t.offsetTop-this._element.offsetTop;if(i.scrollTo){i.scrollTo({top:r,behavior:"smooth"});return}i.scrollTop=r}}))}_getNewObserver(){let e={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver(t=>this._observerCallback(t),e)}_observerCallback(e){let t=s=>this._targetLinks.get(`#${s.target.id}`),i=s=>{this._previousScrollData.visibleEntryTop=s.target.offsetTop,this._process(t(s))},r=(this._rootElement||document.documentElement).scrollTop,o=r>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=r;for(let s of e){if(!s.isIntersecting){this._activeTarget=null,this._clearActiveClass(t(s));continue}let a=s.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(o&&a){if(i(s),!r)return;continue}!o&&!a&&i(s)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;let e=j.find(Eo,this._config.target);for(let t of e){if(!t.hash||Ct(t))continue;let i=j.findOne(decodeURI(t.hash),this._element);An(i)&&(this._targetLinks.set(decodeURI(t.hash),t),this._observableSections.set(t.hash,i))}}_process(e){this._activeTarget!==e&&(this._clearActiveClass(this._config.target),this._activeTarget=e,e.classList.add(_n),this._activateParents(e),x.trigger(this._element,ap,{relatedTarget:e}))}_activateParents(e){if(e.classList.contains(cp)){j.findOne(gp,e.closest(mp)).classList.add(_n);return}for(let t of j.parents(e,dp))for(let i of j.prev(t,pp))i.classList.add(_n)}_clearActiveClass(e){e.classList.remove(_n);let t=j.find(`${Eo}.${_n}`,e);for(let i of t)i.classList.remove(_n)}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]()}})}};x.on(window,lp,()=>{for(let n of j.find(up))ir.getOrCreateInstance(n)});Qe(ir);var Ep="tab",bp="bs.tab",en=`.${bp}`,_p=`hide${en}`,wp=`hidden${en}`,xp=`show${en}`,Tp=`shown${en}`,Cp=`click${en}`,Ap=`keydown${en}`,Sp=`load${en}`,Dp="ArrowLeft",oa="ArrowRight",Op="ArrowUp",sa="ArrowDown",bo="Home",aa="End",Xt="active",la="fade",_o="show",Lp="dropdown",Ra=".dropdown-toggle",Mp=".dropdown-menu",wo=`:not(${Ra})`,kp='.list-group, .nav, [role="tablist"]',Np=".nav-item, .list-group-item",Ip=`.nav-link${wo}, .list-group-item${wo}, [role="tab"]${wo}`,Ha='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',xo=`${Ip}, ${Ha}`,Rp=`.${Xt}[data-bs-toggle="tab"], .${Xt}[data-bs-toggle="pill"], .${Xt}[data-bs-toggle="list"]`,Jt=class n extends je{constructor(e){super(e),this._parent=this._element.closest(kp),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),x.on(this._element,Ap,t=>this._keydown(t)))}static get NAME(){return Ep}show(){let e=this._element;if(this._elemIsActive(e))return;let t=this._getActiveElem(),i=t?x.trigger(t,_p,{relatedTarget:e}):null;x.trigger(e,xp,{relatedTarget:t}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(t,e),this._activate(e,t))}_activate(e,t){if(!e)return;e.classList.add(Xt),this._activate(j.getElementFromSelector(e));let i=()=>{if(e.getAttribute("role")!=="tab"){e.classList.add(_o);return}e.removeAttribute("tabindex"),e.setAttribute("aria-selected",!0),this._toggleDropDown(e,!0),x.trigger(e,Tp,{relatedTarget:t})};this._queueCallback(i,e,e.classList.contains(la))}_deactivate(e,t){if(!e)return;e.classList.remove(Xt),e.blur(),this._deactivate(j.getElementFromSelector(e));let i=()=>{if(e.getAttribute("role")!=="tab"){e.classList.remove(_o);return}e.setAttribute("aria-selected",!1),e.setAttribute("tabindex","-1"),this._toggleDropDown(e,!1),x.trigger(e,wp,{relatedTarget:t})};this._queueCallback(i,e,e.classList.contains(la))}_keydown(e){if(![Dp,oa,Op,sa,bo,aa].includes(e.key))return;e.stopPropagation(),e.preventDefault();let t=this._getChildren().filter(r=>!Ct(r)),i;if([bo,aa].includes(e.key))i=t[e.key===bo?0:t.length-1];else{let r=[oa,sa].includes(e.key);i=Do(t,e.target,r,!0)}i&&(i.focus({preventScroll:!0}),n.getOrCreateInstance(i).show())}_getChildren(){return j.find(xo,this._parent)}_getActiveElem(){return this._getChildren().find(e=>this._elemIsActive(e))||null}_setInitialAttributes(e,t){this._setAttributeIfNotExists(e,"role","tablist");for(let i of t)this._setInitialAttributesOnChild(i)}_setInitialAttributesOnChild(e){e=this._getInnerElement(e);let t=this._elemIsActive(e),i=this._getOuterElement(e);e.setAttribute("aria-selected",t),i!==e&&this._setAttributeIfNotExists(i,"role","presentation"),t||e.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(e,"role","tab"),this._setInitialAttributesOnTargetPanel(e)}_setInitialAttributesOnTargetPanel(e){let t=j.getElementFromSelector(e);t&&(this._setAttributeIfNotExists(t,"role","tabpanel"),e.id&&this._setAttributeIfNotExists(t,"aria-labelledby",`${e.id}`))}_toggleDropDown(e,t){let i=this._getOuterElement(e);if(!i.classList.contains(Lp))return;let r=(o,s)=>{let a=j.findOne(o,i);a&&a.classList.toggle(s,t)};r(Ra,Xt),r(Mp,_o),i.setAttribute("aria-expanded",t)}_setAttributeIfNotExists(e,t,i){e.hasAttribute(t)||e.setAttribute(t,i)}_elemIsActive(e){return e.classList.contains(Xt)}_getInnerElement(e){return e.matches(xo)?e:j.findOne(xo,e)}_getOuterElement(e){return e.closest(Np)||e}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]()}})}};x.on(document,Cp,Ha,function(n){["A","AREA"].includes(this.tagName)&&n.preventDefault(),!Ct(this)&&Jt.getOrCreateInstance(this).show()});x.on(window,Sp,()=>{for(let n of j.find(Rp))Jt.getOrCreateInstance(n)});Qe(Jt);var Hp="toast",Pp="bs.toast",Lt=`.${Pp}`,Fp=`mouseover${Lt}`,Bp=`mouseout${Lt}`,$p=`focusin${Lt}`,Vp=`focusout${Lt}`,zp=`hide${Lt}`,jp=`hidden${Lt}`,Wp=`show${Lt}`,qp=`shown${Lt}`,Up="fade",ca="hide",Ui="show",Yi="showing",Yp={animation:"boolean",autohide:"boolean",delay:"number"},Gp={animation:!0,autohide:!0,delay:5e3},pt=class n extends je{constructor(e,t){super(e,t),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return Gp}static get DefaultType(){return Yp}static get NAME(){return Hp}show(){if(x.trigger(this._element,Wp).defaultPrevented)return;this._clearTimeout(),this._config.animation&&this._element.classList.add(Up);let t=()=>{this._element.classList.remove(Yi),x.trigger(this._element,qp),this._maybeScheduleHide()};this._element.classList.remove(ca),ii(this._element),this._element.classList.add(Ui,Yi),this._queueCallback(t,this._element,this._config.animation)}hide(){if(!this.isShown()||x.trigger(this._element,zp).defaultPrevented)return;let t=()=>{this._element.classList.add(ca),this._element.classList.remove(Yi,Ui),x.trigger(this._element,jp)};this._element.classList.add(Yi),this._queueCallback(t,this._element,this._config.animation)}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(Ui),super.dispose()}isShown(){return this._element.classList.contains(Ui)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout(()=>{this.hide()},this._config.delay)))}_onInteraction(e,t){switch(e.type){case"mouseover":case"mouseout":{this._hasMouseInteraction=t;break}case"focusin":case"focusout":{this._hasKeyboardInteraction=t;break}}if(t){this._clearTimeout();return}let i=e.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){x.on(this._element,Fp,e=>this._onInteraction(e,!0)),x.on(this._element,Bp,e=>this._onInteraction(e,!1)),x.on(this._element,$p,e=>this._onInteraction(e,!0)),x.on(this._element,Vp,e=>this._onInteraction(e,!1))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=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](this)}})}};rr(pt);Qe(pt);var Kp=function(){"use strict";let htmx={onLoad:null,process:null,on:null,off:null,trigger:null,ajax:null,find:null,findAll:null,closest:null,values:function(n,e){return getInputValues(n,e||"post").values},remove:null,addClass:null,removeClass:null,toggleClass:null,takeClass:null,swap:null,defineExtension:null,removeExtension:null,logAll:null,logNone:null,logger:null,config:{historyEnabled:!0,historyCacheSize:10,refreshOnHistoryMiss:!1,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:!0,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:!0,allowScriptTags:!0,inlineScriptNonce:"",inlineStyleNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:!1,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",scrollBehavior:"instant",defaultFocusScroll:!1,getCacheBusterParam:!1,globalViewTransitions:!1,methodsThatUseUrlParams:["get","delete"],selfRequestsOnly:!0,ignoreTitle:!1,scrollIntoViewOnBoost:!0,triggerSpecsCache:null,disableInheritance:!1,responseHandling:[{code:"204",swap:!1},{code:"[23]..",swap:!0},{code:"[45]..",swap:!1,error:!0}],allowNestedOobSwaps:!0,historyRestoreAsHxRequest:!0},parseInterval:null,location,_:null,version:"2.0.6"};htmx.onLoad=onLoadHelper,htmx.process=processNode,htmx.on=addEventListenerImpl,htmx.off=removeEventListenerImpl,htmx.trigger=triggerEvent,htmx.ajax=ajaxHelper,htmx.find=find,htmx.findAll=findAll,htmx.closest=closest,htmx.remove=removeElement,htmx.addClass=addClassToElement,htmx.removeClass=removeClassFromElement,htmx.toggleClass=toggleClassOnElement,htmx.takeClass=takeClassForElement,htmx.swap=swap,htmx.defineExtension=defineExtension,htmx.removeExtension=removeExtension,htmx.logAll=logAll,htmx.logNone=logNone,htmx.parseInterval=parseInterval,htmx._=internalEval;let internalAPI={addTriggerHandler,bodyContains,canAccessLocalStorage,findThisElement,filterValues,swap,hasAttribute,getAttributeValue,getClosestAttributeValue,getClosestMatch,getExpressionVars,getHeaders,getInputValues,getInternalData,getSwapSpecification,getTriggerSpecs,getTarget,makeFragment,mergeObjects,makeSettleInfo,oobSwap,querySelectorExt,settleImmediately,shouldCancel,triggerEvent,triggerErrorEvent,withExtensions},VERBS=["get","post","put","delete","patch"],VERB_SELECTOR=VERBS.map(function(n){return"[hx-"+n+"], [data-hx-"+n+"]"}).join(", ");function parseInterval(n){if(n==null)return;let e=NaN;return n.slice(-2)=="ms"?e=parseFloat(n.slice(0,-2)):n.slice(-1)=="s"?e=parseFloat(n.slice(0,-1))*1e3:n.slice(-1)=="m"?e=parseFloat(n.slice(0,-1))*1e3*60:e=parseFloat(n),isNaN(e)?void 0:e}function getRawAttribute(n,e){return n instanceof Element&&n.getAttribute(e)}function hasAttribute(n,e){return!!n.hasAttribute&&(n.hasAttribute(e)||n.hasAttribute("data-"+e))}function getAttributeValue(n,e){return getRawAttribute(n,e)||getRawAttribute(n,"data-"+e)}function parentElt(n){let e=n.parentElement;return!e&&n.parentNode instanceof ShadowRoot?n.parentNode:e}function getDocument(){return document}function getRootNode(n,e){return n.getRootNode?n.getRootNode({composed:e}):getDocument()}function getClosestMatch(n,e){for(;n&&!e(n);)n=parentElt(n);return n||null}function getAttributeValueWithDisinheritance(n,e,t){let i=getAttributeValue(e,t),r=getAttributeValue(e,"hx-disinherit");var o=getAttributeValue(e,"hx-inherit");if(n!==e){if(htmx.config.disableInheritance)return o&&(o==="*"||o.split(" ").indexOf(t)>=0)?i:null;if(r&&(r==="*"||r.split(" ").indexOf(t)>=0))return"unset"}return i}function getClosestAttributeValue(n,e){let t=null;if(getClosestMatch(n,function(i){return!!(t=getAttributeValueWithDisinheritance(n,asElement(i),e))}),t!=="unset")return t}function matches(n,e){return n instanceof Element&&n.matches(e)}function getStartTag(n){let t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i.exec(n);return t?t[1].toLowerCase():""}function parseHTML(n){return new DOMParser().parseFromString(n,"text/html")}function takeChildrenFor(n,e){for(;e.childNodes.length>0;)n.append(e.childNodes[0])}function duplicateScript(n){let e=getDocument().createElement("script");return forEach(n.attributes,function(t){e.setAttribute(t.name,t.value)}),e.textContent=n.textContent,e.async=!1,htmx.config.inlineScriptNonce&&(e.nonce=htmx.config.inlineScriptNonce),e}function isJavaScriptScriptNode(n){return n.matches("script")&&(n.type==="text/javascript"||n.type==="module"||n.type==="")}function normalizeScriptTags(n){Array.from(n.querySelectorAll("script")).forEach(e=>{if(isJavaScriptScriptNode(e)){let t=duplicateScript(e),i=e.parentNode;try{i.insertBefore(t,e)}catch(r){logError(r)}finally{e.remove()}}})}function makeFragment(n){let e=n.replace(/]*)?>[\s\S]*?<\/head>/i,""),t=getStartTag(e),i;if(t==="html"){i=new DocumentFragment;let o=parseHTML(n);takeChildrenFor(i,o.body),i.title=o.title}else if(t==="body"){i=new DocumentFragment;let o=parseHTML(e);takeChildrenFor(i,o.body),i.title=o.title}else{let o=parseHTML('");i=o.querySelector("template").content,i.title=o.title;var r=i.querySelector("title");r&&r.parentNode===i&&(r.remove(),i.title=r.innerText)}return i&&(htmx.config.allowScriptTags?normalizeScriptTags(i):i.querySelectorAll("script").forEach(o=>o.remove())),i}function maybeCall(n){n&&n()}function isType(n,e){return Object.prototype.toString.call(n)==="[object "+e+"]"}function isFunction(n){return typeof n=="function"}function isRawObject(n){return isType(n,"Object")}function getInternalData(n){let e="htmx-internal-data",t=n[e];return t||(t=n[e]={}),t}function toArray(n){let e=[];if(n)for(let t=0;t=0}function bodyContains(n){return n.getRootNode({composed:!0})===document}function splitOnWhitespace(n){return n.trim().split(/\s+/)}function mergeObjects(n,e){for(let t in e)e.hasOwnProperty(t)&&(n[t]=e[t]);return n}function parseJSON(n){try{return JSON.parse(n)}catch(e){return logError(e),null}}function canAccessLocalStorage(){let n="htmx:sessionStorageTest";try{return sessionStorage.setItem(n,n),sessionStorage.removeItem(n),!0}catch(e){return!1}}function normalizePath(n){let e=new URL(n,"http://x");return e&&(n=e.pathname+e.search),n!="/"&&(n=n.replace(/\/+$/,"")),n}function internalEval(str){return maybeEval(getDocument().body,function(){return eval(str)})}function onLoadHelper(n){return htmx.on("htmx:load",function(t){n(t.detail.elt)})}function logAll(){htmx.logger=function(n,e,t){console&&console.log(e,n,t)}}function logNone(){htmx.logger=null}function find(n,e){return typeof n!="string"?n.querySelector(e):find(getDocument(),n)}function findAll(n,e){return typeof n!="string"?n.querySelectorAll(e):findAll(getDocument(),n)}function getWindow(){return window}function removeElement(n,e){n=resolveTarget(n),e?getWindow().setTimeout(function(){removeElement(n),n=null},e):parentElt(n).removeChild(n)}function asElement(n){return n instanceof Element?n:null}function asHtmlElement(n){return n instanceof HTMLElement?n:null}function asString(n){return typeof n=="string"?n:null}function asParentNode(n){return n instanceof Element||n instanceof Document||n instanceof DocumentFragment?n:null}function addClassToElement(n,e,t){n=asElement(resolveTarget(n)),n&&(t?getWindow().setTimeout(function(){addClassToElement(n,e),n=null},t):n.classList&&n.classList.add(e))}function removeClassFromElement(n,e,t){let i=asElement(resolveTarget(n));i&&(t?getWindow().setTimeout(function(){removeClassFromElement(i,e),i=null},t):i.classList&&(i.classList.remove(e),i.classList.length===0&&i.removeAttribute("class")))}function toggleClassOnElement(n,e){n=resolveTarget(n),n.classList.toggle(e)}function takeClassForElement(n,e){n=resolveTarget(n),forEach(n.parentElement.children,function(t){removeClassFromElement(t,e)}),addClassToElement(asElement(n),e)}function closest(n,e){return n=asElement(resolveTarget(n)),n?n.closest(e):null}function startsWith(n,e){return n.substring(0,e.length)===e}function endsWith(n,e){return n.substring(n.length-e.length)===e}function normalizeSelector(n){let e=n.trim();return startsWith(e,"<")&&endsWith(e,"/>")?e.substring(1,e.length-2):e}function querySelectorAllExt(n,e,t){if(e.indexOf("global ")===0)return querySelectorAllExt(n,e.slice(7),!0);n=resolveTarget(n);let i=[];{let s=0,a=0;for(let l=0;l"&&s--}a0;){let s=normalizeSelector(i.shift()),a;s.indexOf("closest ")===0?a=closest(asElement(n),normalizeSelector(s.slice(8))):s.indexOf("find ")===0?a=find(asParentNode(n),normalizeSelector(s.slice(5))):s==="next"||s==="nextElementSibling"?a=asElement(n).nextElementSibling:s.indexOf("next ")===0?a=scanForwardQuery(n,normalizeSelector(s.slice(5)),!!t):s==="previous"||s==="previousElementSibling"?a=asElement(n).previousElementSibling:s.indexOf("previous ")===0?a=scanBackwardsQuery(n,normalizeSelector(s.slice(9)),!!t):s==="document"?a=document:s==="window"?a=window:s==="body"?a=document.body:s==="root"?a=getRootNode(n,!!t):s==="host"?a=n.getRootNode().host:o.push(s),a&&r.push(a)}if(o.length>0){let s=o.join(","),a=asParentNode(getRootNode(n,!!t));r.push(...toArray(a.querySelectorAll(s)))}return r}var scanForwardQuery=function(n,e,t){let i=asParentNode(getRootNode(n,t)).querySelectorAll(e);for(let r=0;r=0;r--){let o=i[r];if(o.compareDocumentPosition(n)===Node.DOCUMENT_POSITION_FOLLOWING)return o}};function querySelectorExt(n,e){return typeof n!="string"?querySelectorAllExt(n,e)[0]:querySelectorAllExt(getDocument().body,n)[0]}function resolveTarget(n,e){return typeof n=="string"?find(asParentNode(e)||document,n):n}function processEventArgs(n,e,t,i){return isFunction(e)?{target:getDocument().body,event:asString(n),listener:e,options:t}:{target:resolveTarget(n),event:asString(e),listener:t,options:i}}function addEventListenerImpl(n,e,t,i){return ready(function(){let o=processEventArgs(n,e,t,i);o.target.addEventListener(o.event,o.listener,o.options)}),isFunction(e)?e:t}function removeEventListenerImpl(n,e,t){return ready(function(){let i=processEventArgs(n,e,t);i.target.removeEventListener(i.event,i.listener)}),isFunction(e)?e:t}let DUMMY_ELT=getDocument().createElement("output");function findAttributeTargets(n,e){let t=getClosestAttributeValue(n,e);if(t){if(t==="this")return[findThisElement(n,e)];{let i=querySelectorAllExt(n,t);if(/(^|,)(\s*)inherit(\s*)($|,)/.test(t)){let o=asElement(getClosestMatch(n,function(s){return s!==n&&hasAttribute(asElement(s),e)}));o&&i.push(...findAttributeTargets(o,e))}return i.length===0?(logError('The selector "'+t+'" on '+e+" returned no matches!"),[DUMMY_ELT]):i}}}function findThisElement(n,e){return asElement(getClosestMatch(n,function(t){return getAttributeValue(asElement(t),e)!=null}))}function getTarget(n){let e=getClosestAttributeValue(n,"hx-target");return e?e==="this"?findThisElement(n,"hx-target"):querySelectorExt(n,e):getInternalData(n).boosted?getDocument().body:n}function shouldSettleAttribute(n){return htmx.config.attributesToSettle.includes(n)}function cloneAttributes(n,e){forEach(n.attributes,function(t){!e.hasAttribute(t.name)&&shouldSettleAttribute(t.name)&&n.removeAttribute(t.name)}),forEach(e.attributes,function(t){shouldSettleAttribute(t.name)&&n.setAttribute(t.name,t.value)})}function isInlineSwap(n,e){let t=getExtensions(e);for(let i=0;i0?(o=n.substring(0,n.indexOf(":")),r=n.substring(n.indexOf(":")+1)):o=n),e.removeAttribute("hx-swap-oob"),e.removeAttribute("data-hx-swap-oob");let s=querySelectorAllExt(i,r,!1);return s.length?(forEach(s,function(a){let l,c=e.cloneNode(!0);l=getDocument().createDocumentFragment(),l.appendChild(c),isInlineSwap(o,a)||(l=asParentNode(c));let u={shouldSwap:!0,target:a,fragment:l};triggerEvent(a,"htmx:oobBeforeSwap",u)&&(a=u.target,u.shouldSwap&&(handlePreservedElements(l),swapWithStyle(o,a,a,l,t),restorePreservedElements()),forEach(t.elts,function(d){triggerEvent(d,"htmx:oobAfterSwap",u)}))}),e.parentNode.removeChild(e)):(e.parentNode.removeChild(e),triggerErrorEvent(getDocument().body,"htmx:oobErrorNoTarget",{content:e})),n}function restorePreservedElements(){let n=find("#--htmx-preserve-pantry--");if(n){for(let e of[...n.children]){let t=find("#"+e.id);t.parentNode.moveBefore(e,t),t.remove()}n.remove()}}function handlePreservedElements(n){forEach(findAll(n,"[hx-preserve], [data-hx-preserve]"),function(e){let t=getAttributeValue(e,"id"),i=getDocument().getElementById(t);if(i!=null)if(e.moveBefore){let r=find("#--htmx-preserve-pantry--");r==null&&(getDocument().body.insertAdjacentHTML("afterend","
"),r=find("#--htmx-preserve-pantry--")),r.moveBefore(i,null)}else e.parentNode.replaceChild(i,e)})}function handleAttributes(n,e,t){forEach(e.querySelectorAll("[id]"),function(i){let r=getRawAttribute(i,"id");if(r&&r.length>0){let o=r.replace("'","\\'"),s=i.tagName.replace(":","\\:"),a=asParentNode(n),l=a&&a.querySelector(s+"[id='"+o+"']");if(l&&l!==a){let c=i.cloneNode();cloneAttributes(i,l),t.tasks.push(function(){cloneAttributes(i,c)})}}})}function makeAjaxLoadTask(n){return function(){removeClassFromElement(n,htmx.config.addedClass),processNode(asElement(n)),processFocus(asParentNode(n)),triggerEvent(n,"htmx:load")}}function processFocus(n){let e="[autofocus]",t=asHtmlElement(matches(n,e)?n:n.querySelector(e));t!=null&&t.focus()}function insertNodesBefore(n,e,t,i){for(handleAttributes(n,t,i);t.childNodes.length>0;){let r=t.firstChild;addClassToElement(asElement(r),htmx.config.addedClass),n.insertBefore(r,e),r.nodeType!==Node.TEXT_NODE&&r.nodeType!==Node.COMMENT_NODE&&i.tasks.push(makeAjaxLoadTask(r))}}function stringHash(n,e){let t=0;for(;t0}function swap(n,e,t,i){i||(i={});let r=null,o=null,s=function(){maybeCall(i.beforeSwapCallback),n=resolveTarget(n);let c=i.contextElement?getRootNode(i.contextElement,!1):getDocument(),u=document.activeElement,d={};d={elt:u,start:u?u.selectionStart:null,end:u?u.selectionEnd:null};let p=makeSettleInfo(n);if(t.swapStyle==="textContent")n.textContent=e;else{let m=makeFragment(e);if(p.title=i.title||m.title,i.historyRequest&&(m=m.querySelector("[hx-history-elt],[data-hx-history-elt]")||m),i.selectOOB){let v=i.selectOOB.split(",");for(let w=0;w0?getWindow().setTimeout(y,t.settleDelay):y()},a=htmx.config.globalViewTransitions;t.hasOwnProperty("transition")&&(a=t.transition);let l=i.contextElement||getDocument();if(a&&triggerEvent(l,"htmx:beforeTransition",i.eventInfo)&&typeof Promise!="undefined"&&document.startViewTransition){let c=new Promise(function(d,p){r=d,o=p}),u=s;s=function(){document.startViewTransition(function(){return u(),c})}}try{t!=null&&t.swapDelay&&t.swapDelay>0?getWindow().setTimeout(s,t.swapDelay):s()}catch(c){throw triggerErrorEvent(l,"htmx:swapError",i.eventInfo),maybeCall(o),c}}function handleTriggerHeader(n,e,t){let i=n.getResponseHeader(e);if(i.indexOf("{")===0){let r=parseJSON(i);for(let o in r)if(r.hasOwnProperty(o)){let s=r[o];isRawObject(s)?t=s.target!==void 0?s.target:t:s={value:s},triggerEvent(t,o,s)}}else{let r=i.split(",");for(let o=0;o0;){let s=e[0];if(s==="]"){if(i--,i===0){o===null&&(r=r+"true"),e.shift(),r+=")})";try{let a=maybeEval(n,function(){return Function(r)()},function(){return!0});return a.source=r,a}catch(a){return triggerErrorEvent(getDocument().body,"htmx:syntax:error",{error:a,source:r}),null}}}else s==="["&&i++;isPossibleRelativeReference(s,o,t)?r+="(("+t+"."+s+") ? ("+t+"."+s+") : (window."+s+"))":r=r+s,o=e.shift()}}}function consumeUntil(n,e){let t="";for(;n.length>0&&!e.test(n[0]);)t+=n.shift();return t}function consumeCSSSelector(n){let e;return n.length>0&&COMBINED_SELECTOR_START.test(n[0])?(n.shift(),e=consumeUntil(n,COMBINED_SELECTOR_END).trim(),n.shift()):e=consumeUntil(n,WHITESPACE_OR_COMMA),e}let INPUT_SELECTOR="input, textarea, select";function parseAndCacheTrigger(n,e,t){let i=[],r=tokenizeString(e);do{consumeUntil(r,NOT_WHITESPACE);let a=r.length,l=consumeUntil(r,/[,\[\s]/);if(l!=="")if(l==="every"){let c={trigger:"every"};consumeUntil(r,NOT_WHITESPACE),c.pollInterval=parseInterval(consumeUntil(r,/[,\[\s]/)),consumeUntil(r,NOT_WHITESPACE);var o=maybeGenerateConditional(n,r,"event");o&&(c.eventFilter=o),i.push(c)}else{let c={trigger:l};var o=maybeGenerateConditional(n,r,"event");for(o&&(c.eventFilter=o),consumeUntil(r,NOT_WHITESPACE);r.length>0&&r[0]!==",";){let d=r.shift();if(d==="changed")c.changed=!0;else if(d==="once")c.once=!0;else if(d==="consume")c.consume=!0;else if(d==="delay"&&r[0]===":")r.shift(),c.delay=parseInterval(consumeUntil(r,WHITESPACE_OR_COMMA));else if(d==="from"&&r[0]===":"){if(r.shift(),COMBINED_SELECTOR_START.test(r[0]))var s=consumeCSSSelector(r);else{var s=consumeUntil(r,WHITESPACE_OR_COMMA);if(s==="closest"||s==="find"||s==="next"||s==="previous"){r.shift();let y=consumeCSSSelector(r);y.length>0&&(s+=" "+y)}}c.from=s}else d==="target"&&r[0]===":"?(r.shift(),c.target=consumeCSSSelector(r)):d==="throttle"&&r[0]===":"?(r.shift(),c.throttle=parseInterval(consumeUntil(r,WHITESPACE_OR_COMMA))):d==="queue"&&r[0]===":"?(r.shift(),c.queue=consumeUntil(r,WHITESPACE_OR_COMMA)):d==="root"&&r[0]===":"?(r.shift(),c[d]=consumeCSSSelector(r)):d==="threshold"&&r[0]===":"?(r.shift(),c[d]=consumeUntil(r,WHITESPACE_OR_COMMA)):triggerErrorEvent(n,"htmx:syntax:error",{token:r.shift()});consumeUntil(r,NOT_WHITESPACE)}i.push(c)}r.length===a&&triggerErrorEvent(n,"htmx:syntax:error",{token:r.shift()}),consumeUntil(r,NOT_WHITESPACE)}while(r[0]===","&&r.shift());return t&&(t[e]=i),i}function getTriggerSpecs(n){let e=getAttributeValue(n,"hx-trigger"),t=[];if(e){let i=htmx.config.triggerSpecsCache;t=i&&i[e]||parseAndCacheTrigger(n,e,i)}return t.length>0?t:matches(n,"form")?[{trigger:"submit"}]:matches(n,'input[type="button"], input[type="submit"]')?[{trigger:"click"}]:matches(n,INPUT_SELECTOR)?[{trigger:"change"}]:[{trigger:"click"}]}function cancelPolling(n){getInternalData(n).cancelled=!0}function processPolling(n,e,t){let i=getInternalData(n);i.timeout=getWindow().setTimeout(function(){bodyContains(n)&&i.cancelled!==!0&&(maybeFilterEvent(t,n,makeEvent("hx:poll:trigger",{triggerSpec:t,target:n}))||e(n),processPolling(n,e,t))},t.pollInterval)}function isLocalLink(n){return location.hostname===n.hostname&&getRawAttribute(n,"href")&&getRawAttribute(n,"href").indexOf("#")!==0}function eltIsDisabled(n){return closest(n,htmx.config.disableSelector)}function boostElement(n,e,t){if(n instanceof HTMLAnchorElement&&isLocalLink(n)&&(n.target===""||n.target==="_self")||n.tagName==="FORM"&&String(getRawAttribute(n,"method")).toLowerCase()!=="dialog"){e.boosted=!0;let i,r;if(n.tagName==="A")i="get",r=getRawAttribute(n,"href");else{let o=getRawAttribute(n,"method");i=o?o.toLowerCase():"get",r=getRawAttribute(n,"action"),(r==null||r==="")&&(r=location.href),i==="get"&&r.includes("?")&&(r=r.replace(/\?[^#]+/,""))}t.forEach(function(o){addEventListener(n,function(s,a){let l=asElement(s);if(eltIsDisabled(l)){cleanUpElement(l);return}issueAjaxRequest(i,r,l,a)},e,o,!0)})}}function shouldCancel(n,e){return!!((n.type==="submit"||n.type==="click")&&(e=asElement(n.target)||e,e.tagName==="FORM"||e.form&&e.type==="submit"||(e=e.closest("a"),e&&e.href&&(e.getAttribute("href")==="#"||e.getAttribute("href").indexOf("#")!==0))))}function ignoreBoostedAnchorCtrlClick(n,e){return getInternalData(n).boosted&&n instanceof HTMLAnchorElement&&e.type==="click"&&(e.ctrlKey||e.metaKey)}function maybeFilterEvent(n,e,t){let i=n.eventFilter;if(i)try{return i.call(e,t)!==!0}catch(r){let o=i.source;return triggerErrorEvent(getDocument().body,"htmx:eventFilter:error",{error:r,source:o}),!0}return!1}function addEventListener(n,e,t,i,r){let o=getInternalData(n),s;i.from?s=querySelectorAllExt(n,i.from):s=[n],i.changed&&("lastValue"in o||(o.lastValue=new WeakMap),s.forEach(function(a){o.lastValue.has(i)||o.lastValue.set(i,new WeakMap),o.lastValue.get(i).set(a,a.value)})),forEach(s,function(a){let l=function(c){if(!bodyContains(n)){a.removeEventListener(i.trigger,l);return}if(ignoreBoostedAnchorCtrlClick(n,c)||((r||shouldCancel(c,n))&&c.preventDefault(),maybeFilterEvent(i,n,c)))return;let u=getInternalData(c);if(u.triggerSpec=i,u.handledFor==null&&(u.handledFor=[]),u.handledFor.indexOf(n)<0){if(u.handledFor.push(n),i.consume&&c.stopPropagation(),i.target&&c.target&&!matches(asElement(c.target),i.target))return;if(i.once){if(o.triggeredOnce)return;o.triggeredOnce=!0}if(i.changed){let d=c.target,p=d.value,y=o.lastValue.get(i);if(y.has(d)&&y.get(d)===p)return;y.set(d,p)}if(o.delayed&&clearTimeout(o.delayed),o.throttle)return;i.throttle>0?o.throttle||(triggerEvent(n,"htmx:trigger"),e(n,c),o.throttle=getWindow().setTimeout(function(){o.throttle=null},i.throttle)):i.delay>0?o.delayed=getWindow().setTimeout(function(){triggerEvent(n,"htmx:trigger"),e(n,c)},i.delay):(triggerEvent(n,"htmx:trigger"),e(n,c))}};t.listenerInfos==null&&(t.listenerInfos=[]),t.listenerInfos.push({trigger:i.trigger,listener:l,on:a}),a.addEventListener(i.trigger,l)})}let windowIsScrolling=!1,scrollHandler=null;function initScrollHandler(){scrollHandler||(scrollHandler=function(){windowIsScrolling=!0},window.addEventListener("scroll",scrollHandler),window.addEventListener("resize",scrollHandler),setInterval(function(){windowIsScrolling&&(windowIsScrolling=!1,forEach(getDocument().querySelectorAll("[hx-trigger*='revealed'],[data-hx-trigger*='revealed']"),function(n){maybeReveal(n)}))},200))}function maybeReveal(n){!hasAttribute(n,"data-hx-revealed")&&isScrolledIntoView(n)&&(n.setAttribute("data-hx-revealed","true"),getInternalData(n).initHash?triggerEvent(n,"revealed"):n.addEventListener("htmx:afterProcessNode",function(){triggerEvent(n,"revealed")},{once:!0}))}function loadImmediately(n,e,t,i){let r=function(){t.loaded||(t.loaded=!0,triggerEvent(n,"htmx:trigger"),e(n))};i>0?getWindow().setTimeout(r,i):r()}function processVerbs(n,e,t){let i=!1;return forEach(VERBS,function(r){if(hasAttribute(n,"hx-"+r)){let o=getAttributeValue(n,"hx-"+r);i=!0,e.path=o,e.verb=r,t.forEach(function(s){addTriggerHandler(n,s,e,function(a,l){let c=asElement(a);if(eltIsDisabled(c)){cleanUpElement(c);return}issueAjaxRequest(r,o,c,l)})})}}),i}function addTriggerHandler(n,e,t,i){if(e.trigger==="revealed")initScrollHandler(),addEventListener(n,i,t,e),maybeReveal(asElement(n));else if(e.trigger==="intersect"){let r={};e.root&&(r.root=querySelectorExt(n,e.root)),e.threshold&&(r.threshold=parseFloat(e.threshold)),new IntersectionObserver(function(s){for(let a=0;a0?(t.polling=!0,processPolling(asElement(n),i,e)):addEventListener(n,i,t,e)}function shouldProcessHxOn(n){let e=asElement(n);if(!e)return!1;let t=e.attributes;for(let i=0;i", "+o).join(""))}else return[]}function maybeSetLastButtonClicked(n){let e=getTargetButton(n.target),t=getRelatedFormData(n);t&&(t.lastButtonClicked=e)}function maybeUnsetLastButtonClicked(n){let e=getRelatedFormData(n);e&&(e.lastButtonClicked=null)}function getTargetButton(n){return closest(asElement(n),"button, input[type='submit']")}function getRelatedForm(n){return n.form||closest(n,"form")}function getRelatedFormData(n){let e=getTargetButton(n.target);if(!e)return;let t=getRelatedForm(e);return getInternalData(t)}function initButtonTracking(n){n.addEventListener("click",maybeSetLastButtonClicked),n.addEventListener("focusin",maybeSetLastButtonClicked),n.addEventListener("focusout",maybeUnsetLastButtonClicked)}function addHxOnEventHandler(n,e,t){let i=getInternalData(n);Array.isArray(i.onHandlers)||(i.onHandlers=[]);let r,o=function(s){maybeEval(n,function(){eltIsDisabled(n)||(r||(r=new Function("event",t)),r.call(n,s))})};n.addEventListener(e,o),i.onHandlers.push({event:e,listener:o})}function processHxOnWildcard(n){deInitOnHandlers(n);for(let e=0;ehtmx.config.historyCacheSize;)o.shift();for(;o.length>0;)try{sessionStorage.setItem("htmx-history-cache",JSON.stringify(o));break}catch(a){triggerErrorEvent(getDocument().body,"htmx:historyCacheError",{cause:a,cache:o}),o.shift()}}function getCachedHistory(n){if(!canAccessLocalStorage())return null;n=normalizePath(n);let e=parseJSON(sessionStorage.getItem("htmx-history-cache"))||[];for(let t=0;t=200&&this.status<400?(i.response=this.response,triggerEvent(getDocument().body,"htmx:historyCacheMissLoad",i),swap(i.historyElt,i.response,t,{contextElement:i.historyElt,historyRequest:!0}),setCurrentPathForHistory(i.path),triggerEvent(getDocument().body,"htmx:historyRestore",{path:n,cacheMiss:!0,serverResponse:i.response})):triggerErrorEvent(getDocument().body,"htmx:historyCacheMissLoadError",i)},triggerEvent(getDocument().body,"htmx:historyCacheMiss",i)&&e.send()}function restoreHistory(n){saveCurrentPageToHistory(),n=n||location.pathname+location.search;let e=getCachedHistory(n);if(e){let t={swapStyle:"innerHTML",swapDelay:0,settleDelay:0,scroll:e.scroll},i={path:n,item:e,historyElt:getHistoryElement(),swapSpec:t};triggerEvent(getDocument().body,"htmx:historyCacheHit",i)&&(swap(i.historyElt,e.content,t,{contextElement:i.historyElt,title:e.title}),setCurrentPathForHistory(i.path),triggerEvent(getDocument().body,"htmx:historyRestore",i))}else htmx.config.refreshOnHistoryMiss?htmx.location.reload(!0):loadHistoryFromServer(n)}function addRequestIndicatorClasses(n){let e=findAttributeTargets(n,"hx-indicator");return e==null&&(e=[n]),forEach(e,function(t){let i=getInternalData(t);i.requestCount=(i.requestCount||0)+1,t.classList.add.call(t.classList,htmx.config.requestClass)}),e}function disableElements(n){let e=findAttributeTargets(n,"hx-disabled-elt");return e==null&&(e=[]),forEach(e,function(t){let i=getInternalData(t);i.requestCount=(i.requestCount||0)+1,t.setAttribute("disabled",""),t.setAttribute("data-disabled-by-htmx","")}),e}function removeRequestIndicators(n,e){forEach(n.concat(e),function(t){let i=getInternalData(t);i.requestCount=(i.requestCount||1)-1}),forEach(n,function(t){getInternalData(t).requestCount===0&&t.classList.remove.call(t.classList,htmx.config.requestClass)}),forEach(e,function(t){getInternalData(t).requestCount===0&&(t.removeAttribute("disabled"),t.removeAttribute("data-disabled-by-htmx"))})}function haveSeenNode(n,e){for(let t=0;te.indexOf(r)<0):i=i.filter(r=>r!==e),t.delete(n),forEach(i,r=>t.append(n,r))}}function getValueFromInput(n){return n instanceof HTMLSelectElement&&n.multiple?toArray(n.querySelectorAll("option:checked")).map(function(e){return e.value}):n instanceof HTMLInputElement&&n.files?toArray(n.files):n.value}function processInputValue(n,e,t,i,r){if(!(i==null||haveSeenNode(n,i))){if(n.push(i),shouldInclude(i)){let o=getRawAttribute(i,"name");addValueToFormData(o,getValueFromInput(i),e),r&&validateElement(i,t)}i instanceof HTMLFormElement&&(forEach(i.elements,function(o){n.indexOf(o)>=0?removeValueFromFormData(o.name,getValueFromInput(o),e):n.push(o),r&&validateElement(o,t)}),new FormData(i).forEach(function(o,s){o instanceof File&&o.name===""||addValueToFormData(s,o,e)}))}}function validateElement(n,e){let t=n;t.willValidate&&(triggerEvent(t,"htmx:validation:validate"),t.checkValidity()||(e.push({elt:t,message:t.validationMessage,validity:t.validity}),triggerEvent(t,"htmx:validation:failed",{message:t.validationMessage,validity:t.validity})))}function overrideFormData(n,e){for(let t of e.keys())n.delete(t);return e.forEach(function(t,i){n.append(i,t)}),n}function getInputValues(n,e){let t=[],i=new FormData,r=new FormData,o=[],s=getInternalData(n);s.lastButtonClicked&&!bodyContains(s.lastButtonClicked)&&(s.lastButtonClicked=null);let a=n instanceof HTMLFormElement&&n.noValidate!==!0||getAttributeValue(n,"hx-validate")==="true";if(s.lastButtonClicked&&(a=a&&s.lastButtonClicked.formNoValidate!==!0),e!=="get"&&processInputValue(t,r,o,getRelatedForm(n),a),processInputValue(t,i,o,n,a),s.lastButtonClicked||n.tagName==="BUTTON"||n.tagName==="INPUT"&&getRawAttribute(n,"type")==="submit"){let c=s.lastButtonClicked||n,u=getRawAttribute(c,"name");addValueToFormData(u,c.value,r)}let l=findAttributeTargets(n,"hx-include");return forEach(l,function(c){processInputValue(t,i,o,asElement(c),a),matches(c,"form")||forEach(asParentNode(c).querySelectorAll(INPUT_SELECTOR),function(u){processInputValue(t,i,o,u,a)})}),overrideFormData(i,r),{errors:o,formData:i,values:formDataProxy(i)}}function appendParam(n,e,t){n!==""&&(n+="&"),String(t)==="[object Object]"&&(t=JSON.stringify(t));let i=encodeURIComponent(t);return n+=encodeURIComponent(e)+"="+i,n}function urlEncode(n){n=formDataFromObject(n);let e="";return n.forEach(function(t,i){e=appendParam(e,i,t)}),e}function getHeaders(n,e,t){let i={"HX-Request":"true","HX-Trigger":getRawAttribute(n,"id"),"HX-Trigger-Name":getRawAttribute(n,"name"),"HX-Target":getAttributeValue(e,"id"),"HX-Current-URL":location.href};return getValuesForElement(n,"hx-headers",!1,i),t!==void 0&&(i["HX-Prompt"]=t),getInternalData(n).boosted&&(i["HX-Boosted"]="true"),i}function filterValues(n,e){let t=getClosestAttributeValue(e,"hx-params");if(t){if(t==="none")return new FormData;if(t==="*")return n;if(t.indexOf("not ")===0)return forEach(t.slice(4).split(","),function(i){i=i.trim(),n.delete(i)}),n;{let i=new FormData;return forEach(t.split(","),function(r){r=r.trim(),n.has(r)&&n.getAll(r).forEach(function(o){i.append(r,o)})}),i}}else return n}function isAnchorLink(n){return!!getRawAttribute(n,"href")&&getRawAttribute(n,"href").indexOf("#")>=0}function getSwapSpecification(n,e){let t=e||getClosestAttributeValue(n,"hx-swap"),i={swapStyle:getInternalData(n).boosted?"innerHTML":htmx.config.defaultSwapStyle,swapDelay:htmx.config.defaultSwapDelay,settleDelay:htmx.config.defaultSettleDelay};if(htmx.config.scrollIntoViewOnBoost&&getInternalData(n).boosted&&!isAnchorLink(n)&&(i.show="top"),t){let s=splitOnWhitespace(t);if(s.length>0)for(let a=0;a0?r.join(":"):null;i.scroll=u,i.scrollTarget=o}else if(l.indexOf("show:")===0){var r=l.slice(5).split(":");let d=r.pop();var o=r.length>0?r.join(":"):null;i.show=d,i.showTarget=o}else if(l.indexOf("focus-scroll:")===0){let c=l.slice(13);i.focusScroll=c=="true"}else a==0?i.swapStyle=l:logError("Unknown modifier in hx-swap: "+l)}}return i}function usesFormData(n){return getClosestAttributeValue(n,"hx-encoding")==="multipart/form-data"||matches(n,"form")&&getRawAttribute(n,"enctype")==="multipart/form-data"}function encodeParamsForBody(n,e,t){let i=null;return withExtensions(e,function(r){i==null&&(i=r.encodeParameters(n,t,e))}),i!=null?i:usesFormData(e)?overrideFormData(new FormData,formDataFromObject(t)):urlEncode(t)}function makeSettleInfo(n){return{tasks:[],elts:[n]}}function updateScrollState(n,e){let t=n[0],i=n[n.length-1];if(e.scroll){var r=null;e.scrollTarget&&(r=asElement(querySelectorExt(t,e.scrollTarget))),e.scroll==="top"&&(t||r)&&(r=r||t,r.scrollTop=0),e.scroll==="bottom"&&(i||r)&&(r=r||i,r.scrollTop=r.scrollHeight),typeof e.scroll=="number"&&getWindow().setTimeout(function(){window.scrollTo(0,e.scroll)},0)}if(e.show){var r=null;if(e.showTarget){let s=e.showTarget;e.showTarget==="window"&&(s="body"),r=asElement(querySelectorExt(t,s))}e.show==="top"&&(t||r)&&(r=r||t,r.scrollIntoView({block:"start",behavior:htmx.config.scrollBehavior})),e.show==="bottom"&&(i||r)&&(r=r||i,r.scrollIntoView({block:"end",behavior:htmx.config.scrollBehavior}))}}function getValuesForElement(n,e,t,i,r){if(i==null&&(i={}),n==null)return i;let o=getAttributeValue(n,e);if(o){let s=o.trim(),a=t;if(s==="unset")return null;s.indexOf("javascript:")===0?(s=s.slice(11),a=!0):s.indexOf("js:")===0&&(s=s.slice(3),a=!0),s.indexOf("{")!==0&&(s="{"+s+"}");let l;a?l=maybeEval(n,function(){return r?Function("event","return ("+s+")").call(n,r):Function("return ("+s+")").call(n)},{}):l=parseJSON(s);for(let c in l)l.hasOwnProperty(c)&&i[c]==null&&(i[c]=l[c])}return getValuesForElement(asElement(parentElt(n)),e,t,i,r)}function maybeEval(n,e,t){return htmx.config.allowEval?e():(triggerErrorEvent(n,"htmx:evalDisallowedError"),t)}function getHXVarsForElement(n,e,t){return getValuesForElement(n,"hx-vars",!0,t,e)}function getHXValsForElement(n,e,t){return getValuesForElement(n,"hx-vals",!1,t,e)}function getExpressionVars(n,e){return mergeObjects(getHXVarsForElement(n,e),getHXValsForElement(n,e))}function safelySetHeaderValue(n,e,t){if(t!==null)try{n.setRequestHeader(e,t)}catch(i){n.setRequestHeader(e,encodeURIComponent(t)),n.setRequestHeader(e+"-URI-AutoEncoded","true")}}function getPathFromResponse(n){if(n.responseURL)try{let e=new URL(n.responseURL);return e.pathname+e.search}catch(e){triggerErrorEvent(getDocument().body,"htmx:badResponseUrl",{url:n.responseURL})}}function hasHeader(n,e){return e.test(n.getAllResponseHeaders())}function ajaxHelper(n,e,t){if(n=n.toLowerCase(),t){if(t instanceof Element||typeof t=="string")return issueAjaxRequest(n,e,null,null,{targetOverride:resolveTarget(t)||DUMMY_ELT,returnPromise:!0});{let i=resolveTarget(t.target);return(t.target&&!i||t.source&&!i&&!resolveTarget(t.source))&&(i=DUMMY_ELT),issueAjaxRequest(n,e,resolveTarget(t.source),t.event,{handler:t.handler,headers:t.headers,values:t.values,targetOverride:i,swapOverride:t.swap,select:t.select,returnPromise:!0})}}else return issueAjaxRequest(n,e,null,null,{returnPromise:!0})}function hierarchyForElt(n){let e=[];for(;n;)e.push(n),n=n.parentElement;return e}function verifyPath(n,e,t){let i=new URL(e,location.protocol!=="about:"?location.href:window.origin),o=(location.protocol!=="about:"?location.origin:window.origin)===i.origin;return htmx.config.selfRequestsOnly&&!o?!1:triggerEvent(n,"htmx:validateUrl",mergeObjects({url:i,sameHost:o},t))}function formDataFromObject(n){if(n instanceof FormData)return n;let e=new FormData;for(let t in n)n.hasOwnProperty(t)&&(n[t]&&typeof n[t].forEach=="function"?n[t].forEach(function(i){e.append(t,i)}):typeof n[t]=="object"&&!(n[t]instanceof Blob)?e.append(t,JSON.stringify(n[t])):e.append(t,n[t]));return e}function formDataArrayProxy(n,e,t){return new Proxy(t,{get:function(i,r){return typeof r=="number"?i[r]:r==="length"?i.length:r==="push"?function(o){i.push(o),n.append(e,o)}:typeof i[r]=="function"?function(){i[r].apply(i,arguments),n.delete(e),i.forEach(function(o){n.append(e,o)})}:i[r]&&i[r].length===1?i[r][0]:i[r]},set:function(i,r,o){return i[r]=o,n.delete(e),i.forEach(function(s){n.append(e,s)}),!0}})}function formDataProxy(n){return new Proxy(n,{get:function(e,t){if(typeof t=="symbol"){let r=Reflect.get(e,t);return typeof r=="function"?function(){return r.apply(n,arguments)}:r}if(t==="toJSON")return()=>Object.fromEntries(n);if(t in e&&typeof e[t]=="function")return function(){return n[t].apply(n,arguments)};let i=n.getAll(t);if(i.length!==0)return i.length===1?i[0]:formDataArrayProxy(e,t,i)},set:function(e,t,i){return typeof t!="string"?!1:(e.delete(t),i&&typeof i.forEach=="function"?i.forEach(function(r){e.append(t,r)}):typeof i=="object"&&!(i instanceof Blob)?e.append(t,JSON.stringify(i)):e.append(t,i),!0)},deleteProperty:function(e,t){return typeof t=="string"&&e.delete(t),!0},ownKeys:function(e){return Reflect.ownKeys(Object.fromEntries(e))},getOwnPropertyDescriptor:function(e,t){return Reflect.getOwnPropertyDescriptor(Object.fromEntries(e),t)}})}function issueAjaxRequest(n,e,t,i,r,o){let s=null,a=null;if(r=r!=null?r:{},r.returnPromise&&typeof Promise!="undefined")var l=new Promise(function(U,Z){s=U,a=Z});t==null&&(t=getDocument().body);let c=r.handler||handleAjaxResponse,u=r.select||null;if(!bodyContains(t))return maybeCall(s),l;let d=r.targetOverride||asElement(getTarget(t));if(d==null||d==DUMMY_ELT)return triggerErrorEvent(t,"htmx:targetError",{target:getClosestAttributeValue(t,"hx-target")}),maybeCall(a),l;let p=getInternalData(t),y=p.lastButtonClicked;if(y){let U=getRawAttribute(y,"formaction");U!=null&&(e=U);let Z=getRawAttribute(y,"formmethod");if(Z!=null)if(VERBS.includes(Z.toLowerCase()))n=Z;else return maybeCall(s),l}let m=getClosestAttributeValue(t,"hx-confirm");if(o===void 0&&triggerEvent(t,"htmx:confirm",{target:d,elt:t,path:e,verb:n,triggeringEvent:i,etc:r,issueRequest:function(oe){return issueAjaxRequest(n,e,t,i,r,!!oe)},question:m})===!1)return maybeCall(s),l;let v=t,w=getClosestAttributeValue(t,"hx-sync"),T=null,_=!1;if(w){let U=w.split(":"),Z=U[0].trim();if(Z==="this"?v=findThisElement(t,"hx-sync"):v=asElement(querySelectorExt(t,Z)),w=(U[1]||"drop").trim(),p=getInternalData(v),w==="drop"&&p.xhr&&p.abortable!==!0)return maybeCall(s),l;if(w==="abort"){if(p.xhr)return maybeCall(s),l;_=!0}else w==="replace"?triggerEvent(v,"htmx:abort"):w.indexOf("queue")===0&&(T=(w.split(" ")[1]||"last").trim())}if(p.xhr)if(p.abortable)triggerEvent(v,"htmx:abort");else{if(T==null){if(i){let U=getInternalData(i);U&&U.triggerSpec&&U.triggerSpec.queue&&(T=U.triggerSpec.queue)}T==null&&(T="last")}return p.queuedRequests==null&&(p.queuedRequests=[]),T==="first"&&p.queuedRequests.length===0?p.queuedRequests.push(function(){issueAjaxRequest(n,e,t,i,r)}):T==="all"?p.queuedRequests.push(function(){issueAjaxRequest(n,e,t,i,r)}):T==="last"&&(p.queuedRequests=[],p.queuedRequests.push(function(){issueAjaxRequest(n,e,t,i,r)})),maybeCall(s),l}let S=new XMLHttpRequest;p.xhr=S,p.abortable=_;let A=function(){p.xhr=null,p.abortable=!1,p.queuedRequests!=null&&p.queuedRequests.length>0&&p.queuedRequests.shift()()},K=getClosestAttributeValue(t,"hx-prompt");if(K){var z=prompt(K);if(z===null||!triggerEvent(t,"htmx:prompt",{prompt:z,target:d}))return maybeCall(s),A(),l}if(m&&!o&&!confirm(m))return maybeCall(s),A(),l;let L=getHeaders(t,d,z);n!=="get"&&!usesFormData(t)&&(L["Content-Type"]="application/x-www-form-urlencoded"),r.headers&&(L=mergeObjects(L,r.headers));let H=getInputValues(t,n),N=H.errors,Y=H.formData;r.values&&overrideFormData(Y,formDataFromObject(r.values));let B=formDataFromObject(getExpressionVars(t,i)),ie=overrideFormData(Y,B),J=filterValues(ie,t);htmx.config.getCacheBusterParam&&n==="get"&&J.set("org.htmx.cache-buster",getRawAttribute(d,"id")||"true"),(e==null||e==="")&&(e=location.href);let Te=getValuesForElement(t,"hx-request"),Ce=getInternalData(t).boosted,se=htmx.config.methodsThatUseUrlParams.indexOf(n)>=0,ne={boosted:Ce,useUrlParams:se,formData:J,parameters:formDataProxy(J),unfilteredFormData:ie,unfilteredParameters:formDataProxy(ie),headers:L,elt:t,target:d,verb:n,errors:N,withCredentials:r.credentials||Te.credentials||htmx.config.withCredentials,timeout:r.timeout||Te.timeout||htmx.config.timeout,path:e,triggeringEvent:i};if(!triggerEvent(t,"htmx:configRequest",ne))return maybeCall(s),A(),l;if(e=ne.path,n=ne.verb,L=ne.headers,J=formDataFromObject(ne.parameters),N=ne.errors,se=ne.useUrlParams,N&&N.length>0)return triggerEvent(t,"htmx:validation:halted",ne),maybeCall(s),A(),l;let qe=e.split("#"),Re=qe[0],W=qe[1],M=e;if(se&&(M=Re,!J.keys().next().done&&(M.indexOf("?")<0?M+="?":M+="&",M+=urlEncode(J),W&&(M+="#"+W))),!verifyPath(t,M,ne))return triggerErrorEvent(t,"htmx:invalidPath",ne),maybeCall(a),A(),l;if(S.open(n.toUpperCase(),M,!0),S.overrideMimeType("text/html"),S.withCredentials=ne.withCredentials,S.timeout=ne.timeout,!Te.noHeaders){for(let U in L)if(L.hasOwnProperty(U)){let Z=L[U];safelySetHeaderValue(S,U,Z)}}let D={xhr:S,target:d,requestConfig:ne,etc:r,boosted:Ce,select:u,pathInfo:{requestPath:e,finalRequestPath:M,responsePath:null,anchor:W}};if(S.onload=function(){try{let U=hierarchyForElt(t);if(D.pathInfo.responsePath=getPathFromResponse(S),c(t,D),D.keepIndicators!==!0&&removeRequestIndicators($,V),triggerEvent(t,"htmx:afterRequest",D),triggerEvent(t,"htmx:afterOnLoad",D),!bodyContains(t)){let Z=null;for(;U.length>0&&Z==null;){let oe=U.shift();bodyContains(oe)&&(Z=oe)}Z&&(triggerEvent(Z,"htmx:afterRequest",D),triggerEvent(Z,"htmx:afterOnLoad",D))}maybeCall(s)}catch(U){throw triggerErrorEvent(t,"htmx:onLoadError",mergeObjects({error:U},D)),U}finally{A()}},S.onerror=function(){removeRequestIndicators($,V),triggerErrorEvent(t,"htmx:afterRequest",D),triggerErrorEvent(t,"htmx:sendError",D),maybeCall(a),A()},S.onabort=function(){removeRequestIndicators($,V),triggerErrorEvent(t,"htmx:afterRequest",D),triggerErrorEvent(t,"htmx:sendAbort",D),maybeCall(a),A()},S.ontimeout=function(){removeRequestIndicators($,V),triggerErrorEvent(t,"htmx:afterRequest",D),triggerErrorEvent(t,"htmx:timeout",D),maybeCall(a),A()},!triggerEvent(t,"htmx:beforeRequest",D))return maybeCall(s),A(),l;var $=addRequestIndicatorClasses(t),V=disableElements(t);forEach(["loadstart","loadend","progress","abort"],function(U){forEach([S,S.upload],function(Z){Z.addEventListener(U,function(oe){triggerEvent(t,"htmx:xhr:"+U,{lengthComputable:oe.lengthComputable,loaded:oe.loaded,total:oe.total})})})}),triggerEvent(t,"htmx:beforeSend",D);let q=se?null:encodeParamsForBody(S,t,J);return S.send(q),l}function determineHistoryUpdates(n,e){let t=e.xhr,i=null,r=null;if(hasHeader(t,/HX-Push:/i)?(i=t.getResponseHeader("HX-Push"),r="push"):hasHeader(t,/HX-Push-Url:/i)?(i=t.getResponseHeader("HX-Push-Url"),r="push"):hasHeader(t,/HX-Replace-Url:/i)&&(i=t.getResponseHeader("HX-Replace-Url"),r="replace"),i)return i==="false"?{}:{type:r,path:i};let o=e.pathInfo.finalRequestPath,s=e.pathInfo.responsePath,a=getClosestAttributeValue(n,"hx-push-url"),l=getClosestAttributeValue(n,"hx-replace-url"),c=getInternalData(n).boosted,u=null,d=null;return a?(u="push",d=a):l?(u="replace",d=l):c&&(u="push",d=s||o),d?d==="false"?{}:(d==="true"&&(d=s||o),e.pathInfo.anchor&&d.indexOf("#")===-1&&(d=d+"#"+e.pathInfo.anchor),{type:u,path:d}):{}}function codeMatches(n,e){var t=new RegExp(n.code);return t.test(e.toString(10))}function resolveResponseHandling(n){for(var e=0;e ."+htmx.config.indicatorClass+"{opacity:0} ."+htmx.config.requestClass+" ."+htmx.config.indicatorClass+"{opacity:1; transition: opacity 200ms ease-in;} ."+htmx.config.requestClass+"."+htmx.config.indicatorClass+"{opacity:1; transition: opacity 200ms ease-in;} ")}}function getMetaConfig(){let n=getDocument().querySelector('meta[name="htmx-config"]');return n?parseJSON(n.content):null}function mergeMetaConfig(){let n=getMetaConfig();n&&(htmx.config=mergeObjects(htmx.config,n))}return ready(function(){mergeMetaConfig(),insertIndicatorStyles();let n=getDocument().body;processNode(n);let e=getDocument().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");n.addEventListener("htmx:abort",function(i){let r=i.target,o=getInternalData(r);o&&o.xhr&&o.xhr.abort()});let t=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(i){i.state&&i.state.htmx?(restoreHistory(),forEach(e,function(r){triggerEvent(r,"htmx:restored",{document:getDocument(),triggerEvent})})):t&&t(i)},getWindow().setTimeout(function(){triggerEvent(n,"htmx:load",{}),n=null},0)}),htmx}(),b_=Kp;function ko(n,e){n.split(/\s+/).forEach(t=>{e(t)})}var oi=class{constructor(){this._events={}}on(e,t){ko(e,i=>{let r=this._events[i]||[];r.push(t),this._events[i]=r})}off(e,t){var i=arguments.length;if(i===0){this._events={};return}ko(e,r=>{if(i===1){delete this._events[r];return}let o=this._events[r];o!==void 0&&(o.splice(o.indexOf(t),1),this._events[r]=o)})}trigger(e,...t){var i=this;ko(e,r=>{let o=i._events[r];o!==void 0&&o.forEach(s=>{s.apply(i,t)})})}};function No(n){return n.plugins={},class extends n{constructor(){super(...arguments),this.plugins={names:[],settings:{},requested:{},loaded:{}}}static define(e,t){n.plugins[e]={name:e,fn:t}}initializePlugins(e){var t,i;let r=this,o=[];if(Array.isArray(e))e.forEach(s=>{typeof s=="string"?o.push(s):(r.plugins.settings[s.name]=s.options,o.push(s.name))});else if(e)for(t in e)e.hasOwnProperty(t)&&(r.plugins.settings[t]=e[t],o.push(t));for(;i=o.shift();)r.require(i)}loadPlugin(e){var t=this,i=t.plugins,r=n.plugins[e];if(!n.plugins.hasOwnProperty(e))throw new Error('Unable to find "'+e+'" plugin');i.requested[e]=!0,i.loaded[e]=r.fn.apply(t,[t.plugins.settings[e]||{}]),i.names.push(e)}require(e){var t=this,i=t.plugins;if(!t.plugins.loaded.hasOwnProperty(e)){if(i.requested[e])throw new Error('Plugin has circular dependency ("'+e+'")');t.loadPlugin(e)}return i.loaded[e]}}}var si=n=>(n=n.filter(Boolean),n.length<2?n[0]||"":Qp(n)==1?"["+n.join("")+"]":"(?:"+n.join("|")+")"),Io=n=>{if(!Xp(n))return n.join("");let e="",t=0,i=()=>{t>1&&(e+="{"+t+"}")};return n.forEach((r,o)=>{if(r===n[o-1]){t++;return}i(),e+=r,t=1}),i(),e},Ro=n=>{let e=Array.from(n);return si(e)},Xp=n=>new Set(n).size!==n.length,Mt=n=>(n+"").replace(/([\$\(\)\*\+\.\?\[\]\^\{\|\}\\])/gu,"\\$1"),Qp=n=>n.reduce((e,t)=>Math.max(e,Jp(t)),0),Jp=n=>Array.from(n).length;var Ho=n=>{if(n.length===1)return[[n]];let e=[],t=n.substring(1);return Ho(t).forEach(function(r){let o=r.slice(0);o[0]=n.charAt(0)+o[0],e.push(o),o=r.slice(0),o.unshift(n.charAt(0)),e.push(o)}),e};var Zp=[[0,65535]],em="[\u0300-\u036F\xB7\u02BE\u02BC]",or,$a,tm=3,Po={},Pa={"/":"\u2044\u2215",0:"\u07C0",a:"\u2C65\u0250\u0251",aa:"\uA733",ae:"\xE6\u01FD\u01E3",ao:"\uA735",au:"\uA737",av:"\uA739\uA73B",ay:"\uA73D",b:"\u0180\u0253\u0183",c:"\uA73F\u0188\u023C\u2184",d:"\u0111\u0257\u0256\u1D05\u018C\uABB7\u0501\u0266",e:"\u025B\u01DD\u1D07\u0247",f:"\uA77C\u0192",g:"\u01E5\u0260\uA7A1\u1D79\uA77F\u0262",h:"\u0127\u2C68\u2C76\u0265",i:"\u0268\u0131",j:"\u0249\u0237",k:"\u0199\u2C6A\uA741\uA743\uA745\uA7A3",l:"\u0142\u019A\u026B\u2C61\uA749\uA747\uA781\u026D",m:"\u0271\u026F\u03FB",n:"\uA7A5\u019E\u0272\uA791\u1D0E\u043B\u0509",o:"\xF8\u01FF\u0254\u0275\uA74B\uA74D\u1D11",oe:"\u0153",oi:"\u01A3",oo:"\uA74F",ou:"\u0223",p:"\u01A5\u1D7D\uA751\uA753\uA755\u03C1",q:"\uA757\uA759\u024B",r:"\u024D\u027D\uA75B\uA7A7\uA783",s:"\xDF\u023F\uA7A9\uA785\u0282",t:"\u0167\u01AD\u0288\u2C66\uA787",th:"\xFE",tz:"\uA729",u:"\u0289",v:"\u028B\uA75F\u028C",vy:"\uA761",w:"\u2C73",y:"\u01B4\u024F\u1EFF",z:"\u01B6\u0225\u0240\u2C6C\uA763",hv:"\u0195"};for(let n in Pa){let e=Pa[n]||"";for(let t=0;t{or===void 0&&(or=am(n||Zp))},Fa=(n,e="NFKD")=>n.normalize(e),ai=n=>Array.from(n).reduce((e,t)=>e+rm(t),""),rm=n=>(n=Fa(n).toLowerCase().replace(nm,e=>Po[e]||""),Fa(n,"NFC"));function*om(n){for(let[e,t]of n)for(let i=e;i<=t;i++){let r=String.fromCharCode(i),o=ai(r);o!=r.toLowerCase()&&(o.length>tm||o.length!=0&&(yield{folded:o,composed:r,code_point:i}))}}var sm=n=>{let e={},t=(i,r)=>{let o=e[i]||new Set,s=new RegExp("^"+Ro(o)+"$","iu");r.match(s)||(o.add(Mt(r)),e[i]=o)};for(let i of om(n))t(i.folded,i.folded),t(i.folded,i.composed);return e},am=n=>{let e=sm(n),t={},i=[];for(let o in e){let s=e[o];s&&(t[o]=Ro(s)),o.length>1&&i.push(Mt(o))}i.sort((o,s)=>s.length-o.length);let r=si(i);return $a=new RegExp("^"+r,"u"),t},lm=(n,e=1)=>{let t=0;return n=n.map(i=>(or[i]&&(t+=i.length),or[i]||i)),t>=e?Io(n):""},cm=(n,e=1)=>(e=Math.max(e,n.length-1),si(Ho(n).map(t=>lm(t,e)))),Ba=(n,e=!0)=>{let t=n.length>1?1:0;return si(n.map(i=>{let r=[],o=e?i.length():i.length()-1;for(let s=0;s{for(let t of e){if(t.start!=n.start||t.end!=n.end||t.substrs.join("")!==n.substrs.join(""))continue;let i=n.parts,r=s=>{for(let a of i){if(a.start===s.start&&a.substr===s.substr)return!1;if(!(s.length==1||a.length==1)&&(s.starta.start||a.starts.start))return!0}return!1};if(!(t.parts.filter(r).length>0))return!0}return!1},sr=class n{constructor(){ee(this,"parts");ee(this,"substrs");ee(this,"start");ee(this,"end");this.parts=[],this.substrs=[],this.start=0,this.end=0}add(e){e&&(this.parts.push(e),this.substrs.push(e.substr),this.start=Math.min(e.start,this.start),this.end=Math.max(e.end,this.end))}last(){return this.parts[this.parts.length-1]}length(){return this.parts.length}clone(e,t){let i=new n,r=JSON.parse(JSON.stringify(this.parts)),o=r.pop();for(let l of r)i.add(l);let s=t.substr.substring(0,e-o.start),a=s.length;return i.add({start:o.start,end:o.start+a,length:a,substr:s}),i}},Va=n=>{im(),n=ai(n);let e="",t=[new sr];for(let i=0;i0){l=l.sort((u,d)=>u.length()-d.length());for(let u of l)um(u,t)||t.push(u);continue}if(i>0&&c.size==1&&!c.has("3")){e+=Ba(t,!1);let u=new sr,d=t[0];d&&u.add(d.last()),t=[u]}}return e+=Ba(t,!0),e};var za=(n,e)=>{if(n)return n[e]},ja=(n,e)=>{if(n){for(var t,i=e.split(".");(t=i.shift())&&(n=n[t]););return n}},ar=(n,e,t)=>{var i,r;return!n||(n=n+"",e.regex==null)||(r=n.search(e.regex),r===-1)?0:(i=e.string.length/n.length,r===0&&(i+=.5),i*t)},lr=(n,e)=>{var t=n[e];if(typeof t=="function")return t;t&&!Array.isArray(t)&&(n[e]=[t])},li=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)},Wa=(n,e)=>typeof n=="number"&&typeof e=="number"?n>e?1:ne?1:e>n?-1:0);var cr=class{constructor(e,t){ee(this,"items");ee(this,"settings");this.items=e,this.settings=t||{diacritics:!0}}tokenize(e,t,i){if(!e||!e.length)return[];let r=[],o=e.split(/\s+/);var s;return i&&(s=new RegExp("^("+Object.keys(i).map(Mt).join("|")+"):(.*)$")),o.forEach(a=>{let l,c=null,u=null;s&&(l=a.match(s))&&(c=l[1],a=l[2]),a.length>0&&(this.settings.diacritics?u=Va(a)||null:u=Mt(a),u&&t&&(u="\\b"+u)),r.push({string:a,regex:u?new RegExp(u,"iu"):null,field:c})}),r}getScoreFunction(e,t){var i=this.prepareSearch(e,t);return this._getScoreFunction(i)}_getScoreFunction(e){let t=e.tokens,i=t.length;if(!i)return function(){return 0};let r=e.options.fields,o=e.weights,s=r.length,a=e.getAttrFn;if(!s)return function(){return 1};let l=function(){return s===1?function(c,u){let d=r[0].field;return ar(a(u,d),c,o[d]||1)}:function(c,u){var d=0;if(c.field){let p=a(u,c.field);!c.regex&&p?d+=1/s:d+=ar(p,c,1)}else li(o,(p,y)=>{d+=ar(a(u,y),c,p)});return d/s}}();return i===1?function(c){return l(t[0],c)}:e.options.conjunction==="and"?function(c){var u,d=0;for(let p of t){if(u=l(p,c),u<=0)return 0;d+=u}return d/i}:function(c){var u=0;return li(t,d=>{u+=l(d,c)}),u/i}}getSortFunction(e,t){var i=this.prepareSearch(e,t);return this._getSortFunction(i)}_getSortFunction(e){var t,i=[];let r=this,o=e.options,s=!e.query&&o.sort_empty?o.sort_empty:o.sort;if(typeof s=="function")return s.bind(this);let a=function(c,u){return c==="$score"?u.score:e.getAttrFn(r.items[u.id],c)};if(s)for(let c of s)(e.query||c.field!=="$score")&&i.push(c);if(e.query){t=!0;for(let c of i)if(c.field==="$score"){t=!1;break}t&&i.unshift({field:"$score",direction:"desc"})}else i=i.filter(c=>c.field!=="$score");return i.length?function(c,u){var d,p;for(let y of i)if(p=y.field,d=(y.direction==="desc"?-1:1)*Wa(a(p,c),a(p,u)),d)return d;return 0}:null}prepareSearch(e,t){let i={};var r=Object.assign({},t);if(lr(r,"sort"),lr(r,"sort_empty"),r.fields){lr(r,"fields");let o=[];r.fields.forEach(s=>{typeof s=="string"&&(s={field:s,weight:1}),o.push(s),i[s.field]="weight"in s?s.weight:1}),r.fields=o}return{options:r,query:e.toLowerCase().trim(),tokens:this.tokenize(e,r.respect_word_boundaries,i),total:0,items:[],weights:i,getAttrFn:r.nesting?ja:za}}search(e,t){var i=this,r,o;o=this.prepareSearch(e,t),t=o.options,e=o.query;let s=t.score||i._getScoreFunction(o);e.length?li(i.items,(l,c)=>{r=s(l),(t.filter===!1||r>0)&&o.items.push({score:r,id:c})}):li(i.items,(l,c)=>{o.items.push({score:1,id:c})});let a=i._getSortFunction(o);return a&&o.items.sort(a),o.total=o.items.length,typeof t.limit=="number"&&(o.items=o.items.slice(0,t.limit)),o}};var We=n=>typeof n=="undefined"||n===null?null:ci(n),ci=n=>typeof n=="boolean"?n?"1":"0":n+"",ur=n=>(n+"").replace(/&/g,"&").replace(//g,">").replace(/"/g,"""),qa=(n,e)=>e>0?window.setTimeout(n,e):(n.call(null),null),Ua=(n,e)=>{var t;return function(i,r){var o=this;t&&(o.loading=Math.max(o.loading-1,0),clearTimeout(t)),t=setTimeout(function(){t=null,o.loadedSearches[i]=!0,n.call(o,i,r)},e)}},Fo=(n,e,t)=>{var i,r=n.trigger,o={};n.trigger=function(){var s=arguments[0];if(e.indexOf(s)!==-1)o[s]=arguments;else return r.apply(n,arguments)},t.apply(n,[]),n.trigger=r;for(i of e)i in o&&r.apply(n,o[i])},Ya=n=>({start:n.selectionStart||0,length:(n.selectionEnd||0)-(n.selectionStart||0)}),fe=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},Se=(n,e,t,i)=>{n.addEventListener(e,t,i)},kt=(n,e)=>{if(!e||!e[n])return!1;var t=(e.altKey?1:0)+(e.ctrlKey?1:0)+(e.shiftKey?1:0)+(e.metaKey?1:0);return t===1},dr=(n,e)=>{let t=n.getAttribute("id");return t||(n.setAttribute("id",e),e)},Bo=n=>n.replace(/[\\"']/g,"\\$&"),Nt=(n,e)=>{e&&n.append(e)},ve=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)};var nt=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if($o(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},$o=n=>typeof n=="string"&&n.indexOf("<")>-1,Ga=n=>n.replace(/['"\\]/g,"\\$&"),fr=(n,e)=>{var t=document.createEvent("HTMLEvents");t.initEvent(e,!0,!1),n.dispatchEvent(t)},ui=(n,e)=>{Object.assign(n.style,e)},Pe=(n,...e)=>{var t=Ka(e);n=Xa(n),n.map(i=>{t.map(r=>{i.classList.add(r)})})},gt=(n,...e)=>{var t=Ka(e);n=Xa(n),n.map(i=>{t.map(r=>{i.classList.remove(r)})})},Ka=n=>{var e=[];return ve(n,t=>{typeof t=="string"&&(t=t.trim().split(/[\t\n\f\r\s]/)),Array.isArray(t)&&(e=e.concat(t))}),e.filter(Boolean)},Xa=n=>(Array.isArray(n)||(n=[n]),n),hr=(n,e,t)=>{if(!(t&&!t.contains(n)))for(;n&&n.matches;){if(n.matches(e))return n;n=n.parentNode}},Vo=(n,e=0)=>e>0?n[n.length-1]:n[0],Qa=n=>Object.keys(n).length===0,zo=(n,e)=>{if(!n)return-1;e=e||n.nodeName;for(var t=0;n=n.previousElementSibling;)n.matches(e)&&t++;return t},le=(n,e)=>{ve(e,(t,i)=>{t==null?n.removeAttribute(i):n.setAttribute(i,""+t)})},di=(n,e)=>{n.parentNode&&n.parentNode.replaceChild(e,n)};var Ja=(n,e)=>{if(e===null)return;if(typeof e=="string"){if(!e.length)return;e=new RegExp(e,"i")}let t=o=>{var s=o.data.match(e);if(s&&o.data.length>0){var a=document.createElement("span");a.className="highlight";var l=o.splitText(s.index);l.splitText(s[0].length);var c=l.cloneNode(!0);return a.appendChild(c),di(l,a),1}return 0},i=o=>{o.nodeType===1&&o.childNodes&&!/(script|style)/i.test(o.tagName)&&(o.className!=="highlight"||o.tagName!=="SPAN")&&Array.from(o.childNodes).forEach(s=>{r(s)})},r=o=>o.nodeType===3?t(o):(i(o),0);r(n)},Za=n=>{var e=n.querySelectorAll("span.highlight");Array.prototype.forEach.call(e,function(t){var i=t.parentNode;i.replaceChild(t.firstChild,t),i.normalize()})};var dm=typeof navigator=="undefined"?!1:/Mac/.test(navigator.userAgent),fi=dm?"metaKey":"ctrlKey";var jo={options:[],optgroups:[],plugins:[],delimiter:",",splitOn:null,persist:!0,diacritics:!0,create:null,createOnBlur:!1,createFilter:null,highlight:!0,openOnFocus:!0,shouldOpen:null,maxOptions:50,maxItems:null,hideSelected:null,duplicates:!1,addPrecedence:!1,selectOnTab:!1,preload:null,allowEmptyOption:!1,refreshThrottle:300,loadThrottle:300,loadingClass:"loading",dataAttr:null,optgroupField:"optgroup",valueField:"value",labelField:"text",disabledField:"disabled",optgroupLabelField:"label",optgroupValueField:"value",lockOptgroupOrder:!1,sortField:"$order",searchField:["text"],searchConjunction:"and",mode:null,wrapperClass:"ts-wrapper",controlClass:"ts-control",dropdownClass:"ts-dropdown",dropdownContentClass:"ts-dropdown-content",itemClass:"item",optionClass:"option",dropdownParent:null,controlInput:'',copyClassesToDropdown:!1,placeholder:null,hidePlaceholder:null,shouldLoad:function(n){return n.length>0},render:{}};function pr(n,e){var t=Object.assign({},jo,e),i=t.dataAttr,r=t.labelField,o=t.valueField,s=t.disabledField,a=t.optgroupField,l=t.optgroupLabelField,c=t.optgroupValueField,u=n.tagName.toLowerCase(),d=n.getAttribute("placeholder")||n.getAttribute("data-placeholder");if(!d&&!t.allowEmptyOption){let v=n.querySelector('option[value=""]');v&&(d=v.textContent)}var p={placeholder:d,options:[],optgroups:[],items:[],maxItems:null},y=()=>{var v,w=p.options,T={},_=1;let S=0;var A=L=>{var H=Object.assign({},L.dataset),N=i&&H[i];return typeof N=="string"&&N.length&&(H=Object.assign(H,JSON.parse(N))),H},K=(L,H)=>{var N=We(L.value);if(N!=null&&!(!N&&!t.allowEmptyOption)){if(T.hasOwnProperty(N)){if(H){var Y=T[N][a];Y?Array.isArray(Y)?Y.push(H):T[N][a]=[Y,H]:T[N][a]=H}}else{var B=A(L);B[r]=B[r]||L.textContent,B[o]=B[o]||N,B[s]=B[s]||L.disabled,B[a]=B[a]||H,B.$option=L,B.$order=B.$order||++S,T[N]=B,w.push(B)}L.selected&&p.items.push(N)}},z=L=>{var H,N;N=A(L),N[l]=N[l]||L.getAttribute("label")||"",N[c]=N[c]||_++,N[s]=N[s]||L.disabled,N.$order=N.$order||++S,p.optgroups.push(N),H=N[c],ve(L.children,Y=>{K(Y,H)})};p.maxItems=n.hasAttribute("multiple")?null:1,ve(n.children,L=>{v=L.tagName.toLowerCase(),v==="optgroup"?z(L):v==="option"&&K(L)})},m=()=>{let v=n.getAttribute(i);if(v)p.options=JSON.parse(v),ve(p.options,T=>{p.items.push(T[o])});else{var w=n.value.trim()||"";if(!t.allowEmptyOption&&!w.length)return;let T=w.split(t.delimiter);ve(T,_=>{let S={};S[r]=_,S[o]=_,p.options.push(S)}),p.items=T}};return u==="select"?y():m(),Object.assign({},jo,p,e)}var nl=0,_e=class extends No(oi){constructor(e,t){super(),this.order=0,this.isOpen=!1,this.isDisabled=!1,this.isReadOnly=!1,this.isInvalid=!1,this.isValid=!0,this.isLocked=!1,this.isFocused=!1,this.isInputHidden=!1,this.isSetup=!1,this.ignoreFocus=!1,this.ignoreHover=!1,this.hasOptions=!1,this.lastValue="",this.caretPos=0,this.loading=0,this.loadedSearches={},this.activeOption=null,this.activeItems=[],this.optgroups={},this.options={},this.userOptions={},this.items=[],this.refreshTimeout=null,nl++;var i,r=nt(e);if(r.tomselect)throw new Error("Tom Select already initialized on this element");r.tomselect=this;var o=window.getComputedStyle&&window.getComputedStyle(r,null);i=o.getPropertyValue("direction");let s=pr(r,t);this.settings=s,this.input=r,this.tabIndex=r.tabIndex||0,this.is_select_tag=r.tagName.toLowerCase()==="select",this.rtl=/rtl/i.test(i),this.inputId=dr(r,"tomselect-"+nl),this.isRequired=r.required,this.sifter=new cr(this.options,{diacritics:s.diacritics}),s.mode=s.mode||(s.maxItems===1?"single":"multi"),typeof s.hideSelected!="boolean"&&(s.hideSelected=s.mode==="multi"),typeof s.hidePlaceholder!="boolean"&&(s.hidePlaceholder=s.mode!=="multi");var a=s.createFilter;typeof a!="function"&&(typeof a=="string"&&(a=new RegExp(a)),a instanceof RegExp?s.createFilter=w=>a.test(w):s.createFilter=w=>this.settings.duplicates||!this.options[w]),this.initializePlugins(s.plugins),this.setupCallbacks(),this.setupTemplates();let l=nt("
"),c=nt("
"),u=this._render("dropdown"),d=nt('
'),p=this.input.getAttribute("class")||"",y=s.mode;var m;if(Pe(l,s.wrapperClass,p,y),Pe(c,s.controlClass),Nt(l,c),Pe(u,s.dropdownClass,y),s.copyClassesToDropdown&&Pe(u,p),Pe(d,s.dropdownContentClass),Nt(u,d),nt(s.dropdownParent||l).appendChild(u),$o(s.controlInput)){m=nt(s.controlInput);var v=["autocorrect","autocapitalize","autocomplete","spellcheck"];ve(v,w=>{r.getAttribute(w)&&le(m,{[w]:r.getAttribute(w)})}),m.tabIndex=-1,c.appendChild(m),this.focus_node=m}else s.controlInput?(m=nt(s.controlInput),this.focus_node=m):(m=nt(""),this.focus_node=c);this.wrapper=l,this.dropdown=u,this.dropdown_content=d,this.control=c,this.control_input=m,this.setup()}setup(){let e=this,t=e.settings,i=e.control_input,r=e.dropdown,o=e.dropdown_content,s=e.wrapper,a=e.control,l=e.input,c=e.focus_node,u={passive:!0},d=e.inputId+"-ts-dropdown";le(o,{id:d}),le(c,{role:"combobox","aria-haspopup":"listbox","aria-expanded":"false","aria-controls":d});let p=dr(c,e.inputId+"-ts-control"),y="label[for='"+Ga(e.inputId)+"']",m=document.querySelector(y),v=e.focus.bind(e);if(m){Se(m,"click",v),le(m,{for:p});let _=dr(m,e.inputId+"-ts-label");le(c,{"aria-labelledby":_}),le(o,{"aria-labelledby":_})}if(s.style.width=l.style.width,e.plugins.names.length){let _="plugin-"+e.plugins.names.join(" plugin-");Pe([s,r],_)}(t.maxItems===null||t.maxItems>1)&&e.is_select_tag&&le(l,{multiple:"multiple"}),t.placeholder&&le(i,{placeholder:t.placeholder}),!t.splitOn&&t.delimiter&&(t.splitOn=new RegExp("\\s*"+Mt(t.delimiter)+"+\\s*")),t.load&&t.loadThrottle&&(t.load=Ua(t.load,t.loadThrottle)),Se(r,"mousemove",()=>{e.ignoreHover=!1}),Se(r,"mouseenter",_=>{var S=hr(_.target,"[data-selectable]",r);S&&e.onOptionHover(_,S)},{capture:!0}),Se(r,"click",_=>{let S=hr(_.target,"[data-selectable]");S&&(e.onOptionSelect(_,S),fe(_,!0))}),Se(a,"click",_=>{var S=hr(_.target,"[data-ts-item]",a);if(S&&e.onItemSelect(_,S)){fe(_,!0);return}i.value==""&&(e.onClick(),fe(_,!0))}),Se(c,"keydown",_=>e.onKeyDown(_)),Se(i,"keypress",_=>e.onKeyPress(_)),Se(i,"input",_=>e.onInput(_)),Se(c,"blur",_=>e.onBlur(_)),Se(c,"focus",_=>e.onFocus(_)),Se(i,"paste",_=>e.onPaste(_));let w=_=>{let S=_.composedPath()[0];if(!s.contains(S)&&!r.contains(S)){e.isFocused&&e.blur(),e.inputState();return}S==i&&e.isOpen?_.stopPropagation():fe(_,!0)},T=()=>{e.isOpen&&e.positionDropdown()};Se(document,"mousedown",w),Se(window,"scroll",T,u),Se(window,"resize",T,u),this._destroy=()=>{document.removeEventListener("mousedown",w),window.removeEventListener("scroll",T),window.removeEventListener("resize",T),m&&m.removeEventListener("click",v)},this.revertSettings={innerHTML:l.innerHTML,tabIndex:l.tabIndex},l.tabIndex=-1,l.insertAdjacentElement("afterend",e.wrapper),e.sync(!1),t.items=[],delete t.optgroups,delete t.options,Se(l,"invalid",()=>{e.isValid&&(e.isValid=!1,e.isInvalid=!0,e.refreshState())}),e.updateOriginalInput(),e.refreshItems(),e.close(!1),e.inputState(),e.isSetup=!0,l.disabled?e.disable():l.readOnly?e.setReadOnly(!0):e.enable(),e.on("change",this.onChange),Pe(l,"tomselected","ts-hidden-accessible"),e.trigger("initialize"),t.preload===!0&&e.preload()}setupOptions(e=[],t=[]){this.addOptions(e),ve(t,i=>{this.registerOptionGroup(i)})}setupTemplates(){var e=this,t=e.settings.labelField,i=e.settings.optgroupLabelField,r={optgroup:o=>{let s=document.createElement("div");return s.className="optgroup",s.appendChild(o.options),s},optgroup_header:(o,s)=>'
'+s(o[i])+"
",option:(o,s)=>"
"+s(o[t])+"
",item:(o,s)=>"
"+s(o[t])+"
",option_create:(o,s)=>'
Add '+s(o.input)+"
",no_results:()=>'
No results found
',loading:()=>'
',not_loading:()=>{},dropdown:()=>"
"};e.settings.render=Object.assign({},r,e.settings.render)}setupCallbacks(){var e,t,i={initialize:"onInitialize",change:"onChange",item_add:"onItemAdd",item_remove:"onItemRemove",item_select:"onItemSelect",clear:"onClear",option_add:"onOptionAdd",option_remove:"onOptionRemove",option_clear:"onOptionClear",optgroup_add:"onOptionGroupAdd",optgroup_remove:"onOptionGroupRemove",optgroup_clear:"onOptionGroupClear",dropdown_open:"onDropdownOpen",dropdown_close:"onDropdownClose",type:"onType",load:"onLoad",focus:"onFocus",blur:"onBlur"};for(e in i)t=this.settings[i[e]],t&&this.on(e,t)}sync(e=!0){let t=this,i=e?pr(t.input,{delimiter:t.settings.delimiter}):t.settings;t.setupOptions(i.options,i.optgroups),t.setValue(i.items||[],!0),t.lastQuery=null}onClick(){var e=this;if(e.activeItems.length>0){e.clearActiveItems(),e.focus();return}e.isFocused&&e.isOpen?e.blur():e.focus()}onMouseDown(){}onChange(){fr(this.input,"input"),fr(this.input,"change")}onPaste(e){var t=this;if(t.isInputHidden||t.isLocked){fe(e);return}t.settings.splitOn&&setTimeout(()=>{var i=t.inputValue();if(i.match(t.settings.splitOn)){var r=i.trim().split(t.settings.splitOn);ve(r,o=>{We(o)&&(this.options[o]?t.addItem(o):t.createItem(o))})}},0)}onKeyPress(e){var t=this;if(t.isLocked){fe(e);return}var i=String.fromCharCode(e.keyCode||e.which);if(t.settings.create&&t.settings.mode==="multi"&&i===t.settings.delimiter){t.createItem(),fe(e);return}}onKeyDown(e){var t=this;if(t.ignoreHover=!0,t.isLocked){e.keyCode!==9&&fe(e);return}switch(e.keyCode){case 65:if(kt(fi,e)&&t.control_input.value==""){fe(e),t.selectAll();return}break;case 27:t.isOpen&&(fe(e,!0),t.close()),t.clearActiveItems();return;case 40:if(!t.isOpen&&t.hasOptions)t.open();else if(t.activeOption){let i=t.getAdjacent(t.activeOption,1);i&&t.setActiveOption(i)}fe(e);return;case 38:if(t.activeOption){let i=t.getAdjacent(t.activeOption,-1);i&&t.setActiveOption(i)}fe(e);return;case 13:t.canSelect(t.activeOption)?(t.onOptionSelect(e,t.activeOption),fe(e)):t.settings.create&&t.createItem()?fe(e):document.activeElement==t.control_input&&t.isOpen&&fe(e);return;case 37:t.advanceSelection(-1,e);return;case 39:t.advanceSelection(1,e);return;case 9:t.settings.selectOnTab&&(t.canSelect(t.activeOption)&&(t.onOptionSelect(e,t.activeOption),fe(e)),t.settings.create&&t.createItem()&&fe(e));return;case 8:case 46:t.deleteSelection(e);return}t.isInputHidden&&!kt(fi,e)&&fe(e)}onInput(e){if(this.isLocked)return;let t=this.inputValue();if(this.lastValue!==t){if(this.lastValue=t,t==""){this._onInput();return}this.refreshTimeout&&window.clearTimeout(this.refreshTimeout),this.refreshTimeout=qa(()=>{this.refreshTimeout=null,this._onInput()},this.settings.refreshThrottle)}}_onInput(){let e=this.lastValue;this.settings.shouldLoad.call(this,e)&&this.load(e),this.refreshOptions(),this.trigger("type",e)}onOptionHover(e,t){this.ignoreHover||this.setActiveOption(t,!1)}onFocus(e){var t=this,i=t.isFocused;if(t.isDisabled||t.isReadOnly){t.blur(),fe(e);return}t.ignoreFocus||(t.isFocused=!0,t.settings.preload==="focus"&&t.preload(),i||t.trigger("focus"),t.activeItems.length||(t.inputState(),t.refreshOptions(!!t.settings.openOnFocus)),t.refreshState())}onBlur(e){if(document.hasFocus()!==!1){var t=this;if(t.isFocused){t.isFocused=!1,t.ignoreFocus=!1;var i=()=>{t.close(),t.setActiveItem(),t.setCaret(t.items.length),t.trigger("blur")};t.settings.create&&t.settings.createOnBlur?t.createItem(null,i):i()}}}onOptionSelect(e,t){var i,r=this;t.parentElement&&t.parentElement.matches("[data-disabled]")||(t.classList.contains("create")?r.createItem(null,()=>{r.settings.closeAfterSelect&&r.close()}):(i=t.dataset.value,typeof i!="undefined"&&(r.lastQuery=null,r.addItem(i),r.settings.closeAfterSelect&&r.close(),!r.settings.hideSelected&&e.type&&/click/.test(e.type)&&r.setActiveOption(t))))}canSelect(e){return!!(this.isOpen&&e&&this.dropdown_content.contains(e))}onItemSelect(e,t){var i=this;return!i.isLocked&&i.settings.mode==="multi"?(fe(e),i.setActiveItem(t,e),!0):!1}canLoad(e){return!(!this.settings.load||this.loadedSearches.hasOwnProperty(e))}load(e){let t=this;if(!t.canLoad(e))return;Pe(t.wrapper,t.settings.loadingClass),t.loading++;let i=t.loadCallback.bind(t);t.settings.load.call(t,e,i)}loadCallback(e,t){let i=this;i.loading=Math.max(i.loading-1,0),i.lastQuery=null,i.clearActiveOption(),i.setupOptions(e,t),i.refreshOptions(i.isFocused&&!i.isInputHidden),i.loading||gt(i.wrapper,i.settings.loadingClass),i.trigger("load",e,t)}preload(){var e=this.wrapper.classList;e.contains("preloaded")||(e.add("preloaded"),this.load(""))}setTextboxValue(e=""){var t=this.control_input,i=t.value!==e;i&&(t.value=e,fr(t,"update"),this.lastValue=e)}getValue(){return this.is_select_tag&&this.input.hasAttribute("multiple")?this.items:this.items.join(this.settings.delimiter)}setValue(e,t){var i=t?[]:["change"];Fo(this,i,()=>{this.clear(t),this.addItems(e,t)})}setMaxItems(e){e===0&&(e=null),this.settings.maxItems=e,this.refreshState()}setActiveItem(e,t){var i=this,r,o,s,a,l,c;if(i.settings.mode!=="single"){if(!e){i.clearActiveItems(),i.isFocused&&i.inputState();return}if(r=t&&t.type.toLowerCase(),r==="click"&&kt("shiftKey",t)&&i.activeItems.length){for(c=i.getLastActive(),s=Array.prototype.indexOf.call(i.control.children,c),a=Array.prototype.indexOf.call(i.control.children,e),s>a&&(l=s,s=a,a=l),o=s;o<=a;o++)e=i.control.children[o],i.activeItems.indexOf(e)===-1&&i.setActiveItemClass(e);fe(t)}else r==="click"&&kt(fi,t)||r==="keydown"&&kt("shiftKey",t)?e.classList.contains("active")?i.removeActiveItem(e):i.setActiveItemClass(e):(i.clearActiveItems(),i.setActiveItemClass(e));i.inputState(),i.isFocused||i.focus()}}setActiveItemClass(e){let t=this,i=t.control.querySelector(".last-active");i&>(i,"last-active"),Pe(e,"active last-active"),t.trigger("item_select",e),t.activeItems.indexOf(e)==-1&&t.activeItems.push(e)}removeActiveItem(e){var t=this.activeItems.indexOf(e);this.activeItems.splice(t,1),gt(e,"active")}clearActiveItems(){gt(this.activeItems,"active"),this.activeItems=[]}setActiveOption(e,t=!0){e!==this.activeOption&&(this.clearActiveOption(),e&&(this.activeOption=e,le(this.focus_node,{"aria-activedescendant":e.getAttribute("id")}),le(e,{"aria-selected":"true"}),Pe(e,"active"),t&&this.scrollToOption(e)))}scrollToOption(e,t){if(!e)return;let i=this.dropdown_content,r=i.clientHeight,o=i.scrollTop||0,s=e.offsetHeight,a=e.getBoundingClientRect().top-i.getBoundingClientRect().top+o;a+s>r+o?this.scroll(a-r+s,t):a{e.setActiveItemClass(i)}))}inputState(){var e=this;e.control.contains(e.control_input)&&(le(e.control_input,{placeholder:e.settings.placeholder}),e.activeItems.length>0||!e.isFocused&&e.settings.hidePlaceholder&&e.items.length>0?(e.setTextboxValue(),e.isInputHidden=!0):(e.settings.hidePlaceholder&&e.items.length>0&&le(e.control_input,{placeholder:""}),e.isInputHidden=!1),e.wrapper.classList.toggle("input-hidden",e.isInputHidden))}inputValue(){return this.control_input.value.trim()}focus(){var e=this;e.isDisabled||e.isReadOnly||(e.ignoreFocus=!0,e.control_input.offsetWidth?e.control_input.focus():e.focus_node.focus(),setTimeout(()=>{e.ignoreFocus=!1,e.onFocus()},0))}blur(){this.focus_node.blur(),this.onBlur()}getScoreFunction(e){return this.sifter.getScoreFunction(e,this.getSearchOptions())}getSearchOptions(){var e=this.settings,t=e.sortField;return typeof e.sortField=="string"&&(t=[{field:e.sortField}]),{fields:e.searchField,conjunction:e.searchConjunction,sort:t,nesting:e.nesting}}search(e){var t,i,r=this,o=this.getSearchOptions();if(r.settings.score&&(i=r.settings.score.call(r,e),typeof i!="function"))throw new Error('Tom Select "score" setting must be a function that returns a function');return e!==r.lastQuery?(r.lastQuery=e,t=r.sifter.search(e,Object.assign(o,{score:i})),r.currentResults=t):t=Object.assign({},r.currentResults),r.settings.hideSelected&&(t.items=t.items.filter(s=>{let a=We(s.id);return!(a&&r.items.indexOf(a)!==-1)})),t}refreshOptions(e=!0){var t,i,r,o,s,a,l,c,u,d;let p={},y=[];var m=this,v=m.inputValue();let w=v===m.lastQuery||v==""&&m.lastQuery==null;var T=m.search(v),_=null,S=m.settings.shouldOpen||!1,A=m.dropdown_content;w&&(_=m.activeOption,_&&(u=_.closest("[data-group]"))),o=T.items.length,typeof m.settings.maxOptions=="number"&&(o=Math.min(o,m.settings.maxOptions)),o>0&&(S=!0);let K=(L,H)=>{let N=p[L];if(N!==void 0){let B=y[N];if(B!==void 0)return[N,B.fragment]}let Y=document.createDocumentFragment();return N=y.length,y.push({fragment:Y,order:H,optgroup:L}),[N,Y]};for(t=0;t0&&(B=B.cloneNode(!0),le(B,{id:N.$id+"-clone-"+i,"aria-selected":null}),B.classList.add("ts-cloned"),gt(B,"active"),m.activeOption&&m.activeOption.dataset.value==H&&u&&u.dataset.group===s.toString()&&(_=B)),Ce.appendChild(B),s!=""&&(p[s]=Te)}}m.settings.lockOptgroupOrder&&y.sort((L,H)=>L.order-H.order),l=document.createDocumentFragment(),ve(y,L=>{let H=L.fragment,N=L.optgroup;if(!H||!H.children.length)return;let Y=m.optgroups[N];if(Y!==void 0){let B=document.createDocumentFragment(),ie=m.render("optgroup_header",Y);Nt(B,ie),Nt(B,H);let J=m.render("optgroup",{group:Y,options:B});Nt(l,J)}else Nt(l,H)}),A.innerHTML="",Nt(A,l),m.settings.highlight&&(Za(A),T.query.length&&T.tokens.length&&ve(T.tokens,L=>{Ja(A,L.regex)}));var z=L=>{let H=m.render(L,{input:v});return H&&(S=!0,A.insertBefore(H,A.firstChild)),H};if(m.loading?z("loading"):m.settings.shouldLoad.call(m,v)?T.items.length===0&&z("no_results"):z("not_loading"),c=m.canCreate(v),c&&(d=z("option_create")),m.hasOptions=T.items.length>0||c,S){if(T.items.length>0){if(!_&&m.settings.mode==="single"&&m.items[0]!=null&&(_=m.getOption(m.items[0])),!A.contains(_)){let L=0;d&&!m.settings.addPrecedence&&(L=1),_=m.selectable()[L]}}else d&&(_=d);e&&!m.isOpen&&(m.open(),m.scrollToOption(_,"auto")),m.setActiveOption(_)}else m.clearActiveOption(),e&&m.isOpen&&m.close(!1)}selectable(){return this.dropdown_content.querySelectorAll("[data-selectable]")}addOption(e,t=!1){let i=this;if(Array.isArray(e))return i.addOptions(e,t),!1;let r=We(e[i.settings.valueField]);return r===null||i.options.hasOwnProperty(r)?!1:(e.$order=e.$order||++i.order,e.$id=i.inputId+"-opt-"+e.$order,i.options[r]=e,i.lastQuery=null,t&&(i.userOptions[r]=t,i.trigger("option_add",r,e)),r)}addOptions(e,t=!1){ve(e,i=>{this.addOption(i,t)})}registerOption(e){return this.addOption(e)}registerOptionGroup(e){var t=We(e[this.settings.optgroupValueField]);return t===null?!1:(e.$order=e.$order||++this.order,this.optgroups[t]=e,t)}addOptionGroup(e,t){var i;t[this.settings.optgroupValueField]=e,(i=this.registerOptionGroup(t))&&this.trigger("optgroup_add",i,t)}removeOptionGroup(e){this.optgroups.hasOwnProperty(e)&&(delete this.optgroups[e],this.clearCache(),this.trigger("optgroup_remove",e))}clearOptionGroups(){this.optgroups={},this.clearCache(),this.trigger("optgroup_clear")}updateOption(e,t){let i=this;var r,o;let s=We(e),a=We(t[i.settings.valueField]);if(s===null)return;let l=i.options[s];if(l==null)return;if(typeof a!="string")throw new Error("Value must be set in option data");let c=i.getOption(s),u=i.getItem(s);if(t.$order=t.$order||l.$order,delete i.options[s],i.uncacheValue(a),i.options[a]=t,c){if(i.dropdown_content.contains(c)){let d=i._render("option",t);di(c,d),i.activeOption===c&&i.setActiveOption(d)}c.remove()}u&&(o=i.items.indexOf(s),o!==-1&&i.items.splice(o,1,a),r=i._render("item",t),u.classList.contains("active")&&Pe(r,"active"),di(u,r)),i.lastQuery=null}removeOption(e,t){let i=this;e=ci(e),i.uncacheValue(e),delete i.userOptions[e],delete i.options[e],i.lastQuery=null,i.trigger("option_remove",e),i.removeItem(e,t)}clearOptions(e){let t=(e||this.clearFilter).bind(this);this.loadedSearches={},this.userOptions={},this.clearCache();let i={};ve(this.options,(r,o)=>{t(r,o)&&(i[o]=r)}),this.options=this.sifter.items=i,this.lastQuery=null,this.trigger("option_clear")}clearFilter(e,t){return this.items.indexOf(t)>=0}getOption(e,t=!1){let i=We(e);if(i===null)return null;let r=this.options[i];if(r!=null){if(r.$div)return r.$div;if(t)return this._render("option",r)}return null}getAdjacent(e,t,i="option"){var r=this,o;if(!e)return null;i=="item"?o=r.controlChildren():o=r.dropdown_content.querySelectorAll("[data-selectable]");for(let s=0;s0?o[s+1]:o[s-1];return null}getItem(e){if(typeof e=="object")return e;var t=We(e);return t!==null?this.control.querySelector(`[data-value="${Bo(t)}"]`):null}addItems(e,t){var i=this,r=Array.isArray(e)?e:[e];r=r.filter(s=>i.items.indexOf(s)===-1);let o=r[r.length-1];r.forEach(s=>{i.isPending=s!==o,i.addItem(s,t)})}addItem(e,t){var i=t?[]:["change","dropdown_close"];Fo(this,i,()=>{var r,o;let s=this,a=s.settings.mode,l=We(e);if(!(l&&s.items.indexOf(l)!==-1&&(a==="single"&&s.close(),a==="single"||!s.settings.duplicates))&&!(l===null||!s.options.hasOwnProperty(l))&&(a==="single"&&s.clear(t),!(a==="multi"&&s.isFull()))){if(r=s._render("item",s.options[l]),s.control.contains(r)&&(r=r.cloneNode(!0)),o=s.isFull(),s.items.splice(s.caretPos,0,l),s.insertAtCaret(r),s.isSetup){if(!s.isPending&&s.settings.hideSelected){let c=s.getOption(l),u=s.getAdjacent(c,1);u&&s.setActiveOption(u)}!s.isPending&&!s.settings.closeAfterSelect&&s.refreshOptions(s.isFocused&&a!=="single"),s.settings.closeAfterSelect!=!1&&s.isFull()?s.close():s.isPending||s.positionDropdown(),s.trigger("item_add",l,r),s.isPending||s.updateOriginalInput({silent:t})}(!s.isPending||!o&&s.isFull())&&(s.inputState(),s.refreshState())}})}removeItem(e=null,t){let i=this;if(e=i.getItem(e),!e)return;var r,o;let s=e.dataset.value;r=zo(e),e.remove(),e.classList.contains("active")&&(o=i.activeItems.indexOf(e),i.activeItems.splice(o,1),gt(e,"active")),i.items.splice(r,1),i.lastQuery=null,!i.settings.persist&&i.userOptions.hasOwnProperty(s)&&i.removeOption(s,t),r{}){arguments.length===3&&(t=arguments[2]),typeof t!="function"&&(t=()=>{});var i=this,r=i.caretPos,o;if(e=e||i.inputValue(),!i.canCreate(e))return t(),!1;i.lock();var s=!1,a=l=>{if(i.unlock(),!l||typeof l!="object")return t();var c=We(l[i.settings.valueField]);if(typeof c!="string")return t();i.setTextboxValue(),i.addOption(l,!0),i.setCaret(r),i.addItem(c),t(l),s=!0};return typeof i.settings.create=="function"?o=i.settings.create.call(this,e,a):o={[i.settings.labelField]:e,[i.settings.valueField]:e},s||a(o),!0}refreshItems(){var e=this;e.lastQuery=null,e.isSetup&&e.addItems(e.items),e.updateOriginalInput(),e.refreshState()}refreshState(){let e=this;e.refreshValidityState();let t=e.isFull(),i=e.isLocked;e.wrapper.classList.toggle("rtl",e.rtl);let r=e.wrapper.classList;r.toggle("focus",e.isFocused),r.toggle("disabled",e.isDisabled),r.toggle("readonly",e.isReadOnly),r.toggle("required",e.isRequired),r.toggle("invalid",!e.isValid),r.toggle("locked",i),r.toggle("full",t),r.toggle("input-active",e.isFocused&&!e.isInputHidden),r.toggle("dropdown-active",e.isOpen),r.toggle("has-options",Qa(e.options)),r.toggle("has-items",e.items.length>0)}refreshValidityState(){var e=this;e.input.validity&&(e.isValid=e.input.validity.valid,e.isInvalid=!e.isValid)}isFull(){return this.settings.maxItems!==null&&this.items.length>=this.settings.maxItems}updateOriginalInput(e={}){let t=this;var i,r;let o=t.input.querySelector('option[value=""]');if(t.is_select_tag){let l=function(c,u,d){return c||(c=nt('")),c!=o&&t.input.append(c),s.push(c),(c!=o||a>0)&&(c.selected=!0),c},s=[],a=t.input.querySelectorAll("option:checked").length;t.input.querySelectorAll("option:checked").forEach(c=>{c.selected=!1}),t.items.length==0&&t.settings.mode=="single"?l(o,"",""):t.items.forEach(c=>{if(i=t.options[c],r=i[t.settings.labelField]||"",s.includes(i.$option)){let u=t.input.querySelector(`option[value="${Bo(c)}"]:not(:checked)`);l(u,c,r)}else i.$option=l(i.$option,c,r)})}else t.input.value=t.getValue();t.isSetup&&(e.silent||t.trigger("change",t.getValue()))}open(){var e=this;e.isLocked||e.isOpen||e.settings.mode==="multi"&&e.isFull()||(e.isOpen=!0,le(e.focus_node,{"aria-expanded":"true"}),e.refreshState(),ui(e.dropdown,{visibility:"hidden",display:"block"}),e.positionDropdown(),ui(e.dropdown,{visibility:"visible",display:"block"}),e.focus(),e.trigger("dropdown_open",e.dropdown))}close(e=!0){var t=this,i=t.isOpen;e&&(t.setTextboxValue(),t.settings.mode==="single"&&t.items.length&&t.inputState()),t.isOpen=!1,le(t.focus_node,{"aria-expanded":"false"}),ui(t.dropdown,{display:"none"}),t.settings.hideSelected&&t.clearActiveOption(),t.refreshState(),i&&t.trigger("dropdown_close",t.dropdown)}positionDropdown(){if(this.settings.dropdownParent==="body"){var e=this.control,t=e.getBoundingClientRect(),i=e.offsetHeight+t.top+window.scrollY,r=t.left+window.scrollX;ui(this.dropdown,{width:t.width+"px",top:i+"px",left:r+"px"})}}clear(e){var t=this;if(t.items.length){var i=t.controlChildren();ve(i,r=>{t.removeItem(r,!0)}),t.inputState(),e||t.updateOriginalInput(),t.trigger("clear")}}insertAtCaret(e){let t=this,i=t.caretPos,r=t.control;r.insertBefore(e,r.children[i]||null),t.setCaret(i+1)}deleteSelection(e){var t,i,r,o,s=this;t=e&&e.keyCode===8?-1:1,i=Ya(s.control_input);let a=[];if(s.activeItems.length)o=Vo(s.activeItems,t),r=zo(o),t>0&&r++,ve(s.activeItems,l=>a.push(l));else if((s.isFocused||s.settings.mode==="single")&&s.items.length){let l=s.controlChildren(),c;t<0&&i.start===0&&i.length===0?c=l[s.caretPos-1]:t>0&&i.start===s.inputValue().length&&(c=l[s.caretPos]),c!==void 0&&a.push(c)}if(!s.shouldDelete(a,e))return!1;for(fe(e,!0),typeof r!="undefined"&&s.setCaret(r);a.length;)s.removeItem(a.pop());return s.inputState(),s.positionDropdown(),s.refreshOptions(!1),!0}shouldDelete(e,t){let i=e.map(r=>r.dataset.value);return!(!i.length||typeof this.settings.onDelete=="function"&&this.settings.onDelete(i,t)===!1)}advanceSelection(e,t){var i,r,o=this;o.rtl&&(e*=-1),!o.inputValue().length&&(kt(fi,t)||kt("shiftKey",t)?(i=o.getLastActive(e),i?i.classList.contains("active")?r=o.getAdjacent(i,e,"item"):r=i:e>0?r=o.control_input.nextElementSibling:r=o.control_input.previousElementSibling,r&&(r.classList.contains("active")&&o.removeActiveItem(i),o.setActiveItemClass(r))):o.moveCaret(e))}moveCaret(e){}getLastActive(e){let t=this.control.querySelector(".last-active");if(t)return t;var i=this.control.querySelectorAll(".active");if(i)return Vo(i,e)}setCaret(e){this.caretPos=this.items.length}controlChildren(){return Array.from(this.control.querySelectorAll("[data-ts-item]"))}lock(){this.setLocked(!0)}unlock(){this.setLocked(!1)}setLocked(e=this.isReadOnly||this.isDisabled){this.isLocked=e,this.refreshState()}disable(){this.setDisabled(!0),this.close()}enable(){this.setDisabled(!1)}setDisabled(e){this.focus_node.tabIndex=e?-1:this.tabIndex,this.isDisabled=e,this.input.disabled=e,this.control_input.disabled=e,this.setLocked()}setReadOnly(e){this.isReadOnly=e,this.input.readOnly=e,this.control_input.readOnly=e,this.setLocked()}destroy(){var e=this,t=e.revertSettings;e.trigger("destroy"),e.off(),e.wrapper.remove(),e.dropdown.remove(),e.input.innerHTML=t.innerHTML,e.input.tabIndex=t.tabIndex,gt(e.input,"tomselected","ts-hidden-accessible"),e._destroy(),delete e.input.tomselect}render(e,t){var i,r;let o=this;if(typeof this.settings.render[e]!="function"||(r=o.settings.render[e].call(this,t,ur),!r))return null;if(r=nt(r),e==="option"||e==="option_create"?t[o.settings.disabledField]?le(r,{"aria-disabled":"true"}):le(r,{"data-selectable":""}):e==="optgroup"&&(i=t.group[o.settings.optgroupValueField],le(r,{"data-group":i}),t.group[o.settings.disabledField]&&le(r,{"data-disabled":""})),e==="option"||e==="item"){let s=ci(t[o.settings.valueField]);le(r,{"data-value":s}),e==="item"?(Pe(r,o.settings.itemClass),le(r,{"data-ts-item":""})):(Pe(r,o.settings.optionClass),le(r,{role:"option",id:t.$id}),t.$div=r,o.options[s]=t)}return r}_render(e,t){let i=this.render(e,t);if(i==null)throw"HTMLElement expected";return i}clearCache(){ve(this.options,e=>{e.$div&&(e.$div.remove(),delete e.$div)})}uncacheValue(e){let t=this.getOption(e);t&&t.remove()}canCreate(e){return this.settings.create&&e.length>0&&this.settings.createFilter.call(this,e)}hook(e,t,i){var r=this,o=r[t];r[t]=function(){var s,a;return e==="after"&&(s=o.apply(r,arguments)),a=i.apply(r,arguments),e==="instead"?a:(e==="before"&&(s=o.apply(r,arguments)),s)}}};var _m=(n,e,t,i)=>{n.addEventListener(e,t,i)};function il(){_m(this.input,"change",()=>{this.sync()})}var wm=n=>typeof n=="undefined"||n===null?null:xm(n),xm=n=>typeof n=="boolean"?n?"1":"0":n+"",rl=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},Tm=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(Cm(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},Cm=n=>typeof n=="string"&&n.indexOf("<")>-1;function ol(n){var e=this,t=e.onOptionSelect;e.settings.hideSelected=!1;let i=Object.assign({className:"tomselect-checkbox",checkedClassNames:void 0,uncheckedClassNames:void 0},n);var r=function(a,l){l?(a.checked=!0,i.uncheckedClassNames&&a.classList.remove(...i.uncheckedClassNames),i.checkedClassNames&&a.classList.add(...i.checkedClassNames)):(a.checked=!1,i.checkedClassNames&&a.classList.remove(...i.checkedClassNames),i.uncheckedClassNames&&a.classList.add(...i.uncheckedClassNames))},o=function(a){setTimeout(()=>{var l=a.querySelector("input."+i.className);l instanceof HTMLInputElement&&r(l,a.classList.contains("selected"))},1)};e.hook("after","setupTemplates",()=>{var s=e.settings.render.option;e.settings.render.option=(a,l)=>{var c=Tm(s.call(e,a,l)),u=document.createElement("input");i.className&&u.classList.add(i.className),u.addEventListener("click",function(p){rl(p)}),u.type="checkbox";let d=wm(a[e.settings.valueField]);return r(u,!!(d&&e.items.indexOf(d)>-1)),c.prepend(u),c}}),e.on("item_remove",s=>{var a=e.getOption(s);a&&(a.classList.remove("selected"),o(a))}),e.on("item_add",s=>{var a=e.getOption(s);a&&o(a)}),e.hook("instead","onOptionSelect",(s,a)=>{if(a.classList.contains("selected")){a.classList.remove("selected"),e.removeItem(a.dataset.value),e.refreshOptions(),rl(s,!0);return}t.call(e,s,a),o(a)})}var Am=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(Sm(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},Sm=n=>typeof n=="string"&&n.indexOf("<")>-1;function sl(n){let e=this,t=Object.assign({className:"clear-button",title:"Clear All",html:i=>`
`},n);e.on("initialize",()=>{var i=Am(t.html(t));i.addEventListener("click",r=>{e.isLocked||(e.clear(),e.settings.mode==="single"&&e.settings.allowEmptyOption&&e.addItem(""),r.preventDefault(),r.stopPropagation())}),e.control.appendChild(i)})}var Dm=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},Dn=(n,e,t,i)=>{n.addEventListener(e,t,i)},Om=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)},Lm=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(Mm(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},Mm=n=>typeof n=="string"&&n.indexOf("<")>-1,km=(n,e)=>{Om(e,(t,i)=>{t==null?n.removeAttribute(i):n.setAttribute(i,""+t)})},Nm=(n,e)=>{var t;(t=n.parentNode)==null||t.insertBefore(e,n.nextSibling)},Im=(n,e)=>{var t;(t=n.parentNode)==null||t.insertBefore(e,n)},Rm=(n,e)=>{do{var t;if(e=(t=e)==null?void 0:t.previousElementSibling,n==e)return!0}while(e&&e.previousElementSibling);return!1};function al(){var n=this;if(n.settings.mode!=="multi")return;var e=n.lock,t=n.unlock;let i=!0,r;n.hook("after","setupTemplates",()=>{var o=n.settings.render.item;n.settings.render.item=(s,a)=>{let l=Lm(o.call(n,s,a));km(l,{draggable:"true"});let c=v=>{i||Dm(v),v.stopPropagation()},u=v=>{r=l,setTimeout(()=>{l.classList.add("ts-dragging")},0)},d=v=>{v.preventDefault(),l.classList.add("ts-drag-over"),y(l,r)},p=()=>{l.classList.remove("ts-drag-over")},y=(v,w)=>{w!==void 0&&(Rm(w,l)?Nm(v,w):Im(v,w))},m=()=>{var v;document.querySelectorAll(".ts-drag-over").forEach(T=>T.classList.remove("ts-drag-over")),(v=r)==null||v.classList.remove("ts-dragging"),r=void 0;var w=[];n.control.querySelectorAll("[data-value]").forEach(T=>{if(T.dataset.value){let _=T.dataset.value;_&&w.push(_)}}),n.setValue(w)};return Dn(l,"mousedown",c),Dn(l,"dragstart",u),Dn(l,"dragenter",d),Dn(l,"dragover",d),Dn(l,"dragleave",p),Dn(l,"dragend",m),l}}),n.hook("instead","lock",()=>(i=!1,e.call(n))),n.hook("instead","unlock",()=>(i=!0,t.call(n)))}var Hm=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},Pm=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(Fm(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},Fm=n=>typeof n=="string"&&n.indexOf("<")>-1;function ll(n){let e=this,t=Object.assign({title:"Untitled",headerClass:"dropdown-header",titleRowClass:"dropdown-header-title",labelClass:"dropdown-header-label",closeClass:"dropdown-header-close",html:i=>'
'+i.title+'×
'},n);e.on("initialize",()=>{var i=Pm(t.html(t)),r=i.querySelector("."+t.closeClass);r&&r.addEventListener("click",o=>{Hm(o,!0),e.close()}),e.dropdown.insertBefore(i,e.dropdown.firstChild)})}var Bm=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)},$m=(n,...e)=>{var t=Vm(e);n=zm(n),n.map(i=>{t.map(r=>{i.classList.remove(r)})})},Vm=n=>{var e=[];return Bm(n,t=>{typeof t=="string"&&(t=t.trim().split(/[\t\n\f\r\s]/)),Array.isArray(t)&&(e=e.concat(t))}),e.filter(Boolean)},zm=n=>(Array.isArray(n)||(n=[n]),n),jm=(n,e)=>{if(!n)return-1;e=e||n.nodeName;for(var t=0;n=n.previousElementSibling;)n.matches(e)&&t++;return t};function cl(){var n=this;n.hook("instead","setCaret",e=>{n.settings.mode==="single"||!n.control.contains(n.control_input)?e=n.items.length:(e=Math.max(0,Math.min(n.items.length,e)),e!=n.caretPos&&!n.isPending&&n.controlChildren().forEach((t,i)=>{i{if(!n.isFocused)return;let t=n.getLastActive(e);if(t){let i=jm(t);n.setCaret(e>0?i+1:i),n.setActiveItem(),$m(t,"last-active")}else n.setCaret(n.caretPos+e)})}var Wm=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},qm=(n,e,t,i)=>{n.addEventListener(e,t,i)},Um=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)},ul=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(Ym(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},Ym=n=>typeof n=="string"&&n.indexOf("<")>-1,Gm=(n,...e)=>{var t=Km(e);n=Xm(n),n.map(i=>{t.map(r=>{i.classList.add(r)})})},Km=n=>{var e=[];return Um(n,t=>{typeof t=="string"&&(t=t.trim().split(/[\t\n\f\r\s]/)),Array.isArray(t)&&(e=e.concat(t))}),e.filter(Boolean)},Xm=n=>(Array.isArray(n)||(n=[n]),n);function dl(){let n=this;n.settings.shouldOpen=!0,n.hook("before","setup",()=>{n.focus_node=n.control,Gm(n.control_input,"dropdown-input");let e=ul('