Removed unsafe-inline JS from CSP and other fixes

- 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.
This commit is contained in:
BlackDex
2022-12-28 20:05:10 +01:00
committed by Daniel García
parent ab2dd0f300
commit 2020a302d0
18 changed files with 946 additions and 718 deletions

26
src/static/scripts/404.css vendored Normal file
View File

@@ -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%;
}

45
src/static/scripts/admin.css vendored Normal file
View File

@@ -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;
}

65
src/static/scripts/admin.js vendored Normal file
View File

@@ -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");
}
});

219
src/static/scripts/admin_diagnostics.js vendored Normal file
View File

@@ -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);
});

View File

@@ -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);
});

180
src/static/scripts/admin_settings.js vendored Normal file
View File

@@ -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);
});

246
src/static/scripts/admin_users.js vendored Normal file
View File

@@ -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);
});

View File

@@ -10874,5 +10874,3 @@ textarea.form-control-lg {
display: none !important;
}
}
/*# sourceMappingURL=bootstrap.css.map */