1
0
mirror of https://github.com/dani-garcia/vaultwarden.git synced 2024-11-09 20:42:36 +01:00

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> {
// If addding more routes here, consider also adding them to
// 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() {
routes![web_index, web_index_head, app_id, web_files, attachments, alive, alive_head, static_files]
} else {
routes![attachments, alive, alive_head, static_files]
routes.append(&mut routes![web_index, web_index_head, app_id, web_files]);
}
#[cfg(debug_assertions)]
if CONFIG.reload_templates() {
routes.append(&mut routes![_static_files_dev]);
}
routes
}
pub fn catchers() -> Vec<Catcher> {
@ -116,7 +122,30 @@ fn alive_head(_conn: DbConn) -> EmptyResult {
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> {
match filename {
"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")))
}
"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"))),
"datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))),
"datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
"jquery-3.6.4.slim.js" => {
Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.4.slim.js")))
"jquery-3.7.0.slim.js" => {
Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.7.0.slim.js")))
}
_ => err!(format!("Static file not found: {filename}")),
}

View File

@ -37,36 +37,107 @@ function _post(url, successMsg, errMsg, body, reload_page = true) {
mode: "same-origin",
credentials: "same-origin",
headers: { "Content-Type": "application/json" }
}).then( resp => {
}).then(resp => {
if (resp.ok) {
msg(successMsg, reload_page);
// Abuse the catch handler by setting error to false and continue
return Promise.reject({error: false});
return Promise.reject({ error: false });
}
respStatus = resp.status;
respStatusText = resp.statusText;
return resp.text();
}).then( respText => {
}).then(respText => {
try {
const respJson = JSON.parse(respText);
if (respJson.ErrorModel && respJson.ErrorModel.Message) {
return respJson.ErrorModel.Message;
} else {
return Promise.reject({body:`${respStatus} - ${respStatusText}\n\nUnknown error`, error: true});
return Promise.reject({ body: `${respStatus} - ${respStatusText}\n\nUnknown error`, error: true });
}
} catch (e) {
return Promise.reject({body:`${respStatus} - ${respStatusText}\n\n[Catch] ${e}`, error: true});
return Promise.reject({ body: `${respStatus} - ${respStatusText}\n\n[Catch] ${e}`, error: true });
}
}).then( apiMsg => {
}).then(apiMsg => {
msg(`${errMsg}\n${apiMsg}`, reload_page);
}).catch( e => {
}).catch(e => {
if (e.error === false) { return true; }
else { msg(`${errMsg}\n${e.body}`, reload_page); }
});
}
// 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
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
const pathname = window.location.pathname;
if (pathname === "") return;

View File

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

