diff --git a/netbox/project-static/dist/netbox.css b/netbox/project-static/dist/netbox.css index 0696d2e82..36ed4defc 100644 Binary files a/netbox/project-static/dist/netbox.css and b/netbox/project-static/dist/netbox.css differ diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index 59f703d4b..f4d0311ab 100644 Binary files a/netbox/project-static/dist/netbox.js and b/netbox/project-static/dist/netbox.js differ diff --git a/netbox/project-static/dist/netbox.js.map b/netbox/project-static/dist/netbox.js.map index 00b92809a..c5cd1a402 100644 Binary files a/netbox/project-static/dist/netbox.js.map and b/netbox/project-static/dist/netbox.js.map differ diff --git a/netbox/project-static/src/forms/savedFiltersSelect.ts b/netbox/project-static/src/forms/savedFiltersSelect.ts new file mode 100644 index 000000000..1d06a8d0b --- /dev/null +++ b/netbox/project-static/src/forms/savedFiltersSelect.ts @@ -0,0 +1,30 @@ +import { isTruthy } from '../util'; + +/** + * Handle saved filter change event. + * + * @param event "change" event for the saved filter select + */ +function handleSavedFilterChange(event: Event): void { + const savedFilter = event.currentTarget as HTMLSelectElement; + let baseUrl = savedFilter.baseURI.split('?')[0]; + const preFilter = '?'; + + const selectedOptions = Array.from(savedFilter.options) + .filter(option => option.selected) + .map(option => `filter_id=${option.value}`) + .join('&'); + + baseUrl += `${preFilter}${selectedOptions}`; + document.location.href = baseUrl; +} + +export function initSavedFilterSelect(): void { + const divResults = document.getElementById('results'); + if (isTruthy(divResults)) { + const savedFilterSelect = document.getElementById('id_filter_id'); + if (isTruthy(savedFilterSelect)) { + savedFilterSelect.addEventListener('change', handleSavedFilterChange); + } + } +} diff --git a/netbox/project-static/src/netbox.ts b/netbox/project-static/src/netbox.ts index 59faab222..ce0aad93f 100644 --- a/netbox/project-static/src/netbox.ts +++ b/netbox/project-static/src/netbox.ts @@ -13,6 +13,7 @@ import { initSideNav } from './sidenav'; import { initDashboard } from './dashboard'; import { initRackElevation } from './racks'; import { initHtmx } from './htmx'; +import { initSavedFilterSelect } from './forms/savedFiltersSelect'; function initDocument(): void { for (const init of [ @@ -31,6 +32,7 @@ function initDocument(): void { initDashboard, initRackElevation, initHtmx, + initSavedFilterSelect, ]) { init(); } diff --git a/netbox/project-static/styles/netbox.scss b/netbox/project-static/styles/netbox.scss index b04b85fc9..af2905312 100644 --- a/netbox/project-static/styles/netbox.scss +++ b/netbox/project-static/styles/netbox.scss @@ -7,6 +7,7 @@ // Overrides of external libraries @import 'overrides/bootstrap'; @import 'overrides/tabler'; +@import 'overrides/tomselect'; // Transitional styling to ease migration of templates from NetBox v3.x @import 'transitional/badges'; diff --git a/netbox/project-static/styles/overrides/_tomselect.scss b/netbox/project-static/styles/overrides/_tomselect.scss new file mode 100644 index 000000000..29aa9d361 --- /dev/null +++ b/netbox/project-static/styles/overrides/_tomselect.scss @@ -0,0 +1,8 @@ +.ts-wrapper.multi { + .ts-control { + padding: 7px 7px 3px 7px; + div { + margin: 0 4px 4px 0; + } + } +} diff --git a/netbox/templates/inc/table_controls_htmx.html b/netbox/templates/inc/table_controls_htmx.html index bbbcff59c..ec913d32a 100644 --- a/netbox/templates/inc/table_controls_htmx.html +++ b/netbox/templates/inc/table_controls_htmx.html @@ -1,25 +1,37 @@ {% load helpers %} {% load i18n %} -
+
- + - {% block extra_table_controls %}{% endblock %} + {% block extra_table_controls %}{% endblock %}
+ +
+
+
+ +
+ {{ filter_form.filter_id }} +
+
+
{% if request.user.is_authenticated and table_modal %}
-
{% endif %}
+ diff --git a/netbox/utilities/templatetags/helpers.py b/netbox/utilities/templatetags/helpers.py index b9a3e0005..3595c0666 100644 --- a/netbox/utilities/templatetags/helpers.py +++ b/netbox/utilities/templatetags/helpers.py @@ -281,6 +281,10 @@ def applied_filters(context, model, form, query_params): if filter_name not in querydict: continue + # Skip saved filters, as they're displayed alongside the quick search widget + if filter_name == 'filter_id': + continue + bound_field = form.fields[filter_name].get_bound_field(form, filter_name) querydict.pop(filter_name) display_value = ', '.join([str(v) for v in get_selected_values(form, filter_name)])