geforkt von mirrored/vaultwarden
610b183cef
- Updated Rust deps and one small change regarding chrono - Updated bootstrap 5 css - Updated datatables - Replaced identicon.js with jdenticon. identicon.js is unmaintained ( https://github.com/stewartlord/identicon.js/issues/52 ) The icon's are very different, but nice. It also doesn't need custom code to find and update the icons our selfs.
287 Zeilen
14 KiB
Handlebars
287 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>
|
|
<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"> 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.1.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>
|