Multiple Admin Interface fixes and some others.

Misc:
- Fixed hadolint workflow, new git cli needs some extra arguments.
- Add ignore paths to all specific on triggers.
- Updated hadolint version.
- Made SMTP_DEBUG read-only, since it can't be changed at runtime.

Admin:
- Migrated from Bootstrap v4 to v5
- Updated jquery to v3.6.0
- Updated Datatables
- Made Javascript strict
- Added a way to show which ENV Vars are overridden.
- Changed the way to provide data for handlebars.
- Fixed date/time check.
- Made support string use details and summary feature of markdown/github.
This commit is contained in:
BlackDex
2021-06-19 19:22:19 +02:00
parent 5772836be5
commit 8615736e84
15 changed files with 11393 additions and 8885 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -4,13 +4,18 @@
*
* To rebuild or modify this file with the latest versions of the included
* software please visit:
* https://datatables.net/download/#bs4/dt-1.10.23
* https://datatables.net/download/#bs5/dt-1.10.25
*
* Included libraries:
* DataTables 1.10.23
* DataTables 1.10.25
*/
@charset "UTF-8";
/*! Bootstrap 5 integration for DataTables
*
* ©2020 SpryMedia Ltd, all rights reserved.
* License: MIT datatables.net/license/mit
*/
table.dataTable {
clear: both;
margin-top: 6px !important;
@@ -105,7 +110,7 @@ table.dataTable > thead .sorting_asc_disabled:after,
table.dataTable > thead .sorting_desc_disabled:before,
table.dataTable > thead .sorting_desc_disabled:after {
position: absolute;
bottom: 0.9em;
bottom: 0.5em;
display: block;
opacity: 0.3;
}
@@ -193,18 +198,27 @@ table.dataTable.table-sm .sorting_desc:after {
table.table-bordered.dataTable {
border-right-width: 0;
}
table.table-bordered.dataTable thead tr:first-child th,
table.table-bordered.dataTable thead tr:first-child td {
border-top-width: 1px;
}
table.table-bordered.dataTable th,
table.table-bordered.dataTable td {
border-left-width: 0;
}
table.table-bordered.dataTable th:first-child, table.table-bordered.dataTable th:first-child,
table.table-bordered.dataTable td:first-child,
table.table-bordered.dataTable td:first-child {
border-left-width: 1px;
}
table.table-bordered.dataTable th:last-child, table.table-bordered.dataTable th:last-child,
table.table-bordered.dataTable td:last-child,
table.table-bordered.dataTable td:last-child {
border-right-width: 1px;
}
table.table-bordered.dataTable tbody th,
table.table-bordered.dataTable tbody td {
border-bottom-width: 0;
table.table-bordered.dataTable th,
table.table-bordered.dataTable td {
border-bottom-width: 1px;
}
div.dataTables_scrollHead table.table-bordered {

View File

@@ -4,24 +4,24 @@
*
* To rebuild or modify this file with the latest versions of the included
* software please visit:
* https://datatables.net/download/#bs4/dt-1.10.23
* https://datatables.net/download/#bs5/dt-1.10.25
*
* Included libraries:
* DataTables 1.10.23
* DataTables 1.10.25
*/
/*! DataTables 1.10.23
* ©2008-2020 SpryMedia Ltd - datatables.net/license
/*! DataTables 1.10.25
* ©2008-2021 SpryMedia Ltd - datatables.net/license
*/
/**
* @summary DataTables
* @description Paginate, search and order HTML tables
* @version 1.10.23
* @version 1.10.25
* @file jquery.dataTables.js
* @author SpryMedia Ltd
* @contact www.datatables.net
* @copyright Copyright 2008-2020 SpryMedia Ltd.
* @copyright Copyright 2008-2021 SpryMedia Ltd.
*
* This source file is free software, available under the following license:
* MIT license - http://datatables.net/license
@@ -1100,6 +1100,8 @@
_fnLanguageCompat( json );
_fnCamelToHungarian( defaults.oLanguage, json );
$.extend( true, oLanguage, json );
_fnCallbackFire( oSettings, null, 'i18n', [oSettings]);
_fnInitialise( oSettings );
},
error: function () {
@@ -1109,6 +1111,9 @@
} );
bInitHandedOff = true;
}
else {
_fnCallbackFire( oSettings, null, 'i18n', [oSettings]);
}
/*
* Stripes
@@ -1260,7 +1265,7 @@
var tbody = $this.children('tbody');
if ( tbody.length === 0 ) {
tbody = $('<tbody/>').appendTo($this);
tbody = $('<tbody/>').insertAfter(thead);
}
oSettings.nTBody = tbody[0];
@@ -2315,8 +2320,9 @@
}
// Only a single match is needed for html type since it is
// bottom of the pile and very similar to string
if ( detectedType === 'html' ) {
// bottom of the pile and very similar to string - but it
// must not be empty
if ( detectedType === 'html' && ! _empty(cache[k]) ) {
break;
}
}
@@ -3421,9 +3427,10 @@
/**
* Insert the required TR nodes into the table for display
* @param {object} oSettings dataTables settings object
* @param ajaxComplete true after ajax call to complete rendering
* @memberof DataTable#oApi
*/
function _fnDraw( oSettings )
function _fnDraw( oSettings, ajaxComplete )
{
/* Provide a pre-callback function which can be used to cancel the draw is false is returned */
var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] );
@@ -3472,8 +3479,9 @@
{
oSettings.iDraw++;
}
else if ( !oSettings.bDestroying && !_fnAjaxUpdate( oSettings ) )
else if ( !oSettings.bDestroying && !ajaxComplete)
{
_fnAjaxUpdate( oSettings );
return;
}
@@ -4005,21 +4013,16 @@
*/
function _fnAjaxUpdate( settings )
{
if ( settings.bAjaxDataGet ) {
settings.iDraw++;
_fnProcessingDisplay( settings, true );
settings.iDraw++;
_fnProcessingDisplay( settings, true );
_fnBuildAjax(
settings,
_fnAjaxParameters( settings ),
function(json) {
_fnAjaxUpdateDraw( settings, json );
}
);
return false;
}
return true;
_fnBuildAjax(
settings,
_fnAjaxParameters( settings ),
function(json) {
_fnAjaxUpdateDraw( settings, json );
}
);
}
@@ -4172,14 +4175,12 @@
}
settings.aiDisplay = settings.aiDisplayMaster.slice();
settings.bAjaxDataGet = false;
_fnDraw( settings );
_fnDraw( settings, true );
if ( ! settings._bInitComplete ) {
_fnInitComplete( settings, json );
}
settings.bAjaxDataGet = true;
_fnProcessingDisplay( settings, false );
}
@@ -6108,7 +6109,7 @@
{
var col = columns[i];
var asSorting = col.asSorting;
var sTitle = col.sTitle.replace( /<.*?>/g, "" );
var sTitle = col.ariaTitle || col.sTitle.replace( /<.*?>/g, "" );
var th = col.nTh;
// IE7 is throwing an error when setting these properties with jQuery's
@@ -9542,7 +9543,7 @@
* @type string
* @default Version number
*/
DataTable.version = "1.10.23";
DataTable.version = "1.10.25";
/**
* Private data store, containing all of the settings objects that are
@@ -13623,13 +13624,6 @@
*/
"sAjaxDataProp": null,
/**
* Note if draw should be blocked while getting data
* @type boolean
* @default true
*/
"bAjaxDataGet": true,
/**
* The last jQuery XHR object that was used for server-side data gathering.
* This can be used for working with the XHR information in one of the
@@ -13966,7 +13960,7 @@
*
* @type string
*/
build:"bs4/dt-1.10.23",
build:"bs5/dt-1.10.25",
/**
@@ -14494,8 +14488,8 @@
"sSortAsc": "sorting_asc",
"sSortDesc": "sorting_desc",
"sSortable": "sorting", /* Sortable in both directions */
"sSortableAsc": "sorting_asc_disabled",
"sSortableDesc": "sorting_desc_disabled",
"sSortableAsc": "sorting_desc_disabled",
"sSortableDesc": "sorting_asc_disabled",
"sSortableNone": "sorting_disabled",
"sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
@@ -14936,7 +14930,6 @@
cell
.removeClass(
column.sSortingClass +' '+
classes.sSortAsc +' '+
classes.sSortDesc
)
@@ -15061,6 +15054,11 @@
decimal+(d - intPart).toFixed( precision ).substring( 2 ):
'';
// If zero, then can't have a negative prefix
if (intPart === 0 && parseFloat(floatPart) === 0) {
negative = '';
}
return negative + (prefix||'') +
intPart.toString().replace(
/\B(?=(\d{3})+(?!\d))/g, thousands
@@ -15395,12 +15393,12 @@
}));
/*! DataTables Bootstrap 4 integration
* ©2011-2017 SpryMedia Ltd - datatables.net/license
/*! DataTables Bootstrap 5 integration
* 2020 SpryMedia Ltd - datatables.net/license
*/
/**
* DataTables integration for Bootstrap 4. This requires Bootstrap 4 and
* DataTables integration for Bootstrap 4. This requires Bootstrap 5 and
* DataTables 1.10 or newer.
*
* This file sets the defaults and adds options to DataTables to style its
@@ -15452,9 +15450,9 @@ $.extend( true, DataTable.defaults, {
/* Default class modification */
$.extend( DataTable.ext.classes, {
sWrapper: "dataTables_wrapper dt-bootstrap4",
sWrapper: "dataTables_wrapper dt-bootstrap5",
sFilterInput: "form-control form-control-sm",
sLengthSelect: "custom-select custom-select-sm form-control form-control-sm",
sLengthSelect: "form-select form-select-sm",
sProcessing: "dataTables_processing card",
sPageButton: "paginate_button page-item"
} );

