Update admin interface

- Updated the admin interface dependencies.
- Replace bootstrap-native with bootstrap
- Added auto theme with an option to switch to dark/light
- Some small color changes
- Added an dev only function to always load static files from disk
This commit is contained in:
BlackDex 2023-07-27 22:51:22 +02:00
parent f579a4154c
commit 83d5432cbf
No known key found for this signature in database
GPG key ID: 58C80A2AA6C765E1
16 changed files with 9163 additions and 7737 deletions

View file

@ -14,11 +14,17 @@ use crate::{
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
// If addding more routes here, consider also adding them to // If addding more routes here, consider also adding them to
// crate::utils::LOGGED_ROUTES to make sure they appear in the log // crate::utils::LOGGED_ROUTES to make sure they appear in the log
let mut routes = routes![attachments, alive, alive_head, static_files];
if CONFIG.web_vault_enabled() { if CONFIG.web_vault_enabled() {
routes![web_index, web_index_head, app_id, web_files, attachments, alive, alive_head, static_files] routes.append(&mut routes![web_index, web_index_head, app_id, web_files]);
} else {
routes![attachments, alive, alive_head, static_files]
} }
#[cfg(debug_assertions)]
if CONFIG.reload_templates() {
routes.append(&mut routes![_static_files_dev]);
}
routes
} }
pub fn catchers() -> Vec<Catcher> { pub fn catchers() -> Vec<Catcher> {
@ -116,7 +122,30 @@ fn alive_head(_conn: DbConn) -> EmptyResult {
Ok(()) Ok(())
} }
#[get("/vw_static/<filename>")] // This endpoint/function is used during development and development only.
// It allows to easily develop the admin interface by always loading the files from disk instead from a slice of bytes
// This will only be active during a debug build and only when `RELOAD_TEMPLATES` is set to `true`
// NOTE: Do not forget to add any new files added to the `static_files` function below!
#[cfg(debug_assertions)]
#[get("/vw_static/<filename>", rank = 1)]
pub async fn _static_files_dev(filename: PathBuf) -> Option<NamedFile> {
warn!("LOADING STATIC FILES FROM DISK");
let file = filename.to_str().unwrap_or_default();
let ext = filename.extension().unwrap_or_default();
let path = if ext == "png" || ext == "svg" {
tokio::fs::canonicalize(Path::new(file!()).parent().unwrap().join("../static/images/").join(file)).await
} else {
tokio::fs::canonicalize(Path::new(file!()).parent().unwrap().join("../static/scripts/").join(file)).await
};
if let Ok(path) = path {
return NamedFile::open(path).await.ok();
};
None
}
#[get("/vw_static/<filename>", rank = 2)]
pub fn static_files(filename: &str) -> Result<(ContentType, &'static [u8]), Error> { pub fn static_files(filename: &str) -> Result<(ContentType, &'static [u8]), Error> {
match filename { match filename {
"404.png" => Ok((ContentType::PNG, include_bytes!("../static/images/404.png"))), "404.png" => Ok((ContentType::PNG, include_bytes!("../static/images/404.png"))),
@ -138,12 +167,12 @@ pub fn static_files(filename: &str) -> Result<(ContentType, &'static [u8]), Erro
Ok((ContentType::JavaScript, include_bytes!("../static/scripts/admin_diagnostics.js"))) Ok((ContentType::JavaScript, include_bytes!("../static/scripts/admin_diagnostics.js")))
} }
"bootstrap.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/bootstrap.css"))), "bootstrap.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/bootstrap.css"))),
"bootstrap-native.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/bootstrap-native.js"))), "bootstrap.bundle.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/bootstrap.bundle.js"))),
"jdenticon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon.js"))), "jdenticon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon.js"))),
"datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))), "datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))),
"datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))), "datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
"jquery-3.6.4.slim.js" => { "jquery-3.7.0.slim.js" => {
Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.4.slim.js"))) Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.7.0.slim.js")))
} }
_ => err!(format!("Static file not found: {filename}")), _ => err!(format!("Static file not found: {filename}")),
} }

View file

