geforkt von mirrored/vaultwarden
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.
Dieser Commit ist enthalten in:
Ursprung
5772836be5
Commit
8615736e84
15 geänderte Dateien mit 11393 neuen und 8885 gelöschten Zeilen
17
.github/workflows/build.yml
gevendort
17
.github/workflows/build.yml
gevendort
|
@ -2,6 +2,19 @@ name: Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- "*.md"
|
||||||
|
- "*.txt"
|
||||||
|
- ".dockerignore"
|
||||||
|
- ".env.template"
|
||||||
|
- ".gitattributes"
|
||||||
|
- ".gitignore"
|
||||||
|
- "azure-pipelines.yml"
|
||||||
|
- "docker/**"
|
||||||
|
- "hooks/**"
|
||||||
|
- "tools/**"
|
||||||
|
- ".github/FUNDING.yml"
|
||||||
|
- ".github/ISSUE_TEMPLATE/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
# Ignore when there are only changes done too one of these paths
|
# Ignore when there are only changes done too one of these paths
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
|
@ -39,13 +52,13 @@ jobs:
|
||||||
features: [sqlite,mysql,postgresql] # Remember to update the `cargo test` to match the amount of features
|
features: [sqlite,mysql,postgresql] # Remember to update the `cargo test` to match the amount of features
|
||||||
channel: nightly
|
channel: nightly
|
||||||
os: ubuntu-18.04
|
os: ubuntu-18.04
|
||||||
ext:
|
ext: ""
|
||||||
# - target-triple: x86_64-unknown-linux-gnu
|
# - target-triple: x86_64-unknown-linux-gnu
|
||||||
# host-triple: x86_64-unknown-linux-gnu
|
# host-triple: x86_64-unknown-linux-gnu
|
||||||
# features: "sqlite,mysql,postgresql"
|
# features: "sqlite,mysql,postgresql"
|
||||||
# channel: stable
|
# channel: stable
|
||||||
# os: ubuntu-18.04
|
# os: ubuntu-18.04
|
||||||
# ext:
|
# ext: ""
|
||||||
|
|
||||||
name: Building ${{ matrix.channel }}-${{ matrix.target-triple }}
|
name: Building ${{ matrix.channel }}-${{ matrix.target-triple }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
9
.github/workflows/hadolint.yml
gevendort
9
.github/workflows/hadolint.yml
gevendort
|
@ -2,6 +2,9 @@ name: Hadolint
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
# Ignore when there are only changes done too one of these paths
|
||||||
|
paths:
|
||||||
|
- "docker/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
# Ignore when there are only changes done too one of these paths
|
# Ignore when there are only changes done too one of these paths
|
||||||
paths:
|
paths:
|
||||||
|
@ -22,14 +25,14 @@ jobs:
|
||||||
- name: Download hadolint
|
- name: Download hadolint
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
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
|
sudo chmod +x /usr/local/bin/hadolint
|
||||||
env:
|
env:
|
||||||
HADOLINT_VERSION: 2.3.0
|
HADOLINT_VERSION: 2.5.0
|
||||||
# End Download hadolint
|
# End Download hadolint
|
||||||
|
|
||||||
# Test Dockerfiles
|
# Test Dockerfiles
|
||||||
- name: Run hadolint
|
- name: Run hadolint
|
||||||
shell: bash
|
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
|
# End Test Dockerfiles
|
||||||
|
|
|
@ -196,9 +196,7 @@ fn _validate_token(token: &str) -> bool {
|
||||||
struct AdminTemplateData {
|
struct AdminTemplateData {
|
||||||
page_content: String,
|
page_content: String,
|
||||||
version: Option<&'static str>,
|
version: Option<&'static str>,
|
||||||
users: Option<Vec<Value>>,
|
page_data: Option<Value>,
|
||||||
organizations: Option<Vec<Value>>,
|
|
||||||
diagnostics: Option<Value>,
|
|
||||||
config: Value,
|
config: Value,
|
||||||
can_backup: bool,
|
can_backup: bool,
|
||||||
logged_in: bool,
|
logged_in: bool,
|
||||||
|
@ -214,51 +212,19 @@ impl AdminTemplateData {
|
||||||
can_backup: *CAN_BACKUP,
|
can_backup: *CAN_BACKUP,
|
||||||
logged_in: true,
|
logged_in: true,
|
||||||
urlpath: CONFIG.domain_path(),
|
urlpath: CONFIG.domain_path(),
|
||||||
users: None,
|
page_data: None,
|
||||||
organizations: None,
|
|
||||||
diagnostics: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn users(users: Vec<Value>) -> Self {
|
fn with_data(page_content: &str, page_data: Value) -> Self {
|
||||||
Self {
|
Self {
|
||||||
page_content: String::from("admin/users"),
|
page_content: String::from(page_content),
|
||||||
version: VERSION,
|
version: VERSION,
|
||||||
users: Some(users),
|
page_data: Some(page_data),
|
||||||
config: CONFIG.prepare_json(),
|
config: CONFIG.prepare_json(),
|
||||||
can_backup: *CAN_BACKUP,
|
can_backup: *CAN_BACKUP,
|
||||||
logged_in: true,
|
logged_in: true,
|
||||||
urlpath: CONFIG.domain_path(),
|
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();
|
.collect();
|
||||||
|
|
||||||
let text = AdminTemplateData::users(users_json).render()?;
|
let text = AdminTemplateData::with_data("admin/users", json!(users_json)).render()?;
|
||||||
Ok(Html(text))
|
Ok(Html(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,7 +432,7 @@ fn organizations_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<St
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let text = AdminTemplateData::organizations(organizations_json).render()?;
|
let text = AdminTemplateData::with_data("admin/organizations", json!(organizations_json)).render()?;
|
||||||
Ok(Html(text))
|
Ok(Html(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -592,11 +558,12 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu
|
||||||
"db_type": *DB_TYPE,
|
"db_type": *DB_TYPE,
|
||||||
"db_version": get_sql_server_version(&conn),
|
"db_version": get_sql_server_version(&conn),
|
||||||
"admin_url": format!("{}/diagnostics", admin_url(Referer(None))),
|
"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_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
|
"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))
|
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"))),
|
"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.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))),
|
||||||
"datatables.css" => Ok(Content(ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
|
"datatables.css" => Ok(Content(ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
|
||||||
"jquery-3.5.1.slim.js" => {
|
"jquery-3.6.0.slim.js" => {
|
||||||
Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.5.1.slim.js")))
|
Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.0.slim.js")))
|
||||||
}
|
}
|
||||||
_ => err!(format!("Static file not found: {}", filename)),
|
_ => err!(format!("Static file not found: {}", filename)),
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,8 @@ macro_rules! make_config {
|
||||||
|
|
||||||
_env: ConfigBuilder,
|
_env: ConfigBuilder,
|
||||||
_usr: ConfigBuilder,
|
_usr: ConfigBuilder,
|
||||||
|
|
||||||
|
_overrides: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||||
|
@ -113,8 +115,7 @@ macro_rules! make_config {
|
||||||
|
|
||||||
/// Merges the values of both builders into a new builder.
|
/// Merges the values of both builders into a new builder.
|
||||||
/// If both have the same element, `other` wins.
|
/// If both have the same element, `other` wins.
|
||||||
fn merge(&self, other: &Self, show_overrides: bool) -> Self {
|
fn merge(&self, other: &Self, show_overrides: bool, overrides: &mut Vec<String>) -> Self {
|
||||||
let mut overrides = Vec::new();
|
|
||||||
let mut builder = self.clone();
|
let mut builder = self.clone();
|
||||||
$($(
|
$($(
|
||||||
if let v @Some(_) = &other.$name {
|
if let v @Some(_) = &other.$name {
|
||||||
|
@ -176,9 +177,9 @@ macro_rules! make_config {
|
||||||
)+)+
|
)+)+
|
||||||
|
|
||||||
pub fn prepare_json(&self) -> serde_json::Value {
|
pub fn prepare_json(&self) -> serde_json::Value {
|
||||||
let (def, cfg) = {
|
let (def, cfg, overriden) = {
|
||||||
let inner = &self.inner.read().unwrap();
|
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 {
|
fn _get_form_type(rust_type: &str) -> &'static str {
|
||||||
|
@ -210,6 +211,7 @@ macro_rules! make_config {
|
||||||
"default": def.$name,
|
"default": def.$name,
|
||||||
"type": _get_form_type(stringify!($ty)),
|
"type": _get_form_type(stringify!($ty)),
|
||||||
"doc": _get_doc(concat!($($doc),+)),
|
"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 },
|
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
|
/// 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;
|
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!
|
/// 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!
|
/// 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;
|
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!
|
/// 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();
|
let _usr = ConfigBuilder::from_file(&CONFIG_FILE).unwrap_or_default();
|
||||||
|
|
||||||
// Create merged config, config file overwrites env
|
// 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
|
// Fill any missing with defaults
|
||||||
let config = builder.build();
|
let config = builder.build();
|
||||||
|
@ -651,6 +663,7 @@ impl Config {
|
||||||
config,
|
config,
|
||||||
_env,
|
_env,
|
||||||
_usr,
|
_usr,
|
||||||
|
_overrides,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -666,9 +679,10 @@ impl Config {
|
||||||
let config_str = serde_json::to_string_pretty(&builder)?;
|
let config_str = serde_json::to_string_pretty(&builder)?;
|
||||||
|
|
||||||
// Prepare the combined config
|
// Prepare the combined config
|
||||||
|
let mut overrides = Vec::new();
|
||||||
let config = {
|
let config = {
|
||||||
let env = &self.inner.read().unwrap()._env;
|
let env = &self.inner.read().unwrap()._env;
|
||||||
env.merge(&builder, false).build()
|
env.merge(&builder, false, &mut overrides).build()
|
||||||
};
|
};
|
||||||
validate_config(&config)?;
|
validate_config(&config)?;
|
||||||
|
|
||||||
|
@ -677,6 +691,7 @@ impl Config {
|
||||||
let mut writer = self.inner.write().unwrap();
|
let mut writer = self.inner.write().unwrap();
|
||||||
writer.config = config;
|
writer.config = config;
|
||||||
writer._usr = builder;
|
writer._usr = builder;
|
||||||
|
writer._overrides = overrides;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Save to file
|
//Save to file
|
||||||
|
@ -690,7 +705,8 @@ impl Config {
|
||||||
pub fn update_config_partial(&self, other: ConfigBuilder) -> Result<(), Error> {
|
pub fn update_config_partial(&self, other: ConfigBuilder) -> Result<(), Error> {
|
||||||
let builder = {
|
let builder = {
|
||||||
let usr = &self.inner.read().unwrap()._usr;
|
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)
|
self.update_config(builder)
|
||||||
}
|
}
|
||||||
|
@ -751,6 +767,7 @@ impl Config {
|
||||||
let mut writer = self.inner.write().unwrap();
|
let mut writer = self.inner.write().unwrap();
|
||||||
writer.config = config;
|
writer.config = config;
|
||||||
writer._usr = usr;
|
writer._usr = usr;
|
||||||
|
writer._overrides = Vec::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
5015
src/static/scripts/bootstrap-native.js
gevendort
5015
src/static/scripts/bootstrap-native.js
gevendort
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
14435
src/static/scripts/bootstrap.css
gevendort
14435
src/static/scripts/bootstrap.css
gevendort
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
26
src/static/scripts/datatables.css
gevendort
26
src/static/scripts/datatables.css
gevendort
|
@ -4,13 +4,18 @@
|
||||||
*
|
*
|
||||||
* To rebuild or modify this file with the latest versions of the included
|
* To rebuild or modify this file with the latest versions of the included
|
||||||
* software please visit:
|
* software please visit:
|
||||||
* https://datatables.net/download/#bs4/dt-1.10.23
|
* https://datatables.net/download/#bs5/dt-1.10.25
|
||||||
*
|
*
|
||||||
* Included libraries:
|
* Included libraries:
|
||||||
* DataTables 1.10.23
|
* DataTables 1.10.25
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@charset "UTF-8";
|
@charset "UTF-8";
|
||||||
|
/*! Bootstrap 5 integration for DataTables
|
||||||
|
*
|
||||||
|
* ©2020 SpryMedia Ltd, all rights reserved.
|
||||||
|
* License: MIT datatables.net/license/mit
|
||||||
|
*/
|
||||||
table.dataTable {
|
table.dataTable {
|
||||||
clear: both;
|
clear: both;
|
||||||
margin-top: 6px !important;
|
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:before,
|
||||||
table.dataTable > thead .sorting_desc_disabled:after {
|
table.dataTable > thead .sorting_desc_disabled:after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0.9em;
|
bottom: 0.5em;
|
||||||
display: block;
|
display: block;
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
|
@ -193,18 +198,27 @@ table.dataTable.table-sm .sorting_desc:after {
|
||||||
table.table-bordered.dataTable {
|
table.table-bordered.dataTable {
|
||||||
border-right-width: 0;
|
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 th,
|
||||||
table.table-bordered.dataTable td {
|
table.table-bordered.dataTable td {
|
||||||
border-left-width: 0;
|
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 th:last-child, table.table-bordered.dataTable th:last-child,
|
||||||
table.table-bordered.dataTable td:last-child,
|
table.table-bordered.dataTable td:last-child,
|
||||||
table.table-bordered.dataTable td:last-child {
|
table.table-bordered.dataTable td:last-child {
|
||||||
border-right-width: 1px;
|
border-right-width: 1px;
|
||||||
}
|
}
|
||||||
table.table-bordered.dataTable tbody th,
|
table.table-bordered.dataTable th,
|
||||||
table.table-bordered.dataTable tbody td {
|
table.table-bordered.dataTable td {
|
||||||
border-bottom-width: 0;
|
border-bottom-width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.dataTables_scrollHead table.table-bordered {
|
div.dataTables_scrollHead table.table-bordered {
|
||||||
|
|
90
src/static/scripts/datatables.js
gevendort
90
src/static/scripts/datatables.js
gevendort
|
@ -4,24 +4,24 @@
|
||||||
*
|
*
|
||||||
* To rebuild or modify this file with the latest versions of the included
|
* To rebuild or modify this file with the latest versions of the included
|
||||||
* software please visit:
|
* software please visit:
|
||||||
* https://datatables.net/download/#bs4/dt-1.10.23
|
* https://datatables.net/download/#bs5/dt-1.10.25
|
||||||
*
|
*
|
||||||
* Included libraries:
|
* Included libraries:
|
||||||
* DataTables 1.10.23
|
* DataTables 1.10.25
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*! DataTables 1.10.23
|
/*! DataTables 1.10.25
|
||||||
* ©2008-2020 SpryMedia Ltd - datatables.net/license
|
* ©2008-2021 SpryMedia Ltd - datatables.net/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary DataTables
|
* @summary DataTables
|
||||||
* @description Paginate, search and order HTML tables
|
* @description Paginate, search and order HTML tables
|
||||||
* @version 1.10.23
|
* @version 1.10.25
|
||||||
* @file jquery.dataTables.js
|
* @file jquery.dataTables.js
|
||||||
* @author SpryMedia Ltd
|
* @author SpryMedia Ltd
|
||||||
* @contact www.datatables.net
|
* @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:
|
* This source file is free software, available under the following license:
|
||||||
* MIT license - http://datatables.net/license
|
* MIT license - http://datatables.net/license
|
||||||
|
@ -1100,6 +1100,8 @@
|
||||||
_fnLanguageCompat( json );
|
_fnLanguageCompat( json );
|
||||||
_fnCamelToHungarian( defaults.oLanguage, json );
|
_fnCamelToHungarian( defaults.oLanguage, json );
|
||||||
$.extend( true, oLanguage, json );
|
$.extend( true, oLanguage, json );
|
||||||
|
|
||||||
|
_fnCallbackFire( oSettings, null, 'i18n', [oSettings]);
|
||||||
_fnInitialise( oSettings );
|
_fnInitialise( oSettings );
|
||||||
},
|
},
|
||||||
error: function () {
|
error: function () {
|
||||||
|
@ -1109,6 +1111,9 @@
|
||||||
} );
|
} );
|
||||||
bInitHandedOff = true;
|
bInitHandedOff = true;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
_fnCallbackFire( oSettings, null, 'i18n', [oSettings]);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Stripes
|
* Stripes
|
||||||
|
@ -1260,7 +1265,7 @@
|
||||||
|
|
||||||
var tbody = $this.children('tbody');
|
var tbody = $this.children('tbody');
|
||||||
if ( tbody.length === 0 ) {
|
if ( tbody.length === 0 ) {
|
||||||
tbody = $('<tbody/>').appendTo($this);
|
tbody = $('<tbody/>').insertAfter(thead);
|
||||||
}
|
}
|
||||||
oSettings.nTBody = tbody[0];
|
oSettings.nTBody = tbody[0];
|
||||||
|
|
||||||
|
@ -2315,8 +2320,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only a single match is needed for html type since it is
|
// Only a single match is needed for html type since it is
|
||||||
// bottom of the pile and very similar to string
|
// bottom of the pile and very similar to string - but it
|
||||||
if ( detectedType === 'html' ) {
|
// must not be empty
|
||||||
|
if ( detectedType === 'html' && ! _empty(cache[k]) ) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3421,9 +3427,10 @@
|
||||||
/**
|
/**
|
||||||
* Insert the required TR nodes into the table for display
|
* Insert the required TR nodes into the table for display
|
||||||
* @param {object} oSettings dataTables settings object
|
* @param {object} oSettings dataTables settings object
|
||||||
|
* @param ajaxComplete true after ajax call to complete rendering
|
||||||
* @memberof DataTable#oApi
|
* @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 */
|
/* Provide a pre-callback function which can be used to cancel the draw is false is returned */
|
||||||
var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] );
|
var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] );
|
||||||
|
@ -3472,8 +3479,9 @@
|
||||||
{
|
{
|
||||||
oSettings.iDraw++;
|
oSettings.iDraw++;
|
||||||
}
|
}
|
||||||
else if ( !oSettings.bDestroying && !_fnAjaxUpdate( oSettings ) )
|
else if ( !oSettings.bDestroying && !ajaxComplete)
|
||||||
{
|
{
|
||||||
|
_fnAjaxUpdate( oSettings );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4005,21 +4013,16 @@
|
||||||
*/
|
*/
|
||||||
function _fnAjaxUpdate( settings )
|
function _fnAjaxUpdate( settings )
|
||||||
{
|
{
|
||||||
if ( settings.bAjaxDataGet ) {
|
settings.iDraw++;
|
||||||
settings.iDraw++;
|
_fnProcessingDisplay( settings, true );
|
||||||
_fnProcessingDisplay( settings, true );
|
|
||||||
|
|
||||||
_fnBuildAjax(
|
_fnBuildAjax(
|
||||||
settings,
|
settings,
|
||||||
_fnAjaxParameters( settings ),
|
_fnAjaxParameters( settings ),
|
||||||
function(json) {
|
function(json) {
|
||||||
_fnAjaxUpdateDraw( settings, json );
|
_fnAjaxUpdateDraw( settings, json );
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -4172,14 +4175,12 @@
|
||||||
}
|
}
|
||||||
settings.aiDisplay = settings.aiDisplayMaster.slice();
|
settings.aiDisplay = settings.aiDisplayMaster.slice();
|
||||||
|
|
||||||
settings.bAjaxDataGet = false;
|
_fnDraw( settings, true );
|
||||||
_fnDraw( settings );
|
|
||||||
|
|
||||||
if ( ! settings._bInitComplete ) {
|
if ( ! settings._bInitComplete ) {
|
||||||
_fnInitComplete( settings, json );
|
_fnInitComplete( settings, json );
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.bAjaxDataGet = true;
|
|
||||||
_fnProcessingDisplay( settings, false );
|
_fnProcessingDisplay( settings, false );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6108,7 +6109,7 @@
|
||||||
{
|
{
|
||||||
var col = columns[i];
|
var col = columns[i];
|
||||||
var asSorting = col.asSorting;
|
var asSorting = col.asSorting;
|
||||||
var sTitle = col.sTitle.replace( /<.*?>/g, "" );
|
var sTitle = col.ariaTitle || col.sTitle.replace( /<.*?>/g, "" );
|
||||||
var th = col.nTh;
|
var th = col.nTh;
|
||||||
|
|
||||||
// IE7 is throwing an error when setting these properties with jQuery's
|
// IE7 is throwing an error when setting these properties with jQuery's
|
||||||
|
@ -9542,7 +9543,7 @@
|
||||||
* @type string
|
* @type string
|
||||||
* @default Version number
|
* @default Version number
|
||||||
*/
|
*/
|
||||||
DataTable.version = "1.10.23";
|
DataTable.version = "1.10.25";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private data store, containing all of the settings objects that are
|
* Private data store, containing all of the settings objects that are
|
||||||
|
@ -13623,13 +13624,6 @@
|
||||||
*/
|
*/
|
||||||
"sAjaxDataProp": null,
|
"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.
|
* 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
|
* This can be used for working with the XHR information in one of the
|
||||||
|
@ -13966,7 +13960,7 @@
|
||||||
*
|
*
|
||||||
* @type string
|
* @type string
|
||||||
*/
|
*/
|
||||||
build:"bs4/dt-1.10.23",
|
build:"bs5/dt-1.10.25",
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14494,8 +14488,8 @@
|
||||||
"sSortAsc": "sorting_asc",
|
"sSortAsc": "sorting_asc",
|
||||||
"sSortDesc": "sorting_desc",
|
"sSortDesc": "sorting_desc",
|
||||||
"sSortable": "sorting", /* Sortable in both directions */
|
"sSortable": "sorting", /* Sortable in both directions */
|
||||||
"sSortableAsc": "sorting_asc_disabled",
|
"sSortableAsc": "sorting_desc_disabled",
|
||||||
"sSortableDesc": "sorting_desc_disabled",
|
"sSortableDesc": "sorting_asc_disabled",
|
||||||
"sSortableNone": "sorting_disabled",
|
"sSortableNone": "sorting_disabled",
|
||||||
"sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
|
"sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
|
||||||
|
|
||||||
|
@ -14936,7 +14930,6 @@
|
||||||
|
|
||||||
cell
|
cell
|
||||||
.removeClass(
|
.removeClass(
|
||||||
column.sSortingClass +' '+
|
|
||||||
classes.sSortAsc +' '+
|
classes.sSortAsc +' '+
|
||||||
classes.sSortDesc
|
classes.sSortDesc
|
||||||
)
|
)
|
||||||
|
@ -15061,6 +15054,11 @@
|
||||||
decimal+(d - intPart).toFixed( precision ).substring( 2 ):
|
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||'') +
|
return negative + (prefix||'') +
|
||||||
intPart.toString().replace(
|
intPart.toString().replace(
|
||||||
/\B(?=(\d{3})+(?!\d))/g, thousands
|
/\B(?=(\d{3})+(?!\d))/g, thousands
|
||||||
|
@ -15395,12 +15393,12 @@
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
/*! DataTables Bootstrap 4 integration
|
/*! DataTables Bootstrap 5 integration
|
||||||
* ©2011-2017 SpryMedia Ltd - datatables.net/license
|
* 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.
|
* DataTables 1.10 or newer.
|
||||||
*
|
*
|
||||||
* This file sets the defaults and adds options to DataTables to style its
|
* This file sets the defaults and adds options to DataTables to style its
|
||||||
|
@ -15452,9 +15450,9 @@ $.extend( true, DataTable.defaults, {
|
||||||
|
|
||||||
/* Default class modification */
|
/* Default class modification */
|
||||||
$.extend( DataTable.ext.classes, {
|
$.extend( DataTable.ext.classes, {
|
||||||
sWrapper: "dataTables_wrapper dt-bootstrap4",
|
sWrapper: "dataTables_wrapper dt-bootstrap5",
|
||||||
sFilterInput: "form-control form-control-sm",
|
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",
|
sProcessing: "dataTables_processing card",
|
||||||
sPageButton: "paginate_button page-item"
|
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/
|
* https://jquery.com/
|
||||||
*
|
*
|
||||||
* Includes Sizzle.js
|
* Includes Sizzle.js
|
||||||
* https://sizzlejs.com/
|
* https://sizzlejs.com/
|
||||||
*
|
*
|
||||||
* Copyright JS Foundation and other contributors
|
* Copyright OpenJS Foundation and other contributors
|
||||||
* Released under the MIT license
|
* Released under the MIT license
|
||||||
* https://jquery.org/license
|
* https://jquery.org/license
|
||||||
*
|
*
|
||||||
* Date: 2020-05-04T22:49Z
|
* Date: 2021-03-02T17:08Z
|
||||||
*/
|
*/
|
||||||
( function( global, factory ) {
|
( function( global, factory ) {
|
||||||
|
|
||||||
|
@ -76,12 +76,16 @@ var support = {};
|
||||||
|
|
||||||
var isFunction = function isFunction( obj ) {
|
var isFunction = function isFunction( obj ) {
|
||||||
|
|
||||||
// Support: Chrome <=57, Firefox <=52
|
// Support: Chrome <=57, Firefox <=52
|
||||||
// In some browsers, typeof returns "function" for HTML <object> elements
|
// In some browsers, typeof returns "function" for HTML <object> elements
|
||||||
// (i.e., `typeof document.createElement( "object" ) === "function"`).
|
// (i.e., `typeof document.createElement( "object" ) === "function"`).
|
||||||
// We don't want to classify *any* DOM node as a function.
|
// We don't want to classify *any* DOM node as a function.
|
||||||
return typeof obj === "function" && typeof obj.nodeType !== "number";
|
// 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 ) {
|
var isWindow = function isWindow( obj ) {
|
||||||
|
@ -147,7 +151,7 @@ function toType( obj ) {
|
||||||
|
|
||||||
|
|
||||||
var
|
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
|
// Define a local copy of jQuery
|
||||||
jQuery = function( selector, context ) {
|
jQuery = function( selector, context ) {
|
||||||
|
@ -401,7 +405,7 @@ jQuery.extend( {
|
||||||
if ( isArrayLike( Object( arr ) ) ) {
|
if ( isArrayLike( Object( arr ) ) ) {
|
||||||
jQuery.merge( ret,
|
jQuery.merge( ret,
|
||||||
typeof arr === "string" ?
|
typeof arr === "string" ?
|
||||||
[ arr ] : arr
|
[ arr ] : arr
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
push.call( ret, arr );
|
push.call( ret, arr );
|
||||||
|
@ -496,9 +500,9 @@ if ( typeof Symbol === "function" ) {
|
||||||
|
|
||||||
// Populate the class2type map
|
// Populate the class2type map
|
||||||
jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
|
jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
|
||||||
function( _i, name ) {
|
function( _i, name ) {
|
||||||
class2type[ "[object " + name + "]" ] = name.toLowerCase();
|
class2type[ "[object " + name + "]" ] = name.toLowerCase();
|
||||||
} );
|
} );
|
||||||
|
|
||||||
function isArrayLike( obj ) {
|
function isArrayLike( obj ) {
|
||||||
|
|
||||||
|
@ -518,14 +522,14 @@ function isArrayLike( obj ) {
|
||||||
}
|
}
|
||||||
var Sizzle =
|
var Sizzle =
|
||||||
/*!
|
/*!
|
||||||
* Sizzle CSS Selector Engine v2.3.5
|
* Sizzle CSS Selector Engine v2.3.6
|
||||||
* https://sizzlejs.com/
|
* https://sizzlejs.com/
|
||||||
*
|
*
|
||||||
* Copyright JS Foundation and other contributors
|
* Copyright JS Foundation and other contributors
|
||||||
* Released under the MIT license
|
* Released under the MIT license
|
||||||
* https://js.foundation/
|
* https://js.foundation/
|
||||||
*
|
*
|
||||||
* Date: 2020-03-14
|
* Date: 2021-02-16
|
||||||
*/
|
*/
|
||||||
( function( window ) {
|
( function( window ) {
|
||||||
var i,
|
var i,
|
||||||
|
@ -1108,8 +1112,8 @@ support = Sizzle.support = {};
|
||||||
* @returns {Boolean} True iff elem is a non-HTML XML node
|
* @returns {Boolean} True iff elem is a non-HTML XML node
|
||||||
*/
|
*/
|
||||||
isXML = Sizzle.isXML = function( elem ) {
|
isXML = Sizzle.isXML = function( elem ) {
|
||||||
var namespace = elem.namespaceURI,
|
var namespace = elem && elem.namespaceURI,
|
||||||
docElem = ( elem.ownerDocument || elem ).documentElement;
|
docElem = elem && ( elem.ownerDocument || elem ).documentElement;
|
||||||
|
|
||||||
// Support: IE <=8
|
// Support: IE <=8
|
||||||
// Assume HTML when documentElement doesn't yet exist, such as inside loading iframes
|
// 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 ) {
|
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 );
|
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 ),
|
resolveContexts = Array( i ),
|
||||||
resolveValues = slice.call( arguments ),
|
resolveValues = slice.call( arguments ),
|
||||||
|
|
||||||
// the master Deferred
|
// the primary Deferred
|
||||||
master = jQuery.Deferred(),
|
primary = jQuery.Deferred(),
|
||||||
|
|
||||||
// subordinate callback factory
|
// subordinate callback factory
|
||||||
updateFunc = function( i ) {
|
updateFunc = function( i ) {
|
||||||
|
@ -4006,30 +4010,30 @@ jQuery.extend( {
|
||||||
resolveContexts[ i ] = this;
|
resolveContexts[ i ] = this;
|
||||||
resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
|
resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
|
||||||
if ( !( --remaining ) ) {
|
if ( !( --remaining ) ) {
|
||||||
master.resolveWith( resolveContexts, resolveValues );
|
primary.resolveWith( resolveContexts, resolveValues );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Single- and empty arguments are adopted like Promise.resolve
|
// Single- and empty arguments are adopted like Promise.resolve
|
||||||
if ( remaining <= 1 ) {
|
if ( remaining <= 1 ) {
|
||||||
adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject,
|
adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject,
|
||||||
!remaining );
|
!remaining );
|
||||||
|
|
||||||
// Use .then() to unwrap secondary thenables (cf. gh-3000)
|
// Use .then() to unwrap secondary thenables (cf. gh-3000)
|
||||||
if ( master.state() === "pending" ||
|
if ( primary.state() === "pending" ||
|
||||||
isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
|
isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
|
||||||
|
|
||||||
return master.then();
|
return primary.then();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multiple arguments are aggregated like Promise.all array elements
|
// Multiple arguments are aggregated like Promise.all array elements
|
||||||
while ( i-- ) {
|
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++ ) {
|
for ( ; i < len; i++ ) {
|
||||||
fn(
|
fn(
|
||||||
elems[ i ], key, raw ?
|
elems[ i ], key, raw ?
|
||||||
value :
|
value :
|
||||||
value.call( elems[ i ], i, fn( elems[ i ], key ) )
|
value.call( elems[ i ], i, fn( elems[ i ], key ) )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5089,10 +5093,7 @@ function buildFragment( elems, context, scripts, selection, ignored ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var
|
var rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
|
||||||
rkeyEvent = /^key/,
|
|
||||||
rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
|
|
||||||
rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
|
|
||||||
|
|
||||||
function returnTrue() {
|
function returnTrue() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -5387,8 +5388,8 @@ jQuery.event = {
|
||||||
event = jQuery.event.fix( nativeEvent ),
|
event = jQuery.event.fix( nativeEvent ),
|
||||||
|
|
||||||
handlers = (
|
handlers = (
|
||||||
dataPriv.get( this, "events" ) || Object.create( null )
|
dataPriv.get( this, "events" ) || Object.create( null )
|
||||||
)[ event.type ] || [],
|
)[ event.type ] || [],
|
||||||
special = jQuery.event.special[ event.type ] || {};
|
special = jQuery.event.special[ event.type ] || {};
|
||||||
|
|
||||||
// Use the fix-ed jQuery.Event rather than the (read-only) native event
|
// Use the fix-ed jQuery.Event rather than the (read-only) native event
|
||||||
|
@ -5512,12 +5513,12 @@ jQuery.event = {
|
||||||
get: isFunction( hook ) ?
|
get: isFunction( hook ) ?
|
||||||
function() {
|
function() {
|
||||||
if ( this.originalEvent ) {
|
if ( this.originalEvent ) {
|
||||||
return hook( this.originalEvent );
|
return hook( this.originalEvent );
|
||||||
}
|
}
|
||||||
} :
|
} :
|
||||||
function() {
|
function() {
|
||||||
if ( this.originalEvent ) {
|
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
|
// Cancel the outer synthetic event
|
||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
event.preventDefault();
|
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
|
// If this is an inner synthetic event for an event with a bubbling surrogate
|
||||||
|
@ -5821,34 +5828,7 @@ jQuery.each( {
|
||||||
targetTouches: true,
|
targetTouches: true,
|
||||||
toElement: true,
|
toElement: true,
|
||||||
touches: true,
|
touches: true,
|
||||||
|
which: 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;
|
|
||||||
}
|
|
||||||
}, jQuery.event.addProp );
|
}, jQuery.event.addProp );
|
||||||
|
|
||||||
jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) {
|
jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) {
|
||||||
|
@ -5874,6 +5854,12 @@ jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateTyp
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Suppress native focus or blur as it's already being fired
|
||||||
|
// in leverageNative.
|
||||||
|
_default: function() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
delegateType: delegateType
|
delegateType: delegateType
|
||||||
};
|
};
|
||||||
} );
|
} );
|
||||||
|
@ -6541,6 +6527,10 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
|
||||||
// set in CSS while `offset*` properties report correct values.
|
// set in CSS while `offset*` properties report correct values.
|
||||||
// Behavior in IE 9 is more subtle than in newer versions & it passes
|
// 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!
|
// 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() {
|
reliableTrDimensions: function() {
|
||||||
var table, tr, trChild, trStyle;
|
var table, tr, trChild, trStyle;
|
||||||
if ( reliableTrDimensionsVal == null ) {
|
if ( reliableTrDimensionsVal == null ) {
|
||||||
|
@ -6548,17 +6538,32 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
|
||||||
tr = document.createElement( "tr" );
|
tr = document.createElement( "tr" );
|
||||||
trChild = document.createElement( "div" );
|
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";
|
tr.style.height = "1px";
|
||||||
trChild.style.height = "9px";
|
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
|
documentElement
|
||||||
.appendChild( table )
|
.appendChild( table )
|
||||||
.appendChild( tr )
|
.appendChild( tr )
|
||||||
.appendChild( trChild );
|
.appendChild( trChild );
|
||||||
|
|
||||||
trStyle = window.getComputedStyle( tr );
|
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 );
|
documentElement.removeChild( table );
|
||||||
}
|
}
|
||||||
|
@ -7022,10 +7027,10 @@ jQuery.each( [ "height", "width" ], function( _i, dimension ) {
|
||||||
// Running getBoundingClientRect on a disconnected node
|
// Running getBoundingClientRect on a disconnected node
|
||||||
// in IE throws an error.
|
// in IE throws an error.
|
||||||
( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
|
( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
|
||||||
swap( elem, cssShow, function() {
|
swap( elem, cssShow, function() {
|
||||||
return getWidthOrHeight( elem, dimension, extra );
|
return getWidthOrHeight( elem, dimension, extra );
|
||||||
} ) :
|
} ) :
|
||||||
getWidthOrHeight( elem, dimension, extra );
|
getWidthOrHeight( elem, dimension, extra );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -7084,7 +7089,7 @@ jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
|
||||||
swap( elem, { marginLeft: 0 }, function() {
|
swap( elem, { marginLeft: 0 }, function() {
|
||||||
return elem.getBoundingClientRect().left;
|
return elem.getBoundingClientRect().left;
|
||||||
} )
|
} )
|
||||||
) + "px";
|
) + "px";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -7608,8 +7613,8 @@ jQuery.fn.extend( {
|
||||||
if ( this.setAttribute ) {
|
if ( this.setAttribute ) {
|
||||||
this.setAttribute( "class",
|
this.setAttribute( "class",
|
||||||
className || value === false ?
|
className || value === false ?
|
||||||
"" :
|
"" :
|
||||||
dataPriv.get( this, "__className__" ) || ""
|
dataPriv.get( this, "__className__" ) || ""
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7624,7 +7629,7 @@ jQuery.fn.extend( {
|
||||||
while ( ( elem = this[ i++ ] ) ) {
|
while ( ( elem = this[ i++ ] ) ) {
|
||||||
if ( elem.nodeType === 1 &&
|
if ( elem.nodeType === 1 &&
|
||||||
( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) {
|
( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7914,9 +7919,7 @@ jQuery.extend( jQuery.event, {
|
||||||
special.bindType || type;
|
special.bindType || type;
|
||||||
|
|
||||||
// jQuery handler
|
// jQuery handler
|
||||||
handle = (
|
handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] &&
|
||||||
dataPriv.get( cur, "events" ) || Object.create( null )
|
|
||||||
)[ event.type ] &&
|
|
||||||
dataPriv.get( cur, "handle" );
|
dataPriv.get( cur, "handle" );
|
||||||
if ( handle ) {
|
if ( handle ) {
|
||||||
handle.apply( cur, data );
|
handle.apply( cur, data );
|
||||||
|
@ -8057,7 +8060,7 @@ if ( !support.focusin ) {
|
||||||
|
|
||||||
// Cross-browser xml parsing
|
// Cross-browser xml parsing
|
||||||
jQuery.parseXML = function( data ) {
|
jQuery.parseXML = function( data ) {
|
||||||
var xml;
|
var xml, parserErrorElem;
|
||||||
if ( !data || typeof data !== "string" ) {
|
if ( !data || typeof data !== "string" ) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -8066,12 +8069,17 @@ jQuery.parseXML = function( data ) {
|
||||||
// IE throws on parseFromString with invalid input.
|
// IE throws on parseFromString with invalid input.
|
||||||
try {
|
try {
|
||||||
xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
|
xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
|
||||||
} catch ( e ) {
|
} catch ( e ) {}
|
||||||
xml = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
|
parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ];
|
||||||
jQuery.error( "Invalid XML: " + data );
|
if ( !xml || parserErrorElem ) {
|
||||||
|
jQuery.error( "Invalid XML: " + (
|
||||||
|
parserErrorElem ?
|
||||||
|
jQuery.map( parserErrorElem.childNodes, function( el ) {
|
||||||
|
return el.textContent;
|
||||||
|
} ).join( "\n" ) :
|
||||||
|
data
|
||||||
|
) );
|
||||||
}
|
}
|
||||||
return xml;
|
return xml;
|
||||||
};
|
};
|
||||||
|
@ -8172,16 +8180,14 @@ jQuery.fn.extend( {
|
||||||
// Can add propHook for "elements" to filter or add form elements
|
// Can add propHook for "elements" to filter or add form elements
|
||||||
var elements = jQuery.prop( this, "elements" );
|
var elements = jQuery.prop( this, "elements" );
|
||||||
return elements ? jQuery.makeArray( elements ) : this;
|
return elements ? jQuery.makeArray( elements ) : this;
|
||||||
} )
|
} ).filter( function() {
|
||||||
.filter( function() {
|
|
||||||
var type = this.type;
|
var type = this.type;
|
||||||
|
|
||||||
// Use .is( ":disabled" ) so that fieldset[disabled] works
|
// Use .is( ":disabled" ) so that fieldset[disabled] works
|
||||||
return this.name && !jQuery( this ).is( ":disabled" ) &&
|
return this.name && !jQuery( this ).is( ":disabled" ) &&
|
||||||
rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
|
rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
|
||||||
( this.checked || !rcheckableType.test( type ) );
|
( this.checked || !rcheckableType.test( type ) );
|
||||||
} )
|
} ).map( function( _i, elem ) {
|
||||||
.map( function( _i, elem ) {
|
|
||||||
var val = jQuery( this ).val();
|
var val = jQuery( this ).val();
|
||||||
|
|
||||||
if ( val == null ) {
|
if ( val == null ) {
|
||||||
|
@ -8387,12 +8393,6 @@ jQuery.offset = {
|
||||||
options.using.call( elem, props );
|
options.using.call( elem, props );
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if ( typeof props.top === "number" ) {
|
|
||||||
props.top += "px";
|
|
||||||
}
|
|
||||||
if ( typeof props.left === "number" ) {
|
|
||||||
props.left += "px";
|
|
||||||
}
|
|
||||||
curElem.css( props );
|
curElem.css( props );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8561,8 +8561,11 @@ jQuery.each( [ "top", "left" ], function( _i, prop ) {
|
||||||
|
|
||||||
// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
|
// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
|
||||||
jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
|
jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
|
||||||
jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
|
jQuery.each( {
|
||||||
function( defaultExtra, funcName ) {
|
padding: "inner" + name,
|
||||||
|
content: type,
|
||||||
|
"": "outer" + name
|
||||||
|
}, function( defaultExtra, funcName ) {
|
||||||
|
|
||||||
// Margin is only for outerHeight, outerWidth
|
// Margin is only for outerHeight, outerWidth
|
||||||
jQuery.fn[ funcName ] = function( margin, value ) {
|
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 " +
|
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
|
||||||
"change select submit keydown keypress keyup contextmenu" ).split( " " ),
|
"change select submit keydown keypress keyup contextmenu" ).split( " " ),
|
||||||
function( _i, name ) {
|
function( _i, name ) {
|
||||||
|
@ -8642,7 +8646,8 @@ jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
|
||||||
this.on( name, null, data, fn ) :
|
this.on( name, null, data, fn ) :
|
||||||
this.trigger( name );
|
this.trigger( name );
|
||||||
};
|
};
|
||||||
} );
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,14 +15,16 @@
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
}
|
}
|
||||||
.navbar .vaultwarden-icon {
|
.vaultwarden-icon {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
width: auto;
|
width: auto;
|
||||||
margin: -5px -3px 0 0;
|
margin: -5px 0 0 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script src="{{urlpath}}/bwrs_static/identicon.js"></script>
|
<script src="{{urlpath}}/bwrs_static/identicon.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
'use strict';
|
||||||
|
|
||||||
function reload() { window.location.reload(); }
|
function reload() { window.location.reload(); }
|
||||||
function msg(text, reload_page = true) {
|
function msg(text, reload_page = true) {
|
||||||
text && alert(text);
|
text && alert(text);
|
||||||
|
@ -78,19 +80,18 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="bg-light">
|
<body class="bg-light">
|
||||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4 shadow fixed-top">
|
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4 shadow fixed-top">
|
||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<a class="navbar-brand" href="{{urlpath}}/admin"><img class="pr-1 vaultwarden-icon" src="{{urlpath}}/bwrs_static/vaultwarden-icon.png" alt="V">aultwarden Admin</a>
|
<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-toggle="collapse" data-target="#navbarCollapse"
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse"
|
||||||
aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||||
<ul class="navbar-nav mr-auto">
|
<ul class="navbar-nav me-auto">
|
||||||
{{#if logged_in}}
|
{{#if logged_in}}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{{urlpath}}/admin">Settings</a>
|
<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! -->
|
<!-- This script needs to be at the bottom, else it will fail! -->
|
||||||
<script>
|
<script>
|
||||||
|
'use strict';
|
||||||
|
|
||||||
// get current URL path and assign 'active' class to the correct nav-item
|
// get current URL path and assign 'active' class to the correct nav-item
|
||||||
(function () {
|
(() => {
|
||||||
var pathname = window.location.pathname;
|
var pathname = window.location.pathname;
|
||||||
if (pathname === "") return;
|
if (pathname === "") return;
|
||||||
var navItem = document.querySelectorAll('.navbar-nav .nav-item a[href="'+pathname+'"]');
|
var navItem = document.querySelectorAll('.navbar-nav .nav-item a[href="'+pathname+'"]');
|
||||||
if (navItem.length === 1) {
|
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>
|
</script>
|
||||||
<!-- This script needs to be at the bottom, else it will fail! -->
|
|
||||||
<script src="{{urlpath}}/bwrs_static/bootstrap-native.js"></script>
|
<script src="{{urlpath}}/bwrs_static/bootstrap-native.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -7,37 +7,37 @@
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-sm-5">Server Installed
|
<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 bg-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 bg-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-info d-none" id="server-branch" title="This is a branched version.">Branched</span>
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="col-sm-7">
|
<dd class="col-sm-7">
|
||||||
<span id="server-installed">{{version}}</span>
|
<span id="server-installed">{{version}}</span>
|
||||||
</dd>
|
</dd>
|
||||||
<dt class="col-sm-5">Server Latest
|
<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>
|
</dt>
|
||||||
<dd class="col-sm-7">
|
<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>
|
</dd>
|
||||||
{{#if diagnostics.web_vault_enabled}}
|
{{#if page_data.web_vault_enabled}}
|
||||||
<dt class="col-sm-5">Web Installed
|
<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 bg-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-warning d-none" id="web-warning" title="There seems to be an update available.">Update</span>
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="col-sm-7">
|
<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>
|
</dd>
|
||||||
{{#unless diagnostics.running_within_docker}}
|
{{#unless page_data.running_within_docker}}
|
||||||
<dt class="col-sm-5">Web Latest
|
<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>
|
</dt>
|
||||||
<dd class="col-sm-7">
|
<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>
|
</dd>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#unless diagnostics.web_vault_enabled}}
|
{{#unless page_data.web_vault_enabled}}
|
||||||
<dt class="col-sm-5">Web Installed</dt>
|
<dt class="col-sm-5">Web Installed</dt>
|
||||||
<dd class="col-sm-7">
|
<dd class="col-sm-7">
|
||||||
<span id="web-installed">Web Vault is disabled</span>
|
<span id="web-installed">Web Vault is disabled</span>
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
<dt class="col-sm-5">Database</dt>
|
<dt class="col-sm-5">Database</dt>
|
||||||
<dd class="col-sm-7">
|
<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>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
@ -57,96 +57,105 @@
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-sm-5">Running within Docker</dt>
|
<dt class="col-sm-5">Running within Docker</dt>
|
||||||
<dd class="col-sm-7">
|
<dd class="col-sm-7">
|
||||||
{{#if diagnostics.running_within_docker}}
|
{{#if page_data.running_within_docker}}
|
||||||
<span class="d-block"><b>Yes</b></span>
|
<span class="d-block"><b>Yes</b></span>
|
||||||
{{/if}}
|
{{/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>
|
<span class="d-block"><b>No</b></span>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</dd>
|
</dd>
|
||||||
<dt class="col-sm-5">Uses a reverse proxy</dt>
|
<dt class="col-sm-5">Uses a reverse proxy</dt>
|
||||||
<dd class="col-sm-7">
|
<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>
|
<span class="d-block" title="IP Header found."><b>Yes</b></span>
|
||||||
{{/if}}
|
{{/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>
|
<span class="d-block" title="No IP Header found."><b>No</b></span>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</dd>
|
</dd>
|
||||||
{{!-- Only show this if the IP Header Exists --}}
|
{{!-- 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
|
<dt class="col-sm-5">IP header
|
||||||
{{#if diagnostics.ip_header_match}}
|
{{#if page_data.ip_header_match}}
|
||||||
<span class="badge badge-success" title="IP_HEADER config seems to be valid.">Match</span>
|
<span class="badge bg-success" title="IP_HEADER config seems to be valid.">Match</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#unless diagnostics.ip_header_match}}
|
{{#unless page_data.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>
|
<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}}
|
{{/unless}}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="col-sm-7">
|
<dd class="col-sm-7">
|
||||||
{{#if diagnostics.ip_header_match}}
|
{{#if page_data.ip_header_match}}
|
||||||
<span class="d-block"><b>Config/Server:</b> {{ diagnostics.ip_header_name }}</span>
|
<span class="d-block"><b>Config/Server:</b> {{ page_data.ip_header_name }}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#unless diagnostics.ip_header_match}}
|
{{#unless page_data.ip_header_match}}
|
||||||
<span class="d-block"><b>Config:</b> {{ diagnostics.ip_header_config }}</span>
|
<span class="d-block"><b>Config:</b> {{ page_data.ip_header_config }}</span>
|
||||||
<span class="d-block"><b>Server:</b> {{ diagnostics.ip_header_name }}</span>
|
<span class="d-block"><b>Server:</b> {{ page_data.ip_header_name }}</span>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</dd>
|
</dd>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{!-- End if IP Header Exists --}}
|
{{!-- End if IP Header Exists --}}
|
||||||
<dt class="col-sm-5">Internet access
|
<dt class="col-sm-5">Internet access
|
||||||
{{#if diagnostics.has_http_access}}
|
{{#if page_data.has_http_access}}
|
||||||
<span class="badge badge-success" title="We have internet access!">Ok</span>
|
<span class="badge bg-success" title="We have internet access!">Ok</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#unless diagnostics.has_http_access}}
|
{{#unless page_data.has_http_access}}
|
||||||
<span class="badge badge-danger" title="There seems to be no internet access. Please fix.">Error</span>
|
<span class="badge bg-danger" title="There seems to be no internet access. Please fix.">Error</span>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="col-sm-7">
|
<dd class="col-sm-7">
|
||||||
{{#if diagnostics.has_http_access}}
|
{{#if page_data.has_http_access}}
|
||||||
<span class="d-block"><b>Yes</b></span>
|
<span class="d-block"><b>Yes</b></span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#unless diagnostics.has_http_access}}
|
{{#unless page_data.has_http_access}}
|
||||||
<span class="d-block"><b>No</b></span>
|
<span class="d-block"><b>No</b></span>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</dd>
|
</dd>
|
||||||
<dt class="col-sm-5">Internet access via a proxy</dt>
|
<dt class="col-sm-5">Internet access via a proxy</dt>
|
||||||
<dd class="col-sm-7">
|
<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>
|
<span class="d-block" title="Internet access goes via a proxy (HTTPS_PROXY or HTTP_PROXY is configured)."><b>Yes</b></span>
|
||||||
{{/if}}
|
{{/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>
|
<span class="d-block" title="We have direct internet access, no outgoing proxy configured."><b>No</b></span>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</dd>
|
</dd>
|
||||||
<dt class="col-sm-5">DNS (github.com)
|
<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 bg-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-danger d-none" id="dns-warning" title="DNS Resolving failed. Please fix.">Error</span>
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="col-sm-7">
|
<dd class="col-sm-7">
|
||||||
<span id="dns-resolved">{{diagnostics.dns_resolved}}</span>
|
<span id="dns-resolved">{{page_data.dns_resolved}}</span>
|
||||||
</dd>
|
</dd>
|
||||||
<dt class="col-sm-5">Date & Time (Local)</dt>
|
<dt class="col-sm-5">Date & Time (Local)</dt>
|
||||||
<dd class="col-sm-7">
|
<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>
|
</dd>
|
||||||
<dt class="col-sm-5">Date & Time (UTC)
|
<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 bg-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-danger d-none" id="time-warning" title="Time offsets are too mouch at drift.">Error</span>
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="col-sm-7">
|
<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>
|
<span id="time-browser" class="d-block"><b>Browser:</b> <span id="time-browser-string"></span></span>
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<dt class="col-sm-5">Domain configuration
|
<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 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 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 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 badge-success d-none" id="https-success" title="Configurued to use HTTPS">HTTPS</span>
|
<span class="badge bg-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-danger d-none" id="https-warning" title="Not configured to use HTTPS.
Some features may not work as expected!">No HTTPS</span>
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="col-sm-7">
|
<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>
|
<span id="domain-browser" class="d-block"><b>Browser:</b> <span id="domain-browser-string"></span></span>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
@ -173,10 +182,17 @@
|
||||||
<dt class="col-sm-3">
|
<dt class="col-sm-3">
|
||||||
<button type="button" id="gen-support" class="btn btn-primary" onclick="generateSupportString(); return false;">Generate Support String</button>
|
<button type="button" id="gen-support" class="btn btn-primary" onclick="generateSupportString(); return false;">Generate Support String</button>
|
||||||
<br><br>
|
<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>
|
</dt>
|
||||||
<dd class="col-sm-9">
|
<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>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
@ -185,10 +201,13 @@
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
dnsCheck = false;
|
'use strict';
|
||||||
timeCheck = false;
|
|
||||||
domainCheck = false;
|
var dnsCheck = false;
|
||||||
httpsCheck = false;
|
var timeCheck = false;
|
||||||
|
var domainCheck = false;
|
||||||
|
var httpsCheck = false;
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
// ================================
|
// ================================
|
||||||
// Date & Time Check
|
// Date & Time Check
|
||||||
|
@ -203,7 +222,10 @@
|
||||||
document.getElementById("time-browser-string").innerText = browserUTC;
|
document.getElementById("time-browser-string").innerText = browserUTC;
|
||||||
|
|
||||||
const serverUTC = document.getElementById("time-server-string").innerText;
|
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) {
|
if (timeDrift > 30 || timeDrift < -30) {
|
||||||
document.getElementById('time-warning').classList.remove('d-none');
|
document.getElementById('time-warning').classList.remove('d-none');
|
||||||
} else {
|
} else {
|
||||||
|
@ -233,7 +255,7 @@
|
||||||
const webInstalled = document.getElementById('web-installed').innerText;
|
const webInstalled = document.getElementById('web-installed').innerText;
|
||||||
checkVersions('server', serverInstalled, serverLatest, serverLatestCommit);
|
checkVersions('server', serverInstalled, serverLatest, serverLatestCommit);
|
||||||
|
|
||||||
{{#unless diagnostics.running_within_docker}}
|
{{#unless page_data.running_within_docker}}
|
||||||
const webLatest = document.getElementById('web-latest').innerText;
|
const webLatest = document.getElementById('web-latest').innerText;
|
||||||
checkVersions('web', webInstalled, webLatest);
|
checkVersions('web', webInstalled, webLatest);
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
@ -303,30 +325,38 @@
|
||||||
// ================================
|
// ================================
|
||||||
// Generate support string to be pasted on github or the forum
|
// Generate support string to be pasted on github or the forum
|
||||||
async function generateSupportString() {
|
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 += "* Vaultwarden version: v{{ version }}\n";
|
||||||
supportString += "* Web-vault version: v{{ diagnostics.web_vault_version }}\n";
|
supportString += "* Web-vault version: v{{ page_data.web_vault_version }}\n";
|
||||||
supportString += "* Running within Docker: {{ diagnostics.running_within_docker }}\n";
|
supportString += "* Running within Docker: {{ page_data.running_within_docker }}\n";
|
||||||
supportString += "* Uses a reverse proxy: {{ diagnostics.ip_header_exists }}\n";
|
supportString += "* Environment settings overridden: ";
|
||||||
{{#if diagnostics.ip_header_exists}}
|
{{#if page_data.overrides}}
|
||||||
supportString += "* IP Header check: {{ diagnostics.ip_header_match }} ({{ diagnostics.ip_header_name }})\n";
|
supportString += "true\n"
|
||||||
|
{{else}}
|
||||||
|
supportString += "false\n"
|
||||||
{{/if}}
|
{{/if}}
|
||||||
supportString += "* Internet access: {{ diagnostics.has_http_access }}\n";
|
supportString += "* Uses a reverse proxy: {{ page_data.ip_header_exists }}\n";
|
||||||
supportString += "* Internet access via a proxy: {{ diagnostics.uses_proxy }}\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 += "* DNS Check: " + dnsCheck + "\n";
|
||||||
supportString += "* Time Check: " + timeCheck + "\n";
|
supportString += "* Time Check: " + timeCheck + "\n";
|
||||||
supportString += "* Domain Configuration Check: " + domainCheck + "\n";
|
supportString += "* Domain Configuration Check: " + domainCheck + "\n";
|
||||||
supportString += "* HTTPS Check: " + httpsCheck + "\n";
|
supportString += "* HTTPS Check: " + httpsCheck + "\n";
|
||||||
supportString += "* Database type: {{ diagnostics.db_type }}\n";
|
supportString += "* Database type: {{ page_data.db_type }}\n";
|
||||||
supportString += "* Database version: {{ diagnostics.db_version }}\n";
|
supportString += "* Database version: {{ page_data.db_version }}\n";
|
||||||
supportString += "* Clients used: \n";
|
supportString += "* Clients used: \n";
|
||||||
supportString += "* Reverse proxy and version: \n";
|
supportString += "* Reverse proxy and version: \n";
|
||||||
supportString += "* Other relevant information: \n";
|
supportString += "* Other relevant information: \n";
|
||||||
|
|
||||||
jsonResponse = await fetch('{{urlpath}}/admin/diagnostics/config');
|
let jsonResponse = await fetch('{{urlpath}}/admin/diagnostics/config');
|
||||||
configJson = await jsonResponse.json();
|
const configJson = await jsonResponse.json();
|
||||||
supportString += "\n### Config (Generated via diagnostics page)\n```json\n" + JSON.stringify(configJson, undefined, 2) + "\n```\n";
|
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').innerText = supportString;
|
||||||
document.getElementById('support-string').classList.remove('d-none');
|
document.getElementById('support-string').classList.remove('d-none');
|
||||||
|
@ -334,16 +364,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyToClipboard() {
|
function copyToClipboard() {
|
||||||
const str = document.getElementById('support-string').innerText;
|
const supportStr = document.getElementById('support-string').innerText;
|
||||||
const el = document.createElement('textarea');
|
const tmpCopyEl = 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<main class="container-xl">
|
<main class="container-xl">
|
||||||
<div id="organizations-block" class="my-3 p-3 bg-white rounded shadow">
|
<div id="organizations-block" class="my-3 p-3 bg-white rounded shadow">
|
||||||
<h6 class="border-bottom pb-2 mb-3">Organizations</h6>
|
<h6 class="border-bottom pb-2 mb-3">Organizations</h6>
|
||||||
|
|
||||||
<div class="table-responsive-xl small">
|
<div class="table-responsive-xl small">
|
||||||
<table id="orgs-table" class="table table-sm table-striped table-hover">
|
<table id="orgs-table" class="table table-sm table-striped table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -10,19 +9,19 @@
|
||||||
<th>Users</th>
|
<th>Users</th>
|
||||||
<th>Items</th>
|
<th>Items</th>
|
||||||
<th>Attachments</th>
|
<th>Attachments</th>
|
||||||
<th style="width: 120px; min-width: 120px;">Actions</th>
|
<th style="width: 130px; min-width: 130px;">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{#each organizations}}
|
{{#each page_data}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<img class="mr-2 float-left rounded identicon" data-src="{{Id}}">
|
<img class="float-start me-2 rounded identicon" data-src="{{Id}}">
|
||||||
<div class="float-left">
|
<div class="float-start">
|
||||||
<strong>{{Name}}</strong>
|
<strong>{{Name}}</strong>
|
||||||
<span class="mr-2">({{BillingEmail}})</span>
|
<span class="me-2">({{BillingEmail}})</span>
|
||||||
<span class="d-block">
|
<span class="d-block">
|
||||||
<span class="badge badge-success">{{Id}}</span>
|
<span class="badge bg-success">{{Id}}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
@ -38,7 +37,7 @@
|
||||||
<span class="d-block"><strong>Size:</strong> {{attachment_size}}</span>
|
<span class="d-block"><strong>Size:</strong> {{attachment_size}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</td>
|
</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>
|
<a class="d-block" href="#" onclick='deleteOrganization({{jsesc Id}}, {{jsesc Name}}, {{jsesc BillingEmail}})'>Delete Organization</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -46,14 +45,15 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{urlpath}}/bwrs_static/datatables.css" />
|
<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 src="{{urlpath}}/bwrs_static/datatables.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
'use strict';
|
||||||
|
|
||||||
function deleteOrganization(id, name, billing_email) {
|
function deleteOrganization(id, name, billing_email) {
|
||||||
// First make sure the user wants to delete this organization
|
// 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!");
|
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({
|
$('#orgs-table').DataTable({
|
||||||
"responsive": true,
|
"responsive": true,
|
||||||
"lengthMenu": [ [-1, 5, 10, 25, 50], ["All", 5, 10, 25, 50] ],
|
"lengthMenu": [ [-1, 5, 10, 25, 50], ["All", 5, 10, 25, 50] ],
|
||||||
|
|
|
@ -3,34 +3,32 @@
|
||||||
<div>
|
<div>
|
||||||
<h6 class="text-white mb-3">Configuration</h6>
|
<h6 class="text-white mb-3">Configuration</h6>
|
||||||
<div class="small text-white mb-3">
|
<div class="small text-white mb-3">
|
||||||
NOTE: The settings here override the environment variables. Once saved, it's recommended to stop setting
|
<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>
|
||||||
them to avoid confusion. This does not apply to the read-only section, which can only be set through the
|
This does not apply to the read-only section, which can only be set via environment variables.<br>
|
||||||
environment.
|
Settings which are overridden are shown with <span class="is-overridden-true">double underscores</span>.
|
||||||
</div>
|
</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}}
|
{{#each config}}
|
||||||
{{#if groupdoc}}
|
{{#if groupdoc}}
|
||||||
<div class="card bg-light mb-3">
|
<div class="card bg-light mb-3">
|
||||||
<div class="card-header"><button type="button" class="btn btn-link collapsed" data-toggle="collapse"
|
<div class="card-header" role="button" data-bs-toggle="collapse" data-bs-target="#g_{{group}}">
|
||||||
data-target="#g_{{group}}">{{groupdoc}}</button></div>
|
<button type="button" class="btn btn-link text-decoration-none collapsed" data-bs-toggle="collapse" data-bs-target="#g_{{group}}">{{groupdoc}}</button>
|
||||||
<div id="g_{{group}}" class="card-body collapse" data-parent="#config-form">
|
</div>
|
||||||
|
<div id="g_{{group}}" class="card-body collapse">
|
||||||
{{#each elements}}
|
{{#each elements}}
|
||||||
{{#if editable}}
|
{{#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"}}
|
{{#case type "text" "number" "password"}}
|
||||||
<label for="input_{{name}}" class="col-sm-3 col-form-label">{{doc.name}}</label>
|
<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}}"
|
<input class="form-control conf-{{type}}" id="input_{{name}}" type="{{type}}"
|
||||||
name="{{name}}" value="{{value}}" {{#if default}} placeholder="Default: {{default}}"
|
name="{{name}}" value="{{value}}" {{#if default}} placeholder="Default: {{default}}"{{/if}}>
|
||||||
{{/if}}>
|
|
||||||
|
|
||||||
{{#case type "password"}}
|
{{#case type "password"}}
|
||||||
<div class="input-group-append">
|
<button class="btn btn-outline-secondary input-group-text" type="button" onclick="toggleVis('input_{{name}}');">Show/hide</button>
|
||||||
<button class="btn btn-outline-secondary" type="button"
|
|
||||||
onclick="toggleVis('input_{{name}}');">Show/hide</button>
|
|
||||||
</div>
|
|
||||||
{{/case}}
|
{{/case}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/case}}
|
{{/case}}
|
||||||
{{#case type "checkbox"}}
|
{{#case type "checkbox"}}
|
||||||
|
@ -48,13 +46,12 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{#case group "smtp"}}
|
{{#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>
|
<label for="smtp-test-email" class="col-sm-3 col-form-label">Test SMTP</label>
|
||||||
<div class="col-sm-8 input-group">
|
<div class="col-sm-8 input-group">
|
||||||
<input class="form-control" id="smtp-test-email" type="email" placeholder="Enter test email">
|
<input class="form-control" id="smtp-test-email" type="email" placeholder="Enter test email" required>
|
||||||
<div class="input-group-append">
|
<button type="button" class="btn btn-outline-primary input-group-text" onclick="smtpTest(); return false;">Send test email</button>
|
||||||
<button type="button" class="btn btn-outline-primary" onclick="smtpTest(); return false;">Send test email</button>
|
<div class="invalid-tooltip">Please provide a valid email address</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/case}}
|
{{/case}}
|
||||||
|
@ -64,9 +61,11 @@
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
<div class="card bg-light mb-3">
|
<div class="card bg-light mb-3">
|
||||||
<div class="card-header"><button type="button" class="btn btn-link collapsed" data-toggle="collapse"
|
<div class="card-header" role="button" data-bs-toggle="collapse" data-bs-target="#g_readonly">
|
||||||
data-target="#g_readonly">Read-Only Config</button></div>
|
<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 id="g_readonly" class="card-body collapse" data-parent="#config-form">
|
</div>
|
||||||
|
|
||||||
|
<div id="g_readonly" class="card-body collapse">
|
||||||
<div class="small mb-3">
|
<div class="small mb-3">
|
||||||
NOTE: These options can't be modified in the editor because they would require the server
|
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
|
to be restarted. To modify them, you need to set the correct environment variables when
|
||||||
|
@ -76,19 +75,17 @@
|
||||||
{{#each config}}
|
{{#each config}}
|
||||||
{{#each elements}}
|
{{#each elements}}
|
||||||
{{#unless editable}}
|
{{#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"}}
|
{{#case type "text" "number" "password"}}
|
||||||
<label for="input_{{name}}" class="col-sm-3 col-form-label">{{doc.name}}</label>
|
<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}}"
|
<input readonly class="form-control" id="input_{{name}}" type="{{type}}"
|
||||||
value="{{value}}" {{#if default}} placeholder="Default: {{default}}" {{/if}}>
|
value="{{value}}" {{#if default}} placeholder="Default: {{default}}" {{/if}}>
|
||||||
|
|
||||||
{{#case type "password"}}
|
{{#case type "password"}}
|
||||||
<div class="input-group-append">
|
<button class="btn btn-outline-secondary" type="button" onclick="toggleVis('input_{{name}}');">Show/hide</button>
|
||||||
<button class="btn btn-outline-secondary" type="button"
|
|
||||||
onclick="toggleVis('input_{{name}}');">Show/hide</button>
|
|
||||||
</div>
|
|
||||||
{{/case}}
|
{{/case}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/case}}
|
{{/case}}
|
||||||
{{#case type "checkbox"}}
|
{{#case type "checkbox"}}
|
||||||
|
@ -112,9 +109,10 @@
|
||||||
|
|
||||||
{{#if can_backup}}
|
{{#if can_backup}}
|
||||||
<div class="card bg-light mb-3">
|
<div class="card bg-light mb-3">
|
||||||
<div class="card-header"><button type="button" class="btn btn-link collapsed" data-toggle="collapse"
|
<div class="card-header" role="button" data-bs-toggle="collapse" data-bs-target="#g_database">
|
||||||
data-target="#g_database">Backup Database</button></div>
|
<button type="button" class="btn btn-link text-decoration-none collapsed" data-bs-toggle="collapse" data-bs-target="#g_database">Backup Database</button>
|
||||||
<div id="g_database" class="card-body collapse" data-parent="#config-form">
|
</div>
|
||||||
|
<div id="g_database" class="card-body collapse">
|
||||||
<div class="small mb-3">
|
<div class="small mb-3">
|
||||||
WARNING: This function only creates a backup copy of the SQLite database.
|
WARNING: This function only creates a backup copy of the SQLite database.
|
||||||
This does not include any configuration or file attachment data that may
|
This does not include any configuration or file attachment data that may
|
||||||
|
@ -128,7 +126,7 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Save</button>
|
<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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -139,16 +137,34 @@
|
||||||
/* Most modern browsers support this now. */
|
/* Most modern browsers support this now. */
|
||||||
color: orangered;
|
color: orangered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.is-overridden-true {
|
||||||
|
text-decoration: underline double;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
'use strict';
|
||||||
|
|
||||||
function smtpTest() {
|
function smtpTest() {
|
||||||
if (formHasChanges(config_form)) {
|
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.");
|
alert("Config has been changed but not yet saved.\nPlease save the changes first before sending a test email.");
|
||||||
return false;
|
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/",
|
_post("{{urlpath}}/admin/test/smtp/",
|
||||||
"SMTP Test email sent correctly",
|
"SMTP Test email sent correctly",
|
||||||
"Error sending SMTP test email", data, false);
|
"Error sending SMTP test email", data, false);
|
||||||
|
@ -157,21 +173,21 @@
|
||||||
function getFormData() {
|
function getFormData() {
|
||||||
let data = {};
|
let data = {};
|
||||||
|
|
||||||
document.querySelectorAll(".conf-checkbox").forEach(function (e, i) {
|
document.querySelectorAll(".conf-checkbox").forEach(function (e) {
|
||||||
data[e.name] = e.checked;
|
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;
|
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;
|
data[e.name] = e.value || null;
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
function saveConfig() {
|
function saveConfig() {
|
||||||
data = JSON.stringify(getFormData());
|
const data = JSON.stringify(getFormData());
|
||||||
_post("{{urlpath}}/admin/config/", "Config saved correctly",
|
_post("{{urlpath}}/admin/config/", "Config saved correctly",
|
||||||
"Error saving config", data);
|
"Error saving config", data);
|
||||||
return false;
|
return false;
|
||||||
|
@ -198,10 +214,10 @@
|
||||||
function masterCheck(check_id, inputs_query) {
|
function masterCheck(check_id, inputs_query) {
|
||||||
function onChanged(checkbox, inputs_query) {
|
function onChanged(checkbox, inputs_query) {
|
||||||
return function _fn() {
|
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;
|
checkbox.disabled = false;
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
const checkbox = document.getElementById(check_id);
|
const checkbox = document.getElementById(check_id);
|
||||||
const onChange = onChanged(checkbox, inputs_query);
|
const onChange = onChanged(checkbox, inputs_query);
|
||||||
|
@ -238,7 +254,6 @@
|
||||||
Array.from(risk_el).forEach((el) => {
|
Array.from(risk_el).forEach((el) => {
|
||||||
if (el.innerText.toLowerCase().includes('risks') ) {
|
if (el.innerText.toLowerCase().includes('risks') ) {
|
||||||
el.parentElement.className += ' alert-danger'
|
el.parentElement.className += ' alert-danger'
|
||||||
console.log(el)
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,34 +7,34 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>User</th>
|
<th>User</th>
|
||||||
<th style="width:65px; min-width: 65px;">Created at</th>
|
<th style="width: 85px; min-width: 70px;">Created at</th>
|
||||||
<th style="width:70px; min-width: 65px;">Last Active</th>
|
<th style="width: 85px; min-width: 70px;">Last Active</th>
|
||||||
<th style="width:35px; min-width: 35px;">Items</th>
|
<th style="width: 35px; min-width: 35px;">Items</th>
|
||||||
<th>Attachments</th>
|
<th>Attachments</th>
|
||||||
<th style="min-width: 120px;">Organizations</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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{#each users}}
|
{{#each page_data}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<img class="float-left mr-2 rounded identicon" data-src="{{Email}}">
|
<img class="float-start me-2 rounded identicon" data-src="{{Email}}">
|
||||||
<div class="float-left">
|
<div class="float-start">
|
||||||
<strong>{{Name}}</strong>
|
<strong>{{Name}}</strong>
|
||||||
<span class="d-block">{{Email}}</span>
|
<span class="d-block">{{Email}}</span>
|
||||||
<span class="d-block">
|
<span class="d-block">
|
||||||
{{#unless user_enabled}}
|
{{#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}}
|
{{/unless}}
|
||||||
{{#if TwoFactorEnabled}}
|
{{#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}}
|
{{/if}}
|
||||||
{{#case _Status 1}}
|
{{#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}}
|
{{/case}}
|
||||||
{{#if EmailVerified}}
|
{{#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}}
|
{{/if}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -57,11 +57,11 @@
|
||||||
<td>
|
<td>
|
||||||
<div class="overflow-auto" style="max-height: 120px;">
|
<div class="overflow-auto" style="max-height: 120px;">
|
||||||
{{#each Organizations}}
|
{{#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}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td style="font-size: 90%; text-align: right; padding-right: 15px">
|
<td class="text-end pe-2 small">
|
||||||
{{#if TwoFactorEnabled}}
|
{{#if TwoFactorEnabled}}
|
||||||
<a class="d-block" href="#" onclick='remove2fa({{jsesc Id}})'>Remove all 2FA</a>
|
<a class="d-block" href="#" onclick='remove2fa({{jsesc Id}})'>Remove all 2FA</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -85,7 +85,7 @@
|
||||||
Force clients to resync
|
Force clients to resync
|
||||||
</button>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -94,8 +94,8 @@
|
||||||
<h6 class="mb-0 text-white">Invite User</h6>
|
<h6 class="mb-0 text-white">Invite User</h6>
|
||||||
<small>Email:</small>
|
<small>Email:</small>
|
||||||
|
|
||||||
<form class="form-inline" id="invite-form" onsubmit="inviteUser(); return false;">
|
<form class="form-inline input-group w-50" id="invite-form" onsubmit="inviteUser(); return false;">
|
||||||
<input type="email" class="form-control w-50 mr-2" id="email-invite" placeholder="Enter email">
|
<input type="email" class="form-control me-2" id="email-invite" placeholder="Enter email" required>
|
||||||
<button type="submit" class="btn btn-primary">Invite</button>
|
<button type="submit" class="btn btn-primary">Invite</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -106,9 +106,7 @@
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h6 class="modal-title" id="userOrgTypeDialogTitle"></h6>
|
<h6 class="modal-title" id="userOrgTypeDialogTitle"></h6>
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<form class="form" id="userOrgTypeForm" onsubmit="updateUserOrgType(); return false;">
|
<form class="form" id="userOrgTypeForm" onsubmit="updateUserOrgType(); return false;">
|
||||||
<input type="hidden" name="user_uuid" id="userOrgTypeUserUuid" value="">
|
<input type="hidden" name="user_uuid" id="userOrgTypeUserUuid" value="">
|
||||||
|
@ -128,7 +126,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<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>
|
<button type="submit" class="btn btn-sm btn-primary">Change Role</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -138,9 +136,11 @@
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{urlpath}}/bwrs_static/datatables.css" />
|
<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 src="{{urlpath}}/bwrs_static/datatables.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
'use strict';
|
||||||
|
|
||||||
function deleteUser(id, mail) {
|
function deleteUser(id, mail) {
|
||||||
var input_mail = prompt("To delete user '" + mail + "', please type the email below")
|
var input_mail = prompt("To delete user '" + mail + "', please type the email below")
|
||||||
if (input_mail != null) {
|
if (input_mail != null) {
|
||||||
|
@ -191,8 +191,8 @@
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function inviteUser() {
|
function inviteUser() {
|
||||||
inv = document.getElementById("email-invite");
|
const inv = document.getElementById("email-invite");
|
||||||
data = JSON.stringify({ "email": inv.value });
|
const data = JSON.stringify({ "email": inv.value });
|
||||||
inv.value = "";
|
inv.value = "";
|
||||||
_post("{{urlpath}}/admin/invite/", "User invited correctly",
|
_post("{{urlpath}}/admin/invite/", "User invited correctly",
|
||||||
"Error inviting user", data);
|
"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];
|
let orgtype = OrgTypes[e.dataset.orgtype];
|
||||||
e.style.backgroundColor = orgtype.color;
|
e.style.backgroundColor = orgtype.color;
|
||||||
e.title = orgtype.name;
|
e.title = orgtype.name;
|
||||||
|
@ -225,7 +225,7 @@
|
||||||
let sortDate = a.replace(/(<([^>]+)>)/gi, "").trim();
|
let sortDate = a.replace(/(<([^>]+)>)/gi, "").trim();
|
||||||
if ( sortDate !== '' ) {
|
if ( sortDate !== '' ) {
|
||||||
let dtParts = sortDate.split(' ');
|
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('-');
|
var dateParts = dtParts[0].split('-');
|
||||||
x = (dateParts[0] + dateParts[1] + dateParts[2] + timeParts[0] + timeParts[1] + ((undefined != timeParts[2]) ? timeParts[2] : 0)) * 1;
|
x = (dateParts[0] + dateParts[1] + dateParts[2] + timeParts[0] + timeParts[1] + ((undefined != timeParts[2]) ? timeParts[2] : 0)) * 1;
|
||||||
if ( isNaN(x) ) {
|
if ( isNaN(x) ) {
|
||||||
|
@ -246,7 +246,7 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function(event) {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
$('#users-table').DataTable({
|
$('#users-table').DataTable({
|
||||||
"responsive": true,
|
"responsive": true,
|
||||||
"lengthMenu": [ [-1, 5, 10, 25, 50], ["All", 5, 10, 25, 50] ],
|
"lengthMenu": [ [-1, 5, 10, 25, 50], ["All", 5, 10, 25, 50] ],
|
||||||
|
@ -275,7 +275,7 @@
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
// Prevent accidental submission of the form with valid elements after the modal has been hidden.
|
// 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("userOrgTypeDialogTitle").innerHTML = '';
|
||||||
document.getElementById("userOrgTypeUserUuid").value = '';
|
document.getElementById("userOrgTypeUserUuid").value = '';
|
||||||
document.getElementById("userOrgTypeOrgUuid").value = '';
|
document.getElementById("userOrgTypeOrgUuid").value = '';
|
||||||
|
|
Laden …
In neuem Issue referenzieren