View File

@@ -1,15 +1,15 @@
/*!
* jQuery JavaScript Library v3.5.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector
* jQuery JavaScript Library v3.6.0 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector
* https://jquery.com/
*
* Includes Sizzle.js
* https://sizzlejs.com/
*
* Copyright JS Foundation and other contributors
* Copyright OpenJS Foundation and other contributors
* Released under the MIT license
* https://jquery.org/license
*
* Date: 2020-05-04T22:49Z
* Date: 2021-03-02T17:08Z
*/
( function( global, factory ) {
@@ -76,12 +76,16 @@ var support = {};
var isFunction = function isFunction( obj ) {
// Support: Chrome <=57, Firefox <=52
// In some browsers, typeof returns "function" for HTML <object> elements
// (i.e., `typeof document.createElement( "object" ) === "function"`).
// We don't want to classify *any* DOM node as a function.
return typeof obj === "function" && typeof obj.nodeType !== "number";
};
// Support: Chrome <=57, Firefox <=52
// In some browsers, typeof returns "function" for HTML <object> elements
// (i.e., `typeof document.createElement( "object" ) === "function"`).
// We don't want to classify *any* DOM node as a function.
// Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5
// Plus for old WebKit, typeof returns "function" for HTML collections
// (e.g., `typeof document.getElementsByTagName("div") === "function"`). (gh-4756)
return typeof obj === "function" && typeof obj.nodeType !== "number" &&
typeof obj.item !== "function";
};
var isWindow = function isWindow( obj ) {
@@ -147,7 +151,7 @@ function toType( obj ) {
var
version = "3.5.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector",
version = "3.6.0 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector",
// Define a local copy of jQuery
jQuery = function( selector, context ) {
@@ -401,7 +405,7 @@ jQuery.extend( {
if ( isArrayLike( Object( arr ) ) ) {
jQuery.merge( ret,
typeof arr === "string" ?
[ arr ] : arr
[ arr ] : arr
);
} else {
push.call( ret, arr );
@@ -496,9 +500,9 @@ if ( typeof Symbol === "function" ) {
// Populate the class2type map
jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
function( _i, name ) {
class2type[ "[object " + name + "]" ] = name.toLowerCase();
} );
function( _i, name ) {
class2type[ "[object " + name + "]" ] = name.toLowerCase();
} );
function isArrayLike( obj ) {
@@ -518,14 +522,14 @@ function isArrayLike( obj ) {
}
var Sizzle =
/*!
* Sizzle CSS Selector Engine v2.3.5
* Sizzle CSS Selector Engine v2.3.6
* https://sizzlejs.com/
*
* Copyright JS Foundation and other contributors
* Released under the MIT license
* https://js.foundation/
*
* Date: 2020-03-14
* Date: 2021-02-16
*/
( function( window ) {
var i,
@@ -1108,8 +1112,8 @@ support = Sizzle.support = {};
* @returns {Boolean} True iff elem is a non-HTML XML node
*/
isXML = Sizzle.isXML = function( elem ) {
var namespace = elem.namespaceURI,
docElem = ( elem.ownerDocument || elem ).documentElement;
var namespace = elem && elem.namespaceURI,
docElem = elem && ( elem.ownerDocument || elem ).documentElement;
// Support: IE <=8
// Assume HTML when documentElement doesn't yet exist, such as inside loading iframes
@@ -3024,9 +3028,9 @@ var rneedsContext = jQuery.expr.match.needsContext;
function nodeName( elem, name ) {
return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
};
}
var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
@@ -3997,8 +4001,8 @@ jQuery.extend( {
resolveContexts = Array( i ),
resolveValues = slice.call( arguments ),
// the master Deferred
master = jQuery.Deferred(),
// the primary Deferred
primary = jQuery.Deferred(),
// subordinate callback factory
updateFunc = function( i ) {
@@ -4006,30 +4010,30 @@ jQuery.extend( {
resolveContexts[ i ] = this;
resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
if ( !( --remaining ) ) {
master.resolveWith( resolveContexts, resolveValues );
primary.resolveWith( resolveContexts, resolveValues );
}
};
};
// Single- and empty arguments are adopted like Promise.resolve
if ( remaining <= 1 ) {
adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject,
adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject,
!remaining );
// Use .then() to unwrap secondary thenables (cf. gh-3000)
if ( master.state() === "pending" ||
if ( primary.state() === "pending" ||
isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
return master.then();
return primary.then();
}
}
// Multiple arguments are aggregated like Promise.all array elements
while ( i-- ) {
adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject );
}
return master.promise();
return primary.promise();
}
} );
@@ -4180,8 +4184,8 @@ var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
for ( ; i < len; i++ ) {
fn(
elems[ i ], key, raw ?
value :
value.call( elems[ i ], i, fn( elems[ i ], key ) )
value :
value.call( elems[ i ], i, fn( elems[ i ], key ) )
);
}
}
@@ -5089,10 +5093,7 @@ function buildFragment( elems, context, scripts, selection, ignored ) {
}
var
rkeyEvent = /^key/,
rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
var rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
function returnTrue() {
return true;
@@ -5387,8 +5388,8 @@ jQuery.event = {
event = jQuery.event.fix( nativeEvent ),
handlers = (
dataPriv.get( this, "events" ) || Object.create( null )
)[ event.type ] || [],
dataPriv.get( this, "events" ) || Object.create( null )
)[ event.type ] || [],
special = jQuery.event.special[ event.type ] || {};
// Use the fix-ed jQuery.Event rather than the (read-only) native event
@@ -5512,12 +5513,12 @@ jQuery.event = {
get: isFunction( hook ) ?
function() {
if ( this.originalEvent ) {
return hook( this.originalEvent );
return hook( this.originalEvent );
}
} :
function() {
if ( this.originalEvent ) {
return this.originalEvent[ name ];
return this.originalEvent[ name ];
}
},
@@ -5656,7 +5657,13 @@ function leverageNative( el, type, expectSync ) {
// Cancel the outer synthetic event
event.stopImmediatePropagation();
event.preventDefault();
return result.value;
// Support: Chrome 86+
// In Chrome, if an element having a focusout handler is blurred by
// clicking outside of it, it invokes the handler synchronously. If
// that handler calls `.remove()` on the element, the data is cleared,
// leaving `result` undefined. We need to guard against this.
return result && result.value;
}
// If this is an inner synthetic event for an event with a bubbling surrogate
@@ -5821,34 +5828,7 @@ jQuery.each( {
targetTouches: true,
toElement: true,
touches: true,
which: function( event ) {
var button = event.button;
// Add which for key events
if ( event.which == null && rkeyEvent.test( event.type ) ) {
return event.charCode != null ? event.charCode : event.keyCode;
}
// Add which for click: 1 === left; 2 === middle; 3 === right
if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) {
if ( button & 1 ) {
return 1;
}
if ( button & 2 ) {
return 3;
}
if ( button & 4 ) {
return 2;
}
return 0;
}
return event.which;
}
which: true
}, jQuery.event.addProp );
jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) {
@@ -5874,6 +5854,12 @@ jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateTyp
return true;
},
// Suppress native focus or blur as it's already being fired
// in leverageNative.
_default: function() {
return true;
},
delegateType: delegateType
};
} );
@@ -6541,6 +6527,10 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
// set in CSS while `offset*` properties report correct values.
// Behavior in IE 9 is more subtle than in newer versions & it passes
// some versions of this test; make sure not to make it pass there!
//
// Support: Firefox 70+
// Only Firefox includes border widths
// in computed dimensions. (gh-4529)
reliableTrDimensions: function() {
var table, tr, trChild, trStyle;
if ( reliableTrDimensionsVal == null ) {
@@ -6548,17 +6538,32 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
tr = document.createElement( "tr" );
trChild = document.createElement( "div" );
table.style.cssText = "position:absolute;left:-11111px";
table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate";
tr.style.cssText = "border:1px solid";
// Support: Chrome 86+
// Height set through cssText does not get applied.
// Computed height then comes back as 0.
tr.style.height = "1px";
trChild.style.height = "9px";
// Support: Android 8 Chrome 86+
// In our bodyBackground.html iframe,
// display for all div elements is set to "inline",
// which causes a problem only in Android 8 Chrome 86.
// Ensuring the div is display: block
// gets around this issue.
trChild.style.display = "block";
documentElement
.appendChild( table )
.appendChild( tr )
.appendChild( trChild );
trStyle = window.getComputedStyle( tr );
reliableTrDimensionsVal = parseInt( trStyle.height ) > 3;
reliableTrDimensionsVal = ( parseInt( trStyle.height, 10 ) +
parseInt( trStyle.borderTopWidth, 10 ) +
parseInt( trStyle.borderBottomWidth, 10 ) ) === tr.offsetHeight;
documentElement.removeChild( table );
}
@@ -7022,10 +7027,10 @@ jQuery.each( [ "height", "width" ], function( _i, dimension ) {
// Running getBoundingClientRect on a disconnected node
// in IE throws an error.
( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
swap( elem, cssShow, function() {
return getWidthOrHeight( elem, dimension, extra );
} ) :
getWidthOrHeight( elem, dimension, extra );
swap( elem, cssShow, function() {
return getWidthOrHeight( elem, dimension, extra );
} ) :
getWidthOrHeight( elem, dimension, extra );
}
},
@@ -7084,7 +7089,7 @@ jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
swap( elem, { marginLeft: 0 }, function() {
return elem.getBoundingClientRect().left;
} )
) + "px";
) + "px";
}
}
);
@@ -7608,8 +7613,8 @@ jQuery.fn.extend( {
if ( this.setAttribute ) {
this.setAttribute( "class",
className || value === false ?
"" :
dataPriv.get( this, "__className__" ) || ""
"" :
dataPriv.get( this, "__className__" ) || ""
);
}
}
@@ -7624,7 +7629,7 @@ jQuery.fn.extend( {
while ( ( elem = this[ i++ ] ) ) {
if ( elem.nodeType === 1 &&
( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) {
return true;
return true;
}
}
@@ -7914,9 +7919,7 @@ jQuery.extend( jQuery.event, {
special.bindType || type;
// jQuery handler
handle = (
dataPriv.get( cur, "events" ) || Object.create( null )
)[ event.type ] &&
handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] &&
dataPriv.get( cur, "handle" );
if ( handle ) {
handle.apply( cur, data );
@@ -8057,7 +8060,7 @@ if ( !support.focusin ) {
// Cross-browser xml parsing
jQuery.parseXML = function( data ) {
var xml;
var xml, parserErrorElem;
if ( !data || typeof data !== "string" ) {
return null;
}
@@ -8066,12 +8069,17 @@ jQuery.parseXML = function( data ) {
// IE throws on parseFromString with invalid input.
try {
xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
} catch ( e ) {
xml = undefined;
}
} catch ( e ) {}
if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
jQuery.error( "Invalid XML: " + data );
parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ];
if ( !xml || parserErrorElem ) {
jQuery.error( "Invalid XML: " + (
parserErrorElem ?
jQuery.map( parserErrorElem.childNodes, function( el ) {
return el.textContent;
} ).join( "\n" ) :
data
) );
}
return xml;
};
@@ -8172,16 +8180,14 @@ jQuery.fn.extend( {
// Can add propHook for "elements" to filter or add form elements
var elements = jQuery.prop( this, "elements" );
return elements ? jQuery.makeArray( elements ) : this;
} )
.filter( function() {
} ).filter( function() {
var type = this.type;
// Use .is( ":disabled" ) so that fieldset[disabled] works
return this.name && !jQuery( this ).is( ":disabled" ) &&
rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
( this.checked || !rcheckableType.test( type ) );
} )
.map( function( _i, elem ) {
} ).map( function( _i, elem ) {
var val = jQuery( this ).val();
if ( val == null ) {
@@ -8387,12 +8393,6 @@ jQuery.offset = {
options.using.call( elem, props );
} else {
if ( typeof props.top === "number" ) {
props.top += "px";
}
if ( typeof props.left === "number" ) {
props.left += "px";
}
curElem.css( props );
}
}
@@ -8561,8 +8561,11 @@ jQuery.each( [ "top", "left" ], function( _i, prop ) {
// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
function( defaultExtra, funcName ) {
jQuery.each( {
padding: "inner" + name,
content: type,
"": "outer" + name
}, function( defaultExtra, funcName ) {
// Margin is only for outerHeight, outerWidth
jQuery.fn[ funcName ] = function( margin, value ) {
@@ -8631,7 +8634,8 @@ jQuery.fn.extend( {
}
} );
jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
jQuery.each(
( "blur focus focusin focusout resize scroll click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup contextmenu" ).split( " " ),
function( _i, name ) {
@@ -8642,7 +8646,8 @@ jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
this.on( name, null, data, fn ) :
this.trigger( name );
};
} );
}
);

View File

@@ -15,14 +15,16 @@
width: 48px;
height: 48px;
}
.navbar .vaultwarden-icon {
.vaultwarden-icon {
height: 32px;
width: auto;
margin: -5px -3px 0 0;
margin: -5px 0 0 0;
}
</style>
<script src="{{urlpath}}/bwrs_static/identicon.js"></script>
<script>
'use strict';
function reload() { window.location.reload(); }
function msg(text, reload_page = true) {
text && alert(text);
@@ -78,19 +80,18 @@
});
}
</script>
</head>
<body class="bg-light">
<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="pr-1 vaultwarden-icon" src="{{urlpath}}/bwrs_static/vaultwarden-icon.png" alt="V">aultwarden Admin</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse"
<a class="navbar-brand" href="{{urlpath}}/admin"><img class="vaultwarden-icon" src="{{urlpath}}/bwrs_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">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto">
<ul class="navbar-nav me-auto">
{{#if logged_in}}
<li class="nav-item">
<a class="nav-link" href="{{urlpath}}/admin">Settings</a>
@@ -121,17 +122,19 @@
<!-- This script needs to be at the bottom, else it will fail! -->
<script>
'use strict';
// get current URL path and assign 'active' class to the correct nav-item
(function () {
(() => {
var pathname = window.location.pathname;
if (pathname === "") return;
var navItem = document.querySelectorAll('.navbar-nav .nav-item a[href="'+pathname+'"]');
if (navItem.length === 1) {
navItem[0].parentElement.className = navItem[0].parentElement.className + ' active';
navItem[0].className = navItem[0].className + ' active';
navItem[0].setAttribute('aria-current', 'page');
}
})();
</script>
<!-- This script needs to be at the bottom, else it will fail! -->
<script src="{{urlpath}}/bwrs_static/bootstrap-native.js"></script>
</body>
</html>

View File

@@ -7,37 +7,37 @@
<div class="col-md">
<dl class="row">
<dt class="col-sm-5">Server Installed
<span class="badge badge-success d-none" id="server-success" title="Latest version is installed.">Ok</span>
<span class="badge badge-warning d-none" id="server-warning" title="There seems to be an update available.">Update</span>
<span class="badge badge-info d-none" id="server-branch" title="This is a branched version.">Branched</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-info d-none" id="server-branch" title="This is a branched version.">Branched</span>
</dt>
<dd class="col-sm-7">
<span id="server-installed">{{version}}</span>
</dd>
<dt class="col-sm-5">Server Latest
<span class="badge badge-secondary d-none" id="server-failed" title="Unable to determine latest version.">Unknown</span>
<span class="badge bg-secondary d-none" id="server-failed" title="Unable to determine latest version.">Unknown</span>
</dt>
<dd class="col-sm-7">
<span id="server-latest">{{diagnostics.latest_release}}<span id="server-latest-commit" class="d-none">-{{diagnostics.latest_commit}}</span></span>
<span id="server-latest">{{page_data.latest_release}}<span id="server-latest-commit" class="d-none">-{{page_data.latest_commit}}</span></span>
</dd>
{{#if diagnostics.web_vault_enabled}}
{{#if page_data.web_vault_enabled}}
<dt class="col-sm-5">Web Installed
<span class="badge badge-success d-none" id="web-success" title="Latest version is installed.">Ok</span>
<span class="badge badge-warning d-none" id="web-warning" title="There seems to be an update available.">Update</span>
<span class="badge bg-success d-none" id="web-success" title="Latest version is installed.">Ok</span>
<span class="badge bg-warning d-none" id="web-warning" title="There seems to be an update available.">Update</span>
</dt>
<dd class="col-sm-7">
<span id="web-installed">{{diagnostics.web_vault_version}}</span>
<span id="web-installed">{{page_data.web_vault_version}}</span>
</dd>
{{#unless diagnostics.running_within_docker}}
{{#unless page_data.running_within_docker}}
<dt class="col-sm-5">Web Latest
<span class="badge badge-secondary d-none" id="web-failed" title="Unable to determine latest version.">Unknown</span>
<span class="badge bg-secondary d-none" id="web-failed" title="Unable to determine latest version.">Unknown</span>
</dt>
<dd class="col-sm-7">
<span id="web-latest">{{diagnostics.latest_web_build}}</span>
<span id="web-latest">{{page_data.latest_web_build}}</span>
</dd>
{{/unless}}
{{/if}}
{{#unless diagnostics.web_vault_enabled}}
{{#unless page_data.web_vault_enabled}}
<dt class="col-sm-5">Web Installed</dt>
<dd class="col-sm-7">
<span id="web-installed">Web Vault is disabled</span>
@@ -45,7 +45,7 @@
{{/unless}}
<dt class="col-sm-5">Database</dt>
<dd class="col-sm-7">
<span><b>{{diagnostics.db_type}}:</b> {{diagnostics.db_version}}</span>
<span><b>{{page_data.db_type}}:</b> {{page_data.db_version}}</span>
</dd>
</dl>
</div>
@@ -57,96 +57,105 @@
<dl class="row">
<dt class="col-sm-5">Running within Docker</dt>
<dd class="col-sm-7">
{{#if diagnostics.running_within_docker}}
{{#if page_data.running_within_docker}}
<span class="d-block"><b>Yes</b></span>
{{/if}}
{{#unless diagnostics.running_within_docker}}
{{#unless page_data.running_within_docker}}
<span class="d-block"><b>No</b></span>
{{/unless}}
</dd>
<dt class="col-sm-5">Environment settings overridden</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>
{{/if}}
{{#unless page_data.overrides}}
<span class="d-block"><b>No</b></span>
{{/unless}}
</dd>
<dt class="col-sm-5">Uses a reverse proxy</dt>
<dd class="col-sm-7">
{{#if diagnostics.ip_header_exists}}
{{#if page_data.ip_header_exists}}
<span class="d-block" title="IP Header found."><b>Yes</b></span>
{{/if}}
{{#unless diagnostics.ip_header_exists}}
{{#unless page_data.ip_header_exists}}
<span class="d-block" title="No IP Header found."><b>No</b></span>
{{/unless}}
</dd>
{{!-- Only show this if the IP Header Exists --}}
{{#if diagnostics.ip_header_exists}}
{{#if page_data.ip_header_exists}}
<dt class="col-sm-5">IP header
{{#if diagnostics.ip_header_match}}
<span class="badge badge-success" title="IP_HEADER config seems to be valid.">Match</span>
{{#if page_data.ip_header_match}}
<span class="badge bg-success" title="IP_HEADER config seems to be valid.">Match</span>
{{/if}}
{{#unless diagnostics.ip_header_match}}
<span class="badge badge-danger" title="IP_HEADER config seems to be invalid. IP's in the log could be invalid. Please fix.">No Match</span>
{{#unless page_data.ip_header_match}}
<span class="badge bg-danger" title="IP_HEADER config seems to be invalid. IP's in the log could be invalid. Please fix.">No Match</span>
{{/unless}}
</dt>
<dd class="col-sm-7">
{{#if diagnostics.ip_header_match}}
<span class="d-block"><b>Config/Server:</b> {{ diagnostics.ip_header_name }}</span>
{{#if page_data.ip_header_match}}
<span class="d-block"><b>Config/Server:</b> {{ page_data.ip_header_name }}</span>
{{/if}}
{{#unless diagnostics.ip_header_match}}
<span class="d-block"><b>Config:</b> {{ diagnostics.ip_header_config }}</span>
<span class="d-block"><b>Server:</b> {{ diagnostics.ip_header_name }}</span>
{{#unless page_data.ip_header_match}}
<span class="d-block"><b>Config:</b> {{ page_data.ip_header_config }}</span>
<span class="d-block"><b>Server:</b> {{ page_data.ip_header_name }}</span>
{{/unless}}
</dd>
{{/if}}
{{!-- End if IP Header Exists --}}
<dt class="col-sm-5">Internet access
{{#if diagnostics.has_http_access}}
<span class="badge badge-success" title="We have internet access!">Ok</span>
{{#if page_data.has_http_access}}
<span class="badge bg-success" title="We have internet access!">Ok</span>
{{/if}}
{{#unless diagnostics.has_http_access}}
<span class="badge badge-danger" title="There seems to be no internet access. Please fix.">Error</span>
{{#unless page_data.has_http_access}}
<span class="badge bg-danger" title="There seems to be no internet access. Please fix.">Error</span>
{{/unless}}
</dt>
<dd class="col-sm-7">
{{#if diagnostics.has_http_access}}
{{#if page_data.has_http_access}}
<span class="d-block"><b>Yes</b></span>
{{/if}}
{{#unless diagnostics.has_http_access}}
{{#unless page_data.has_http_access}}
<span class="d-block"><b>No</b></span>
{{/unless}}
</dd>
<dt class="col-sm-5">Internet access via a proxy</dt>
<dd class="col-sm-7">
{{#if diagnostics.uses_proxy}}
{{#if page_data.uses_proxy}}
<span class="d-block" title="Internet access goes via a proxy (HTTPS_PROXY or HTTP_PROXY is configured)."><b>Yes</b></span>
{{/if}}
{{#unless diagnostics.uses_proxy}}
{{#unless page_data.uses_proxy}}
<span class="d-block" title="We have direct internet access, no outgoing proxy configured."><b>No</b></span>
{{/unless}}
</dd>
<dt class="col-sm-5">DNS (github.com)
<span class="badge badge-success d-none" id="dns-success" title="DNS Resolving works!">Ok</span>
<span class="badge badge-danger d-none" id="dns-warning" title="DNS Resolving failed. Please fix.">Error</span>
<span class="badge bg-success d-none" id="dns-success" title="DNS Resolving works!">Ok</span>
<span class="badge bg-danger d-none" id="dns-warning" title="DNS Resolving failed. Please fix.">Error</span>
</dt>
<dd class="col-sm-7">
<span id="dns-resolved">{{diagnostics.dns_resolved}}</span>
<span id="dns-resolved">{{page_data.dns_resolved}}</span>
</dd>
<dt class="col-sm-5">Date & Time (Local)</dt>
<dd class="col-sm-7">
<span><b>Server:</b> {{diagnostics.server_time_local}}</span>
<span><b>Server:</b> {{page_data.server_time_local}}</span>
</dd>
<dt class="col-sm-5">Date & Time (UTC)
<span class="badge badge-success d-none" id="time-success" title="Time offsets seem to be correct.">Ok</span>
<span class="badge badge-danger d-none" id="time-warning" title="Time offsets are too mouch at drift.">Error</span>
<span class="badge bg-success d-none" id="time-success" title="Time offsets seem to be correct.">Ok</span>
<span class="badge bg-danger d-none" id="time-warning" title="Time offsets are too mouch at drift.">Error</span>
</dt>
<dd class="col-sm-7">
<span id="time-server" class="d-block"><b>Server:</b> <span id="time-server-string">{{diagnostics.server_time}}</span></span>
<span id="time-server" class="d-block"><b>Server:</b> <span id="time-server-string">{{page_data.server_time}}</span></span>
<span id="time-browser" class="d-block"><b>Browser:</b> <span id="time-browser-string"></span></span>
</dd>
<dt class="col-sm-5">Domain configuration
<span class="badge badge-success d-none" id="domain-success" title="The domain variable matches the browser location and seems to be configured correctly.">Match</span>
<span class="badge badge-danger d-none" id="domain-warning" title="The domain variable does not matches the browsers location.&#013;&#010;The domain variable does not seem to be configured correctly.&#013;&#010;Some features may not work as expected!">No Match</span>
<span class="badge badge-success d-none" id="https-success" title="Configurued to use HTTPS">HTTPS</span>
<span class="badge badge-danger d-none" id="https-warning" title="Not configured to use HTTPS.&#013;&#010;Some features may not work as expected!">No HTTPS</span>
<span class="badge bg-success d-none" id="domain-success" title="The domain variable matches the browser location and seems to be configured correctly.">Match</span>
<span class="badge bg-danger d-none" id="domain-warning" title="The domain variable does not matches the browsers location.&#013;&#010;The domain variable does not seem to be configured correctly.&#013;&#010;Some features may not work as expected!">No Match</span>
<span class="badge bg-success d-none" id="https-success" title="Configurued to use HTTPS">HTTPS</span>
<span class="badge bg-danger d-none" id="https-warning" title="Not configured to use HTTPS.&#013;&#010;Some features may not work as expected!">No HTTPS</span>
</dt>
<dd class="col-sm-7">
<span id="domain-server" class="d-block"><b>Server:</b> <span id="domain-server-string">{{diagnostics.admin_url}}</span></span>
<span id="domain-server" class="d-block"><b>Server:</b> <span id="domain-server-string">{{page_data.admin_url}}</span></span>
<span id="domain-browser" class="d-block"><b>Browser:</b> <span id="domain-browser-string"></span></span>
</dd>
</dl>
@@ -173,10 +182,17 @@
<dt class="col-sm-3">
<button type="button" id="gen-support" class="btn btn-primary" onclick="generateSupportString(); return false;">Generate Support String</button>
<br><br>
<button type="button" id="copy-support" class="btn btn-info d-none" onclick="copyToClipboard(); return false;">Copy To Clipboard</button>
<button type="button" id="copy-support" class="btn btn-info mb-3 d-none" onclick="copyToClipboard(); return false;">Copy To Clipboard</button>
<div class="toast-container position-absolute float-start" style="width: 15rem;">
<div id="toastClipboardCopy" class="toast fade hide" role="status" aria-live="polite" aria-atomic="true" data-bs-autohide="true" data-bs-delay="1500">
<div class="toast-body">
Copied to clipboard!
</div>
</div>
</div>
</dt>
<dd class="col-sm-9">
<pre id="support-string" class="pre-scrollable d-none" style="width: 100%; height: 16em; size: 0.6em; border: 1px solid; padding: 4px;"></pre>
<pre id="support-string" class="pre-scrollable d-none w-100 border p-2" style="height: 16rem;"></pre>
</dd>
</dl>
</div>
@@ -185,10 +201,13 @@
</main>
<script>
dnsCheck = false;
timeCheck = false;
domainCheck = false;
httpsCheck = false;
'use strict';
var dnsCheck = false;
var timeCheck = false;
var domainCheck = false;
var httpsCheck = false;
(() => {
// ================================
// Date & Time Check
@@ -203,7 +222,10 @@
document.getElementById("time-browser-string").innerText = browserUTC;
const serverUTC = document.getElementById("time-server-string").innerText;
const timeDrift = (Date.parse(serverUTC) - Date.parse(browserUTC)) / 1000;
const timeDrift = (
Date.parse(serverUTC.replace(' ', 'T').replace(' UTC', '')) -
Date.parse(browserUTC.replace(' ', 'T').replace(' UTC', ''))
) / 1000;
if (timeDrift > 30 || timeDrift < -30) {
document.getElementById('time-warning').classList.remove('d-none');
} else {
@@ -233,7 +255,7 @@
const webInstalled = document.getElementById('web-installed').innerText;
checkVersions('server', serverInstalled, serverLatest, serverLatestCommit);
{{#unless diagnostics.running_within_docker}}
{{#unless page_data.running_within_docker}}
const webLatest = document.getElementById('web-latest').innerText;
checkVersions('web', webInstalled, webLatest);
{{/unless}}
@@ -303,30 +325,38 @@
// ================================
// Generate support string to be pasted on github or the forum
async function generateSupportString() {
supportString = "### Your environment (Generated via diagnostics page)\n";
let supportString = "### Your environment (Generated via diagnostics page)\n";
supportString += "* Vaultwarden version: v{{ version }}\n";
supportString += "* Web-vault version: v{{ diagnostics.web_vault_version }}\n";
supportString += "* Running within Docker: {{ diagnostics.running_within_docker }}\n";
supportString += "* Uses a reverse proxy: {{ diagnostics.ip_header_exists }}\n";
{{#if diagnostics.ip_header_exists}}
supportString += "* IP Header check: {{ diagnostics.ip_header_match }} ({{ diagnostics.ip_header_name }})\n";
supportString += "* Web-vault version: v{{ page_data.web_vault_version }}\n";
supportString += "* Running within Docker: {{ page_data.running_within_docker }}\n";
supportString += "* Environment settings overridden: ";
{{#if page_data.overrides}}
supportString += "true\n"
{{else}}
supportString += "false\n"
{{/if}}
supportString += "* Internet access: {{ diagnostics.has_http_access }}\n";
supportString += "* Internet access via a proxy: {{ diagnostics.uses_proxy }}\n";
supportString += "* Uses a reverse proxy: {{ page_data.ip_header_exists }}\n";
{{#if page_data.ip_header_exists}}
supportString += "* IP Header check: {{ page_data.ip_header_match }} ({{ page_data.ip_header_name }})\n";
{{/if}}
supportString += "* Internet access: {{ page_data.has_http_access }}\n";
supportString += "* Internet access via a proxy: {{ page_data.uses_proxy }}\n";
supportString += "* DNS Check: " + dnsCheck + "\n";
supportString += "* Time Check: " + timeCheck + "\n";
supportString += "* Domain Configuration Check: " + domainCheck + "\n";
supportString += "* HTTPS Check: " + httpsCheck + "\n";
supportString += "* Database type: {{ diagnostics.db_type }}\n";
supportString += "* Database version: {{ diagnostics.db_version }}\n";
supportString += "* Database type: {{ page_data.db_type }}\n";
supportString += "* Database version: {{ page_data.db_version }}\n";
supportString += "* Clients used: \n";
supportString += "* Reverse proxy and version: \n";
supportString += "* Other relevant information: \n";
jsonResponse = await fetch('{{urlpath}}/admin/diagnostics/config');
configJson = await jsonResponse.json();
supportString += "\n### Config (Generated via diagnostics page)\n```json\n" + JSON.stringify(configJson, undefined, 2) + "\n```\n";
let jsonResponse = await fetch('{{urlpath}}/admin/diagnostics/config');
const configJson = await jsonResponse.json();
supportString += "\n### Config (Generated via diagnostics page)\n<details><summary>Show Running Config</summary>\n"
supportString += "\n**Environment settings which are overridden:** {{page_data.overrides}}\n"
supportString += "\n\n```json\n" + JSON.stringify(configJson, undefined, 2) + "\n```\n</details>\n";
document.getElementById('support-string').innerText = supportString;
document.getElementById('support-string').classList.remove('d-none');
@@ -334,16 +364,19 @@
}
function copyToClipboard() {
const str = document.getElementById('support-string').innerText;
const el = document.createElement('textarea');
el.value = str;
el.setAttribute('readonly', '');
el.style.position = 'absolute';
el.style.left = '-9999px';
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
}
const supportStr = document.getElementById('support-string').innerText;
const tmpCopyEl = document.createElement('textarea');
tmpCopyEl.setAttribute('id', 'copy-support-string');
tmpCopyEl.setAttribute('readonly', '');
tmpCopyEl.value = supportStr;
tmpCopyEl.style.position = 'absolute';
tmpCopyEl.style.left = '-9999px';
document.body.appendChild(tmpCopyEl);
tmpCopyEl.select();
document.execCommand('copy');
tmpCopyEl.remove();
new BSN.Toast('#toastClipboardCopy').show();
}
</script>

View File

@@ -1,7 +1,6 @@
<main class="container-xl">
<div id="organizations-block" class="my-3 p-3 bg-white 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">
<thead>
@@ -10,19 +9,19 @@
<th>Users</th>
<th>Items</th>
<th>Attachments</th>
<th style="width: 120px; min-width: 120px;">Actions</th>
<th style="width: 130px; min-width: 130px;">Actions</th>
</tr>
</thead>
<tbody>
{{#each organizations}}
{{#each page_data}}
<tr>
<td>
<img class="mr-2 float-left rounded identicon" data-src="{{Id}}">
<div class="float-left">
<img class="float-start me-2 rounded identicon" data-src="{{Id}}">
<div class="float-start">
<strong>{{Name}}</strong>
<span class="mr-2">({{BillingEmail}})</span>
<span class="me-2">({{BillingEmail}})</span>
<span class="d-block">
<span class="badge badge-success">{{Id}}</span>
<span class="badge bg-success">{{Id}}</span>
</span>
</div>
</td>
@@ -38,7 +37,7 @@
<span class="d-block"><strong>Size:</strong> {{attachment_size}}</span>
{{/if}}
</td>
<td style="font-size: 90%; text-align: right; padding-right: 15px">
<td class="text-end pe-2 small">
<a class="d-block" href="#" onclick='deleteOrganization({{jsesc Id}}, {{jsesc Name}}, {{jsesc BillingEmail}})'>Delete Organization</a>
</td>
</tr>
@@ -46,14 +45,15 @@
</tbody>
</table>
</div>
</div>
</main>
<link rel="stylesheet" href="{{urlpath}}/bwrs_static/datatables.css" />
<script src="{{urlpath}}/bwrs_static/jquery-3.5.1.slim.js"></script>
<script src="{{urlpath}}/bwrs_static/jquery-3.6.0.slim.js"></script>
<script src="{{urlpath}}/bwrs_static/datatables.js"></script>
<script>
'use strict';
function deleteOrganization(id, name, billing_email) {
// First make sure the user wants to delete this organization
var continueDelete = confirm("WARNING: All data of this organization ("+ name +") will be lost!\nMake sure you have a backup, this cannot be undone!");
@@ -79,7 +79,7 @@
}
})();
document.addEventListener("DOMContentLoaded", function(event) {
document.addEventListener("DOMContentLoaded", function() {
$('#orgs-table').DataTable({
"responsive": true,
"lengthMenu": [ [-1, 5, 10, 25, 50], ["All", 5, 10, 25, 50] ],

View File

@@ -3,34 +3,32 @@
<div>
<h6 class="text-white mb-3">Configuration</h6>
<div class="small text-white mb-3">
NOTE: The settings here override the environment variables. Once saved, it's recommended to stop setting
them to avoid confusion. This does not apply to the read-only section, which can only be set through the
environment.
<span class="font-weight-bolder">NOTE:</span> The settings here override the environment variables. Once saved, it's recommended to stop setting them to avoid confusion.<br>
This does not apply to the read-only section, which can only be set via environment variables.<br>
Settings which are overridden are shown with <span class="is-overridden-true">double underscores</span>.
</div>
<form class="form accordion" id="config-form" onsubmit="saveConfig(); return false;">
<form class="form needs-validation" id="config-form" onsubmit="saveConfig(); return false;" novalidate>
{{#each config}}
{{#if groupdoc}}
<div class="card bg-light mb-3">
<div class="card-header"><button type="button" class="btn btn-link collapsed" data-toggle="collapse"
data-target="#g_{{group}}">{{groupdoc}}</button></div>
<div id="g_{{group}}" class="card-body collapse" data-parent="#config-form">
<div class="card-header" role="button" data-bs-toggle="collapse" data-bs-target="#g_{{group}}">
<button type="button" class="btn btn-link text-decoration-none collapsed" data-bs-toggle="collapse" data-bs-target="#g_{{group}}">{{groupdoc}}</button>
</div>
<div id="g_{{group}}" class="card-body collapse">
{{#each elements}}
{{#if editable}}
<div class="form-group row align-items-center" title="[{{name}}] {{doc.description}}">
<div class="row my-2 align-items-center is-overridden-{{overridden}}" title="[{{name}}] {{doc.description}}">
{{#case type "text" "number" "password"}}
<label for="input_{{name}}" class="col-sm-3 col-form-label">{{doc.name}}</label>
<div class="col-sm-8 input-group">
<div class="col-sm-8">
<div class="input-group">
<input class="form-control conf-{{type}}" id="input_{{name}}" type="{{type}}"
name="{{name}}" value="{{value}}" {{#if default}} placeholder="Default: {{default}}"
{{/if}}>
name="{{name}}" value="{{value}}" {{#if default}} placeholder="Default: {{default}}"{{/if}}>
{{#case type "password"}}
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button"
onclick="toggleVis('input_{{name}}');">Show/hide</button>
</div>
<button class="btn btn-outline-secondary input-group-text" type="button" onclick="toggleVis('input_{{name}}');">Show/hide</button>
{{/case}}
</div>
</div>
{{/case}}
{{#case type "checkbox"}}
@@ -48,13 +46,12 @@
{{/if}}
{{/each}}
{{#case group "smtp"}}
<div class="form-group row align-items-center pt-3 border-top" title="Send a test email to given email address">
<div class="row my-2 align-items-center pt-3 border-top" title="Send a test email to given email address">
<label for="smtp-test-email" class="col-sm-3 col-form-label">Test SMTP</label>
<div class="col-sm-8 input-group">
<input class="form-control" id="smtp-test-email" type="email" placeholder="Enter test email">
<div class="input-group-append">
<button type="button" class="btn btn-outline-primary" onclick="smtpTest(); return false;">Send test email</button>
</div>
<input class="form-control" id="smtp-test-email" type="email" placeholder="Enter test email" required>
<button type="button" class="btn btn-outline-primary input-group-text" onclick="smtpTest(); return false;">Send test email</button>
<div class="invalid-tooltip">Please provide a valid email address</div>
</div>
</div>
{{/case}}
@@ -64,9 +61,11 @@
{{/each}}
<div class="card bg-light mb-3">
<div class="card-header"><button type="button" class="btn btn-link collapsed" data-toggle="collapse"
data-target="#g_readonly">Read-Only Config</button></div>
<div id="g_readonly" class="card-body collapse" data-parent="#config-form">
<div class="card-header" role="button" data-bs-toggle="collapse" data-bs-target="#g_readonly">
<button type="button" class="btn btn-link text-decoration-none collapsed" data-bs-toggle="collapse" data-bs-target="#g_readonly">Read-Only Config</button>
</div>
<div id="g_readonly" class="card-body collapse">
<div class="small mb-3">
NOTE: These options can't be modified in the editor because they would require the server
to be restarted. To modify them, you need to set the correct environment variables when
@@ -76,19 +75,17 @@
{{#each config}}
{{#each elements}}
{{#unless editable}}
<div class="form-group row align-items-center" title="[{{name}}] {{doc.description}}">
<div class="row my-2 align-items-center" title="[{{name}}] {{doc.description}}">
{{#case type "text" "number" "password"}}
<label for="input_{{name}}" class="col-sm-3 col-form-label">{{doc.name}}</label>
<div class="col-sm-8 input-group">
<div class="col-sm-8">
<div class="input-group">
<input readonly class="form-control" id="input_{{name}}" type="{{type}}"
value="{{value}}" {{#if default}} placeholder="Default: {{default}}" {{/if}}>
{{#case type "password"}}
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button"
onclick="toggleVis('input_{{name}}');">Show/hide</button>
</div>
<button class="btn btn-outline-secondary" type="button" onclick="toggleVis('input_{{name}}');">Show/hide</button>
{{/case}}
</div>
</div>
{{/case}}
{{#case type "checkbox"}}
@@ -112,9 +109,10 @@
{{#if can_backup}}
<div class="card bg-light mb-3">
<div class="card-header"><button type="button" class="btn btn-link collapsed" data-toggle="collapse"
data-target="#g_database">Backup Database</button></div>
<div id="g_database" class="card-body collapse" data-parent="#config-form">
<div class="card-header" role="button" data-bs-toggle="collapse" data-bs-target="#g_database">
<button type="button" class="btn btn-link text-decoration-none collapsed" data-bs-toggle="collapse" data-bs-target="#g_database">Backup Database</button>
</div>
<div id="g_database" class="card-body collapse">
<div class="small mb-3">
WARNING: This function only creates a backup copy of the SQLite database.
This does not include any configuration or file attachment data that may
@@ -128,7 +126,7 @@
{{/if}}
<button type="submit" class="btn btn-primary">Save</button>
<button type="button" class="btn btn-danger float-right" onclick="deleteConf();">Reset defaults</button>
<button type="button" class="btn btn-danger float-end" onclick="deleteConf();">Reset defaults</button>
</form>
</div>
</div>
@@ -139,16 +137,34 @@
/* Most modern browsers support this now. */
color: orangered;
}
.is-overridden-true {
text-decoration: underline double;
}
</style>
<script>
'use strict';
function smtpTest() {
if (formHasChanges(config_form)) {
event.preventDefault();
event.stopPropagation();
alert("Config has been changed but not yet saved.\nPlease save the changes first before sending a test email.");
return false;
}
test_email = document.getElementById("smtp-test-email");
data = JSON.stringify({ "email": test_email.value });
let test_email = document.getElementById("smtp-test-email");
// Do a very very basic email address check.
if (test_email.value.match(/\S+@\S+/i) === null) {
test_email.parentElement.classList.add('was-validated');
event.preventDefault();
event.stopPropagation();
return false;
}
const data = JSON.stringify({ "email": test_email.value });
_post("{{urlpath}}/admin/test/smtp/",
"SMTP Test email sent correctly",
"Error sending SMTP test email", data, false);
@@ -157,21 +173,21 @@
function getFormData() {
let data = {};
document.querySelectorAll(".conf-checkbox").forEach(function (e, i) {
document.querySelectorAll(".conf-checkbox").forEach(function (e) {
data[e.name] = e.checked;
});
document.querySelectorAll(".conf-number").forEach(function (e, i) {
document.querySelectorAll(".conf-number").forEach(function (e) {
data[e.name] = e.value ? +e.value : null;
});
document.querySelectorAll(".conf-text, .conf-password").forEach(function (e, i) {
document.querySelectorAll(".conf-text, .conf-password").forEach(function (e) {
data[e.name] = e.value || null;
});
return data;
}
function saveConfig() {
data = JSON.stringify(getFormData());
const data = JSON.stringify(getFormData());
_post("{{urlpath}}/admin/config/", "Config saved correctly",
"Error saving config", data);
return false;
@@ -198,10 +214,10 @@
function masterCheck(check_id, inputs_query) {
function onChanged(checkbox, inputs_query) {
return function _fn() {
document.querySelectorAll(inputs_query).forEach(function (e, i) { e.disabled = !checkbox.checked; });
document.querySelectorAll(inputs_query).forEach(function (e) { e.disabled = !checkbox.checked; });
checkbox.disabled = false;
};
};
}
const checkbox = document.getElementById(check_id);
const onChange = onChanged(checkbox, inputs_query);
@@ -238,7 +254,6 @@
Array.from(risk_el).forEach((el) => {
if (el.innerText.toLowerCase().includes('risks') ) {
el.parentElement.className += ' alert-danger'
console.log(el)
}
});
}

View File

@@ -7,34 +7,34 @@
<thead>
<tr>
<th>User</th>
<th style="width:65px; min-width: 65px;">Created at</th>
<th style="width:70px; min-width: 65px;">Last Active</th>
<th style="width:35px; min-width: 35px;">Items</th>
<th style="width: 85px; min-width: 70px;">Created at</th>
<th style="width: 85px; min-width: 70px;">Last Active</th>
<th style="width: 35px; min-width: 35px;">Items</th>
<th>Attachments</th>
<th style="min-width: 120px;">Organizations</th>
<th style="width: 120px; min-width: 120px;">Actions</th>
<th style="width: 130px; min-width: 130px;">Actions</th>
</tr>
</thead>
<tbody>
{{#each users}}
{{#each page_data}}
<tr>
<td>
<img class="float-left mr-2 rounded identicon" data-src="{{Email}}">
<div class="float-left">
<img class="float-start me-2 rounded identicon" data-src="{{Email}}">
<div class="float-start">
<strong>{{Name}}</strong>
<span class="d-block">{{Email}}</span>
<span class="d-block">
{{#unless user_enabled}}
<span class="badge badge-danger mr-2" title="User is disabled">Disabled</span>
<span class="badge bg-danger me-2" title="User is disabled">Disabled</span>
{{/unless}}
{{#if TwoFactorEnabled}}
<span class="badge badge-success mr-2" title="2FA is enabled">2FA</span>
<span class="badge bg-success me-2" title="2FA is enabled">2FA</span>
{{/if}}
{{#case _Status 1}}
<span class="badge badge-warning mr-2" title="User is invited">Invited</span>
<span class="badge bg-warning me-2" title="User is invited">Invited</span>
{{/case}}
{{#if EmailVerified}}
<span class="badge badge-success mr-2" title="Email has been verified">Verified</span>
<span class="badge bg-success me-2" title="Email has been verified">Verified</span>
{{/if}}
</span>
</div>
@@ -57,11 +57,11 @@
<td>
<div class="overflow-auto" style="max-height: 120px;">
{{#each Organizations}}
<button class="badge badge-primary" data-toggle="modal" data-target="#userOrgTypeDialog" data-orgtype="{{Type}}" data-orguuid="{{jsesc Id no_quote}}" data-orgname="{{jsesc Name no_quote}}" data-useremail="{{jsesc ../Email no_quote}}" data-useruuid="{{jsesc ../Id no_quote}}">{{Name}}</button>
<button class="badge" data-bs-toggle="modal" data-bs-target="#userOrgTypeDialog" data-orgtype="{{Type}}" data-orguuid="{{jsesc Id no_quote}}" data-orgname="{{jsesc Name no_quote}}" data-useremail="{{jsesc ../Email no_quote}}" data-useruuid="{{jsesc ../Id no_quote}}">{{Name}}</button>
{{/each}}
</div>
</td>
<td style="font-size: 90%; text-align: right; padding-right: 15px">
<td class="text-end pe-2 small">
{{#if TwoFactorEnabled}}
<a class="d-block" href="#" onclick='remove2fa({{jsesc Id}})'>Remove all 2FA</a>
{{/if}}
@@ -85,7 +85,7 @@
Force clients to resync
</button>
<button type="button" class="btn btn-sm btn-primary float-right" onclick="reload();">Reload users</button>
<button type="button" class="btn btn-sm btn-primary float-end" onclick="reload();">Reload users</button>
</div>
</div>
@@ -94,8 +94,8 @@
<h6 class="mb-0 text-white">Invite User</h6>
<small>Email:</small>
<form class="form-inline" id="invite-form" onsubmit="inviteUser(); return false;">
<input type="email" class="form-control w-50 mr-2" id="email-invite" placeholder="Enter email">
<form class="form-inline input-group w-50" id="invite-form" onsubmit="inviteUser(); return false;">
<input type="email" class="form-control me-2" id="email-invite" placeholder="Enter email" required>
<button type="submit" class="btn btn-primary">Invite</button>
</form>
</div>
@@ -106,9 +106,7 @@
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title" id="userOrgTypeDialogTitle"></h6>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form class="form" id="userOrgTypeForm" onsubmit="updateUserOrgType(); return false;">
<input type="hidden" name="user_uuid" id="userOrgTypeUserUuid" value="">
@@ -128,7 +126,7 @@
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-sm btn-primary">Change Role</button>
</div>
</form>
@@ -138,9 +136,11 @@
</main>
<link rel="stylesheet" href="{{urlpath}}/bwrs_static/datatables.css" />
<script src="{{urlpath}}/bwrs_static/jquery-3.5.1.slim.js"></script>
<script src="{{urlpath}}/bwrs_static/jquery-3.6.0.slim.js"></script>
<script src="{{urlpath}}/bwrs_static/datatables.js"></script>
<script>
'use strict';
function deleteUser(id, mail) {
var input_mail = prompt("To delete user '" + mail + "', please type the email below")
if (input_mail != null) {
@@ -191,8 +191,8 @@
return false;
}
function inviteUser() {
inv = document.getElementById("email-invite");
data = JSON.stringify({ "email": inv.value });
const inv = document.getElementById("email-invite");
const data = JSON.stringify({ "email": inv.value });
inv.value = "";
_post("{{urlpath}}/admin/invite/", "User invited correctly",
"Error inviting user", data);
@@ -212,7 +212,7 @@
}
})();
document.querySelectorAll("[data-orgtype]").forEach(function (e, i) {
document.querySelectorAll("[data-orgtype]").forEach(function (e) {
let orgtype = OrgTypes[e.dataset.orgtype];
e.style.backgroundColor = orgtype.color;
e.title = orgtype.name;
@@ -225,7 +225,7 @@
let sortDate = a.replace(/(<([^>]+)>)/gi, "").trim();
if ( sortDate !== '' ) {
let dtParts = sortDate.split(' ');
var timeParts = (undefined != dtParts[1]) ? dtParts[1].split(':') : [00,00,00];
var timeParts = (undefined != dtParts[1]) ? dtParts[1].split(':') : ['00','00','00'];
var dateParts = dtParts[0].split('-');
x = (dateParts[0] + dateParts[1] + dateParts[2] + timeParts[0] + timeParts[1] + ((undefined != timeParts[2]) ? timeParts[2] : 0)) * 1;
if ( isNaN(x) ) {
@@ -246,7 +246,7 @@
}
});
document.addEventListener("DOMContentLoaded", function(event) {
document.addEventListener("DOMContentLoaded", function() {
$('#users-table').DataTable({
"responsive": true,
"lengthMenu": [ [-1, 5, 10, 25, 50], ["All", 5, 10, 25, 50] ],
@@ -275,7 +275,7 @@
}, false);
// Prevent accidental submission of the form with valid elements after the modal has been hidden.
userOrgTypeDialog.addEventListener('hide.bs.modal', function(event){
userOrgTypeDialog.addEventListener('hide.bs.modal', function(){
document.getElementById("userOrgTypeDialogTitle").innerHTML = '';
document.getElementById("userOrgTypeUserUuid").value = '';
document.getElementById("userOrgTypeOrgUuid").value = '';