@ -65,8 +65,79 @@ function _post(url, successMsg, errMsg, body, reload_page = true) {
}); });
} }
// Bootstrap Theme Selector
const getStoredTheme = () => localStorage.getItem("theme");
const setStoredTheme = theme => localStorage.setItem("theme", theme);
const getPreferredTheme = () => {
const storedTheme = getStoredTheme();
if (storedTheme) {
return storedTheme;
}
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
};
const setTheme = theme => {
if (theme === "auto" && window.matchMedia("(prefers-color-scheme: dark)").matches) {
document.documentElement.setAttribute("data-bs-theme", "dark");
} else {
document.documentElement.setAttribute("data-bs-theme", theme);
}
};
setTheme(getPreferredTheme());
const showActiveTheme = (theme, focus = false) => {
const themeSwitcher = document.querySelector("#bd-theme");
if (!themeSwitcher) {
return;
}
const themeSwitcherText = document.querySelector("#bd-theme-text");
const activeThemeIcon = document.querySelector(".theme-icon-active use");
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`);
const svgOfActiveBtn = btnToActive.querySelector("span use").innerText;
document.querySelectorAll("[data-bs-theme-value]").forEach(element => {
element.classList.remove("active");
element.setAttribute("aria-pressed", "false");
});
btnToActive.classList.add("active");
btnToActive.setAttribute("aria-pressed", "true");
activeThemeIcon.innerText = svgOfActiveBtn;
const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})`;
themeSwitcher.setAttribute("aria-label", themeSwitcherLabel);
if (focus) {
themeSwitcher.focus();
}
};
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
const storedTheme = getStoredTheme();
if (storedTheme !== "light" && storedTheme !== "dark") {
setTheme(getPreferredTheme());
}
});
// onLoad events // onLoad events
document.addEventListener("DOMContentLoaded", (/*event*/) => { document.addEventListener("DOMContentLoaded", (/*event*/) => {
showActiveTheme(getPreferredTheme());
document.querySelectorAll("[data-bs-theme-value]")
.forEach(toggle => {
toggle.addEventListener("click", () => {
const theme = toggle.getAttribute("data-bs-theme-value");
setStoredTheme(theme);
setTheme(theme);
showActiveTheme(theme, true);
});
});
// get current URL path and assign "active" class to the correct nav-item // get current URL path and assign "active" class to the correct nav-item
const pathname = window.location.pathname; const pathname = window.location.pathname;
if (pathname === "") return; if (pathname === "") return;

View file

@ -1,6 +1,6 @@
"use strict"; "use strict";
/* eslint-env es2017, browser */ /* eslint-env es2017, browser */
/* global BASE_URL:readable, BSN:readable */ /* global BASE_URL:readable, bootstrap:readable */
var dnsCheck = false; var dnsCheck = false;
var timeCheck = false; var timeCheck = false;
@ -135,7 +135,7 @@ function copyToClipboard(event) {
document.execCommand("copy"); document.execCommand("copy");
tmpCopyEl.remove(); tmpCopyEl.remove();
new BSN.Toast("#toastClipboardCopy").show(); new bootstrap.Toast("#toastClipboardCopy").show();
} }
function checkTimeDrift(utcTimeA, utcTimeB, statusPrefix) { function checkTimeDrift(utcTimeA, utcTimeB, statusPrefix) {

View file

@ -141,19 +141,20 @@ function resendUserInvite (event) {
const ORG_TYPES = { const ORG_TYPES = {
"0": { "0": {
"name": "Owner", "name": "Owner",
"color": "orange" "bg": "orange",
"font": "black"
}, },
"1": { "1": {
"name": "Admin", "name": "Admin",
"color": "blueviolet" "bg": "blueviolet"
}, },
"2": { "2": {
"name": "User", "name": "User",
"color": "blue" "bg": "blue"
}, },
"3": { "3": {
"name": "Manager", "name": "Manager",
"color": "green" "bg": "green"
}, },
}; };
@ -227,7 +228,10 @@ function initUserTable() {
// Color all the org buttons per type // Color all the org buttons per type
document.querySelectorAll("button[data-vw-org-type]").forEach(function(e) { document.querySelectorAll("button[data-vw-org-type]").forEach(function(e) {
const orgType = ORG_TYPES[e.dataset.vwOrgType]; const orgType = ORG_TYPES[e.dataset.vwOrgType];
e.style.backgroundColor = orgType.color; e.style.backgroundColor = orgType.bg;
if (orgType.font !== undefined) {
e.style.color = orgType.font;
}
e.title = orgType.name; e.title = orgType.name;
}); });

File diff suppressed because it is too large Load diff

6313
src/static/scripts/bootstrap.bundle.js vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -4,10 +4,10 @@
* *
* To rebuild or modify this file with the latest versions of the included * To rebuild or modify this file with the latest versions of the included
* software please visit: * software please visit:
* https://datatables.net/download/#bs5/dt-1.13.4 * https://datatables.net/download/#bs5/dt-1.13.6
* *
* Included libraries: * Included libraries:
* DataTables 1.13.4 * DataTables 1.13.6
*/ */
@charset "UTF-8"; @charset "UTF-8";
@ -15,6 +15,13 @@
--dt-row-selected: 13, 110, 253; --dt-row-selected: 13, 110, 253;
--dt-row-selected-text: 255, 255, 255; --dt-row-selected-text: 255, 255, 255;
--dt-row-selected-link: 9, 10, 11; --dt-row-selected-link: 9, 10, 11;
--dt-row-stripe: 0, 0, 0;
--dt-row-hover: 0, 0, 0;
--dt-column-ordering: 0, 0, 0;
--dt-html-background: white;
}
:root.dark {
--dt-html-background: rgb(33, 37, 41);
} }
table.dataTable td.dt-control { table.dataTable td.dt-control {
@ -22,25 +29,19 @@ table.dataTable td.dt-control {
cursor: pointer; cursor: pointer;
} }
table.dataTable td.dt-control:before { table.dataTable td.dt-control:before {
height: 1em;
width: 1em;
margin-top: -9px;
display: inline-block; display: inline-block;
color: white; color: rgba(0, 0, 0, 0.5);
border: 0.15em solid white; content: "►";
border-radius: 1em;
box-shadow: 0 0 0.2em #444;
box-sizing: content-box;
text-align: center;
text-indent: 0 !important;
font-family: "Courier New", Courier, monospace;
line-height: 1em;
content: "+";
background-color: #31b131;
} }
table.dataTable tr.dt-hasChild td.dt-control:before { table.dataTable tr.dt-hasChild td.dt-control:before {
content: "-"; content: "▼";
background-color: #d33333; }
html.dark table.dataTable td.dt-control:before {
color: rgba(255, 255, 255, 0.5);
}
html.dark table.dataTable tr.dt-hasChild td.dt-control:before {
color: rgba(255, 255, 255, 0.5);
} }
table.dataTable thead > tr > th.sorting, table.dataTable thead > tr > th.sorting_asc, table.dataTable thead > tr > th.sorting_desc, table.dataTable thead > tr > th.sorting_asc_disabled, table.dataTable thead > tr > th.sorting_desc_disabled, table.dataTable thead > tr > th.sorting, table.dataTable thead > tr > th.sorting_asc, table.dataTable thead > tr > th.sorting_desc, table.dataTable thead > tr > th.sorting_asc_disabled, table.dataTable thead > tr > th.sorting_desc_disabled,
@ -303,14 +304,14 @@ table.dataTable > tbody > tr.selected a {
color: rgb(var(--dt-row-selected-link)); color: rgb(var(--dt-row-selected-link));
} }
table.dataTable.table-striped > tbody > tr.odd > * { table.dataTable.table-striped > tbody > tr.odd > * {
box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.05); box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.05);
} }
table.dataTable.table-striped > tbody > tr.odd.selected > * { table.dataTable.table-striped > tbody > tr.odd.selected > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95); box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95);
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.95); box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.95);
} }
table.dataTable.table-hover > tbody > tr:hover > * { table.dataTable.table-hover > tbody > tr:hover > * {
box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.075); box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.075);
} }
table.dataTable.table-hover > tbody > tr.selected:hover > * { table.dataTable.table-hover > tbody > tr.selected:hover > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975); box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975);
@ -441,4 +442,10 @@ div.table-responsive > div.dataTables_wrapper > div.row > div[class^=col-]:last-
padding-right: 0; padding-right: 0;
} }
:root[data-bs-theme=dark] {
--dt-row-hover: 255, 255, 255;
--dt-row-stripe: 255, 255, 255;
--dt-column-ordering: 255, 255, 255;
}

