mirror of
https://github.com/dani-garcia/vaultwarden
synced 2024-09-26 04:18:57 +02:00
f4a9645b54
Squashed commit of the following: commit 1bdf1c7954e0731c95703d10118f3874ab5155d3 Merge:8ba6e61
7257251
Author: Daniel García <dani-garcia@users.noreply.github.com> Date: Sun Jan 23 23:40:17 2022 +0100 Merge branch 'remove-bwrs' of https://github.com/RealOrangeOne/vaultwarden into RealOrangeOne-remove-bwrs commit7257251ecf
Author: Jake Howard <git@theorangeone.net> Date: Thu Jan 6 17:48:18 2022 +0000 Use `or_else` to save potentially unnecessary function call commit40ae81dd3c
Author: Jake Howard <git@theorangeone.net> Date: Wed Jan 5 21:18:24 2022 +0000 Move $BWRS_VERSION fallback into build.rs commit743ef74b30
Author: Jake Howard <git@theorangeone.net> Date: Sat Jan 1 23:08:27 2022 +0000 Revert "Add feature to enable use of `Option::or` in const context" This reverts commitfe8e043b8a
. We want to run on stable soon, where these features are not supported commita1f0da638c
Author: Jake Howard <git@theorangeone.net> Date: Sat Jan 1 13:04:47 2022 +0000 Rename web vault version file https://github.com/dani-garcia/bw_web_builds/pull/58 commitfe8e043b8a
Author: Jake Howard <git@theorangeone.net> Date: Sat Jan 1 12:56:44 2022 +0000 Add feature to enable use of `Option::or` in const context commit687435c8b2
Author: Jake Howard <git@theorangeone.net> Date: Sat Jan 1 12:27:28 2022 +0000 Continue to allow using `$BWRS_VERSION` commit8e2f708e50
Author: Jake Howard <git@theorangeone.net> Date: Fri Dec 31 11:41:34 2021 +0000 Remove references to "bwrs" The only remaining one is getting the version of the web vault, which requires coordinating with the web vault patching.
293 lines
14 KiB
Handlebars
293 lines
14 KiB
Handlebars
<main class="container-xl">
|
|
<div id="users-block" class="my-3 p-3 bg-white rounded shadow">
|
|
<h6 class="border-bottom pb-2 mb-3">Registered Users</h6>
|
|
|
|
<div class="table-responsive-xl small">
|
|
<table id="users-table" class="table table-sm table-striped table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>User</th>
|
|
<th style="width: 85px; min-width: 70px;">Created at</th>
|
|
<th style="width: 85px; min-width: 70px;">Last Active</th>
|
|
<th style="width: 35px; min-width: 35px;">Items</th>
|
|
<th>Attachments</th>
|
|
<th style="min-width: 120px;">Organizations</th>
|
|
<th style="width: 130px; min-width: 130px;">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{#each page_data}}
|
|
<tr>
|
|
<td>
|
|
<img class="float-start me-2 rounded identicon" data-src="{{Email}}">
|
|
<div class="float-start">
|
|
<strong>{{Name}}</strong>
|
|
<span class="d-block">{{Email}}</span>
|
|
<span class="d-block">
|
|
{{#unless user_enabled}}
|
|
<span class="badge bg-danger me-2" title="User is disabled">Disabled</span>
|
|
{{/unless}}
|
|
{{#if TwoFactorEnabled}}
|
|
<span class="badge bg-success me-2" title="2FA is enabled">2FA</span>
|
|
{{/if}}
|
|
{{#case _Status 1}}
|
|
<span class="badge bg-warning me-2" title="User is invited">Invited</span>
|
|
{{/case}}
|
|
{{#if EmailVerified}}
|
|
<span class="badge bg-success me-2" title="Email has been verified">Verified</span>
|
|
{{/if}}
|
|
</span>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="d-block">{{created_at}}</span>
|
|
</td>
|
|
<td>
|
|
<span class="d-block">{{last_active}}</span>
|
|
</td>
|
|
<td>
|
|
<span class="d-block">{{cipher_count}}</span>
|
|
</td>
|
|
<td>
|
|
<span class="d-block"><strong>Amount:</strong> {{attachment_count}}</span>
|
|
{{#if attachment_count}}
|
|
<span class="d-block"><strong>Size:</strong> {{attachment_size}}</span>
|
|
{{/if}}
|
|
</td>
|
|
<td>
|
|
<div class="overflow-auto" style="max-height: 120px;">
|
|
{{#each Organizations}}
|
|
<button class="badge" data-bs-toggle="modal" data-bs-target="#userOrgTypeDialog" data-orgtype="{{Type}}" data-orguuid="{{jsesc Id no_quote}}" data-orgname="{{jsesc Name no_quote}}" data-useremail="{{jsesc ../Email no_quote}}" data-useruuid="{{jsesc ../Id no_quote}}">{{Name}}</button>
|
|
{{/each}}
|
|
</div>
|
|
</td>
|
|
<td class="text-end px-0 small">
|
|
{{#if TwoFactorEnabled}}
|
|
<button type="button" class="btn btn-sm btn-link p-0 border-0" onclick='remove2fa({{jsesc Id}})'>Remove all 2FA</button>
|
|
{{/if}}
|
|
<button type="button" class="btn btn-sm btn-link p-0 border-0" onclick='deauthUser({{jsesc Id}})'>Deauthorize sessions</button>
|
|
<button type="button" class="btn btn-sm btn-link p-0 border-0" onclick='deleteUser({{jsesc Id}}, {{jsesc Email}})'>Delete User</button>
|
|
{{#if user_enabled}}
|
|
<button type="button" class="btn btn-sm btn-link p-0 border-0" onclick='disableUser({{jsesc Id}}, {{jsesc Email}})'>Disable User</button>
|
|
{{else}}
|
|
<button type="button" class="btn btn-sm btn-link p-0 border-0" onclick='enableUser({{jsesc Id}}, {{jsesc Email}})'>Enable User</button>
|
|
{{/if}}
|
|
</td>
|
|
</tr>
|
|
{{/each}}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="mt-3">
|
|
<button type="button" class="btn btn-sm btn-danger" onclick="updateRevisions();"
|
|
title="Force all clients to fetch new data next time they connect. Useful after restoring a backup to remove any stale data.">
|
|
Force clients to resync
|
|
</button>
|
|
|
|
<button type="button" class="btn btn-sm btn-primary float-end" onclick="reload();">Reload users</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="invite-form-block" class="align-items-center p-3 mb-3 text-white-50 bg-secondary rounded shadow">
|
|
<div>
|
|
<h6 class="mb-0 text-white">Invite User</h6>
|
|
<small>Email:</small>
|
|
|
|
<form class="form-inline input-group w-50" id="invite-form" onsubmit="inviteUser(); return false;">
|
|
<input type="email" class="form-control me-2" id="email-invite" placeholder="Enter email" required>
|
|
<button type="submit" class="btn btn-primary">Invite</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="userOrgTypeDialog" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered modal-sm">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h6 class="modal-title" id="userOrgTypeDialogTitle"></h6>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form class="form" id="userOrgTypeForm" onsubmit="updateUserOrgType(); return false;">
|
|
<input type="hidden" name="user_uuid" id="userOrgTypeUserUuid" value="">
|
|
<input type="hidden" name="org_uuid" id="userOrgTypeOrgUuid" value="">
|
|
<div class="modal-body">
|
|
<div class="radio">
|
|
<label><input type="radio" value="2" class="form-radio-input" name="user_type" id="userOrgTypeUser"> User</label>
|
|
</div>
|
|
<div class="radio">
|
|
<label><input type="radio" value="3" class="form-radio-input" name="user_type" id="userOrgTypeManager"> Manager</label>
|
|
</div>
|
|
<div class="radio">
|
|
<label><input type="radio" value="1" class="form-radio-input" name="user_type" id="userOrgTypeAdmin"> Admin</label>
|
|
</div>
|
|
<div class="radio">
|
|
<label><input type="radio" value="0" class="form-radio-input" name="user_type" id="userOrgTypeOwner"> Owner</label>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-sm btn-primary">Change Role</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
|
|
<script src="{{urlpath}}/vw_static/jquery-3.6.0.slim.js"></script>
|
|
<script src="{{urlpath}}/vw_static/datatables.js"></script>
|
|
<script>
|
|
'use strict';
|
|
|
|
function deleteUser(id, mail) {
|
|
var input_mail = prompt("To delete user '" + mail + "', please type the email below")
|
|
if (input_mail != null) {
|
|
if (input_mail == mail) {
|
|
_post("{{urlpath}}/admin/users/" + id + "/delete",
|
|
"User deleted correctly",
|
|
"Error deleting user");
|
|
} else {
|
|
alert("Wrong email, please try again")
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
function remove2fa(id) {
|
|
_post("{{urlpath}}/admin/users/" + id + "/remove-2fa",
|
|
"2FA removed correctly",
|
|
"Error removing 2FA");
|
|
return false;
|
|
}
|
|
function deauthUser(id) {
|
|
_post("{{urlpath}}/admin/users/" + id + "/deauth",
|
|
"Sessions deauthorized correctly",
|
|
"Error deauthorizing sessions");
|
|
return false;
|
|
}
|
|
function disableUser(id, mail) {
|
|
var confirmed = confirm("Are you sure you want to disable user '" + mail + "'? This will also deauthorize their sessions.")
|
|
if (confirmed) {
|
|
_post("{{urlpath}}/admin/users/" + id + "/disable",
|
|
"User disabled successfully",
|
|
"Error disabling user");
|
|
}
|
|
return false;
|
|
}
|
|
function enableUser(id, mail) {
|
|
var confirmed = confirm("Are you sure you want to enable user '" + mail + "'?")
|
|
if (confirmed) {
|
|
_post("{{urlpath}}/admin/users/" + id + "/enable",
|
|
"User enabled successfully",
|
|
"Error enabling user");
|
|
}
|
|
return false;
|
|
}
|
|
function updateRevisions() {
|
|
_post("{{urlpath}}/admin/users/update_revision",
|
|
"Success, clients will sync next time they connect",
|
|
"Error forcing clients to sync");
|
|
return false;
|
|
}
|
|
function inviteUser() {
|
|
const inv = document.getElementById("email-invite");
|
|
const data = JSON.stringify({ "email": inv.value });
|
|
inv.value = "";
|
|
_post("{{urlpath}}/admin/invite/", "User invited correctly",
|
|
"Error inviting user", data);
|
|
return false;
|
|
}
|
|
|
|
let OrgTypes = {
|
|
"0": { "name": "Owner", "color": "orange" },
|
|
"1": { "name": "Admin", "color": "blueviolet" },
|
|
"2": { "name": "User", "color": "blue" },
|
|
"3": { "name": "Manager", "color": "green" },
|
|
};
|
|
|
|
(async () => {
|
|
for (let e of document.querySelectorAll("img.identicon")) {
|
|
e.src = await identicon(e.dataset.src);
|
|
}
|
|
})();
|
|
|
|
document.querySelectorAll("[data-orgtype]").forEach(function (e) {
|
|
let orgtype = OrgTypes[e.dataset.orgtype];
|
|
e.style.backgroundColor = orgtype.color;
|
|
e.title = orgtype.name;
|
|
});
|
|
|
|
// Special sort function to sort dates in ISO format
|
|
jQuery.extend( jQuery.fn.dataTableExt.oSort, {
|
|
"date-iso-pre": function ( a ) {
|
|
let x;
|
|
let sortDate = a.replace(/(<([^>]+)>)/gi, "").trim();
|
|
if ( sortDate !== '' ) {
|
|
let dtParts = sortDate.split(' ');
|
|
var timeParts = (undefined != dtParts[1]) ? dtParts[1].split(':') : ['00','00','00'];
|
|
var dateParts = dtParts[0].split('-');
|
|
x = (dateParts[0] + dateParts[1] + dateParts[2] + timeParts[0] + timeParts[1] + ((undefined != timeParts[2]) ? timeParts[2] : 0)) * 1;
|
|
if ( isNaN(x) ) {
|
|
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;
|
|
}
|
|
});
|
|
|
|
document.addEventListener("DOMContentLoaded", function() {
|
|
$('#users-table').DataTable({
|
|
"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 }
|
|
]
|
|
});
|
|
});
|
|
|
|
var userOrgTypeDialog = document.getElementById('userOrgTypeDialog');
|
|
// Fill the form and title
|
|
userOrgTypeDialog.addEventListener('show.bs.modal', function(event){
|
|
let userOrgType = event.relatedTarget.getAttribute("data-orgtype");
|
|
let userOrgTypeName = OrgTypes[userOrgType]["name"];
|
|
let orgName = event.relatedTarget.getAttribute("data-orgname");
|
|
let userEmail = event.relatedTarget.getAttribute("data-useremail");
|
|
let orgUuid = event.relatedTarget.getAttribute("data-orguuid");
|
|
let userUuid = event.relatedTarget.getAttribute("data-useruuid");
|
|
|
|
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() {
|
|
let orgForm = document.getElementById("userOrgTypeForm");
|
|
const data = JSON.stringify(Object.fromEntries(new FormData(orgForm).entries()));
|
|
|
|
_post("{{urlpath}}/admin/users/org_type",
|
|
"Updated organization type of the user successfully",
|
|
"Error updating organization type of the user", data);
|
|
return false;
|
|
}
|
|
</script>
|