View File

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

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
* software please visit:
* https://datatables.net/download/#bs5/dt-1.13.4
* https://datatables.net/download/#bs5/dt-1.13.6
*
* Included libraries:
* DataTables 1.13.4
* DataTables 1.13.6
*/
@charset "UTF-8";
@ -15,6 +15,13 @@
--dt-row-selected: 13, 110, 253;
--dt-row-selected-text: 255, 255, 255;
--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 {
@ -22,25 +29,19 @@ table.dataTable td.dt-control {
cursor: pointer;
}
table.dataTable td.dt-control:before {
height: 1em;
width: 1em;
margin-top: -9px;
display: inline-block;
color: white;
border: 0.15em solid white;
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;
color: rgba(0, 0, 0, 0.5);
content: "►";
}
table.dataTable tr.dt-hasChild td.dt-control:before {
content: "-";
background-color: #d33333;
content: "▼";
}
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,
@ -303,14 +304,14 @@ table.dataTable > tbody > tr.selected a {
color: rgb(var(--dt-row-selected-link));
}
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 > * {
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);
}
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 > * {
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;
}
: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
* software please visit:
* https://datatables.net/download/#bs5/dt-1.13.4
* https://datatables.net/download/#bs5/dt-1.13.6
*
* 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
*/
/**
* @summary DataTables
* @description Paginate, search and order HTML tables
* @version 1.13.4
* @version 1.13.6
* @author SpryMedia Ltd
* @contact www.datatables.net
* @copyright SpryMedia Ltd.
@ -50,7 +50,7 @@
// returns a factory function that expects the window object
var jq = require('jquery');
if (typeof window !== 'undefined') {
if (typeof window === 'undefined') {
module.exports = function (root, $) {
if ( ! root ) {
// CommonJS environments without a window global must pass a
@ -1396,7 +1396,7 @@
var _isNumber = function ( d, decimalPoint, formatted ) {
let type = typeof d;
var type = typeof d;
var strType = type === 'string';
if ( type === 'number' || type === 'bigint') {
@ -1530,7 +1530,9 @@
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;
}
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;
}
@ -2351,6 +2356,12 @@
oCol.aDataSort = [ oOptions.iDataSort ];
}
_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 */
@ -4075,11 +4086,16 @@
settings.iDraw++;
_fnProcessingDisplay( settings, true );
// Keep track of drawHold state to handle scrolling after the Ajax call
var drawHold = settings._drawHold;
_fnBuildAjax(
settings,
_fnAjaxParameters( settings ),
function(json) {
settings._drawHold = drawHold;
_fnAjaxUpdateDraw( settings, json );
settings._drawHold = false;
}
);
}
@ -4343,7 +4359,7 @@
_fnThrottle( searchFn, searchDelay ) :
searchFn
)
.on( 'mouseup', function(e) {
.on( 'mouseup.DT', function(e) {
// 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`
// checks the value to see if it has changed. In other browsers it won't have.
@ -4409,7 +4425,7 @@
if ( _fnDataSource( oSettings ) != 'ssp' )
{
/* 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 );
/* Now do the individual column filter */
@ -4578,11 +4594,15 @@
*
* ^(?=.*?\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) === '"' ) {
var m = word.match( /^"(.*)"$/ );
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('"', '');
} );
@ -9386,7 +9406,8 @@
* Set the jQuery or window object to be used by DataTables
*
* @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) {
if (type === 'lib' || module.fn) {
@ -9396,6 +9417,9 @@
window = module;
document = module.document;
}
else if (type === 'datetime' || module.type === 'DateTime') {
DataTable.DateTime = module;
}
}
/**
@ -9755,7 +9779,9 @@
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
@ -9765,7 +9791,7 @@
* @type string
* @default Version number
*/
DataTable.version = "1.13.4";
DataTable.version = "1.13.6";
/**
* Private data store, containing all of the settings objects that are
@ -14189,7 +14215,7 @@
*
* @type string
*/
build:"bs5/dt-1.13.4",
build:"bs5/dt-1.13.6",
/**
@ -14830,7 +14856,7 @@
var btnDisplay, btnClass;
var attach = function( container, buttons ) {
var i, ien, node, button, tabIndex;
var i, ien, node, button;
var disabledClass = classes.sPageButtonDisabled;
var clickHandler = function ( e ) {
_fnPageChange( settings, e.data.action, true );
@ -14845,9 +14871,10 @@
attach( inner, button );
}
else {
var disabled = false;
btnDisplay = null;
btnClass = button;
tabIndex = settings.iTabIndex;
switch ( button ) {
case 'ellipsis':
@ -14858,8 +14885,7 @@
btnDisplay = lang.sFirst;
if ( page === 0 ) {
tabIndex = -1;
btnClass += ' ' + disabledClass;
disabled = true;
}
break;
@ -14867,8 +14893,7 @@
btnDisplay = lang.sPrevious;
if ( page === 0 ) {
tabIndex = -1;
btnClass += ' ' + disabledClass;
disabled = true;
}
break;
@ -14876,8 +14901,7 @@
btnDisplay = lang.sNext;
if ( pages === 0 || page === pages-1 ) {
tabIndex = -1;
btnClass += ' ' + disabledClass;
disabled = true;
}
break;
@ -14885,8 +14909,7 @@
btnDisplay = lang.sLast;
if ( pages === 0 || page === pages-1 ) {
tabIndex = -1;
btnClass += ' ' + disabledClass;
disabled = true;
}
break;
@ -14899,18 +14922,20 @@
if ( btnDisplay !== null ) {
var tag = settings.oInit.pagingTag || 'a';
var disabled = btnClass.indexOf(disabledClass) !== -1;
if (disabled) {
btnClass += ' ' + disabledClass;
}
node = $('<'+tag+'>', {
'class': classes.sPageButton+' '+btnClass,
'aria-controls': settings.sTableId,
'aria-disabled': disabled ? 'true' : null,
'aria-label': aria[ button ],
'aria-role': 'link',
'role': 'link',
'aria-current': btnClass === classes.sPageButtonActive ? 'page' : null,
'data-dt-idx': button,
'tabindex': tabIndex,
'tabindex': disabled ? -1 : settings.iTabIndex,
'id': idx === 0 && typeof button === 'string' ?
settings.sTableId +'_'+ button :
null
@ -15041,7 +15066,7 @@
return -Infinity;
}
let type = typeof d;
var type = typeof d;
if (type === 'number' || type === 'bigint') {
return d;
@ -15415,7 +15440,7 @@
var __thousands = ',';
var __decimal = '.';
if (Intl) {
if (window.Intl !== undefined) {
try {
var num = new Intl.NumberFormat().formatToParts(100000.1);
@ -15718,7 +15743,7 @@
}
};
if (typeof window !== 'undefined') {
if (typeof window === 'undefined') {
module.exports = function (root, $) {
if ( ! root ) {
// 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-disabled': disabled ? 'true' : null,
'aria-label': aria[ button ],
'aria-role': 'link',
'role': 'link',
'aria-current': btnClass === 'active' ? 'page' : null,
'data-dt-idx': button,
'tabindex': settings.iTabIndex,
'tabindex': disabled ? -1 : settings.iTabIndex,
'class': 'page-link'
} )
.html( btnDisplay )

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-bs-theme="auto">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
@ -10,17 +10,17 @@
<link rel="stylesheet" href="{{urlpath}}/vw_static/admin.css" />
<script src="{{urlpath}}/vw_static/admin.js"></script>
</head>
<body class="bg-light">
<body>
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4 shadow fixed-top">
<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>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse"
aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav me-auto">
{{#if logged_in}}
{{#if logged_in}}
<li class="nav-item">
<a class="nav-link" href="{{urlpath}}/admin">Settings</a>
</li>
@ -33,15 +33,59 @@
<li class="nav-item">
<a class="nav-link" href="{{urlpath}}/admin/diagnostics">Diagnostics</a>
</li>
{{/if}}
{{/if}}
<li class="nav-item">
<a class="nav-link" href="{{urlpath}}/" target="_blank" rel="noreferrer">Vault</a>
</li>
</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}}
<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}}
</div>
</div>
</nav>
@ -49,6 +93,6 @@
{{> (lookup this "page_content") }}
<!-- 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>
</html>

View File

@ -1,5 +1,5 @@
<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>
<h3>Versions</h3>
@ -8,8 +8,8 @@
<dl class="row">
<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-warning 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-warning text-dark d-none" id="server-warning" title="There seems to be an update available.">Update</span>
<span class="badge bg-info text-dark d-none" id="server-branch" title="This is a branched version.">Branched</span>
</dt>
<dd class="col-sm-7">
<span id="server-installed">{{page_data.current_release}}</span>

View File

@ -1,15 +1,15 @@
<main class="container-xl">
{{#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>
<h6 class="mb-0 text-white">{{error}}</h6>
<h6 class="mb-0 text-dark">{{error}}</h6>
</div>
</div>
{{/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>
<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>
<form class="form-inline" method="post" action="{{urlpath}}/admin">
@ -17,7 +17,7 @@
{{#if redirect}}
<input type="hidden" id="redirect" name="redirect" value="/{{redirect}}">
{{/if}}
<button type="submit" class="btn btn-primary">Enter</button>
<button type="submit" class="btn btn-primary mt-2">Enter</button>
</form>
</div>
</div>

View File

@ -1,5 +1,5 @@
<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>
<div class="table-responsive-xl small">
<table id="orgs-table" class="table table-sm table-striped table-hover">
@ -59,7 +59,7 @@
</main>
<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/admin_organizations.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>
{{#each page_data.config}}
{{#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>
<div id="g_{{group}}" class="card-body collapse">
{{#each elements}}
@ -64,7 +64,7 @@
{{/if}}
{{/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"
data-bs-toggle="collapse" data-bs-target="#g_readonly">Read-Only Config</button>
<div id="g_readonly" class="card-body collapse">
@ -119,7 +119,7 @@
</div>
{{#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"
data-bs-toggle="collapse" data-bs-target="#g_database">Backup Database</button>
<div id="g_database" class="card-body collapse">

View File

@ -1,5 +1,5 @@
<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>
<div class="table-responsive-xl small">
<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>
{{/if}}
{{#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}}
{{#if EmailVerified}}
<span class="badge bg-success me-2" title="Email has been verified">Verified</span>
@ -140,7 +140,7 @@
</main>
<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/admin_users.js"></script>
<script src="{{urlpath}}/vw_static/jdenticon.js"></script>