mirror of
				https://github.com/dani-garcia/vaultwarden.git
				synced 2025-10-31 18:28:20 +02:00 
			
		
		
		
	Update admin interface (#5880)
- Updated Backend Admin dependencies - Fixed NTP time by using CloudFlare trace - Fixes #5797 - Fixed web-vault version check = Fixes #5761 - Fixed an issue with the css not hiding the 'Create Account' link. There were no braces around the function call. Also added a hide for newer web-vault versions as it still causes confusion with the cached /api/config. Signed-off-by: BlackDex <black.dex@gmail.com>
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							a039e227c7
						
					
				
				
					commit
					3a44dc963b
				
			| @@ -591,20 +591,14 @@ struct GitCommit { | ||||
|     sha: String, | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize)] | ||||
| struct TimeApi { | ||||
|     year: u16, | ||||
|     month: u8, | ||||
|     day: u8, | ||||
|     hour: u8, | ||||
|     minute: u8, | ||||
|     seconds: u8, | ||||
| } | ||||
|  | ||||
| async fn get_json_api<T: DeserializeOwned>(url: &str) -> Result<T, Error> { | ||||
|     Ok(make_http_request(Method::GET, url)?.send().await?.error_for_status()?.json::<T>().await?) | ||||
| } | ||||
|  | ||||
| async fn get_text_api(url: &str) -> Result<String, Error> { | ||||
|     Ok(make_http_request(Method::GET, url)?.send().await?.error_for_status()?.text().await?) | ||||
| } | ||||
|  | ||||
| async fn has_http_access() -> bool { | ||||
|     let Ok(req) = make_http_request(Method::HEAD, "https://github.com/dani-garcia/vaultwarden") else { | ||||
|         return false; | ||||
| @@ -616,9 +610,10 @@ async fn has_http_access() -> bool { | ||||
| } | ||||
|  | ||||
| use cached::proc_macro::cached; | ||||
| /// Cache this function to prevent API call rate limit. Github only allows 60 requests per hour, and we use 3 here already. | ||||
| /// It will cache this function for 300 seconds (5 minutes) which should prevent the exhaustion of the rate limit. | ||||
| #[cached(time = 300, sync_writes = "default")] | ||||
| /// Cache this function to prevent API call rate limit. Github only allows 60 requests per hour, and we use 3 here already | ||||
| /// It will cache this function for 600 seconds (10 minutes) which should prevent the exhaustion of the rate limit | ||||
| /// Any cache will be lost if Vaultwarden is restarted | ||||
| #[cached(time = 600, sync_writes = "default")] | ||||
| async fn get_release_info(has_http_access: bool, running_within_container: bool) -> (String, String, String) { | ||||
|     // If the HTTP Check failed, do not even attempt to check for new versions since we were not able to connect with github.com anyway. | ||||
|     if has_http_access { | ||||
| @@ -636,7 +631,7 @@ async fn get_release_info(has_http_access: bool, running_within_container: bool) | ||||
|                 } | ||||
|                 _ => "-".to_string(), | ||||
|             }, | ||||
|             // Do not fetch the web-vault version when running within a container. | ||||
|             // Do not fetch the web-vault version when running within a container | ||||
|             // The web-vault version is embedded within the container it self, and should not be updated manually | ||||
|             if running_within_container { | ||||
|                 "-".to_string() | ||||
| @@ -658,17 +653,18 @@ async fn get_release_info(has_http_access: bool, running_within_container: bool) | ||||
|  | ||||
| async fn get_ntp_time(has_http_access: bool) -> String { | ||||
|     if has_http_access { | ||||
|         if let Ok(ntp_time) = get_json_api::<TimeApi>("https://www.timeapi.io/api/Time/current/zone?timeZone=UTC").await | ||||
|         { | ||||
|             return format!( | ||||
|                 "{year}-{month:02}-{day:02} {hour:02}:{minute:02}:{seconds:02} UTC", | ||||
|                 year = ntp_time.year, | ||||
|                 month = ntp_time.month, | ||||
|                 day = ntp_time.day, | ||||
|                 hour = ntp_time.hour, | ||||
|                 minute = ntp_time.minute, | ||||
|                 seconds = ntp_time.seconds | ||||
|             ); | ||||
|         if let Ok(cf_trace) = get_text_api("https://cloudflare.com/cdn-cgi/trace").await { | ||||
|             for line in cf_trace.lines() { | ||||
|                 if let Some((key, value)) = line.split_once('=') { | ||||
|                     if key == "ts" { | ||||
|                         let ts = value.split_once('.').map_or(value, |(s, _)| s); | ||||
|                         if let Ok(dt) = chrono::DateTime::parse_from_str(ts, "%s") { | ||||
|                             return dt.format("%Y-%m-%d %H:%M:%S UTC").to_string(); | ||||
|                         } | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     String::from("Unable to fetch NTP time.") | ||||
| @@ -701,6 +697,12 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, mut conn: DbConn) | ||||
|     // Get current running versions | ||||
|     let web_vault_version = get_web_vault_version(); | ||||
|  | ||||
|     // Check if the running version is newer than the latest stable released version | ||||
|     let web_ver_match = semver::VersionReq::parse(&format!(">{latest_web_build}")).unwrap(); | ||||
|     let web_vault_pre_release = web_ver_match.matches( | ||||
|         &semver::Version::parse(&web_vault_version).unwrap_or_else(|_| semver::Version::parse("2025.1.1").unwrap()), | ||||
|     ); | ||||
|  | ||||
|     let diagnostics_json = json!({ | ||||
|         "dns_resolved": dns_resolved, | ||||
|         "current_release": VERSION, | ||||
| @@ -709,6 +711,7 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, mut conn: DbConn) | ||||
|         "web_vault_enabled": &CONFIG.web_vault_enabled(), | ||||
|         "web_vault_version": web_vault_version, | ||||
|         "latest_web_build": latest_web_build, | ||||
|         "web_vault_pre_release": web_vault_pre_release, | ||||
|         "running_within_container": running_within_container, | ||||
|         "container_base_image": if running_within_container { container_base_image() } else { "Not applicable" }, | ||||
|         "has_http_access": has_http_access, | ||||
| @@ -724,6 +727,7 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, mut conn: DbConn) | ||||
|         "overrides": &CONFIG.get_overrides().join(", "), | ||||
|         "host_arch": env::consts::ARCH, | ||||
|         "host_os":  env::consts::OS, | ||||
|         "tz_env": env::var("TZ").unwrap_or_default(), | ||||
|         "server_time_local": Local::now().format("%Y-%m-%d %H:%M:%S %Z").to_string(), | ||||
|         "server_time": Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(), // Run the server date/time check as late as possible to minimize the time difference | ||||
|         "ntp_time": get_ntp_time(has_http_access).await, // Run the ntp check as late as possible to minimize the time difference | ||||
|   | ||||
| @@ -128,9 +128,8 @@ async fn is_email_2fa_required(member_id: Option<MembershipId>, conn: &mut DbCon | ||||
|     if CONFIG.email_2fa_enforce_on_verified_invite() { | ||||
|         return true; | ||||
|     } | ||||
|     if member_id.is_some() { | ||||
|         return OrgPolicy::is_enabled_for_member(&member_id.unwrap(), OrgPolicyType::TwoFactorAuthentication, conn) | ||||
|             .await; | ||||
|     if let Some(member_id) = member_id { | ||||
|         return OrgPolicy::is_enabled_for_member(&member_id, OrgPolicyType::TwoFactorAuthentication, conn).await; | ||||
|     } | ||||
|     false | ||||
| } | ||||
|   | ||||
| @@ -1581,7 +1581,7 @@ async fn move_cipher_selected( | ||||
|         nt.send_cipher_update( | ||||
|             UpdateType::SyncCipherUpdate, | ||||
|             &cipher, | ||||
|             &[user_id.clone()], | ||||
|             std::slice::from_ref(&user_id), | ||||
|             &headers.device.uuid, | ||||
|             None, | ||||
|             &mut conn, | ||||
|   | ||||
							
								
								
									
										4
									
								
								src/static/scripts/admin.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								src/static/scripts/admin.css
									
									
									
									
										vendored
									
									
								
							| @@ -38,8 +38,8 @@ img { | ||||
|     max-width: 130px; | ||||
| } | ||||
| #users-table .vw-actions, #orgs-table .vw-actions { | ||||
|     min-width: 135px; | ||||
|     max-width: 140px; | ||||
|     min-width: 155px; | ||||
|     max-width: 160px; | ||||
| } | ||||
| #users-table .vw-org-cell { | ||||
|     max-height: 120px; | ||||
|   | ||||
							
								
								
									
										17
									
								
								src/static/scripts/admin_diagnostics.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								src/static/scripts/admin_diagnostics.js
									
									
									
									
										vendored
									
									
								
							| @@ -29,7 +29,7 @@ function isValidIp(ip) { | ||||
|     return ipv4Regex.test(ip) || ipv6Regex.test(ip); | ||||
| } | ||||
|  | ||||
| function checkVersions(platform, installed, latest, commit=null) { | ||||
| function checkVersions(platform, installed, latest, commit=null, pre_release=false) { | ||||
|     if (installed === "-" || latest === "-") { | ||||
|         document.getElementById(`${platform}-failed`).classList.remove("d-none"); | ||||
|         return; | ||||
| @@ -37,10 +37,12 @@ function checkVersions(platform, installed, latest, commit=null) { | ||||
|  | ||||
|     // Only check basic versions, no commit revisions | ||||
|     if (commit === null || installed.indexOf("-") === -1) { | ||||
|         if (installed !== latest) { | ||||
|             document.getElementById(`${platform}-warning`).classList.remove("d-none"); | ||||
|         } else { | ||||
|         if (platform === "web" && pre_release === true) { | ||||
|             document.getElementById(`${platform}-prerelease`).classList.remove("d-none"); | ||||
|         } else if (installed == latest) { | ||||
|             document.getElementById(`${platform}-success`).classList.remove("d-none"); | ||||
|         } else { | ||||
|             document.getElementById(`${platform}-warning`).classList.remove("d-none"); | ||||
|         } | ||||
|     } else { | ||||
|         // Check if this is a branched version. | ||||
| @@ -86,7 +88,7 @@ async function generateSupportString(event, dj) { | ||||
|     supportString += `* Running within a container: ${dj.running_within_container} (Base: ${dj.container_base_image})\n`; | ||||
|     supportString += `* Database type: ${dj.db_type}\n`; | ||||
|     supportString += `* Database version: ${dj.db_version}\n`; | ||||
|     supportString += `* Environment settings overridden!: ${dj.overrides !== ""}\n`; | ||||
|     supportString += `* Uses config.json: ${dj.overrides !== ""}\n`; | ||||
|     supportString += `* Uses a reverse proxy: ${dj.ip_header_exists}\n`; | ||||
|     if (dj.ip_header_exists) { | ||||
|         supportString += `* IP Header check: ${dj.ip_header_match} (${dj.ip_header_name})\n`; | ||||
| @@ -94,6 +96,9 @@ async function generateSupportString(event, dj) { | ||||
|     supportString += `* Internet access: ${dj.has_http_access}\n`; | ||||
|     supportString += `* Internet access via a proxy: ${dj.uses_proxy}\n`; | ||||
|     supportString += `* DNS Check: ${dnsCheck}\n`; | ||||
|     if (dj.tz_env !== "") { | ||||
|         supportString += `* TZ environment: ${dj.tz_env}\n`; | ||||
|     } | ||||
|     supportString += `* Browser/Server Time Check: ${timeCheck}\n`; | ||||
|     supportString += `* Server/NTP Time Check: ${ntpTimeCheck}\n`; | ||||
|     supportString += `* Domain Configuration Check: ${domainCheck}\n`; | ||||
| @@ -206,7 +211,7 @@ function initVersionCheck(dj) { | ||||
|     if (!dj.running_within_container) { | ||||
|         const webInstalled = dj.web_vault_version; | ||||
|         const webLatest = dj.latest_web_build; | ||||
|         checkVersions("web", webInstalled, webLatest); | ||||
|         checkVersions("web", webInstalled, webLatest, null, dj.web_vault_pre_release); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										14
									
								
								src/static/scripts/bootstrap.bundle.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								src/static/scripts/bootstrap.bundle.js
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /*! | ||||
|   * Bootstrap v5.3.4 (https://getbootstrap.com/) | ||||
|   * Bootstrap v5.3.6 (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.4'; | ||||
|   const VERSION = '5.3.6'; | ||||
|  | ||||
|   /** | ||||
|    * Class definition | ||||
| @@ -673,6 +673,8 @@ | ||||
|         this[propertyName] = null; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // Private | ||||
|     _queueCallback(callback, element, isAnimated = true) { | ||||
|       executeAfterTransition(callback, element, isAnimated); | ||||
|     } | ||||
| @@ -1604,11 +1606,11 @@ | ||||
|       this._element.style[dimension] = ''; | ||||
|       this._queueCallback(complete, this._element, true); | ||||
|     } | ||||
|  | ||||
|     // Private | ||||
|     _isShown(element = this._element) { | ||||
|       return element.classList.contains(CLASS_NAME_SHOW$7); | ||||
|     } | ||||
|  | ||||
|     // Private | ||||
|     _configAfterMerge(config) { | ||||
|       config.toggle = Boolean(config.toggle); // Coerce string values | ||||
|       config.parent = getElement(config.parent); | ||||
| @@ -3688,6 +3690,9 @@ | ||||
|       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); | ||||
| @@ -6209,7 +6214,6 @@ | ||||
|     } | ||||
|  | ||||
|     // Private | ||||
|  | ||||
|     _maybeScheduleHide() { | ||||
|       if (!this._config.autohide) { | ||||
|         return; | ||||
|   | ||||
							
								
								
									
										45
									
								
								src/static/scripts/bootstrap.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										45
									
								
								src/static/scripts/bootstrap.css
									
									
									
									
										vendored
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| @charset "UTF-8"; | ||||
| /*! | ||||
|  * Bootstrap  v5.3.4 (https://getbootstrap.com/) | ||||
|  * Bootstrap  v5.3.6 (https://getbootstrap.com/) | ||||
|  * Copyright 2011-2025 The Bootstrap Authors | ||||
|  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) | ||||
|  */ | ||||
| @@ -2156,10 +2156,6 @@ progress { | ||||
|   display: block; | ||||
|   padding: 0; | ||||
| } | ||||
| .form-control::-moz-placeholder { | ||||
|   color: var(--bs-secondary-color); | ||||
|   opacity: 1; | ||||
| } | ||||
| .form-control::placeholder { | ||||
|   color: var(--bs-secondary-color); | ||||
|   opacity: 1; | ||||
| @@ -2629,17 +2625,10 @@ textarea.form-control-lg { | ||||
| .form-floating > .form-control-plaintext { | ||||
|   padding: 1rem 0.75rem; | ||||
| } | ||||
| .form-floating > .form-control::-moz-placeholder, .form-floating > .form-control-plaintext::-moz-placeholder { | ||||
|   color: transparent; | ||||
| } | ||||
| .form-floating > .form-control::placeholder, | ||||
| .form-floating > .form-control-plaintext::placeholder { | ||||
|   color: transparent; | ||||
| } | ||||
| .form-floating > .form-control:not(:-moz-placeholder), .form-floating > .form-control-plaintext:not(:-moz-placeholder) { | ||||
|   padding-top: 1.625rem; | ||||
|   padding-bottom: 0.625rem; | ||||
| } | ||||
| .form-floating > .form-control:focus, .form-floating > .form-control:not(:placeholder-shown), | ||||
| .form-floating > .form-control-plaintext:focus, | ||||
| .form-floating > .form-control-plaintext:not(:placeholder-shown) { | ||||
| @@ -2656,9 +2645,6 @@ textarea.form-control-lg { | ||||
|   padding-bottom: 0.625rem; | ||||
|   padding-left: 0.75rem; | ||||
| } | ||||
| .form-floating > .form-control:not(:-moz-placeholder) ~ label { | ||||
|   transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); | ||||
| } | ||||
| .form-floating > .form-control:focus ~ label, | ||||
| .form-floating > .form-control:not(:placeholder-shown) ~ label, | ||||
| .form-floating > .form-control-plaintext ~ label, | ||||
| @@ -2668,15 +2654,6 @@ textarea.form-control-lg { | ||||
| .form-floating > .form-control:-webkit-autofill ~ label { | ||||
|   transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); | ||||
| } | ||||
| .form-floating > textarea:not(:-moz-placeholder) ~ label::after { | ||||
|   position: absolute; | ||||
|   inset: 1rem 0.375rem; | ||||
|   z-index: -1; | ||||
|   height: 1.5em; | ||||
|   content: ""; | ||||
|   background-color: var(--bs-body-bg); | ||||
|   border-radius: var(--bs-border-radius); | ||||
| } | ||||
| .form-floating > textarea:focus ~ label::after, | ||||
| .form-floating > textarea:not(:placeholder-shown) ~ label::after { | ||||
|   position: absolute; | ||||
| @@ -4540,24 +4517,24 @@ textarea.form-control-lg { | ||||
|     border-top-right-radius: 0; | ||||
|     border-bottom-right-radius: 0; | ||||
|   } | ||||
|   .card-group > .card:not(:last-child) .card-img-top, | ||||
|   .card-group > .card:not(:last-child) .card-header { | ||||
|   .card-group > .card:not(:last-child) > .card-img-top, | ||||
|   .card-group > .card:not(:last-child) > .card-header { | ||||
|     border-top-right-radius: 0; | ||||
|   } | ||||
|   .card-group > .card:not(:last-child) .card-img-bottom, | ||||
|   .card-group > .card:not(:last-child) .card-footer { | ||||
|   .card-group > .card:not(:last-child) > .card-img-bottom, | ||||
|   .card-group > .card:not(:last-child) > .card-footer { | ||||
|     border-bottom-right-radius: 0; | ||||
|   } | ||||
|   .card-group > .card:not(:first-child) { | ||||
|     border-top-left-radius: 0; | ||||
|     border-bottom-left-radius: 0; | ||||
|   } | ||||
|   .card-group > .card:not(:first-child) .card-img-top, | ||||
|   .card-group > .card:not(:first-child) .card-header { | ||||
|   .card-group > .card:not(:first-child) > .card-img-top, | ||||
|   .card-group > .card:not(:first-child) > .card-header { | ||||
|     border-top-left-radius: 0; | ||||
|   } | ||||
|   .card-group > .card:not(:first-child) .card-img-bottom, | ||||
|   .card-group > .card:not(:first-child) .card-footer { | ||||
|   .card-group > .card:not(:first-child) > .card-img-bottom, | ||||
|   .card-group > .card:not(:first-child) > .card-footer { | ||||
|     border-bottom-left-radius: 0; | ||||
|   } | ||||
| } | ||||
| @@ -7179,6 +7156,10 @@ textarea.form-control-lg { | ||||
| .visually-hidden-focusable:not(:focus):not(:focus-within):not(caption) { | ||||
|   position: absolute !important; | ||||
| } | ||||
| .visually-hidden *, | ||||
| .visually-hidden-focusable:not(:focus):not(:focus-within) * { | ||||
|   overflow: hidden !important; | ||||
| } | ||||
|  | ||||
| .stretched-link::after { | ||||
|   position: absolute; | ||||
|   | ||||
							
								
								
									
										121
									
								
								src/static/scripts/datatables.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										121
									
								
								src/static/scripts/datatables.css
									
									
									
									
										vendored
									
									
								
							| @@ -4,10 +4,10 @@ | ||||
|  * | ||||
|  * To rebuild or modify this file with the latest versions of the included | ||||
|  * software please visit: | ||||
|  *   https://datatables.net/download/#bs5/dt-2.2.2 | ||||
|  *   https://datatables.net/download/#bs5/dt-2.3.1 | ||||
|  * | ||||
|  * Included libraries: | ||||
|  *   DataTables 2.2.2 | ||||
|  *   DataTables 2.3.1 | ||||
|  */ | ||||
|  | ||||
| :root { | ||||
| @@ -104,24 +104,14 @@ table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { | ||||
|   content: "\25BC"; | ||||
|   content: "\25BC"/""; | ||||
| } | ||||
| table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, table.dataTable thead > tr > th.dt-ordering-asc, table.dataTable thead > tr > th.dt-ordering-desc, | ||||
| table.dataTable thead > tr > td.dt-orderable-asc, | ||||
| table.dataTable thead > tr > td.dt-orderable-desc, | ||||
| table.dataTable thead > tr > td.dt-ordering-asc, | ||||
| table.dataTable thead > tr > td.dt-ordering-desc { | ||||
|   position: relative; | ||||
|   padding-right: 30px; | ||||
| } | ||||
| table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order, | ||||
| table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order, | ||||
| table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order, | ||||
| 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: absolute; | ||||
|   right: 12px; | ||||
|   top: 0; | ||||
|   bottom: 0; | ||||
|   position: relative; | ||||
|   width: 12px; | ||||
|   height: 20px; | ||||
| } | ||||
| 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, | ||||
| @@ -163,6 +153,40 @@ table.dataTable thead > tr > td:active { | ||||
|   outline: none; | ||||
| } | ||||
|  | ||||
| table.dataTable thead > tr > th div.dt-column-header, | ||||
| table.dataTable thead > tr > th div.dt-column-footer, | ||||
| table.dataTable thead > tr > td div.dt-column-header, | ||||
| table.dataTable thead > tr > td div.dt-column-footer, | ||||
| table.dataTable tfoot > tr > th div.dt-column-header, | ||||
| table.dataTable tfoot > tr > th div.dt-column-footer, | ||||
| table.dataTable tfoot > tr > td div.dt-column-header, | ||||
| table.dataTable tfoot > tr > td div.dt-column-footer { | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
|   align-items: center; | ||||
|   gap: 4px; | ||||
| } | ||||
| table.dataTable thead > tr > th div.dt-column-header span.dt-column-title, | ||||
| table.dataTable thead > tr > th div.dt-column-footer span.dt-column-title, | ||||
| table.dataTable thead > tr > td div.dt-column-header span.dt-column-title, | ||||
| table.dataTable thead > tr > td div.dt-column-footer span.dt-column-title, | ||||
| table.dataTable tfoot > tr > th div.dt-column-header span.dt-column-title, | ||||
| table.dataTable tfoot > tr > th div.dt-column-footer span.dt-column-title, | ||||
| table.dataTable tfoot > tr > td div.dt-column-header span.dt-column-title, | ||||
| table.dataTable tfoot > tr > td div.dt-column-footer span.dt-column-title { | ||||
|   flex-grow: 1; | ||||
| } | ||||
| table.dataTable thead > tr > th div.dt-column-header span.dt-column-title:empty, | ||||
| table.dataTable thead > tr > th div.dt-column-footer span.dt-column-title:empty, | ||||
| table.dataTable thead > tr > td div.dt-column-header span.dt-column-title:empty, | ||||
| table.dataTable thead > tr > td div.dt-column-footer span.dt-column-title:empty, | ||||
| table.dataTable tfoot > tr > th div.dt-column-header span.dt-column-title:empty, | ||||
| table.dataTable tfoot > tr > th div.dt-column-footer span.dt-column-title:empty, | ||||
| table.dataTable tfoot > tr > td div.dt-column-header span.dt-column-title:empty, | ||||
| table.dataTable tfoot > tr > td div.dt-column-footer span.dt-column-title:empty { | ||||
|   display: none; | ||||
| } | ||||
|  | ||||
| div.dt-scroll-body > table.dataTable > thead > tr > th, | ||||
| div.dt-scroll-body > table.dataTable > thead > tr > td { | ||||
|   overflow: hidden; | ||||
| @@ -258,10 +282,25 @@ table.dataTable td.dt-type-numeric, | ||||
| table.dataTable td.dt-type-date { | ||||
|   text-align: right; | ||||
| } | ||||
| table.dataTable th.dt-type-numeric div.dt-column-header, | ||||
| table.dataTable th.dt-type-numeric div.dt-column-footer, table.dataTable th.dt-type-date div.dt-column-header, | ||||
| table.dataTable th.dt-type-date div.dt-column-footer, | ||||
| table.dataTable td.dt-type-numeric div.dt-column-header, | ||||
| table.dataTable td.dt-type-numeric div.dt-column-footer, | ||||
| table.dataTable td.dt-type-date div.dt-column-header, | ||||
| table.dataTable td.dt-type-date div.dt-column-footer { | ||||
|   flex-direction: row-reverse; | ||||
| } | ||||
| table.dataTable th.dt-left, | ||||
| table.dataTable td.dt-left { | ||||
|   text-align: left; | ||||
| } | ||||
| table.dataTable th.dt-left div.dt-column-header, | ||||
| table.dataTable th.dt-left div.dt-column-footer, | ||||
| table.dataTable td.dt-left div.dt-column-header, | ||||
| table.dataTable td.dt-left div.dt-column-footer { | ||||
|   flex-direction: row; | ||||
| } | ||||
| table.dataTable th.dt-center, | ||||
| table.dataTable td.dt-center { | ||||
|   text-align: center; | ||||
| @@ -270,10 +309,22 @@ table.dataTable th.dt-right, | ||||
| table.dataTable td.dt-right { | ||||
|   text-align: right; | ||||
| } | ||||
| table.dataTable th.dt-right div.dt-column-header, | ||||
| table.dataTable th.dt-right div.dt-column-footer, | ||||
| table.dataTable td.dt-right div.dt-column-header, | ||||
| table.dataTable td.dt-right div.dt-column-footer { | ||||
|   flex-direction: row-reverse; | ||||
| } | ||||
| table.dataTable th.dt-justify, | ||||
| table.dataTable td.dt-justify { | ||||
|   text-align: justify; | ||||
| } | ||||
| table.dataTable th.dt-justify div.dt-column-header, | ||||
| table.dataTable th.dt-justify div.dt-column-footer, | ||||
| table.dataTable td.dt-justify div.dt-column-header, | ||||
| table.dataTable td.dt-justify div.dt-column-footer { | ||||
|   flex-direction: row; | ||||
| } | ||||
| table.dataTable th.dt-nowrap, | ||||
| table.dataTable td.dt-nowrap { | ||||
|   white-space: nowrap; | ||||
| @@ -295,6 +346,16 @@ table.dataTable tfoot th.dt-head-left, | ||||
| table.dataTable tfoot td.dt-head-left { | ||||
|   text-align: left; | ||||
| } | ||||
| table.dataTable thead th.dt-head-left div.dt-column-header, | ||||
| table.dataTable thead th.dt-head-left div.dt-column-footer, | ||||
| table.dataTable thead td.dt-head-left div.dt-column-header, | ||||
| table.dataTable thead td.dt-head-left div.dt-column-footer, | ||||
| table.dataTable tfoot th.dt-head-left div.dt-column-header, | ||||
| table.dataTable tfoot th.dt-head-left div.dt-column-footer, | ||||
| table.dataTable tfoot td.dt-head-left div.dt-column-header, | ||||
| table.dataTable tfoot td.dt-head-left div.dt-column-footer { | ||||
|   flex-direction: row; | ||||
| } | ||||
| table.dataTable thead th.dt-head-center, | ||||
| table.dataTable thead td.dt-head-center, | ||||
| table.dataTable tfoot th.dt-head-center, | ||||
| @@ -307,12 +368,32 @@ table.dataTable tfoot th.dt-head-right, | ||||
| table.dataTable tfoot td.dt-head-right { | ||||
|   text-align: right; | ||||
| } | ||||
| table.dataTable thead th.dt-head-right div.dt-column-header, | ||||
| table.dataTable thead th.dt-head-right div.dt-column-footer, | ||||
| table.dataTable thead td.dt-head-right div.dt-column-header, | ||||
| table.dataTable thead td.dt-head-right div.dt-column-footer, | ||||
| table.dataTable tfoot th.dt-head-right div.dt-column-header, | ||||
| table.dataTable tfoot th.dt-head-right div.dt-column-footer, | ||||
| table.dataTable tfoot td.dt-head-right div.dt-column-header, | ||||
| table.dataTable tfoot td.dt-head-right div.dt-column-footer { | ||||
|   flex-direction: row-reverse; | ||||
| } | ||||
| table.dataTable thead th.dt-head-justify, | ||||
| table.dataTable thead td.dt-head-justify, | ||||
| table.dataTable tfoot th.dt-head-justify, | ||||
| table.dataTable tfoot td.dt-head-justify { | ||||
|   text-align: justify; | ||||
| } | ||||
| table.dataTable thead th.dt-head-justify div.dt-column-header, | ||||
| table.dataTable thead th.dt-head-justify div.dt-column-footer, | ||||
| table.dataTable thead td.dt-head-justify div.dt-column-header, | ||||
| table.dataTable thead td.dt-head-justify div.dt-column-footer, | ||||
| table.dataTable tfoot th.dt-head-justify div.dt-column-header, | ||||
| table.dataTable tfoot th.dt-head-justify div.dt-column-footer, | ||||
| table.dataTable tfoot td.dt-head-justify div.dt-column-header, | ||||
| table.dataTable tfoot td.dt-head-justify div.dt-column-footer { | ||||
|   flex-direction: row; | ||||
| } | ||||
| table.dataTable thead th.dt-head-nowrap, | ||||
| table.dataTable thead td.dt-head-nowrap, | ||||
| table.dataTable tfoot th.dt-head-nowrap, | ||||
| @@ -410,6 +491,9 @@ div.dt-container div.dt-layout-table > div { | ||||
|     margin-left: 0; | ||||
|   } | ||||
| } | ||||
| div.dt-container { | ||||
|   position: relative; | ||||
| } | ||||
| div.dt-container div.dt-length label { | ||||
|   font-weight: normal; | ||||
|   text-align: left; | ||||
| @@ -498,14 +582,19 @@ table.dataTable.table-sm > thead > tr td.dt-orderable-asc, | ||||
| table.dataTable.table-sm > thead > tr td.dt-orderable-desc, | ||||
| table.dataTable.table-sm > thead > tr td.dt-ordering-asc, | ||||
| table.dataTable.table-sm > thead > tr td.dt-ordering-desc { | ||||
|   padding-right: 20px; | ||||
|   padding-right: 0.25rem; | ||||
| } | ||||
| table.dataTable.table-sm > thead > tr th.dt-orderable-asc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-orderable-desc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-ordering-asc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-ordering-desc span.dt-column-order, | ||||
| table.dataTable.table-sm > thead > tr td.dt-orderable-asc span.dt-column-order, | ||||
| table.dataTable.table-sm > thead > tr td.dt-orderable-desc span.dt-column-order, | ||||
| table.dataTable.table-sm > thead > tr td.dt-ordering-asc span.dt-column-order, | ||||
| table.dataTable.table-sm > thead > tr td.dt-ordering-desc span.dt-column-order { | ||||
|   right: 5px; | ||||
|   right: 0.25rem; | ||||
| } | ||||
| table.dataTable.table-sm > thead > tr th.dt-type-date span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-type-numeric span.dt-column-order, | ||||
| table.dataTable.table-sm > thead > tr td.dt-type-date span.dt-column-order, | ||||
| table.dataTable.table-sm > thead > tr td.dt-type-numeric span.dt-column-order { | ||||
|   left: 0.25rem; | ||||
| } | ||||
|  | ||||
| div.dt-scroll-head table.table-bordered { | ||||
|   | ||||
							
								
								
									
										424
									
								
								src/static/scripts/datatables.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										424
									
								
								src/static/scripts/datatables.js
									
									
									
									
										vendored
									
									
								
							| @@ -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.2.2 | ||||
|  *   https://datatables.net/download/#bs5/dt-2.3.1 | ||||
|  * | ||||
|  * Included libraries: | ||||
|  *   DataTables 2.2.2 | ||||
|  *   DataTables 2.3.1 | ||||
|  */ | ||||
|  | ||||
| /*! DataTables 2.2.2 | ||||
| /*! DataTables 2.3.1 | ||||
|  * © SpryMedia Ltd - datatables.net/license | ||||
|  */ | ||||
|  | ||||
| @@ -101,15 +101,19 @@ | ||||
| 			var defaults = DataTable.defaults; | ||||
| 			var $this = $(this); | ||||
| 			 | ||||
| 			 | ||||
| 			/* Sanity check */ | ||||
| 			// Sanity check | ||||
| 			if ( this.nodeName.toLowerCase() != 'table' ) | ||||
| 			{ | ||||
| 				_fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 ); | ||||
| 				return; | ||||
| 			} | ||||
| 			 | ||||
| 			$(this).trigger( 'options.dt', oInit ); | ||||
| 			// Special case for options | ||||
| 			if (oInit.on && oInit.on.options) { | ||||
| 				_fnListener($this, 'options', oInit.on.options);	 | ||||
| 			} | ||||
| 			 | ||||
| 			$this.trigger( 'options.dt', oInit ); | ||||
| 			 | ||||
| 			/* Backwards compatibility for the defaults */ | ||||
| 			_fnCompatOpts( defaults ); | ||||
| @@ -248,6 +252,9 @@ | ||||
| 				"caption", | ||||
| 				"layout", | ||||
| 				"orderDescReverse", | ||||
| 				"orderIndicators", | ||||
| 				"orderHandler", | ||||
| 				"titleRow", | ||||
| 				"typeDetect", | ||||
| 				[ "iCookieDuration", "iStateDuration" ], // backwards compat | ||||
| 				[ "oSearch", "oPreviousSearch" ], | ||||
| @@ -276,6 +283,13 @@ | ||||
| 			 | ||||
| 			oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId ); | ||||
| 			 | ||||
| 			// Add event listeners | ||||
| 			if (oInit.on) { | ||||
| 				Object.keys(oInit.on).forEach(function (key) { | ||||
| 					_fnListener($this, key, oInit.on[key]); | ||||
| 				}); | ||||
| 			} | ||||
| 			 | ||||
| 			/* Browser support detection */ | ||||
| 			_fnBrowserDetect( oSettings ); | ||||
| 			 | ||||
| @@ -336,7 +350,7 @@ | ||||
| 			/* HTML5 attribute detection - build an mData object automatically if the | ||||
| 			 * attributes are found | ||||
| 			 */ | ||||
| 			var rowOne = $this.children('tbody').find('tr').eq(0); | ||||
| 			var rowOne = $this.children('tbody').find('tr:first-child').eq(0); | ||||
| 			 | ||||
| 			if ( rowOne.length ) { | ||||
| 				var a = function ( cell, name ) { | ||||
| @@ -494,6 +508,13 @@ | ||||
| 	 *  @namespace | ||||
| 	 */ | ||||
| 	DataTable.ext = _ext = { | ||||
| 		/** | ||||
| 		 * DataTables build type (expanded by the download builder) | ||||
| 		 * | ||||
| 		 *  @type string | ||||
| 		 */ | ||||
| 		builder: "bs5/dt-2.3.1", | ||||
| 	 | ||||
| 		/** | ||||
| 		 * Buttons. For use with the Buttons extension for DataTables. This is | ||||
| 		 * defined here so other extensions can define buttons regardless of load | ||||
| @@ -505,6 +526,14 @@ | ||||
| 		buttons: {}, | ||||
| 	 | ||||
| 	 | ||||
| 		/** | ||||
| 		 * ColumnControl buttons and content | ||||
| 		 * | ||||
| 		 *  @type object | ||||
| 		 */ | ||||
| 		ccContent: {}, | ||||
| 	 | ||||
| 	 | ||||
| 		/** | ||||
| 		 * Element class names | ||||
| 		 * | ||||
| @@ -514,14 +543,6 @@ | ||||
| 		classes: {}, | ||||
| 	 | ||||
| 	 | ||||
| 		/** | ||||
| 		 * DataTables build type (expanded by the download builder) | ||||
| 		 * | ||||
| 		 *  @type string | ||||
| 		 */ | ||||
| 		builder: "bs5/dt-2.2.2", | ||||
| 	 | ||||
| 	 | ||||
| 		/** | ||||
| 		 * Error reporting. | ||||
| 		 *  | ||||
| @@ -1887,6 +1908,26 @@ | ||||
| 			init.scrollX = init.scrollX ? '100%' : ''; | ||||
| 		} | ||||
| 	 | ||||
| 		// Objects for ordering | ||||
| 		if ( typeof init.bSort === 'object' ) { | ||||
| 			init.orderIndicators = init.bSort.indicators !== undefined ? init.bSort.indicators : true; | ||||
| 			init.orderHandler = init.bSort.handler !== undefined ? init.bSort.handler : true; | ||||
| 			init.bSort = true; | ||||
| 		} | ||||
| 		else if (init.bSort === false) { | ||||
| 			init.orderIndicators = false; | ||||
| 			init.orderHandler = false; | ||||
| 		} | ||||
| 		else if (init.bSort === true) { | ||||
| 			init.orderIndicators = true; | ||||
| 			init.orderHandler = true; | ||||
| 		} | ||||
| 	 | ||||
| 		// Which cells are the title cells? | ||||
| 		if (typeof init.bSortCellsTop === 'boolean') { | ||||
| 			init.titleRow = init.bSortCellsTop; | ||||
| 		} | ||||
| 	 | ||||
| 		// Column search objects are in an array, so it needs to be converted | ||||
| 		// element by element | ||||
| 		var searchCols = init.aoSearchCols; | ||||
| @@ -3264,7 +3305,7 @@ | ||||
| 	 * @param {*} settings DataTables settings | ||||
| 	 * @param {*} source Source layout array | ||||
| 	 * @param {*} incColumns What columns should be included | ||||
| 	 * @returns Layout array | ||||
| 	 * @returns Layout array in column index order | ||||
| 	 */ | ||||
| 	function _fnHeaderLayout( settings, source, incColumns ) | ||||
| 	{ | ||||
| @@ -3548,7 +3589,9 @@ | ||||
| 	 | ||||
| 		_fnDraw( settings ); | ||||
| 	 | ||||
| 		settings._drawHold = false; | ||||
| 		settings.api.one('draw', function () { | ||||
| 			settings._drawHold = false; | ||||
| 		}); | ||||
| 	} | ||||
| 	 | ||||
| 	 | ||||
| @@ -3560,10 +3603,9 @@ | ||||
| 		var zero = oLang.sZeroRecords; | ||||
| 		var dataSrc = _fnDataSource( settings ); | ||||
| 	 | ||||
| 		if ( | ||||
| 			(settings.iDraw < 1 && dataSrc === 'ssp') || | ||||
| 			(settings.iDraw <= 1 && dataSrc === 'ajax') | ||||
| 		) { | ||||
| 		// Make use of the fact that settings.json is only set once the initial data has | ||||
| 		// been loaded. Show loading when that isn't the case | ||||
| 		if ((dataSrc === 'ssp' || dataSrc === 'ajax') && ! settings.json) { | ||||
| 			zero = oLang.sLoadingRecords; | ||||
| 		} | ||||
| 		else if ( oLang.sEmptyTable && settings.fnRecordsTotal() === 0 ) | ||||
| @@ -3933,6 +3975,7 @@ | ||||
| 		var rows = $(thead).children('tr'); | ||||
| 		var row, cell; | ||||
| 		var i, k, l, iLen, shifted, column, colspan, rowspan; | ||||
| 		var titleRow = settings.titleRow; | ||||
| 		var isHeader = thead && thead.nodeName.toLowerCase() === 'thead'; | ||||
| 		var layout = []; | ||||
| 		var unique; | ||||
| @@ -3961,6 +4004,7 @@ | ||||
| 					cell.nodeName.toUpperCase() == 'TH' | ||||
| 				) { | ||||
| 					var cols = []; | ||||
| 					var jqCell = $(cell); | ||||
| 	 | ||||
| 					// Get the col and rowspan attributes from the DOM and sanitise them | ||||
| 					colspan = cell.getAttribute('colspan') * 1; | ||||
| @@ -3981,7 +4025,7 @@ | ||||
| 					if ( write ) { | ||||
| 						if (unique) { | ||||
| 							// Allow column options to be set from HTML attributes | ||||
| 							_fnColumnOptions( settings, shifted, $(cell).data() ); | ||||
| 							_fnColumnOptions( settings, shifted, jqCell.data() ); | ||||
| 							 | ||||
| 							// Get the width for the column. This can be defined from the | ||||
| 							// width attribute, style attribute or `columns.width` option | ||||
| @@ -3998,7 +4042,14 @@ | ||||
| 								// Column title handling - can be user set, or read from the DOM | ||||
| 								// This happens before the render, so the original is still in place | ||||
| 								if ( columnDef.sTitle !== null && ! columnDef.autoTitle ) { | ||||
| 									cell.innerHTML = columnDef.sTitle; | ||||
| 									if ( | ||||
| 										(titleRow === true && i === 0) || // top row | ||||
| 										(titleRow === false && i === rows.length -1) || // bottom row | ||||
| 										(titleRow === i) || // specific row | ||||
| 										(titleRow === null) | ||||
| 									) { | ||||
| 										cell.innerHTML = columnDef.sTitle; | ||||
| 									} | ||||
| 								} | ||||
| 	 | ||||
| 								if (! columnDef.sTitle && unique) { | ||||
| @@ -4016,12 +4067,12 @@ | ||||
| 							// Fall back to the aria-label attribute on the table header if no ariaTitle is | ||||
| 							// provided. | ||||
| 							if (! columnDef.ariaTitle) { | ||||
| 								columnDef.ariaTitle = $(cell).attr("aria-label") || columnDef.sTitle; | ||||
| 								columnDef.ariaTitle = jqCell.attr("aria-label") || columnDef.sTitle; | ||||
| 							} | ||||
| 	 | ||||
| 							// Column specific class names | ||||
| 							if ( columnDef.className ) { | ||||
| 								$(cell).addClass( columnDef.className ); | ||||
| 								jqCell.addClass( columnDef.className ); | ||||
| 							} | ||||
| 						} | ||||
| 	 | ||||
| @@ -4033,11 +4084,28 @@ | ||||
| 								.appendTo(cell); | ||||
| 						} | ||||
| 	 | ||||
| 						if ( isHeader && $('span.dt-column-order', cell).length === 0) { | ||||
| 						if ( | ||||
| 							settings.orderIndicators && | ||||
| 							isHeader && | ||||
| 							jqCell.filter(':not([data-dt-order=disable])').length !== 0 && | ||||
| 							jqCell.parent(':not([data-dt-order=disable])').length !== 0 && | ||||
| 							$('span.dt-column-order', cell).length === 0 | ||||
| 						) { | ||||
| 							$('<span>') | ||||
| 								.addClass('dt-column-order') | ||||
| 								.appendTo(cell); | ||||
| 						} | ||||
| 	 | ||||
| 						// We need to wrap the elements in the header in another element to use flexbox | ||||
| 						// layout for those elements | ||||
| 						var headerFooter = isHeader ? 'header' : 'footer'; | ||||
| 	 | ||||
| 						if ( $('span.dt-column-' + headerFooter, cell).length === 0) { | ||||
| 							$('<div>') | ||||
| 								.addClass('dt-column-' + headerFooter) | ||||
| 								.append(cell.childNodes) | ||||
| 								.appendTo(cell); | ||||
| 						} | ||||
| 					} | ||||
| 	 | ||||
| 					// If there is col / rowspan, copy the information into the layout grid | ||||
| @@ -4188,6 +4256,11 @@ | ||||
| 		// Allow plug-ins and external processes to modify the data | ||||
| 		_fnCallbackFire( oSettings, null, 'preXhr', [oSettings, data, baseAjax], true ); | ||||
| 	 | ||||
| 		// Custom Ajax option to submit the parameters as a JSON string | ||||
| 		if (baseAjax.submitAs === 'json' && typeof data === 'object') { | ||||
| 			baseAjax.data = JSON.stringify(data); | ||||
| 		} | ||||
| 	 | ||||
| 		if ( typeof ajax === 'function' ) | ||||
| 		{ | ||||
| 			// Is a function - let the caller define what needs to be done | ||||
| @@ -5688,24 +5761,30 @@ | ||||
| 	function _fnSortInit( settings ) { | ||||
| 		var target = settings.nTHead; | ||||
| 		var headerRows = target.querySelectorAll('tr'); | ||||
| 		var legacyTop = settings.bSortCellsTop; | ||||
| 		var titleRow = settings.titleRow; | ||||
| 		var notSelector = ':not([data-dt-order="disable"]):not([data-dt-order="icon-only"])'; | ||||
| 		 | ||||
| 		// Legacy support for `orderCellsTop` | ||||
| 		if (legacyTop === true) { | ||||
| 		if (titleRow === true) { | ||||
| 			target = headerRows[0]; | ||||
| 		} | ||||
| 		else if (legacyTop === false) { | ||||
| 		else if (titleRow === false) { | ||||
| 			target = headerRows[ headerRows.length - 1 ]; | ||||
| 		} | ||||
| 		else if (titleRow !== null) { | ||||
| 			target = headerRows[titleRow]; | ||||
| 		} | ||||
| 		// else - all rows | ||||
| 	 | ||||
| 		_fnSortAttachListener( | ||||
| 			settings, | ||||
| 			target, | ||||
| 			target === settings.nTHead | ||||
| 				? 'tr'+notSelector+' th'+notSelector+', tr'+notSelector+' td'+notSelector | ||||
| 				: 'th'+notSelector+', td'+notSelector | ||||
| 		); | ||||
| 		if (settings.orderHandler) { | ||||
| 			_fnSortAttachListener( | ||||
| 				settings, | ||||
| 				target, | ||||
| 				target === settings.nTHead | ||||
| 					? 'tr'+notSelector+' th'+notSelector+', tr'+notSelector+' td'+notSelector | ||||
| 					: 'th'+notSelector+', td'+notSelector | ||||
| 			); | ||||
| 		} | ||||
| 	 | ||||
| 		// Need to resolve the user input array into our internal structure | ||||
| 		var order = []; | ||||
| @@ -5720,7 +5799,9 @@ | ||||
| 			var run = false; | ||||
| 			var columns = column === undefined | ||||
| 				? _fnColumnsFromHeader( e.target ) | ||||
| 				: [column]; | ||||
| 				: Array.isArray(column) | ||||
| 					? column | ||||
| 					: [column]; | ||||
| 	 | ||||
| 			if ( columns.length ) { | ||||
| 				for ( var i=0, ien=columns.length ; i<ien ; i++ ) { | ||||
| @@ -6343,16 +6424,19 @@ | ||||
| 	 | ||||
| 				// A column name was stored and should be used for restore | ||||
| 				if (typeof col[0] === 'string') { | ||||
| 					// Find the name from the current list of column names | ||||
| 					var idx = currentNames.indexOf(col[0]); | ||||
| 	 | ||||
| 					// Find the name from the current list of column names, or fallback to index 0 | ||||
| 					set[0] = idx >= 0 | ||||
| 						? idx | ||||
| 						: 0; | ||||
| 					if (idx < 0) { | ||||
| 						// If the column was not found ignore it and continue | ||||
| 						return; | ||||
| 					} | ||||
| 	 | ||||
| 					set[0] = idx; | ||||
| 				} | ||||
| 				else if (set[0] >= columns.length) { | ||||
| 					// If a column name, but it is out of bounds, set to 0 | ||||
| 					set[0] = 0; | ||||
| 					// If the column index is out of bounds ignore it and continue | ||||
| 					return; | ||||
| 				} | ||||
| 	 | ||||
| 				settings.aaSorting.push(set); | ||||
| @@ -6765,6 +6849,23 @@ | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	/** | ||||
| 	 * Add one or more listeners to the table | ||||
| 	 * | ||||
| 	 * @param {*} that JQ for the table | ||||
| 	 * @param {*} name Event name | ||||
| 	 * @param {*} src Listener(s) | ||||
| 	 */ | ||||
| 	function _fnListener(that, name, src) { | ||||
| 		if (!Array.isArray(src)) { | ||||
| 			src = [src]; | ||||
| 		} | ||||
| 	 | ||||
| 		for (i=0 ; i<src.length ; i++) { | ||||
| 			that.on(name + '.dt', src[i]); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	 | ||||
| 	 | ||||
| 	/** | ||||
| @@ -7421,12 +7522,24 @@ | ||||
| 		['footer', 'aoFooter'], | ||||
| 	].forEach(function (item) { | ||||
| 		_api_register( 'table().' + item[0] + '.structure()' , function (selector) { | ||||
| 			var indexes = this.columns(selector).indexes().flatten(); | ||||
| 			var indexes = this.columns(selector).indexes().flatten().toArray(); | ||||
| 			var ctx = this.context[0]; | ||||
| 			var structure = _fnHeaderLayout(ctx, ctx[item[1]], indexes); | ||||
| 	 | ||||
| 			return _fnHeaderLayout(ctx, ctx[item[1]], indexes); | ||||
| 		} ); | ||||
| 	}) | ||||
| 			// The structure is in column index order - but from this method we want the return to be | ||||
| 			// in the columns() selector API order. In order to do that we need to map from one form | ||||
| 			// to the other | ||||
| 			var orderedIndexes = indexes.slice().sort(function (a, b) { | ||||
| 				return a - b; | ||||
| 			}); | ||||
| 	 | ||||
| 			return structure.map(function (row) { | ||||
| 				return indexes.map(function (colIdx) { | ||||
| 					return row[orderedIndexes.indexOf(colIdx)]; | ||||
| 				}); | ||||
| 			}); | ||||
| 		}); | ||||
| 	}); | ||||
| 	 | ||||
| 	 | ||||
| 	_api_registerPlural( 'tables().containers()', 'table().container()' , function () { | ||||
| @@ -7775,7 +7888,7 @@ | ||||
| 	{ | ||||
| 		var | ||||
| 			out = [], res, | ||||
| 			a, i, ien, j, jen, | ||||
| 			i, ien, | ||||
| 			selectorType = typeof selector; | ||||
| 	 | ||||
| 		// Can't just check for isArray here, as an API or jQuery instance might be | ||||
| @@ -7785,22 +7898,15 @@ | ||||
| 		} | ||||
| 	 | ||||
| 		for ( i=0, ien=selector.length ; i<ien ; i++ ) { | ||||
| 			// Only split on simple strings - complex expressions will be jQuery selectors | ||||
| 			a = selector[i] && selector[i].split && ! selector[i].match(/[[(:]/) ? | ||||
| 				selector[i].split(',') : | ||||
| 				[ selector[i] ]; | ||||
| 			res = selectFn( typeof selector[i] === 'string' ? selector[i].trim() : selector[i] ); | ||||
| 	 | ||||
| 			for ( j=0, jen=a.length ; j<jen ; j++ ) { | ||||
| 				res = selectFn( typeof a[j] === 'string' ? (a[j]).trim() : a[j] ); | ||||
| 			// Remove empty items | ||||
| 			res = res.filter( function (item) { | ||||
| 				return item !== null && item !== undefined; | ||||
| 			}); | ||||
| 	 | ||||
| 				// Remove empty items | ||||
| 				res = res.filter( function (item) { | ||||
| 					return item !== null && item !== undefined; | ||||
| 				}); | ||||
| 	 | ||||
| 				if ( res && res.length ) { | ||||
| 					out = out.concat( res ); | ||||
| 				} | ||||
| 			if ( res && res.length ) { | ||||
| 				out = out.concat( res ); | ||||
| 			} | ||||
| 		} | ||||
| 	 | ||||
| @@ -7829,6 +7935,7 @@ | ||||
| 		} | ||||
| 	 | ||||
| 		return $.extend( { | ||||
| 			columnOrder: 'implied', | ||||
| 			search: 'none', | ||||
| 			order: 'current', | ||||
| 			page: 'all' | ||||
| @@ -8590,23 +8697,60 @@ | ||||
| 	 | ||||
| 	var __column_header = function ( settings, column, row ) { | ||||
| 		var header = settings.aoHeader; | ||||
| 		var target = row !== undefined | ||||
| 			? row | ||||
| 			: settings.bSortCellsTop // legacy support | ||||
| 				? 0 | ||||
| 				: header.length - 1; | ||||
| 		var titleRow = settings.titleRow; | ||||
| 		var target = null; | ||||
| 	 | ||||
| 		if (row !== undefined) { | ||||
| 			target = row; | ||||
| 		} | ||||
| 		else if (titleRow === true) { // legacy orderCellsTop support | ||||
| 			target = 0; | ||||
| 		} | ||||
| 		else if (titleRow === false) { | ||||
| 			target = header.length - 1; | ||||
| 		} | ||||
| 		else if (titleRow !== null) { | ||||
| 			target = titleRow; | ||||
| 		} | ||||
| 		else { | ||||
| 			// Automatic - find the _last_ unique cell from the top that is not empty (last for | ||||
| 			// backwards compatibility) | ||||
| 			for (var i=0 ; i<header.length ; i++) { | ||||
| 				if (header[i][column].unique && $('span.dt-column-title', header[i][column].cell).text()) { | ||||
| 					target = i; | ||||
| 				} | ||||
| 			} | ||||
| 	 | ||||
| 			if (target === null) { | ||||
| 				target = 0; | ||||
| 			} | ||||
| 		} | ||||
| 	 | ||||
| 		return header[target][column].cell; | ||||
| 	}; | ||||
| 	 | ||||
| 	var __column_header_cells = function (header) { | ||||
| 		var out = []; | ||||
| 	 | ||||
| 		for (var i=0 ; i<header.length ; i++) { | ||||
| 			for (var j=0 ; j<header[i].length ; j++) { | ||||
| 				var cell = header[i][j].cell; | ||||
| 	 | ||||
| 				if (!out.includes(cell)) { | ||||
| 					out.push(cell); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	 | ||||
| 		return out; | ||||
| 	} | ||||
| 	 | ||||
| 	var __column_selector = function ( settings, selector, opts ) | ||||
| 	{ | ||||
| 		var | ||||
| 			columns = settings.aoColumns, | ||||
| 			names = _pluck( columns, 'sName' ), | ||||
| 			titles = _pluck( columns, 'sTitle' ), | ||||
| 			cells = DataTable.util.get('[].[].cell')(settings.aoHeader), | ||||
| 			nodes = _unique( _flatten([], cells) ); | ||||
| 			names, titles, | ||||
| 			nodes = __column_header_cells(settings.aoHeader); | ||||
| 		 | ||||
| 		var run = function ( s ) { | ||||
| 			var selInt = _intVal( s ); | ||||
| @@ -8678,12 +8822,21 @@ | ||||
| 						} ); | ||||
| 	 | ||||
| 					case 'name': | ||||
| 						// Don't get names, unless needed, and only get once if it is | ||||
| 						if (!names) { | ||||
| 							names = _pluck( columns, 'sName' ); | ||||
| 						} | ||||
| 	 | ||||
| 						// match by name. `names` is column index complete and in order | ||||
| 						return names.map( function (name, i) { | ||||
| 							return name === match[1] ? i : null; | ||||
| 						} ); | ||||
| 	 | ||||
| 					case 'title': | ||||
| 						if (!titles) { | ||||
| 							titles = _pluck( columns, 'sTitle' ); | ||||
| 						} | ||||
| 	 | ||||
| 						// match by column title | ||||
| 						return titles.map( function (title, i) { | ||||
| 							return title === match[1] ? i : null; | ||||
| @@ -8722,7 +8875,11 @@ | ||||
| 				[]; | ||||
| 		}; | ||||
| 	 | ||||
| 		return _selector_run( 'column', selector, run, settings, opts ); | ||||
| 		var selected = _selector_run( 'column', selector, run, settings, opts ); | ||||
| 	 | ||||
| 		return opts.columnOrder && opts.columnOrder === 'index' | ||||
| 			? selected.sort(function (a, b) { return a - b; }) | ||||
| 			: selected; // implied | ||||
| 	}; | ||||
| 	 | ||||
| 	 | ||||
| @@ -8846,6 +9003,12 @@ | ||||
| 		}, 1 ); | ||||
| 	} ); | ||||
| 	 | ||||
| 	_api_registerPlural( 'columns().names()', 'column().name()', function () { | ||||
| 		return this.iterator( 'column', function ( settings, column ) { | ||||
| 			return settings.aoColumns[column].sName; | ||||
| 		}, 1 ); | ||||
| 	} ); | ||||
| 	 | ||||
| 	_api_registerPlural( 'columns().nodes()', 'column().nodes()', function () { | ||||
| 		return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { | ||||
| 			return _pluck_order( settings.aoData, rows, 'anCells', column ) ; | ||||
| @@ -9272,7 +9435,10 @@ | ||||
| 		// otherwise a 2D array was passed in | ||||
| 	 | ||||
| 		return this.iterator( 'table', function ( settings ) { | ||||
| 			settings.aaSorting = Array.isArray(order) ? order.slice() : order; | ||||
| 			var resolved = []; | ||||
| 			_fnSortResolve(settings, resolved, order); | ||||
| 	 | ||||
| 			settings.aaSorting = resolved; | ||||
| 		} ); | ||||
| 	} ); | ||||
| 	 | ||||
| @@ -9398,7 +9564,7 @@ | ||||
| 			var fixed = settings.searchFixed; | ||||
| 	 | ||||
| 			if (! name) { | ||||
| 				return Object.keys(fixed) | ||||
| 				return Object.keys(fixed); | ||||
| 			} | ||||
| 			else if (search === undefined) { | ||||
| 				return fixed[name]; | ||||
| @@ -9465,10 +9631,10 @@ | ||||
| 				var fixed = settings.aoColumns[colIdx].searchFixed; | ||||
| 	 | ||||
| 				if (! name) { | ||||
| 					return Object.keys(fixed) | ||||
| 					return Object.keys(fixed); | ||||
| 				} | ||||
| 				else if (search === undefined) { | ||||
| 					return fixed[name]; | ||||
| 					return fixed[name] || null; | ||||
| 				} | ||||
| 				else if (search === null) { | ||||
| 					delete fixed[name]; | ||||
| @@ -9920,14 +10086,9 @@ | ||||
| 				jqTable.append( tfoot ); | ||||
| 			} | ||||
| 	 | ||||
| 			// Clean up the header | ||||
| 			$(thead).find('span.dt-column-order').remove(); | ||||
| 			$(thead).find('span.dt-column-title').each(function () { | ||||
| 				var title = $(this).html(); | ||||
| 				$(this).parent().append(title); | ||||
| 				$(this).remove(); | ||||
| 			}); | ||||
| 	 | ||||
| 			// Clean up the header / footer | ||||
| 			cleanHeader(thead, 'header'); | ||||
| 			cleanHeader(tfoot, 'footer'); | ||||
| 			settings.colgroup.remove(); | ||||
| 	 | ||||
| 			settings.aaSorting = []; | ||||
| @@ -9949,7 +10110,6 @@ | ||||
| 					orderClasses.isDesc | ||||
| 				) | ||||
| 				.css('width', '') | ||||
| 				.removeAttr('data-dt-column') | ||||
| 				.removeAttr('aria-sort'); | ||||
| 	 | ||||
| 			// Add the TR elements back into the table in their original order | ||||
| @@ -10030,6 +10190,19 @@ | ||||
| 			: resolved; | ||||
| 	} ); | ||||
| 	 | ||||
| 	// Needed for header and footer, so pulled into its own function | ||||
| 	function cleanHeader(node, className) { | ||||
| 		$(node).find('span.dt-column-order').remove(); | ||||
| 		$(node).find('span.dt-column-title').each(function () { | ||||
| 			var title = $(this).html(); | ||||
| 			$(this).parent().parent().append(title); | ||||
| 			$(this).remove(); | ||||
| 		}); | ||||
| 		$(node).find('div.dt-column-' + className).remove(); | ||||
| 	 | ||||
| 		$('th, td', node).removeAttr('data-dt-column'); | ||||
| 	} | ||||
| 	 | ||||
| 	/** | ||||
| 	 * Version string for plug-ins to check compatibility. Allowed format is | ||||
| 	 * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used | ||||
| @@ -10038,7 +10211,7 @@ | ||||
| 	 *  @type string | ||||
| 	 *  @default Version number | ||||
| 	 */ | ||||
| 	DataTable.version = "2.2.2"; | ||||
| 	DataTable.version = "2.3.1"; | ||||
| 	 | ||||
| 	/** | ||||
| 	 * Private data store, containing all of the settings objects that are | ||||
| @@ -10645,6 +10818,10 @@ | ||||
| 		"bSortCellsTop": null, | ||||
| 	 | ||||
| 	 | ||||
| 		/** Specify which row is the title row in the header. Replacement for bSortCellsTop */ | ||||
| 		titleRow: null, | ||||
| 	 | ||||
| 	 | ||||
| 		/** | ||||
| 		 * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and | ||||
| 		 * `sorting\_3` to the columns which are currently being sorted on. This is | ||||
| @@ -10922,6 +11099,13 @@ | ||||
| 				1: "entry" | ||||
| 			}, | ||||
| 	 | ||||
| 			/** | ||||
| 			 * Page length options | ||||
| 			 */ | ||||
| 			lengthLabels: { | ||||
| 				'-1': 'All' | ||||
| 			}, | ||||
| 	 | ||||
| 			/** | ||||
| 			 * This string is shown in preference to `zeroRecords` when the table is | ||||
| 			 * empty of data (regardless of filtering). Note that this is an optional | ||||
| @@ -11192,7 +11376,10 @@ | ||||
| 		/** | ||||
| 		 * For server-side processing - use the data from the DOM for the first draw | ||||
| 		 */ | ||||
| 		iDeferLoading: null | ||||
| 		iDeferLoading: null, | ||||
| 	 | ||||
| 		/** Event listeners */ | ||||
| 		on: null | ||||
| 	}; | ||||
| 	 | ||||
| 	_fnHungarianMap( DataTable.defaults ); | ||||
| @@ -12019,10 +12206,7 @@ | ||||
| 	 | ||||
| 		/** | ||||
| 		 * Indicate that if multiple rows are in the header and there is more than | ||||
| 		 * one unique cell per column, if the top one (true) or bottom one (false) | ||||
| 		 * should be used for sorting / title by DataTables. | ||||
| 		 * Note that this parameter will be set by the initialisation routine. To | ||||
| 		 * set a default use {@link DataTable.defaults}. | ||||
| 		 * one unique cell per column. Replaced by titleRow | ||||
| 		 */ | ||||
| 		"bSortCellsTop": null, | ||||
| 	 | ||||
| @@ -12147,7 +12331,19 @@ | ||||
| 		resizeObserver: null, | ||||
| 	 | ||||
| 		/** Keep a record of the last size of the container, so we can skip duplicates */ | ||||
| 		containerWidth: -1 | ||||
| 		containerWidth: -1, | ||||
| 	 | ||||
| 		/** Reverse the initial order of the data set on desc ordering */ | ||||
| 		orderDescReverse: null, | ||||
| 	 | ||||
| 		/** Show / hide ordering indicators in headers */ | ||||
| 		orderIndicators: true, | ||||
| 	 | ||||
| 		/** Default ordering listener */ | ||||
| 		orderHandler: true, | ||||
| 	 | ||||
| 		/** Title row indicator */ | ||||
| 		titleRow: null | ||||
| 	}; | ||||
| 	 | ||||
| 	/** | ||||
| @@ -12977,7 +13173,7 @@ | ||||
| 					cell.addClass(classes.order.none); | ||||
| 				} | ||||
| 	 | ||||
| 				var legacyTop = settings.bSortCellsTop; | ||||
| 				var titleRow = settings.titleRow; | ||||
| 				var headerRows = cell.closest('thead').find('tr'); | ||||
| 				var rowIdx = cell.parent().index(); | ||||
| 	 | ||||
| @@ -12987,11 +13183,10 @@ | ||||
| 					cell.attr('data-dt-order') === 'disable' || | ||||
| 					cell.parent().attr('data-dt-order') === 'disable' || | ||||
| 	 | ||||
| 					// Legacy support for `orderCellsTop`. If it is set, then cells | ||||
| 					// which are not in the top or bottom row of the header (depending | ||||
| 					// on the value) do not get the sorting classes applied to them | ||||
| 					(legacyTop === true && rowIdx !== 0) || | ||||
| 					(legacyTop === false && rowIdx !== headerRows.length - 1) | ||||
| 					// titleRow support, for defining a specific row in the header | ||||
| 					(titleRow === true && rowIdx !== 0) || | ||||
| 					(titleRow === false && rowIdx !== headerRows.length - 1) || | ||||
| 					(typeof titleRow === 'number' && rowIdx !== titleRow) | ||||
| 				) { | ||||
| 					return; | ||||
| 				} | ||||
| @@ -13001,7 +13196,7 @@ | ||||
| 				// `DT` namespace will allow the event to be removed automatically | ||||
| 				// on destroy, while the `dt` namespaced event is the one we are | ||||
| 				// listening for | ||||
| 				$(settings.nTable).on( 'order.dt.DT column-visibility.dt.DT', function ( e, ctx ) { | ||||
| 				$(settings.nTable).on( 'order.dt.DT column-visibility.dt.DT', function ( e, ctx, column ) { | ||||
| 					if ( settings !== ctx ) { // need to check this this is the host | ||||
| 						return;               // table, not a nested one | ||||
| 					} | ||||
| @@ -13012,6 +13207,16 @@ | ||||
| 						return; | ||||
| 					} | ||||
| 	 | ||||
| 					var orderedColumns = _pluck(sorting, 'col'); | ||||
| 	 | ||||
| 					// This handler is only needed on column visibility if the column is part of the | ||||
| 					// ordering. If it isn't, then we can bail out to save performance. It could be a | ||||
| 					// separate event handler, but this is a balance between code reuse / size and performance | ||||
| 					// console.log(e, e.name, column, orderedColumns, orderedColumns.includes(column)) | ||||
| 					if (e.type === 'column-visibility' && ! orderedColumns.includes(column)) { | ||||
| 						return; | ||||
| 					} | ||||
| 	 | ||||
| 					var i; | ||||
| 					var orderClasses = classes.order; | ||||
| 					var columns = ctx.api.columns( cell ); | ||||
| @@ -13020,8 +13225,8 @@ | ||||
| 					var ariaType = ''; | ||||
| 					var indexes = columns.indexes(); | ||||
| 					var sortDirs = columns.orderable(true).flatten(); | ||||
| 					var orderedColumns = _pluck(sorting, 'col'); | ||||
| 					var tabIndex = settings.iTabIndex; | ||||
| 					var canOrder = ctx.orderHandler && orderable; | ||||
| 	 | ||||
| 					cell | ||||
| 						.removeClass( | ||||
| @@ -13029,8 +13234,8 @@ | ||||
| 							orderClasses.isDesc | ||||
| 						) | ||||
| 						.toggleClass( orderClasses.none, ! orderable ) | ||||
| 						.toggleClass( orderClasses.canAsc, orderable && sortDirs.includes('asc') ) | ||||
| 						.toggleClass( orderClasses.canDesc, orderable && sortDirs.includes('desc') ); | ||||
| 						.toggleClass( orderClasses.canAsc, canOrder && sortDirs.includes('asc') ) | ||||
| 						.toggleClass( orderClasses.canDesc, canOrder && sortDirs.includes('desc') ); | ||||
| 	 | ||||
| 					// Determine if all of the columns that this cell covers are included in the | ||||
| 					// current ordering | ||||
| @@ -13789,12 +13994,17 @@ | ||||
| 		} ); | ||||
| 	 | ||||
| 		for ( i=0 ; i<lengths.length ; i++ ) { | ||||
| 			select[0][ i ] = new Option( | ||||
| 				typeof language[i] === 'number' ? | ||||
| 			// Attempt to look up the length from the i18n options | ||||
| 			var label = settings.api.i18n('lengthLabels.' + lengths[i], null); | ||||
| 	 | ||||
| 			if (label === null) { | ||||
| 				// If not present, fallback to old style | ||||
| 				label = typeof language[i] === 'number' ? | ||||
| 					settings.fnFormatNumber( language[i] ) : | ||||
| 					language[i], | ||||
| 				lengths[i] | ||||
| 			); | ||||
| 					language[i]; | ||||
| 			} | ||||
| 	 | ||||
| 			select[0][ i ] = new Option(label, lengths[i]); | ||||
| 		} | ||||
| 	 | ||||
| 		// add for and id to label and input | ||||
|   | ||||
| @@ -24,6 +24,7 @@ | ||||
|                     <dt class="col-sm-5">Web Installed | ||||
|                         <span class="badge bg-success d-none" id="web-success" title="Latest version is installed.">Ok</span> | ||||
|                         <span class="badge bg-warning text-dark d-none" id="web-warning" title="There seems to be an update available.">Update</span> | ||||
|                         <span class="badge bg-info text-dark d-none" id="web-prerelease" title="You seem to be using a pre-release version.">Pre-Release</span> | ||||
|                     </dt> | ||||
|                     <dd class="col-sm-7"> | ||||
|                         <span id="web-installed">{{page_data.web_vault_version}}</span> | ||||
| @@ -68,10 +69,14 @@ | ||||
|                         <span class="d-block"><b>No</b></span> | ||||
|                     {{/unless}} | ||||
|                     </dd> | ||||
|                     <dt class="col-sm-5">Environment settings overridden</dt> | ||||
|                     <dt class="col-sm-5">Uses config.json | ||||
|                     {{#if page_data.overrides}} | ||||
|                         <span class="badge bg-info text-dark" title="Environment variables are overwritten by a config.json.">Note</span> | ||||
|                     {{/if}} | ||||
|                     </dt> | ||||
|                     <dd class="col-sm-7"> | ||||
|                     {{#if page_data.overrides}} | ||||
|                         <span class="d-block" title="The following settings are overridden: {{page_data.overrides}}"><b>Yes</b></span> | ||||
|                         <abbr class="d-block" title="The following settings are overridden: {{page_data.overrides}}"><b>Yes</b></abbr> | ||||
|                     {{/if}} | ||||
|                     {{#unless page_data.overrides}} | ||||
|                         <span class="d-block"><b>No</b></span> | ||||
| @@ -154,7 +159,11 @@ | ||||
|                     <dd class="col-sm-7"> | ||||
|                         <span id="dns-resolved">{{page_data.dns_resolved}}</span> | ||||
|                     </dd> | ||||
|                     <dt class="col-sm-5">Date & Time (Local)</dt> | ||||
|                     <dt class="col-sm-5">Date & Time (Local) | ||||
|                         {{#if page_data.tz_env}} | ||||
|                             <span class="badge bg-success" title="Configured TZ environment variable">{{page_data.tz_env}}</span> | ||||
|                         {{/if}} | ||||
|                     </dt> | ||||
|                     <dd class="col-sm-7"> | ||||
|                         <span><b>Server:</b> {{page_data.server_time_local}}</span> | ||||
|                     </dd> | ||||
|   | ||||
| @@ -43,7 +43,7 @@ | ||||
|                             <span class="d-block"><strong>Groups:</strong> {{group_count}}</span> | ||||
|                             <span class="d-block"><strong>Events:</strong> {{event_count}}</span> | ||||
|                         </td> | ||||
|                         <td class="text-end px-0 small"> | ||||
|                         <td class="text-end px-1 small"> | ||||
|                             <button type="button" class="btn btn-sm btn-link p-0 border-0 float-right" vw-delete-organization data-vw-org-uuid="{{id}}" data-vw-org-name="{{name}}" data-vw-billing-email="{{billingEmail}}">Delete Organization</button><br> | ||||
|                         </td> | ||||
|                     </tr> | ||||
|   | ||||
| @@ -60,7 +60,7 @@ | ||||
|                             {{/each}} | ||||
|                             </div> | ||||
|                         </td> | ||||
|                         <td class="text-end px-0 small"> | ||||
|                         <td class="text-end px-1 small"> | ||||
|                             <span data-vw-user-uuid="{{id}}" data-vw-user-email="{{email}}"> | ||||
|                                 {{#if twoFactorEnabled}} | ||||
|                                 <button type="button" class="btn btn-sm btn-link p-0 border-0 float-right" vw-remove2fa>Remove all 2FA</button><br> | ||||
|   | ||||
| @@ -82,15 +82,19 @@ bit-nav-logo bit-nav-item .bwi-shield { | ||||
| {{#if signup_disabled}} | ||||
| /* From web vault 2025.1.2 and onwards, the signup button is hidden | ||||
|   when signups are disabled as the web vault checks the /api/config endpoint. | ||||
|   Note that the clients tend to aggressively cache this endpoint, so it might | ||||
|   Note that the clients tend to cache this endpoint for about 1 hour, so it might | ||||
|   take a while for the change to take effect. To avoid the button appearing | ||||
|   when it shouldn't, we'll keep this style in place for a couple of versions */ | ||||
| {{#if webver "<2025.3.0"}} | ||||
| /* Hide the register link on the login screen */ | ||||
| {{#if (webver "<2025.3.0")}} | ||||
| app-login form div + div + div + div + hr, | ||||
| app-login form div + div + div + div + hr + p { | ||||
|   @extend %vw-hide; | ||||
| } | ||||
| {{else}} | ||||
| app-root a[routerlink="/signup"] { | ||||
|   @extend %vw-hide; | ||||
| } | ||||
| {{/if}} | ||||
| {{/if}} | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user