1
0
Fork 0
vaultwarden-test/src/static/templates/admin/users.hbs
BlackDex 338756550a
Fix error reporting in admin and some small fixes
- Fixed a bug in JavaScript which caused no messages to be shown to the
user in-case of an error send by the server.
- Changed mail error handling for better error messages
- Changed user/org actions from a to buttons, this should prevent
strange issues in-case of javascript issues and the page does re-load.
- Added Alpine and Debian info for the running docker image

During the mail error testing i encountered a bug which caused lettre to
panic. This panic only happens on debug builds and not release builds,
so no need to update anything on that part. This bug is also already
fixed. See https://github.com/lettre/lettre/issues/678 and https://github.com/lettre/lettre/pull/679

Resolves #2021
Could also fix the issue reported here #2022, or at least no hash `#` in
the url.
2021-10-08 00:01:24 +02:00

294 Zeilen
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">&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}}/bwrs_static/datatables.css" />
<script src="{{urlpath}}/bwrs_static/jquery-3.6.0.slim.js"></script>
<script src="{{urlpath}}/bwrs_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>