vaultwarden/src/static/templates/admin/users.hbs

288 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>
<svg width="48" height="48" class="float-start me-2 rounded" data-jdenticon-value="{{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">&nbsp;User</label>
</div>
<div class="radio">
<label><input type="radio" value="3" class="form-radio-input" name="user_type" id="userOrgTypeManager">&nbsp;Manager</label>
</div>
<div class="radio">
<label><input type="radio" value="1" class="form-radio-input" name="user_type" id="userOrgTypeAdmin">&nbsp;Admin</label>
</div>
<div class="radio">
<label><input type="radio" value="0" class="form-radio-input" name="user_type" id="userOrgTypeOwner">&nbsp;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.2.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" },
};
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>