- Removed `unsafe-inline` for javascript from CSP. The admin interface now uses files instead of inline javascript. - Modified javascript to work not being inline. - Run eslint over javascript and fixed some items. - Added a `to_json` Handlebars helper. Used at the diagnostics page. - Changed `AdminTemplateData` struct to be smaller. The `config` was always added, but only used at one page. Same goes for `can_backup` and `version`. - Also inlined CSS. We can't remove the `unsafe-inline` from css, because that seems to break the web-vault currently. That might need some further checks. But for now the 404 page and all the admin pages are clear of inline scripts and styles.pull/3128/head
parent
b0f03bb49c
commit
0f588ced03
@ -0,0 +1,26 @@
|
||||
body {
|
||||
padding-top: 75px;
|
||||
}
|
||||
.vaultwarden-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
height: 32px;
|
||||
width: auto;
|
||||
margin: -5px 0 0 0;
|
||||
}
|
||||
.footer {
|
||||
padding: 40px 0 40px 0;
|
||||
border-top: 1px solid #dee2e6;
|
||||
}
|
||||
.container {
|
||||
max-width: 980px;
|
||||
}
|
||||
.content {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
.vw-404 {
|
||||
max-width: 500px; width: 100%;
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
body {
|
||||
padding-top: 75px;
|
||||
}
|
||||
img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
.vaultwarden-icon {
|
||||
height: 32px;
|
||||
width: auto;
|
||||
margin: -5px 0 0 0;
|
||||
}
|
||||
/* Special alert-row class to use Bootstrap v5.2+ variable colors */
|
||||
.alert-row {
|
||||
--bs-alert-border: 1px solid var(--bs-alert-border-color);
|
||||
color: var(--bs-alert-color);
|
||||
background-color: var(--bs-alert-bg);
|
||||
border: var(--bs-alert-border);
|
||||
}
|
||||
|
||||
#users-table .vw-created-at, #users-table .vw-last-active {
|
||||
width: 85px;
|
||||
min-width: 70px;
|
||||
}
|
||||
#users-table .vw-items {
|
||||
width: 35px;
|
||||
min-width: 35px;
|
||||
}
|
||||
#users-table .vw-organizations {
|
||||
min-width: 120px;
|
||||
}
|
||||
#users-table .vw-actions, #orgs-table .vw-actions {
|
||||
width: 130px;
|
||||
min-width: 130px;
|
||||
}
|
||||
#users-table .vw-org-cell {
|
||||
max-height: 120px;
|
||||
}
|
||||
|
||||
#support-string {
|
||||
height: 16rem;
|
||||
}
|
||||
.vw-copy-toast {
|
||||
width: 15rem;
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
"use strict";
|
||||
|
||||
function getBaseUrl() {
|
||||
// If the base URL is `https://vaultwarden.example.com/base/path/`,
|
||||
// `window.location.href` should have one of the following forms:
|
||||
//
|
||||
// - `https://vaultwarden.example.com/base/path/`
|
||||
// - `https://vaultwarden.example.com/base/path/#/some/route[?queryParam=...]`
|
||||
//
|
||||
// We want to get to just `https://vaultwarden.example.com/base/path`.
|
||||
const baseUrl = window.location.href;
|
||||
const adminPos = baseUrl.indexOf("/admin");
|
||||
return baseUrl.substring(0, adminPos != -1 ? adminPos : baseUrl.length);
|
||||
}
|
||||
const BASE_URL = getBaseUrl();
|
||||
|
||||
function reload() {
|
||||
// Reload the page by setting the exact same href
|
||||
// Using window.location.reload() could cause a repost.
|
||||
window.location = window.location.href;
|
||||
}
|
||||
|
||||
function msg(text, reload_page = true) {
|
||||
text && alert(text);
|
||||
reload_page && reload();
|
||||
}
|
||||
|
||||
function _post(url, successMsg, errMsg, body, reload_page = true) {
|
||||
fetch(url, {
|
||||
method: "POST",
|
||||
body: body,
|
||||
mode: "same-origin",
|
||||
credentials: "same-origin",
|
||||
headers: { "Content-Type": "application/json" }
|
||||
}).then( resp => {
|
||||
if (resp.ok) { msg(successMsg, reload_page); return Promise.reject({error: false}); }
|
||||
const respStatus = resp.status;
|
||||
const respStatusText = resp.statusText;
|
||||
return resp.text();
|
||||
}).then( respText => {
|
||||
try {
|
||||
const respJson = JSON.parse(respText);
|
||||
return respJson ? respJson.ErrorModel.Message : "Unknown error";
|
||||
} catch (e) {
|
||||
return Promise.reject({body:respStatus + " - " + respStatusText, error: true});
|
||||
}
|
||||
}).then( apiMsg => {
|
||||
msg(errMsg + "\n" + apiMsg, reload_page);
|
||||
}).catch( e => {
|
||||
if (e.error === false) { return true; }
|
||||
else { msg(errMsg + "\n" + e.body, reload_page); }
|
||||
});
|
||||
}
|
||||
|
||||
// onLoad events
|
||||
document.addEventListener("DOMContentLoaded", (/*event*/) => {
|
||||
// get current URL path and assign "active" class to the correct nav-item
|
||||
const pathname = window.location.pathname;
|
||||
if (pathname === "") return;
|
||||
const navItem = document.querySelectorAll(`.navbar-nav .nav-item a[href="${pathname}"]`);
|
||||
if (navItem.length === 1) {
|
||||
navItem[0].className = navItem[0].className + " active";
|
||||
navItem[0].setAttribute("aria-current", "page");
|
||||
}
|
||||
});
|
@ -0,0 +1,219 @@
|
||||
"use strict";
|
||||
|
||||
var dnsCheck = false;
|
||||
var timeCheck = false;
|
||||
var domainCheck = false;
|
||||
var httpsCheck = false;
|
||||
|
||||
// ================================
|
||||
// Date & Time Check
|
||||
const d = new Date();
|
||||
const year = d.getUTCFullYear();
|
||||
const month = String(d.getUTCMonth()+1).padStart(2, "0");
|
||||
const day = String(d.getUTCDate()).padStart(2, "0");
|
||||
const hour = String(d.getUTCHours()).padStart(2, "0");
|
||||
const minute = String(d.getUTCMinutes()).padStart(2, "0");
|
||||
const seconds = String(d.getUTCSeconds()).padStart(2, "0");
|
||||
const browserUTC = `${year}-${month}-${day} ${hour}:${minute}:${seconds} UTC`;
|
||||
|
||||
// ================================
|
||||
// Check if the output is a valid IP
|
||||
const isValidIp = value => (/^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/.test(value) ? true : false);
|
||||
|
||||
function checkVersions(platform, installed, latest, commit=null) {
|
||||
if (installed === "-" || latest === "-") {
|
||||
document.getElementById(`${platform}-failed`).classList.remove("d-none");
|
||||
return;
|
||||
}
|
||||
|
||||
// Only check basic versions, no commit revisions
|
||||
if (commit === null || installed.indexOf("-") === -1) {
|
||||
if (installed !== latest) {
|
||||
document.getElementById(`${platform}-warning`).classList.remove("d-none");
|
||||
} else {
|
||||
document.getElementById(`${platform}-success`).classList.remove("d-none");
|
||||
}
|
||||
} else {
|
||||
// Check if this is a branched version.
|
||||
const branchRegex = /(?:\s)\((.*?)\)/;
|
||||
const branchMatch = installed.match(branchRegex);
|
||||
if (branchMatch !== null) {
|
||||
document.getElementById(`${platform}-branch`).classList.remove("d-none");
|
||||
}
|
||||
|
||||
// This will remove branch info and check if there is a commit hash
|
||||
const installedRegex = /(\d+\.\d+\.\d+)-(\w+)/;
|
||||
const instMatch = installed.match(installedRegex);
|
||||
|
||||
// It could be that a new tagged version has the same commit hash.
|
||||
// In this case the version is the same but only the number is different
|
||||
if (instMatch !== null) {
|
||||
if (instMatch[2] === commit) {
|
||||
// The commit hashes are the same, so latest version is installed
|
||||
document.getElementById(`${platform}-success`).classList.remove("d-none");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (installed === latest) {
|
||||
document.getElementById(`${platform}-success`).classList.remove("d-none");
|
||||
} else {
|
||||
document.getElementById(`${platform}-warning`).classList.remove("d-none");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// Generate support string to be pasted on github or the forum
|
||||
async function generateSupportString(dj) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let supportString = "### Your environment (Generated via diagnostics page)\n";
|
||||
|
||||
supportString += `* Vaultwarden version: v${dj.current_release}\n`;
|
||||
supportString += `* Web-vault version: v${dj.web_vault_version}\n`;
|
||||
supportString += `* OS/Arch: ${dj.host_os}/${dj.host_arch}\n`;
|
||||
supportString += `* Running within Docker: ${dj.running_within_docker} (Base: ${dj.docker_base_image})\n`;
|
||||
supportString += "* Environment settings overridden: ";
|
||||
if (dj.overrides != "") {
|
||||
supportString += "true\n";
|
||||
} else {
|
||||
supportString += "false\n";
|
||||
}
|
||||
supportString += `* Uses a reverse proxy: ${dj.ip_header_exists}\n`;
|
||||
if (dj.ip_header_exists) {
|
||||
supportString += `* IP Header check: ${dj.ip_header_match} (${dj.ip_header_name})\n`;
|
||||
}
|
||||
supportString += `* Internet access: ${dj.has_http_access}\n`;
|
||||
supportString += `* Internet access via a proxy: ${dj.uses_proxy}\n`;
|
||||
supportString += `* DNS Check: ${dnsCheck}\n`;
|
||||
supportString += `* Time Check: ${timeCheck}\n`;
|
||||
supportString += `* Domain Configuration Check: ${domainCheck}\n`;
|
||||
supportString += `* HTTPS Check: ${httpsCheck}\n`;
|
||||
supportString += `* Database type: ${dj.db_type}\n`;
|
||||
supportString += `* Database version: ${dj.db_version}\n`;
|
||||
supportString += "* Clients used: \n";
|
||||
supportString += "* Reverse proxy and version: \n";
|
||||
supportString += "* Other relevant information: \n";
|
||||
|
||||
const jsonResponse = await fetch(`${BASE_URL}/admin/diagnostics/config`, {
|
||||
"headers": { "Accept": "application/json" }
|
||||
});
|
||||
if (!jsonResponse.ok) {
|
||||
alert("Generation failed: " + jsonResponse.statusText);
|
||||
throw new Error(jsonResponse);
|
||||
}
|
||||
const configJson = await jsonResponse.json();
|
||||
supportString += "\n### Config (Generated via diagnostics page)\n<details><summary>Show Running Config</summary>\n";
|
||||
supportString += `\n**Environment settings which are overridden:** ${dj.overrides}\n`;
|
||||
supportString += "\n\n```json\n" + JSON.stringify(configJson, undefined, 2) + "\n```\n</details>\n";
|
||||
|
||||
document.getElementById("support-string").innerText = supportString;
|
||||
document.getElementById("support-string").classList.remove("d-none");
|
||||
document.getElementById("copy-support").classList.remove("d-none");
|
||||
}
|
||||
|
||||
function copyToClipboard() {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const supportStr = document.getElementById("support-string").innerText;
|
||||
const tmpCopyEl = document.createElement("textarea");
|
||||
|
||||
tmpCopyEl.setAttribute("id", "copy-support-string");
|
||||
tmpCopyEl.setAttribute("readonly", "");
|
||||
tmpCopyEl.value = supportStr;
|
||||
tmpCopyEl.style.position = "absolute";
|
||||
tmpCopyEl.style.left = "-9999px";
|
||||
document.body.appendChild(tmpCopyEl);
|
||||
tmpCopyEl.select();
|
||||
document.execCommand("copy");
|
||||
tmpCopyEl.remove();
|
||||
|
||||
new BSN.Toast("#toastClipboardCopy").show();
|
||||
}
|
||||
|
||||
function checkTimeDrift(browserUTC, serverUTC) {
|
||||
const timeDrift = (
|
||||
Date.parse(serverUTC.replace(" ", "T").replace(" UTC", "")) -
|
||||
Date.parse(browserUTC.replace(" ", "T").replace(" UTC", ""))
|
||||
) / 1000;
|
||||
if (timeDrift > 20 || timeDrift < -20) {
|
||||
document.getElementById("time-warning").classList.remove("d-none");
|
||||
} else {
|
||||
document.getElementById("time-success").classList.remove("d-none");
|
||||
timeCheck = true;
|
||||
}
|
||||
}
|
||||
|
||||
function checkDomain(browserURL, serverURL) {
|
||||
if (serverURL == browserURL) {
|
||||
document.getElementById("domain-success").classList.remove("d-none");
|
||||
domainCheck = true;
|
||||
} else {
|
||||
document.getElementById("domain-warning").classList.remove("d-none");
|
||||
}
|
||||
|
||||
// Check for HTTPS at domain-server-string
|
||||
if (serverURL.startsWith("https://") ) {
|
||||
document.getElementById("https-success").classList.remove("d-none");
|
||||
httpsCheck = true;
|
||||
} else {
|
||||
document.getElementById("https-warning").classList.remove("d-none");
|
||||
}
|
||||
}
|
||||
|
||||
function initVersionCheck(dj) {
|
||||
const serverInstalled = dj.current_release;
|
||||
const serverLatest = dj.latest_release;
|
||||
const serverLatestCommit = dj.latest_commit;
|
||||
|
||||
if (serverInstalled.indexOf("-") !== -1 && serverLatest !== "-" && serverLatestCommit !== "-") {
|
||||
document.getElementById("server-latest-commit").classList.remove("d-none");
|
||||
}
|
||||
checkVersions("server", serverInstalled, serverLatest, serverLatestCommit);
|
||||
|
||||
if (!dj.running_within_docker) {
|
||||
const webInstalled = dj.web_vault_version;
|
||||
const webLatest = dj.latest_web_build;
|
||||
checkVersions("web", webInstalled, webLatest);
|
||||
}
|
||||
}
|
||||
|
||||
function checkDns(dns_resolved) {
|
||||
if (isValidIp(dns_resolved)) {
|
||||
document.getElementById("dns-success").classList.remove("d-none");
|
||||
dnsCheck = true;
|
||||
} else {
|
||||
document.getElementById("dns-warning").classList.remove("d-none");
|
||||
}
|
||||
}
|
||||
|
||||
function init(dj) {
|
||||
// Time check
|
||||
document.getElementById("time-browser-string").innerText = browserUTC;
|
||||
checkTimeDrift(browserUTC, dj.server_time);
|
||||
|
||||
// Domain check
|
||||
const browserURL = location.href.toLowerCase();
|
||||
document.getElementById("domain-browser-string").innerText = browserURL;
|
||||
checkDomain(browserURL, dj.admin_url.toLowerCase());
|
||||
|
||||
// Version check
|
||||
initVersionCheck(dj);
|
||||
|
||||
// DNS Check
|
||||
checkDns(dj.dns_resolved);
|
||||
}
|
||||
|
||||
// onLoad events
|
||||
document.addEventListener("DOMContentLoaded", (/*event*/) => {
|
||||
const diag_json = JSON.parse(document.getElementById("diagnostics_json").innerText);
|
||||
init(diag_json);
|
||||
|
||||
document.getElementById("gen-support").addEventListener("click", () => {
|
||||
generateSupportString(diag_json);
|
||||
});
|
||||
document.getElementById("copy-support").addEventListener("click", copyToClipboard);
|
||||
});
|
@ -0,0 +1,54 @@
|
||||
"use strict";
|
||||
|
||||
function deleteOrganization() {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const org_uuid = event.target.dataset.vwOrgUuid;
|
||||
const org_name = event.target.dataset.vwOrgName;
|
||||
const billing_email = event.target.dataset.vwBillingEmail;
|
||||
if (!org_uuid) {
|
||||
alert("Required parameters not found!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// First make sure the user wants to delete this organization
|
||||
const continueDelete = confirm(`WARNING: All data of this organization (${org_name}) will be lost!\nMake sure you have a backup, this cannot be undone!`);
|
||||
if (continueDelete == true) {
|
||||
const input_org_uuid = prompt(`To delete the organization "${org_name} (${billing_email})", please type the organization uuid below.`);
|
||||
if (input_org_uuid != null) {
|
||||
if (input_org_uuid == org_uuid) {
|
||||
_post(`${BASE_URL}/admin/organizations/${org_uuid}/delete`,
|
||||
"Organization deleted correctly",
|
||||
"Error deleting organization"
|
||||
);
|
||||
} else {
|
||||
alert("Wrong organization uuid, please try again");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// onLoad events
|
||||
document.addEventListener("DOMContentLoaded", (/*event*/) => {
|
||||
jQuery("#orgs-table").DataTable({
|
||||
"stateSave": true,
|
||||
"responsive": true,
|
||||
"lengthMenu": [
|
||||
[-1, 5, 10, 25, 50],
|
||||
["All", 5, 10, 25, 50]
|
||||
],
|
||||
"pageLength": -1, // Default show all
|
||||
"columnDefs": [{
|
||||
"targets": 4,
|
||||
"searchable": false,
|
||||
"orderable": false
|
||||
}]
|
||||
});
|
||||
|
||||
// Add click events for organization actions
|
||||
document.querySelectorAll("button[vw-delete-organization]").forEach(btn => {
|
||||
btn.addEventListener("click", deleteOrganization);
|
||||
});
|
||||
|
||||
document.getElementById("reload").addEventListener("click", reload);
|
||||
});
|
@ -0,0 +1,180 @@
|
||||
"use strict";
|
||||
|
||||
function smtpTest() {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (formHasChanges(config_form)) {
|
||||
alert("Config has been changed but not yet saved.\nPlease save the changes first before sending a test email.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const 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");
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = JSON.stringify({ "email": test_email.value });
|
||||
_post(`${BASE_URL}/admin/test/smtp/`,
|
||||
"SMTP Test email sent correctly",
|
||||
"Error sending SMTP test email",
|
||||
data, false
|
||||
);
|
||||
}
|
||||
|
||||
function getFormData() {
|
||||
let data = {};
|
||||
|
||||
document.querySelectorAll(".conf-checkbox").forEach(function (e) {
|
||||
data[e.name] = e.checked;
|
||||
});
|
||||
|
||||
document.querySelectorAll(".conf-number").forEach(function (e) {
|
||||
data[e.name] = e.value ? +e.value : null;
|
||||
});
|
||||
|
||||
document.querySelectorAll(".conf-text, .conf-password").forEach(function (e) {
|
||||
data[e.name] = e.value || null;
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
function saveConfig() {
|
||||
const data = JSON.stringify(getFormData());
|
||||
_post(`${BASE_URL}/admin/config/`,
|
||||
"Config saved correctly",
|
||||
"Error saving config",
|
||||
data
|
||||
);
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
function deleteConf() {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const input = prompt(
|
||||
"This will remove all user configurations, and restore the defaults and the " +
|
||||
"values set by the environment. This operation could be dangerous. Type 'DELETE' to proceed:"
|
||||
);
|
||||
if (input === "DELETE") {
|
||||
_post(`${BASE_URL}/admin/config/delete`,
|
||||
"Config deleted correctly",
|
||||
"Error deleting config"
|
||||
);
|
||||
} else {
|
||||
alert("Wrong input, please try again");
|
||||
}
|
||||
}
|
||||
|
||||
function backupDatabase() {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
_post(`${BASE_URL}/admin/config/backup_db`,
|
||||
"Backup created successfully",
|
||||
"Error creating backup", null, false
|
||||
);
|
||||
}
|
||||
|
||||
// Two functions to help check if there were changes to the form fields
|
||||
// Useful for example during the smtp test to prevent people from clicking save before testing there new settings
|
||||
function initChangeDetection(form) {
|
||||
const ignore_fields = ["smtp-test-email"];
|
||||
Array.from(form).forEach((el) => {
|
||||
if (! ignore_fields.includes(el.id)) {
|
||||
el.dataset.origValue = el.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function formHasChanges(form) {
|
||||
return Array.from(form).some(el => "origValue" in el.dataset && ( el.dataset.origValue !== el.value));
|
||||
}
|
||||
|
||||
// This function will prevent submitting a from when someone presses enter.
|
||||
function preventFormSubmitOnEnter(form) {
|
||||
form.onkeypress = function(e) {
|
||||
const key = e.charCode || e.keyCode || 0;
|
||||
if (key == 13) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// This function will hook into the smtp-test-email input field and will call the smtpTest() function when enter is pressed.
|
||||
function submitTestEmailOnEnter() {
|
||||
const smtp_test_email_input = document.getElementById("smtp-test-email");
|
||||
smtp_test_email_input.onkeypress = function(e) {
|
||||
const key = e.charCode || e.keyCode || 0;
|
||||
if (key == 13) {
|
||||
e.preventDefault();
|
||||
smtpTest();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Colorize some settings which are high risk
|
||||
function colorRiskSettings() {
|
||||
const risk_items = document.getElementsByClassName("col-form-label");
|
||||
Array.from(risk_items).forEach((el) => {
|
||||
if (el.innerText.toLowerCase().includes("risks") ) {
|
||||
el.parentElement.className += " alert-danger";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggleVis(evt) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const elem = document.getElementById(evt.target.dataset.vwPwToggle);
|
||||
const type = elem.getAttribute("type");
|
||||
if (type === "text") {
|
||||
elem.setAttribute("type", "password");
|
||||
} else {
|
||||
elem.setAttribute("type", "text");
|
||||
}
|
||||
}
|
||||
|
||||
function masterCheck(check_id, inputs_query) {
|
||||
function onChanged(checkbox, inputs_query) {
|
||||
return function _fn() {
|
||||
document.querySelectorAll(inputs_query).forEach(function (e) { e.disabled = !checkbox.checked; });
|
||||
checkbox.disabled = false;
|
||||
};
|
||||
}
|
||||
|
||||
const checkbox = document.getElementById(check_id);
|
||||
const onChange = onChanged(checkbox, inputs_query);
|
||||
onChange(); // Trigger the event initially
|
||||
checkbox.addEventListener("change", onChange);
|
||||
}
|
||||
|
||||
const config_form = document.getElementById("config-form");
|
||||
|
||||
// onLoad events
|
||||
document.addEventListener("DOMContentLoaded", (/*event*/) => {
|
||||
initChangeDetection(config_form);
|
||||
// Prevent enter to submitting the form and save the config.
|
||||
// Users need to really click on save, this also to prevent accidental submits.
|
||||
preventFormSubmitOnEnter(config_form);
|
||||
|
||||
submitTestEmailOnEnter();
|
||||
colorRiskSettings();
|
||||
|
||||
document.querySelectorAll("input[id^='input__enable_']").forEach(group_toggle => {
|
||||
const input_id = group_toggle.id.replace("input__enable_", "#g_");
|
||||
masterCheck(group_toggle.id, `${input_id} input`);
|
||||
});
|
||||
|
||||
document.querySelectorAll("button[data-vw-pw-toggle]").forEach(password_toggle_btn => {
|
||||
password_toggle_btn.addEventListener("click", toggleVis);
|
||||
});
|
||||
|
||||
document.getElementById("backupDatabase").addEventListener("click", backupDatabase);
|
||||
document.getElementById("deleteConf").addEventListener("click", deleteConf);
|
||||
document.getElementById("smtpTest").addEventListener("click", smtpTest);
|
||||
|
||||
config_form.addEventListener("submit", saveConfig);
|
||||
});
|
@ -0,0 +1,246 @@
|
||||
"use strict";
|
||||
|
||||
function deleteUser() {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const id = event.target.parentNode.dataset.vwUserUuid;
|
||||
const email = event.target.parentNode.dataset.vwUserEmail;
|
||||
if (!id || !email) {
|
||||
alert("Required parameters not found!");
|
||||
return false;
|
||||
}
|
||||
const input_email = prompt(`To delete user "${email}", please type the email below`);
|
||||
if (input_email != null) {
|
||||
if (input_email == email) {
|
||||
_post(`${BASE_URL}/admin/users/${id}/delete`,
|
||||
"User deleted correctly",
|
||||
"Error deleting user"
|
||||
);
|
||||
} else {
|
||||
alert("Wrong email, please try again");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function remove2fa() {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const id = event.target.parentNode.dataset.vwUserUuid;
|
||||
if (!id) {
|
||||
alert("Required parameters not found!");
|
||||
return false;
|
||||
}
|
||||
_post(`${BASE_URL}/admin/users/${id}/remove-2fa`,
|
||||
"2FA removed correctly",
|
||||
"Error removing 2FA"
|
||||
);
|
||||
}
|
||||
|
||||
function deauthUser() {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const id = event.target.parentNode.dataset.vwUserUuid;
|
||||
if (!id) {
|
||||
alert("Required parameters not found!");
|
||||
return false;
|
||||
}
|
||||
_post(`${BASE_URL}/admin/users/${id}/deauth`,
|
||||
"Sessions deauthorized correctly",
|
||||
"Error deauthorizing sessions"
|
||||
);
|
||||
}
|
||||
|
||||
function disableUser() {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const id = event.target.parentNode.dataset.vwUserUuid;
|
||||
const email = event.target.parentNode.dataset.vwUserEmail;
|
||||
if (!id || !email) {
|
||||
alert("Required parameters not found!");
|
||||
return false;
|
||||
}
|
||||
const confirmed = confirm(`Are you sure you want to disable user "${email}"? This will also deauthorize their sessions.`);
|
||||
if (confirmed) {
|
||||
_post(`${BASE_URL}/admin/users/${id}/disable`,
|
||||
"User disabled successfully",
|
||||
"Error disabling user"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function enableUser() {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const id = event.target.parentNode.dataset.vwUserUuid;
|
||||
const email = event.target.parentNode.dataset.vwUserEmail;
|
||||
if (!id || !email) {
|
||||
alert("Required parameters not found!");
|
||||
return false;
|
||||
}
|
||||
const confirmed = confirm(`Are you sure you want to enable user "${email}"?`);
|
||||
if (confirmed) {
|
||||
_post(`${BASE_URL}/admin/users/${id}/enable`,
|
||||
"User enabled successfully",
|
||||
"Error enabling user"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function updateRevisions() {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
_post(`${BASE_URL}/admin/users/update_revision`,
|
||||
"Success, clients will sync next time they connect",
|
||||
"Error forcing clients to sync"
|
||||
);
|
||||
}
|
||||
|
||||
function inviteUser() {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const email = document.getElementById("inviteEmail");
|
||||
const data = JSON.stringify({
|
||||
"email": email.value
|
||||
});
|
||||
email.value = "";
|
||||
_post(`${BASE_URL}/admin/invite/`,
|
||||
"User invited correctly",
|
||||
"Error inviting user",
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
const ORG_TYPES = {
|
||||
"0": {
|
||||
"name": "Owner",
|
||||
"color": "orange"
|
||||
},
|
||||
"1": {
|
||||
"name": "Admin",
|
||||
"color": "blueviolet"
|
||||
},
|
||||
"2": {
|
||||
"name": "User",
|
||||
"color": "blue"
|
||||
},
|
||||
"3": {
|
||||
"name": "Manager",
|
||||
"color": "green"
|
||||
},
|
||||
};
|
||||
|
||||
// Special sort function to sort dates in ISO format
|
||||
jQuery.extend(jQuery.fn.dataTableExt.oSort, {
|
||||
"date-iso-pre": function(a) {
|
||||
let x;
|
||||
const sortDate = a.replace(/(<([^>]+)>)/gi, "").trim();
|
||||
if (sortDate !== "") {
|
||||
const dtParts = sortDate.split(" ");
|
||||
const timeParts = (undefined != dtParts[1]) ? dtParts[1].split(":") : ["00", "00", "00"];
|
||||
const dateParts = dtParts[0].split("-");
|
||||
x = (dateParts[0] + dateParts[1] + dateParts[2] + timeParts[0] + timeParts[1] + ((undefined != timeParts[2]) ? timeParts[2] : 0)) * 1;
|
||||
if (isNaN(x)) {
|
||||
x = 0;
|
||||
}
|
||||
} else {
|
||||
x = Infinity;
|
||||
}
|
||||
return x;
|
||||
},
|
||||
|
||||
"date-iso-asc": function(a, b) {
|
||||
return a - b;
|
||||
},
|
||||
|
||||
"date-iso-desc": function(a, b) {
|
||||
return b - a;
|
||||
}
|
||||
});
|
||||
|
||||
const userOrgTypeDialog = document.getElementById("userOrgTypeDialog");
|
||||
// Fill the form and title
|
||||
userOrgTypeDialog.addEventListener("show.bs.modal", function(event) {
|
||||
// Get shared values
|
||||
const userEmail = event.relatedTarget.parentNode.dataset.vwUserEmail;
|
||||
const userUuid = event.relatedTarget.parentNode.dataset.vwUserUuid;
|
||||
// Get org specific values
|
||||
const userOrgType = event.relatedTarget.dataset.vwOrgType;
|
||||
const userOrgTypeName = ORG_TYPES[userOrgType]["name"];
|
||||
const orgName = event.relatedTarget.dataset.vwOrgName;
|
||||
const orgUuid = event.relatedTarget.dataset.vwOrgUuid;
|
||||
|
||||
document.getElementById("userOrgTypeDialogTitle").innerHTML = `<b>Update User Type:</b><br><b>Organization:</b> ${orgName}<br><b>User:</b> ${userEmail}`;
|
||||
document.getElementById("userOrgTypeUserUuid").value = userUuid;
|
||||
document.getElementById("userOrgTypeOrgUuid").value = orgUuid;
|
||||
document.getElementById(`userOrgType${userOrgTypeName}`).checked = true;
|
||||
}, false);
|
||||
|
||||
// Prevent accidental submission of the form with valid elements after the modal has been hidden.
|
||||
userOrgTypeDialog.addEventListener("hide.bs.modal", function() {
|
||||
document.getElementById("userOrgTypeDialogTitle").innerHTML = "";
|
||||
document.getElementById("userOrgTypeUserUuid").value = "";
|
||||
document.getElementById("userOrgTypeOrgUuid").value = "";
|
||||
}, false);
|
||||
|
||||
function updateUserOrgType() {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const data = JSON.stringify(Object.fromEntries(new FormData(event.target).entries()));
|
||||
|
||||
_post(`${BASE_URL}/admin/users/org_type`,
|
||||
"Updated organization type of the user successfully",
|
||||
"Error updating organization type of the user",
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
// onLoad events
|
||||
document.addEventListener("DOMContentLoaded", (/*event*/) => {
|
||||
jQuery("#users-table").DataTable({
|
||||
"stateSave": true,
|
||||
"responsive": true,
|
||||
"lengthMenu": [
|
||||
[-1, 5, 10, 25, 50],
|
||||
["All", 5, 10, 25, 50]
|
||||
],
|
||||
"pageLength": -1, // Default show all
|
||||
"columnDefs": [{
|
||||
"targets": [1, 2],
|
||||
"type": "date-iso"
|
||||
}, {
|
||||
"targets": 6,
|
||||
"searchable": false,
|
||||
"orderable": false
|
||||
}]
|
||||
});
|
||||
|
||||
// Color all the org buttons per type
|
||||
document.querySelectorAll("button[data-vw-org-type]").forEach(function(e) {
|
||||
const orgType = ORG_TYPES[e.dataset.vwOrgType];
|
||||
e.style.backgroundColor = orgType.color;
|
||||
e.title = orgType.name;
|
||||
});
|
||||
|
||||
// Add click events for user actions
|
||||
document.querySelectorAll("button[vw-remove2fa]").forEach(btn => {
|
||||
btn.addEventListener("click", remove2fa);
|
||||
});
|
||||
document.querySelectorAll("button[vw-deauth-user]").forEach(btn => {
|
||||
btn.addEventListener("click", deauthUser);
|
||||
});
|
||||
document.querySelectorAll("button[vw-delete-user]").forEach(btn => {
|
||||
btn.addEventListener("click", deleteUser);
|
||||
});
|
||||
document.querySelectorAll("button[vw-disable-user]").forEach(btn => {
|
||||
btn.addEventListener("click", disableUser);
|
||||
});
|
||||
document.querySelectorAll("button[vw-enable-user]").forEach(btn => {
|
||||
btn.addEventListener("click", enableUser);
|
||||
});
|
||||
|
||||
document.getElementById("updateRevisions").addEventListener("click", updateRevisions);
|
||||
document.getElementById("reload").addEventListener("click", reload);
|
||||
document.getElementById("userOrgTypeForm").addEventListener("submit", updateUserOrgType);
|
||||
document.getElementById("inviteUserForm").addEventListener("submit", inviteUser);
|
||||
});
|
Loading…
Reference in new issue