From c9d527d84f139428365628f39aa43b98c01aee32 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Wed, 26 Nov 2025 01:26:10 +0100 Subject: [PATCH 1/3] Add option to prefer IPv6 resolving (#6494) This PR adds an option to prefer IPv6 resolving before IPv4. On IPv6 only systems this could be very useful, but will not solve IPv4 only domains of course. For that you need a DNS64 + NAT64 solution Fixes #6301 Signed-off-by: BlackDex --- .env.template | 5 +++++ src/config.rs | 4 ++++ src/http_client.rs | 5 ++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.env.template b/.env.template index 99f693dd..457ca803 100644 --- a/.env.template +++ b/.env.template @@ -472,6 +472,11 @@ ## Setting this to true will enforce the Single Org Policy to be enabled before you can enable the Reset Password policy. # ENFORCE_SINGLE_ORG_WITH_RESET_PW_POLICY=false +## Prefer IPv6 (AAAA) resolving +## This settings configures the DNS resolver to resolve IPv6 first, and if not available try IPv4 +## This could be useful in IPv6 only environments. +# DNS_PREFER_IPV6=false + ##################################### ### SSO settings (OpenID Connect) ### ##################################### diff --git a/src/config.rs b/src/config.rs index 5582e9b0..1b6d3183 100644 --- a/src/config.rs +++ b/src/config.rs @@ -789,6 +789,10 @@ make_config! { /// Bitwarden enforces this by default. In Vaultwarden we encouraged to use multiple organizations because groups were not available. /// Setting this to true will enforce the Single Org Policy to be enabled before you can enable the Reset Password policy. enforce_single_org_with_reset_pw_policy: bool, false, def, false; + + /// Prefer IPv6 (AAAA) resolving |> This settings configures the DNS resolver to resolve IPv6 first, and if not available try IPv4 + /// This could be useful in IPv6 only environments. + dns_prefer_ipv6: bool, true, def, false; }, /// OpenID Connect SSO settings diff --git a/src/http_client.rs b/src/http_client.rs index b48f340c..5462ef8e 100644 --- a/src/http_client.rs +++ b/src/http_client.rs @@ -185,7 +185,10 @@ impl CustomDnsResolver { fn new() -> Arc { match TokioResolver::builder(TokioConnectionProvider::default()) { - Ok(builder) => { + Ok(mut builder) => { + if CONFIG.dns_prefer_ipv6() { + builder.options_mut().ip_strategy = hickory_resolver::config::LookupIpStrategy::Ipv6thenIpv4; + } let resolver = builder.build(); Arc::new(Self::Hickory(Arc::new(resolver))) } From cb2f5741accd5bf510d03233ed8457cef8a6a35c Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Sat, 29 Nov 2025 22:57:57 +0100 Subject: [PATCH 2/3] Some small admin js/css updates (#6501) * Some small admin js/css updates - Updated JS libraries - Fixed some eslint errors - Small update on the theme icon's to be a bit smaller and better sized. Used OXVG via OXVGUI to shrink and optimze them. Probably Fixes #6493 Signed-off-by: BlackDex * Adjust the size of the moon to be more inline with the other icons Signed-off-by: BlackDex --------- Signed-off-by: BlackDex --- src/static/scripts/admin.js | 2 +- src/static/scripts/admin_users.js | 2 +- src/static/scripts/bootstrap.bundle.js | 7 +- src/static/scripts/bootstrap.css | 7 +- src/static/scripts/datatables.css | 19 +- src/static/scripts/datatables.js | 614 ++++++++++++++----------- src/static/templates/admin/base.hbs | 25 +- 7 files changed, 362 insertions(+), 314 deletions(-) diff --git a/src/static/scripts/admin.js b/src/static/scripts/admin.js index f3c41942..3f6bb1df 100644 --- a/src/static/scripts/admin.js +++ b/src/static/scripts/admin.js @@ -1,6 +1,6 @@ "use strict"; /* eslint-env es2017, browser */ -/* exported BASE_URL, _post */ +/* exported BASE_URL, _post _delete */ function getBaseUrl() { // If the base URL is `https://vaultwarden.example.com/base/path/admin/`, diff --git a/src/static/scripts/admin_users.js b/src/static/scripts/admin_users.js index be30e105..99e39aab 100644 --- a/src/static/scripts/admin_users.js +++ b/src/static/scripts/admin_users.js @@ -1,6 +1,6 @@ "use strict"; /* eslint-env es2017, browser, jquery */ -/* global _post:readable, BASE_URL:readable, reload:readable, jdenticon:readable */ +/* global _post:readable, _delete:readable BASE_URL:readable, reload:readable, jdenticon:readable */ function deleteUser(event) { event.preventDefault(); diff --git a/src/static/scripts/bootstrap.bundle.js b/src/static/scripts/bootstrap.bundle.js index 91eea7e7..93cbd3fe 100644 --- a/src/static/scripts/bootstrap.bundle.js +++ b/src/static/scripts/bootstrap.bundle.js @@ -1,5 +1,5 @@ /*! - * Bootstrap v5.3.7 (https://getbootstrap.com/) + * Bootstrap v5.3.8 (https://getbootstrap.com/) * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ @@ -647,7 +647,7 @@ * Constants */ - const VERSION = '5.3.7'; + const VERSION = '5.3.8'; /** * Class definition @@ -3690,9 +3690,6 @@ this._element.setAttribute('aria-expanded', 'false'); Manipulator.removeDataAttribute(this._menu, 'popper'); EventHandler.trigger(this._element, EVENT_HIDDEN$5, relatedTarget); - - // Explicitly return focus to the trigger element - this._element.focus(); } _getConfig(config) { config = super._getConfig(config); diff --git a/src/static/scripts/bootstrap.css b/src/static/scripts/bootstrap.css index e9479ad9..b83f5079 100644 --- a/src/static/scripts/bootstrap.css +++ b/src/static/scripts/bootstrap.css @@ -1,6 +1,6 @@ @charset "UTF-8"; /*! - * Bootstrap v5.3.7 (https://getbootstrap.com/) + * Bootstrap v5.3.8 (https://getbootstrap.com/) * Copyright 2011-2025 The Bootstrap Authors * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ @@ -547,6 +547,10 @@ legend + * { -webkit-appearance: textfield; outline-offset: -2px; } +[type=search]::-webkit-search-cancel-button { + cursor: pointer; + filter: grayscale(1); +} /* rtl:raw: [type="tel"], @@ -6208,6 +6212,7 @@ textarea.form-control-lg { .spinner-grow, .spinner-border { display: inline-block; + flex-shrink: 0; width: var(--bs-spinner-width); height: var(--bs-spinner-height); vertical-align: var(--bs-spinner-vertical-align); diff --git a/src/static/scripts/datatables.css b/src/static/scripts/datatables.css index 4d927abf..af6a9b1e 100644 --- a/src/static/scripts/datatables.css +++ b/src/static/scripts/datatables.css @@ -4,20 +4,21 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#bs5/dt-2.3.2 + * https://datatables.net/download/#bs5/dt-2.3.5 * * Included libraries: - * DataTables 2.3.2 + * DataTables 2.3.5 */ :root { --dt-row-selected: 13, 110, 253; --dt-row-selected-text: 255, 255, 255; - --dt-row-selected-link: 9, 10, 11; + --dt-row-selected-link: 228, 228, 228; --dt-row-stripe: 0, 0, 0; --dt-row-hover: 0, 0, 0; --dt-column-ordering: 0, 0, 0; --dt-header-align-items: center; + --dt-header-vertical-align: middle; --dt-html-background: white; } :root.dark { @@ -112,7 +113,7 @@ table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order, table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order { position: relative; width: 12px; - height: 20px; + height: 24px; } table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before, @@ -144,7 +145,8 @@ table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { opacity: 0.6; } -table.dataTable thead > tr > th.sorting_desc_disabled span.dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled span.dt-column-order:before, +table.dataTable thead > tr > th.dt-orderable-none:not(.dt-ordering-asc, .dt-ordering-desc) span.dt-column-order:empty, table.dataTable thead > tr > th.sorting_desc_disabled span.dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled span.dt-column-order:before, +table.dataTable thead > tr > td.dt-orderable-none:not(.dt-ordering-asc, .dt-ordering-desc) span.dt-column-order:empty, table.dataTable thead > tr > td.sorting_desc_disabled span.dt-column-order:after, table.dataTable thead > tr > td.sorting_asc_disabled span.dt-column-order:before { display: none; @@ -340,6 +342,7 @@ table.dataTable thead td, table.dataTable tfoot th, table.dataTable tfoot td { text-align: left; + vertical-align: var(--dt-header-vertical-align); } table.dataTable thead th.dt-head-left, table.dataTable thead td.dt-head-left, @@ -422,10 +425,6 @@ table.dataTable tbody td.dt-body-nowrap { white-space: nowrap; } -:root { - --dt-header-align-items: flex-end; -} - /*! Bootstrap 5 integration for DataTables * * ©2020 SpryMedia Ltd, all rights reserved. @@ -453,7 +452,7 @@ table.table.dataTable > tbody > tr.selected > * { color: rgb(var(--dt-row-selected-text)); } table.table.dataTable > tbody > tr.selected a { - color: rgb(9, 10, 11); + color: rgb(228, 228, 228); color: rgb(var(--dt-row-selected-link)); } table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * { diff --git a/src/static/scripts/datatables.js b/src/static/scripts/datatables.js index 0ba22347..961af0b4 100644 --- a/src/static/scripts/datatables.js +++ b/src/static/scripts/datatables.js @@ -4,13 +4,13 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#bs5/dt-2.3.2 + * https://datatables.net/download/#bs5/dt-2.3.5 * * Included libraries: - * DataTables 2.3.2 + * DataTables 2.3.5 */ -/*! DataTables 2.3.2 +/*! DataTables 2.3.5 * © SpryMedia Ltd - datatables.net/license */ @@ -178,6 +178,9 @@ this.id = sId; } + // Replacing an existing colgroup with our own. Not ideal, but a merge could take a lot of code + $this.children('colgroup').remove(); + /* Create the settings object for this table and set some of the default parameters */ var oSettings = $.extend( true, {}, DataTable.models.oSettings, { "sDestroyWidth": $this[0].style.width, @@ -513,7 +516,7 @@ * * @type string */ - builder: "bs5/dt-2.3.2", + builder: "bs5/dt-2.3.5", /** * Buttons. For use with the Buttons extension for DataTables. This is @@ -743,7 +746,7 @@ * * The extension options for ordering of data available here is complimentary * to the default type based ordering that DataTables typically uses. It - * allows much greater control over the the data that is being used to + * allows much greater control over the data that is being used to * order a column, but is necessarily therefore more complex. * * This type of ordering is useful if you want to do ordering based on data @@ -902,7 +905,7 @@ * `{type}-asc` and `{type}-desc` together. It is generally recommended * that only `{type}-pre` is used, as this provides the optimal * implementation in terms of speed, although the others are provided - * for compatibility with existing Javascript sort functions. + * for compatibility with existing JavaScript sort functions. * * `{type}-pre`: Functions defined take a single parameter: * @@ -912,7 +915,7 @@ * * * `{*}` Data to be sorted upon * - * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort + * `{type}-asc` and `{type}-desc`: Functions are typical JavaScript sort * functions, taking two parameters: * * 1. `{*}` Data to compare to the second parameter @@ -1136,7 +1139,7 @@ }; // Convert from a formatted number with characters other than `.` as the - // decimal place, to a Javascript number + // decimal place, to a JavaScript number var _numToDecimal = function ( num, decimalPoint ) { // Cache created regular expressions for speed as this function is called often if ( ! _re_dic[ decimalPoint ] ) { @@ -1202,19 +1205,19 @@ var _pluck = function ( a, prop, prop2 ) { var out = []; - var i=0, ien=a.length; + var i=0, iLen=a.length; // Could have the test in the loop for slightly smaller code, but speed // is essential here if ( prop2 !== undefined ) { - for ( ; i') .html( columns[i][titleProp] || '' ) .appendTo( row ); @@ -3492,6 +3495,14 @@ { var iDataIndex = aiDisplay[j]; var aoData = oSettings.aoData[ iDataIndex ]; + + // Row has been deleted - can't be displayed + if (aoData === null) + { + continue; + } + + // Row node hasn't been created yet if ( aoData.nTr === null ) { _fnCreateTr( oSettings, iDataIndex ); @@ -3620,7 +3631,7 @@ return $( '' ) .append( $('', { - 'colSpan': _fnVisbleColumns( settings ), + 'colSpan': _fnVisibleColumns( settings ), 'class': settings.oClasses.empty.row } ).html( zero ) )[0]; } @@ -3804,7 +3815,7 @@ var line = row[ item ].contents; - for ( var i=0, ien=line.length ; i divBodyEl.clientHeight || divBody.css('overflow-y') == "scroll"; var paddingSide = 'padding' + (browser.bScrollbarLeft ? 'Left' : 'Right' ); @@ -5437,7 +5428,7 @@ visibleColumns = _fnGetColumns( settings, 'bVisible' ), tableWidthAttr = table.getAttribute('width'), // from DOM element tableContainer = table.parentNode, - i, column, columnIdx; + i, j, column, columnIdx; var styleWidth = table.style.width; var containerWidth = _fnWrapperWidth(settings); @@ -5471,17 +5462,16 @@ false ); - // Construct a single row, worst case, table with the widest - // node in the data, assign any user defined widths, then insert it into - // the DOM and allow the browser to do all the hard work of calculating - // table widths + // Construct a worst case table with the widest, assign any user defined + // widths, then insert it into the DOM and allow the browser to do all + // the hard work of calculating table widths var tmpTable = $(table.cloneNode()) .css( 'visibility', 'hidden' ) + .css( 'margin', 0 ) .removeAttr( 'id' ); // Clean up the table body - tmpTable.append('') - var tr = $('').appendTo( tmpTable.find('tbody') ); + tmpTable.append('') // Clone the table header and footer - we can't use the header / footer // from the cloned table, since if scrolling is active, the table's @@ -5521,23 +5511,37 @@ } } ); - // Find the widest piece of data for each column and put it into the table + // Get the widest strings for each of the visible columns and add them to + // our table to create a "worst case" + var longestData = []; + for ( i=0 ; i') - .addClass(autoClass) - .addClass(column.sClass) - .append(insert) - .appendTo(tr); + longestData.push(_fnGetWideStrings(settings, visibleColumns[i])); + } + + if (longestData.length) { + for ( i=0 ; i').appendTo( tmpTable.find('tbody') ); + + for ( j=0 ; j') + .addClass(autoClass) + .addClass(column.sClass) + .append(insert) + .appendTo(tr); + } + } } // Tidy the temporary table - remove name attributes so there aren't @@ -5676,20 +5680,32 @@ } /** - * Get the maximum strlen for each data column + * Get the widest strings for each column. + * + * It is very difficult to determine what the widest string actually is due to variable character + * width and kerning. Doing an exact calculation with the DOM or even Canvas would kill performance + * and this is a critical point, so we use two techniques to determine a collection of the longest + * strings from the column, which will likely contain the widest strings: + * + * 1) Get the top three longest strings from the column + * 2) Get the top three widest words (i.e. an unbreakable phrase) + * * @param {object} settings dataTables settings object * @param {int} colIdx column of interest - * @returns {string} string of the max length + * @returns {string[]} Array of the longest strings * @memberof DataTable#oApi */ - function _fnGetMaxLenString( settings, colIdx ) + function _fnGetWideStrings( settings, colIdx ) { var column = settings.aoColumns[colIdx]; - if (! column.maxLenString) { - var s, max='', maxLen = -1; - - for ( var i=0, ien=settings.aiDisplayMaster.length ; i maxLen ) { - // We want the HTML in the string, but the length that - // is important is the stripped string - max = cellString; - maxLen = s.length; - } + collection.push({ + str: s, + len: s.length + }); + + allStrings.push(s); + } + + // Order and then cut down to the size we need + collection + .sort(function (a, b) { + return b.len - a.len; + }) + .splice(3); + + column.wideStrings = collection.map(function (item) { + return item.str; + }); + + // Longest unbroken string + let parts = allStrings.join(' ').split(' '); + + parts.sort(function (a, b) { + return b.length - a.length; + }); + + if (parts.length) { + column.wideStrings.push(parts[0]); + } + + if (parts.length > 1) { + column.wideStrings.push(parts[1]); } - column.maxLenString = max; + if (parts.length > 2) { + column.wideStrings.push(parts[3]); + } } - return column.maxLenString; + return column.wideStrings; } @@ -5811,7 +5855,7 @@ : [column]; if ( columns.length ) { - for ( var i=0, ien=columns.length ; i - + - - - - - - - - - - + + + - - + + - - + +