View file

@ -4,20 +4,20 @@
* *
* To rebuild or modify this file with the latest versions of the included * To rebuild or modify this file with the latest versions of the included
* software please visit: * software please visit:
* https://datatables.net/download/#bs5/dt-1.13.4 * https://datatables.net/download/#bs5/dt-1.13.6
* *
* Included libraries: * Included libraries:
* DataTables 1.13.4 * DataTables 1.13.6
*/ */
/*! DataTables 1.13.4 /*! DataTables 1.13.6
* ©2008-2023 SpryMedia Ltd - datatables.net/license * ©2008-2023 SpryMedia Ltd - datatables.net/license
*/ */
/** /**
* @summary DataTables * @summary DataTables
* @description Paginate, search and order HTML tables * @description Paginate, search and order HTML tables
* @version 1.13.4 * @version 1.13.6
* @author SpryMedia Ltd * @author SpryMedia Ltd
* @contact www.datatables.net * @contact www.datatables.net
* @copyright SpryMedia Ltd. * @copyright SpryMedia Ltd.
@ -50,7 +50,7 @@
// returns a factory function that expects the window object // returns a factory function that expects the window object
var jq = require('jquery'); var jq = require('jquery');
if (typeof window !== 'undefined') { if (typeof window === 'undefined') {
module.exports = function (root, $) { module.exports = function (root, $) {
if ( ! root ) { if ( ! root ) {
// CommonJS environments without a window global must pass a // CommonJS environments without a window global must pass a
@ -1396,7 +1396,7 @@
var _isNumber = function ( d, decimalPoint, formatted ) { var _isNumber = function ( d, decimalPoint, formatted ) {
let type = typeof d; var type = typeof d;
var strType = type === 'string'; var strType = type === 'string';
if ( type === 'number' || type === 'bigint') { if ( type === 'number' || type === 'bigint') {
@ -1530,7 +1530,9 @@
var _stripHtml = function ( d ) { var _stripHtml = function ( d ) {
return d.replace( _re_html, '' ); return d
.replace( _re_html, '' ) // Complete tags
.replace(/<script/i, ''); // Safety for incomplete script tag
}; };
@ -1904,7 +1906,10 @@
continue; continue;
} }
if ( data === null || data[ a[i] ] === undefined ) { if (data === null || data[ a[i] ] === null) {
return null;
}
else if ( data === undefined || data[ a[i] ] === undefined ) {
return undefined; return undefined;
} }
@ -2351,6 +2356,12 @@
oCol.aDataSort = [ oOptions.iDataSort ]; oCol.aDataSort = [ oOptions.iDataSort ];
} }
_fnMap( oCol, oOptions, "aDataSort" ); _fnMap( oCol, oOptions, "aDataSort" );
// Fall back to the aria-label attribute on the table header if no ariaTitle is
// provided.
if (! oCol.ariaTitle) {
oCol.ariaTitle = th.attr("aria-label");
}
} }
/* Cache the data get and set functions for speed */ /* Cache the data get and set functions for speed */
@ -4075,11 +4086,16 @@
settings.iDraw++; settings.iDraw++;
_fnProcessingDisplay( settings, true ); _fnProcessingDisplay( settings, true );
// Keep track of drawHold state to handle scrolling after the Ajax call
var drawHold = settings._drawHold;
_fnBuildAjax( _fnBuildAjax(
settings, settings,
_fnAjaxParameters( settings ), _fnAjaxParameters( settings ),
function(json) { function(json) {
settings._drawHold = drawHold;
_fnAjaxUpdateDraw( settings, json ); _fnAjaxUpdateDraw( settings, json );
settings._drawHold = false;
} }
); );
} }
@ -4343,7 +4359,7 @@
_fnThrottle( searchFn, searchDelay ) : _fnThrottle( searchFn, searchDelay ) :
searchFn searchFn
) )
.on( 'mouseup', function(e) { .on( 'mouseup.DT', function(e) {
// Edge fix! Edge 17 does not trigger anything other than mouse events when clicking // Edge fix! Edge 17 does not trigger anything other than mouse events when clicking
// on the clear icon (Edge bug 17584515). This is safe in other browsers as `searchFn` // on the clear icon (Edge bug 17584515). This is safe in other browsers as `searchFn`
// checks the value to see if it has changed. In other browsers it won't have. // checks the value to see if it has changed. In other browsers it won't have.
@ -4409,7 +4425,7 @@
if ( _fnDataSource( oSettings ) != 'ssp' ) if ( _fnDataSource( oSettings ) != 'ssp' )
{ {
/* Global filter */ /* Global filter */
_fnFilter( oSettings, oInput.sSearch, iForce, fnRegex(oInput), oInput.bSmart, oInput.bCaseInsensitive, oInput.return ); _fnFilter( oSettings, oInput.sSearch, iForce, fnRegex(oInput), oInput.bSmart, oInput.bCaseInsensitive );
fnSaveFilter( oInput ); fnSaveFilter( oInput );
/* Now do the individual column filter */ /* Now do the individual column filter */
@ -4578,11 +4594,15 @@
* *
* ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$ * ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$
*/ */
var a = $.map( search.match( /"[^"]+"|[^ ]+/g ) || [''], function ( word ) { var a = $.map( search.match( /["\u201C][^"\u201D]+["\u201D]|[^ ]+/g ) || [''], function ( word ) {
if ( word.charAt(0) === '"' ) { if ( word.charAt(0) === '"' ) {
var m = word.match( /^"(.*)"$/ ); var m = word.match( /^"(.*)"$/ );
word = m ? m[1] : word; word = m ? m[1] : word;
} }
else if ( word.charAt(0) === '\u201C' ) {
var m = word.match( /^\u201C(.*)\u201D$/ );
word = m ? m[1] : word;
}
return word.replace('"', ''); return word.replace('"', '');
} ); } );
@ -9386,7 +9406,8 @@
* Set the jQuery or window object to be used by DataTables * Set the jQuery or window object to be used by DataTables
* *
* @param {*} module Library / container object * @param {*} module Library / container object
* @param {string} type Library or container type `lib` or `win`. * @param {string} [type] Library or container type `lib`, `win` or `datetime`.
* If not provided, automatic detection is attempted.
*/ */
DataTable.use = function (module, type) { DataTable.use = function (module, type) {
if (type === 'lib' || module.fn) { if (type === 'lib' || module.fn) {
@ -9396,6 +9417,9 @@
window = module; window = module;
document = module.document; document = module.document;
} }
else if (type === 'datetime' || module.type === 'DateTime') {
DataTable.DateTime = module;
}
} }
/** /**
@ -9755,7 +9779,9 @@
resolved._; resolved._;
} }
return resolved.replace( '%d', plural ); // nb: plural might be undefined, return typeof resolved === 'string'
? resolved.replace( '%d', plural ) // nb: plural might be undefined,
: resolved;
} ); } );
/** /**
* Version string for plug-ins to check compatibility. Allowed format is * Version string for plug-ins to check compatibility. Allowed format is
@ -9765,7 +9791,7 @@
* @type string * @type string
* @default Version number * @default Version number
*/ */
DataTable.version = "1.13.4"; DataTable.version = "1.13.6";
/** /**
* Private data store, containing all of the settings objects that are * Private data store, containing all of the settings objects that are
@ -14189,7 +14215,7 @@
* *
* @type string * @type string
*/ */
build:"bs5/dt-1.13.4", build:"bs5/dt-1.13.6",
/** /**
@ -14830,7 +14856,7 @@
var btnDisplay, btnClass; var btnDisplay, btnClass;
var attach = function( container, buttons ) { var attach = function( container, buttons ) {
var i, ien, node, button, tabIndex; var i, ien, node, button;
var disabledClass = classes.sPageButtonDisabled; var disabledClass = classes.sPageButtonDisabled;
var clickHandler = function ( e ) { var clickHandler = function ( e ) {
_fnPageChange( settings, e.data.action, true ); _fnPageChange( settings, e.data.action, true );
@ -14845,9 +14871,10 @@
attach( inner, button ); attach( inner, button );
} }
else { else {
var disabled = false;
btnDisplay = null; btnDisplay = null;
btnClass = button; btnClass = button;
tabIndex = settings.iTabIndex;
switch ( button ) { switch ( button ) {
case 'ellipsis': case 'ellipsis':
@ -14858,8 +14885,7 @@
btnDisplay = lang.sFirst; btnDisplay = lang.sFirst;
if ( page === 0 ) { if ( page === 0 ) {
tabIndex = -1; disabled = true;
btnClass += ' ' + disabledClass;
} }
break; break;
@ -14867,8 +14893,7 @@
btnDisplay = lang.sPrevious; btnDisplay = lang.sPrevious;
if ( page === 0 ) { if ( page === 0 ) {
tabIndex = -1; disabled = true;
btnClass += ' ' + disabledClass;
} }
break; break;
@ -14876,8 +14901,7 @@
btnDisplay = lang.sNext; btnDisplay = lang.sNext;
if ( pages === 0 || page === pages-1 ) { if ( pages === 0 || page === pages-1 ) {
tabIndex = -1; disabled = true;
btnClass += ' ' + disabledClass;
} }
break; break;
@ -14885,8 +14909,7 @@
btnDisplay = lang.sLast; btnDisplay = lang.sLast;
if ( pages === 0 || page === pages-1 ) { if ( pages === 0 || page === pages-1 ) {
tabIndex = -1; disabled = true;
btnClass += ' ' + disabledClass;
} }
break; break;
@ -14899,18 +14922,20 @@
if ( btnDisplay !== null ) { if ( btnDisplay !== null ) {
var tag = settings.oInit.pagingTag || 'a'; var tag = settings.oInit.pagingTag || 'a';
var disabled = btnClass.indexOf(disabledClass) !== -1;
if (disabled) {
btnClass += ' ' + disabledClass;
}
node = $('<'+tag+'>', { node = $('<'+tag+'>', {
'class': classes.sPageButton+' '+btnClass, 'class': classes.sPageButton+' '+btnClass,
'aria-controls': settings.sTableId, 'aria-controls': settings.sTableId,
'aria-disabled': disabled ? 'true' : null, 'aria-disabled': disabled ? 'true' : null,
'aria-label': aria[ button ], 'aria-label': aria[ button ],
'aria-role': 'link', 'role': 'link',
'aria-current': btnClass === classes.sPageButtonActive ? 'page' : null, 'aria-current': btnClass === classes.sPageButtonActive ? 'page' : null,
'data-dt-idx': button, 'data-dt-idx': button,
'tabindex': tabIndex, 'tabindex': disabled ? -1 : settings.iTabIndex,
'id': idx === 0 && typeof button === 'string' ? 'id': idx === 0 && typeof button === 'string' ?
settings.sTableId +'_'+ button : settings.sTableId +'_'+ button :
null null
@ -15041,7 +15066,7 @@
return -Infinity; return -Infinity;
} }
let type = typeof d; var type = typeof d;
if (type === 'number' || type === 'bigint') { if (type === 'number' || type === 'bigint') {
return d; return d;
@ -15415,7 +15440,7 @@
var __thousands = ','; var __thousands = ',';
var __decimal = '.'; var __decimal = '.';
if (Intl) { if (window.Intl !== undefined) {
try { try {
var num = new Intl.NumberFormat().formatToParts(100000.1); var num = new Intl.NumberFormat().formatToParts(100000.1);
@ -15718,7 +15743,7 @@
} }
}; };
if (typeof window !== 'undefined') { if (typeof window === 'undefined') {
module.exports = function (root, $) { module.exports = function (root, $) {
if ( ! root ) { if ( ! root ) {
// CommonJS environments without a window global must pass a // CommonJS environments without a window global must pass a
@ -15856,10 +15881,10 @@ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, bu
'aria-controls': settings.sTableId, 'aria-controls': settings.sTableId,
'aria-disabled': disabled ? 'true' : null, 'aria-disabled': disabled ? 'true' : null,
'aria-label': aria[ button ], 'aria-label': aria[ button ],
'aria-role': 'link', 'role': 'link',
'aria-current': btnClass === 'active' ? 'page' : null, 'aria-current': btnClass === 'active' ? 'page' : null,
'data-dt-idx': button, 'data-dt-idx': button,
'tabindex': settings.iTabIndex, 'tabindex': disabled ? -1 : settings.iTabIndex,
'class': 'page-link' 'class': 'page-link'
} ) } )
.html( btnDisplay ) .html( btnDisplay )

View file

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-bs-theme="auto">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
@ -10,7 +10,7 @@
<link rel="stylesheet" href="{{urlpath}}/vw_static/admin.css" /> <link rel="stylesheet" href="{{urlpath}}/vw_static/admin.css" />
<script src="{{urlpath}}/vw_static/admin.js"></script> <script src="{{urlpath}}/vw_static/admin.js"></script>
</head> </head>
<body class="bg-light"> <body>
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4 shadow fixed-top"> <nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4 shadow fixed-top">
<div class="container-xl"> <div class="container-xl">
<a class="navbar-brand" href="{{urlpath}}/admin"><img class="vaultwarden-icon" src="{{urlpath}}/vw_static/vaultwarden-icon.png" alt="V">aultwarden Admin</a> <a class="navbar-brand" href="{{urlpath}}/admin"><img class="vaultwarden-icon" src="{{urlpath}}/vw_static/vaultwarden-icon.png" alt="V">aultwarden Admin</a>
@ -39,9 +39,53 @@
</li> </li>
</ul> </ul>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<button
class="btn btn-link nav-link py-0 px-0 px-md-2 dropdown-toggle d-flex align-items-center"
id="bd-theme" type="button" aria-expanded="false" data-bs-toggle="dropdown"
data-bs-display="static" aria-label="Toggle theme (auto)">
<span class="my-1 fs-4 theme-icon-active">
<use>&#9775;</use>
</span>
<span class="d-md-none ms-2" id="bd-theme-text">Toggle theme</span>
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="bd-theme-text">
<li>
<button type="button" class="dropdown-item d-flex align-items-center"
data-bs-theme-value="light" aria-pressed="false">
<span class="me-2 fs-4 theme-icon">
<use>&#9728;</use>
</span>
Light
</button>
</li>
<li>
<button type="button" class="dropdown-item d-flex align-items-center"
data-bs-theme-value="dark" aria-pressed="false">
<span class="me-2 fs-4 theme-icon">
<use>&starf;</use>
</span>
Dark
</button>
</li>
<li>
<button type="button" class="dropdown-item d-flex align-items-center active"
data-bs-theme-value="auto" aria-pressed="true">
<span class="me-2 fs-4 theme-icon">
<use>&#9775;</use>
</span>
Auto
</button>
</li>
</ul>
</li>
</ul>
{{#if logged_in}} {{#if logged_in}}
<a class="btn btn-sm btn-secondary" href="{{urlpath}}/admin/logout">Log Out</a> <a class="btn btn-sm btn-secondary" href="{{urlpath}}/admin/logout">Log Out</a>
{{/if}} {{/if}}
</div> </div>
</div> </div>
</nav> </nav>
@ -49,6 +93,6 @@
{{> (lookup this "page_content") }} {{> (lookup this "page_content") }}
<!-- This script needs to be at the bottom, else it will fail! --> <!-- This script needs to be at the bottom, else it will fail! -->
<script src="{{urlpath}}/vw_static/bootstrap-native.js"></script> <script src="{{urlpath}}/vw_static/bootstrap.bundle.js"></script>
</body> </body>
</html> </html>

View file

@ -1,5 +1,5 @@
<main class="container-xl"> <main class="container-xl">
<div id="diagnostics-block" class="my-3 p-3 bg-white rounded shadow"> <div id="diagnostics-block" class="my-3 p-3 rounded shadow">
<h6 class="border-bottom pb-2 mb-2">Diagnostics</h6> <h6 class="border-bottom pb-2 mb-2">Diagnostics</h6>
<h3>Versions</h3> <h3>Versions</h3>
@ -8,8 +8,8 @@
<dl class="row"> <dl class="row">
<dt class="col-sm-5">Server Installed <dt class="col-sm-5">Server Installed
<span class="badge bg-success d-none" id="server-success" title="Latest version is installed.">Ok</span> <span class="badge bg-success d-none" id="server-success" title="Latest version is installed.">Ok</span>
<span class="badge bg-warning d-none" id="server-warning" title="There seems to be an update available.">Update</span> <span class="badge bg-warning text-dark d-none" id="server-warning" title="There seems to be an update available.">Update</span>
<span class="badge bg-info d-none" id="server-branch" title="This is a branched version.">Branched</span> <span class="badge bg-info text-dark d-none" id="server-branch" title="This is a branched version.">Branched</span>
</dt> </dt>
<dd class="col-sm-7"> <dd class="col-sm-7">
<span id="server-installed">{{page_data.current_release}}</span> <span id="server-installed">{{page_data.current_release}}</span>

View file

@ -1,15 +1,15 @@
<main class="container-xl"> <main class="container-xl">
{{#if error}} {{#if error}}
<div class="align-items-center p-3 mb-3 text-white-50 bg-warning rounded shadow"> <div class="align-items-center p-3 mb-3 text-opacity-50 text-dark bg-warning rounded shadow">
<div> <div>
<h6 class="mb-0 text-white">{{error}}</h6> <h6 class="mb-0 text-dark">{{error}}</h6>
</div> </div>
</div> </div>
{{/if}} {{/if}}
<div class="align-items-center p-3 mb-3 text-white-50 bg-danger rounded shadow"> <div class="align-items-center p-3 mb-3 text-opacity-75 text-light bg-danger rounded shadow">
<div> <div>
<h6 class="mb-0 text-white">Authentication key needed to continue</h6> <h6 class="mb-0 text-light">Authentication key needed to continue</h6>
<small>Please provide it below:</small> <small>Please provide it below:</small>
<form class="form-inline" method="post" action="{{urlpath}}/admin"> <form class="form-inline" method="post" action="{{urlpath}}/admin">
@ -17,7 +17,7 @@
{{#if redirect}} {{#if redirect}}
<input type="hidden" id="redirect" name="redirect" value="/{{redirect}}"> <input type="hidden" id="redirect" name="redirect" value="/{{redirect}}">
{{/if}} {{/if}}
<button type="submit" class="btn btn-primary">Enter</button> <button type="submit" class="btn btn-primary mt-2">Enter</button>
</form> </form>
</div> </div>
</div> </div>

View file

@ -1,5 +1,5 @@
<main class="container-xl"> <main class="container-xl">
<div id="organizations-block" class="my-3 p-3 bg-white rounded shadow"> <div id="organizations-block" class="my-3 p-3 rounded shadow">
<h6 class="border-bottom pb-2 mb-3">Organizations</h6> <h6 class="border-bottom pb-2 mb-3">Organizations</h6>
<div class="table-responsive-xl small"> <div class="table-responsive-xl small">
<table id="orgs-table" class="table table-sm table-striped table-hover"> <table id="orgs-table" class="table table-sm table-striped table-hover">
@ -59,7 +59,7 @@
</main> </main>
<link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" /> <link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
<script src="{{urlpath}}/vw_static/jquery-3.6.4.slim.js"></script> <script src="{{urlpath}}/vw_static/jquery-3.7.0.slim.js"></script>
<script src="{{urlpath}}/vw_static/datatables.js"></script> <script src="{{urlpath}}/vw_static/datatables.js"></script>
<script src="{{urlpath}}/vw_static/admin_organizations.js"></script> <script src="{{urlpath}}/vw_static/admin_organizations.js"></script>
<script src="{{urlpath}}/vw_static/jdenticon.js"></script> <script src="{{urlpath}}/vw_static/jdenticon.js"></script>

View file

@ -17,7 +17,7 @@
<form class="form needs-validation" id="config-form" novalidate> <form class="form needs-validation" id="config-form" novalidate>
{{#each page_data.config}} {{#each page_data.config}}
{{#if groupdoc}} {{#if groupdoc}}
<div class="card bg-light mb-3"> <div class="card mb-3">
<button id="b_{{group}}" type="button" class="card-header text-start btn btn-link text-decoration-none" aria-expanded="false" aria-controls="g_{{group}}" data-bs-toggle="collapse" data-bs-target="#g_{{group}}">{{groupdoc}}</button> <button id="b_{{group}}" type="button" class="card-header text-start btn btn-link text-decoration-none" aria-expanded="false" aria-controls="g_{{group}}" data-bs-toggle="collapse" data-bs-target="#g_{{group}}">{{groupdoc}}</button>
<div id="g_{{group}}" class="card-body collapse"> <div id="g_{{group}}" class="card-body collapse">
{{#each elements}} {{#each elements}}
@ -64,7 +64,7 @@
{{/if}} {{/if}}
{{/each}} {{/each}}
<div class="card bg-light mb-3"> <div class="card mb-3">
<button id="b_readonly" type="button" class="card-header text-start btn btn-link text-decoration-none" aria-expanded="false" aria-controls="g_readonly" <button id="b_readonly" type="button" class="card-header text-start btn btn-link text-decoration-none" aria-expanded="false" aria-controls="g_readonly"
data-bs-toggle="collapse" data-bs-target="#g_readonly">Read-Only Config</button> data-bs-toggle="collapse" data-bs-target="#g_readonly">Read-Only Config</button>
<div id="g_readonly" class="card-body collapse"> <div id="g_readonly" class="card-body collapse">
@ -119,7 +119,7 @@
</div> </div>
{{#if page_data.can_backup}} {{#if page_data.can_backup}}
<div class="card bg-light mb-3"> <div class="card mb-3">
<button id="b_database" type="button" class="card-header text-start btn btn-link text-decoration-none" aria-expanded="false" aria-controls="g_database" <button id="b_database" type="button" class="card-header text-start btn btn-link text-decoration-none" aria-expanded="false" aria-controls="g_database"
data-bs-toggle="collapse" data-bs-target="#g_database">Backup Database</button> data-bs-toggle="collapse" data-bs-target="#g_database">Backup Database</button>
<div id="g_database" class="card-body collapse"> <div id="g_database" class="card-body collapse">

View file

@ -1,5 +1,5 @@
<main class="container-xl"> <main class="container-xl">
<div id="users-block" class="my-3 p-3 bg-white rounded shadow"> <div id="users-block" class="my-3 p-3 rounded shadow">
<h6 class="border-bottom pb-2 mb-3">Registered Users</h6> <h6 class="border-bottom pb-2 mb-3">Registered Users</h6>
<div class="table-responsive-xl small"> <div class="table-responsive-xl small">
<table id="users-table" class="table table-sm table-striped table-hover"> <table id="users-table" class="table table-sm table-striped table-hover">
@ -30,7 +30,7 @@
<span class="badge bg-success me-2" title="2FA is enabled">2FA</span> <span class="badge bg-success me-2" title="2FA is enabled">2FA</span>
{{/if}} {{/if}}
{{#case _Status 1}} {{#case _Status 1}}
<span class="badge bg-warning me-2" title="User is invited">Invited</span> <span class="badge bg-warning text-dark me-2" title="User is invited">Invited</span>
{{/case}} {{/case}}
{{#if EmailVerified}} {{#if EmailVerified}}
<span class="badge bg-success me-2" title="Email has been verified">Verified</span> <span class="badge bg-success me-2" title="Email has been verified">Verified</span>
@ -140,7 +140,7 @@
</main> </main>
<link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" /> <link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
<script src="{{urlpath}}/vw_static/jquery-3.6.4.slim.js"></script> <script src="{{urlpath}}/vw_static/jquery-3.7.0.slim.js"></script>
<script src="{{urlpath}}/vw_static/datatables.js"></script> <script src="{{urlpath}}/vw_static/datatables.js"></script>
<script src="{{urlpath}}/vw_static/admin_users.js"></script> <script src="{{urlpath}}/vw_static/admin_users.js"></script>
<script src="{{urlpath}}/vw_static/jdenticon.js"></script> <script src="{{urlpath}}/vw_static/jdenticon.js"></script>