mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2024-12-23 11:29:04 +00:00
Merge branch 'admin-changes' of https://github.com/BlackDex/vaultwarden into BlackDex-admin-changes
This commit is contained in:
commit
0778bd4bd5
17
.github/workflows/build.yml
vendored
17
.github/workflows/build.yml
vendored
@ -2,6 +2,19 @@ name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- "*.md"
|
||||
- "*.txt"
|
||||
- ".dockerignore"
|
||||
- ".env.template"
|
||||
- ".gitattributes"
|
||||
- ".gitignore"
|
||||
- "azure-pipelines.yml"
|
||||
- "docker/**"
|
||||
- "hooks/**"
|
||||
- "tools/**"
|
||||
- ".github/FUNDING.yml"
|
||||
- ".github/ISSUE_TEMPLATE/**"
|
||||
pull_request:
|
||||
# Ignore when there are only changes done too one of these paths
|
||||
paths-ignore:
|
||||
@ -39,13 +52,13 @@ jobs:
|
||||
features: [sqlite,mysql,postgresql] # Remember to update the `cargo test` to match the amount of features
|
||||
channel: nightly
|
||||
os: ubuntu-18.04
|
||||
ext:
|
||||
ext: ""
|
||||
# - target-triple: x86_64-unknown-linux-gnu
|
||||
# host-triple: x86_64-unknown-linux-gnu
|
||||
# features: "sqlite,mysql,postgresql"
|
||||
# channel: stable
|
||||
# os: ubuntu-18.04
|
||||
# ext:
|
||||
# ext: ""
|
||||
|
||||
name: Building ${{ matrix.channel }}-${{ matrix.target-triple }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
9
.github/workflows/hadolint.yml
vendored
9
.github/workflows/hadolint.yml
vendored
@ -2,6 +2,9 @@ name: Hadolint
|
||||
|
||||
on:
|
||||
push:
|
||||
# Ignore when there are only changes done too one of these paths
|
||||
paths:
|
||||
- "docker/**"
|
||||
pull_request:
|
||||
# Ignore when there are only changes done too one of these paths
|
||||
paths:
|
||||
@ -22,14 +25,14 @@ jobs:
|
||||
- name: Download hadolint
|
||||
shell: bash
|
||||
run: |
|
||||
sudo curl -L https://github.com/hadolint/hadolint/releases/download/v$HADOLINT_VERSION/hadolint-$(uname -s)-$(uname -m) -o /usr/local/bin/hadolint && \
|
||||
sudo curl -L https://github.com/hadolint/hadolint/releases/download/v${HADOLINT_VERSION}/hadolint-$(uname -s)-$(uname -m) -o /usr/local/bin/hadolint && \
|
||||
sudo chmod +x /usr/local/bin/hadolint
|
||||
env:
|
||||
HADOLINT_VERSION: 2.3.0
|
||||
HADOLINT_VERSION: 2.5.0
|
||||
# End Download hadolint
|
||||
|
||||
# Test Dockerfiles
|
||||
- name: Run hadolint
|
||||
shell: bash
|
||||
run: git ls-files --exclude='docker/*/Dockerfile*' --ignored | xargs hadolint
|
||||
run: git ls-files --exclude='docker/*/Dockerfile*' --ignored --cached | xargs hadolint
|
||||
# End Test Dockerfiles
|
||||
|
@ -196,9 +196,7 @@ fn _validate_token(token: &str) -> bool {
|
||||
struct AdminTemplateData {
|
||||
page_content: String,
|
||||
version: Option<&'static str>,
|
||||
users: Option<Vec<Value>>,
|
||||
organizations: Option<Vec<Value>>,
|
||||
diagnostics: Option<Value>,
|
||||
page_data: Option<Value>,
|
||||
config: Value,
|
||||
can_backup: bool,
|
||||
logged_in: bool,
|
||||
@ -214,51 +212,19 @@ impl AdminTemplateData {
|
||||
can_backup: *CAN_BACKUP,
|
||||
logged_in: true,
|
||||
urlpath: CONFIG.domain_path(),
|
||||
users: None,
|
||||
organizations: None,
|
||||
diagnostics: None,
|
||||
page_data: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn users(users: Vec<Value>) -> Self {
|
||||
fn with_data(page_content: &str, page_data: Value) -> Self {
|
||||
Self {
|
||||
page_content: String::from("admin/users"),
|
||||
page_content: String::from(page_content),
|
||||
version: VERSION,
|
||||
users: Some(users),
|
||||
page_data: Some(page_data),
|
||||
config: CONFIG.prepare_json(),
|
||||
can_backup: *CAN_BACKUP,
|
||||
logged_in: true,
|
||||
urlpath: CONFIG.domain_path(),
|
||||
organizations: None,
|
||||
diagnostics: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn organizations(organizations: Vec<Value>) -> Self {
|
||||
Self {
|
||||
page_content: String::from("admin/organizations"),
|
||||
version: VERSION,
|
||||
organizations: Some(organizations),
|
||||
config: CONFIG.prepare_json(),
|
||||
can_backup: *CAN_BACKUP,
|
||||
logged_in: true,
|
||||
urlpath: CONFIG.domain_path(),
|
||||
users: None,
|
||||
diagnostics: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn diagnostics(diagnostics: Value) -> Self {
|
||||
Self {
|
||||
page_content: String::from("admin/diagnostics"),
|
||||
version: VERSION,
|
||||
organizations: None,
|
||||
config: CONFIG.prepare_json(),
|
||||
can_backup: *CAN_BACKUP,
|
||||
logged_in: true,
|
||||
urlpath: CONFIG.domain_path(),
|
||||
users: None,
|
||||
diagnostics: Some(diagnostics),
|
||||
}
|
||||
}
|
||||
|
||||
@ -360,7 +326,7 @@ fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let text = AdminTemplateData::users(users_json).render()?;
|
||||
let text = AdminTemplateData::with_data("admin/users", json!(users_json)).render()?;
|
||||
Ok(Html(text))
|
||||
}
|
||||
|
||||
@ -466,7 +432,7 @@ fn organizations_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<St
|
||||
})
|
||||
.collect();
|
||||
|
||||
let text = AdminTemplateData::organizations(organizations_json).render()?;
|
||||
let text = AdminTemplateData::with_data("admin/organizations", json!(organizations_json)).render()?;
|
||||
Ok(Html(text))
|
||||
}
|
||||
|
||||
@ -592,11 +558,12 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu
|
||||
"db_type": *DB_TYPE,
|
||||
"db_version": get_sql_server_version(&conn),
|
||||
"admin_url": format!("{}/diagnostics", admin_url(Referer(None))),
|
||||
"overrides": &CONFIG.get_overrides().join(", "),
|
||||
"server_time_local": Local::now().format("%Y-%m-%d %H:%M:%S %Z").to_string(),
|
||||
"server_time": Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(), // Run the date/time check as the last item to minimize the difference
|
||||
});
|
||||
|
||||
let text = AdminTemplateData::diagnostics(diagnostics_json).render()?;
|
||||
let text = AdminTemplateData::with_data("admin/diagnostics", diagnostics_json).render()?;
|
||||
Ok(Html(text))
|
||||
}
|
||||
|
||||
|
@ -91,8 +91,8 @@ fn static_files(filename: String) -> Result<Content<&'static [u8]>, Error> {
|
||||
"identicon.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/identicon.js"))),
|
||||
"datatables.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))),
|
||||
"datatables.css" => Ok(Content(ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
|
||||
"jquery-3.5.1.slim.js" => {
|
||||
Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.5.1.slim.js")))
|
||||
"jquery-3.6.0.slim.js" => {
|
||||
Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.0.slim.js")))
|
||||
}
|
||||
_ => err!(format!("Static file not found: {}", filename)),
|
||||
}
|
||||
|
@ -57,6 +57,8 @@ macro_rules! make_config {
|
||||
|
||||
_env: ConfigBuilder,
|
||||
_usr: ConfigBuilder,
|
||||
|
||||
_overrides: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
@ -113,8 +115,7 @@ macro_rules! make_config {
|
||||
|
||||
/// Merges the values of both builders into a new builder.
|
||||
/// If both have the same element, `other` wins.
|
||||
fn merge(&self, other: &Self, show_overrides: bool) -> Self {
|
||||
let mut overrides = Vec::new();
|
||||
fn merge(&self, other: &Self, show_overrides: bool, overrides: &mut Vec<String>) -> Self {
|
||||
let mut builder = self.clone();
|
||||
$($(
|
||||
if let v @Some(_) = &other.$name {
|
||||
@ -176,9 +177,9 @@ macro_rules! make_config {
|
||||
)+)+
|
||||
|
||||
pub fn prepare_json(&self) -> serde_json::Value {
|
||||
let (def, cfg) = {
|
||||
let (def, cfg, overriden) = {
|
||||
let inner = &self.inner.read().unwrap();
|
||||
(inner._env.build(), inner.config.clone())
|
||||
(inner._env.build(), inner.config.clone(), inner._overrides.clone())
|
||||
};
|
||||
|
||||
fn _get_form_type(rust_type: &str) -> &'static str {
|
||||
@ -210,6 +211,7 @@ macro_rules! make_config {
|
||||
"default": def.$name,
|
||||
"type": _get_form_type(stringify!($ty)),
|
||||
"doc": _get_doc(concat!($($doc),+)),
|
||||
"overridden": overriden.contains(&stringify!($name).to_uppercase()),
|
||||
}, )+
|
||||
]}, )+ ])
|
||||
}
|
||||
@ -224,6 +226,15 @@ macro_rules! make_config {
|
||||
stringify!($name): make_config!{ @supportstr $name, cfg.$name, $ty, $none_action },
|
||||
)+)+ })
|
||||
}
|
||||
|
||||
pub fn get_overrides(&self) -> Vec<String> {
|
||||
let overrides = {
|
||||
let inner = &self.inner.read().unwrap();
|
||||
inner._overrides.clone()
|
||||
};
|
||||
|
||||
overrides
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -505,7 +516,7 @@ make_config! {
|
||||
/// Server name sent during HELO |> By default this value should be is on the machine's hostname, but might need to be changed in case it trips some anti-spam filters
|
||||
helo_name: String, true, option;
|
||||
/// Enable SMTP debugging (Know the risks!) |> DANGEROUS: Enabling this will output very detailed SMTP messages. This could contain sensitive information like passwords and usernames! Only enable this during troubleshooting!
|
||||
smtp_debug: bool, true, def, false;
|
||||
smtp_debug: bool, false, def, false;
|
||||
/// Accept Invalid Certs (Know the risks!) |> DANGEROUS: Allow invalid certificates. This option introduces significant vulnerabilities to man-in-the-middle attacks!
|
||||
smtp_accept_invalid_certs: bool, true, def, false;
|
||||
/// Accept Invalid Hostnames (Know the risks!) |> DANGEROUS: Allow invalid hostnames. This option introduces significant vulnerabilities to man-in-the-middle attacks!
|
||||
@ -639,7 +650,8 @@ impl Config {
|
||||
let _usr = ConfigBuilder::from_file(&CONFIG_FILE).unwrap_or_default();
|
||||
|
||||
// Create merged config, config file overwrites env
|
||||
let builder = _env.merge(&_usr, true);
|
||||
let mut _overrides = Vec::new();
|
||||
let builder = _env.merge(&_usr, true, &mut _overrides);
|
||||
|
||||
// Fill any missing with defaults
|
||||
let config = builder.build();
|
||||
@ -651,6 +663,7 @@ impl Config {
|
||||
config,
|
||||
_env,
|
||||
_usr,
|
||||
_overrides,
|
||||
}),
|
||||
})
|
||||
}
|
||||
@ -666,9 +679,10 @@ impl Config {
|
||||
let config_str = serde_json::to_string_pretty(&builder)?;
|
||||
|
||||
// Prepare the combined config
|
||||
let mut overrides = Vec::new();
|
||||
let config = {
|
||||
let env = &self.inner.read().unwrap()._env;
|
||||
env.merge(&builder, false).build()
|
||||
env.merge(&builder, false, &mut overrides).build()
|
||||
};
|
||||
validate_config(&config)?;
|
||||
|
||||
@ -677,6 +691,7 @@ impl Config {
|
||||
let mut writer = self.inner.write().unwrap();
|
||||
writer.config = config;
|
||||
writer._usr = builder;
|
||||
writer._overrides = overrides;
|
||||
}
|
||||
|
||||
//Save to file
|
||||
@ -690,7 +705,8 @@ impl Config {
|
||||
pub fn update_config_partial(&self, other: ConfigBuilder) -> Result<(), Error> {
|
||||
let builder = {
|
||||
let usr = &self.inner.read().unwrap()._usr;
|
||||
usr.merge(&other, false)
|
||||
let mut _overrides = Vec::new();
|
||||
usr.merge(&other, false, &mut _overrides)
|
||||
};
|
||||
self.update_config(builder)
|
||||
}
|
||||
@ -751,6 +767,7 @@ impl Config {
|
||||
let mut writer = self.inner.write().unwrap();
|
||||
writer.config = config;
|
||||
writer._usr = usr;
|
||||
writer._overrides = Vec::new();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
5015
src/static/scripts/bootstrap-native.js
vendored
5015
src/static/scripts/bootstrap-native.js
vendored
File diff suppressed because it is too large
Load Diff
14435
src/static/scripts/bootstrap.css
vendored
14435
src/static/scripts/bootstrap.css
vendored
File diff suppressed because it is too large
Load Diff
26
src/static/scripts/datatables.css
vendored
26
src/static/scripts/datatables.css
vendored
@ -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 {
|
||||
|
90
src/static/scripts/datatables.js
vendored
90
src/static/scripts/datatables.js
vendored
@ -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"
|
||||
} );
|
||||
|
@ -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 );
|
||||
};
|
||||
} );
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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.
The domain variable does not seem to be configured correctly.
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.
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.
The domain variable does not seem to be configured correctly.
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.
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>
|
||||
|
@ -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] ],
|
||||
|
@ -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)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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">×</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 = '';
|
||||
|
Loading…
Reference in New Issue
Block a user