2021-01-19 17:55:21 +01:00
|
|
|
<main class="container-xl">
|
2020-05-28 10:42:36 +02:00
|
|
|
<div id="users-block" class="my-3 p-3 bg-white rounded shadow">
|
2020-09-19 22:19:55 +02:00
|
|
|
<h6 class="border-bottom pb-2 mb-3">Registered Users</h6>
|
2020-05-28 10:42:36 +02:00
|
|
|
|
2020-06-01 18:58:38 +02:00
|
|
|
<div class="table-responsive-xl small">
|
2020-09-19 22:19:55 +02:00
|
|
|
<table id="users-table" class="table table-sm table-striped table-hover">
|
2020-06-01 18:58:38 +02:00
|
|
|
<thead>
|
|
|
|
<tr>
|
2020-09-19 22:19:55 +02:00
|
|
|
<th>User</th>
|
2021-06-19 19:22:19 +02:00
|
|
|
<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>
|
2020-06-03 17:57:03 +02:00
|
|
|
<th>Attachments</th>
|
2020-09-19 22:19:55 +02:00
|
|
|
<th style="min-width: 120px;">Organizations</th>
|
2021-06-19 19:22:19 +02:00
|
|
|
<th style="width: 130px; min-width: 130px;">Actions</th>
|
2020-06-01 18:58:38 +02:00
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
2021-06-19 19:22:19 +02:00
|
|
|
{{#each page_data}}
|
2020-06-01 18:58:38 +02:00
|
|
|
<tr>
|
|
|
|
<td>
|
2022-12-01 17:18:29 +01:00
|
|
|
<svg width="48" height="48" class="float-start me-2 rounded" data-jdenticon-value="{{Email}}">
|
2021-06-19 19:22:19 +02:00
|
|
|
<div class="float-start">
|
2020-09-19 22:19:55 +02:00
|
|
|
<strong>{{Name}}</strong>
|
|
|
|
<span class="d-block">{{Email}}</span>
|
|
|
|
<span class="d-block">
|
2020-11-30 23:12:56 +01:00
|
|
|
{{#unless user_enabled}}
|
2021-06-19 19:22:19 +02:00
|
|
|
<span class="badge bg-danger me-2" title="User is disabled">Disabled</span>
|
2020-11-30 23:12:56 +01:00
|
|
|
{{/unless}}
|
2020-09-19 22:19:55 +02:00
|
|
|
{{#if TwoFactorEnabled}}
|
2021-06-19 19:22:19 +02:00
|
|
|
<span class="badge bg-success me-2" title="2FA is enabled">2FA</span>
|
2020-09-19 22:19:55 +02:00
|
|
|
{{/if}}
|
|
|
|
{{#case _Status 1}}
|
2021-06-19 19:22:19 +02:00
|
|
|
<span class="badge bg-warning me-2" title="User is invited">Invited</span>
|
2020-09-19 22:19:55 +02:00
|
|
|
{{/case}}
|
|
|
|
{{#if EmailVerified}}
|
2021-06-19 19:22:19 +02:00
|
|
|
<span class="badge bg-success me-2" title="Email has been verified">Verified</span>
|
2020-09-19 22:19:55 +02:00
|
|
|
{{/if}}
|
|
|
|
</span>
|
|
|
|
</div>
|
2020-06-01 18:58:38 +02:00
|
|
|
</td>
|
2021-01-19 17:55:21 +01:00
|
|
|
<td>
|
|
|
|
<span class="d-block">{{created_at}}</span>
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
<span class="d-block">{{last_active}}</span>
|
|
|
|
</td>
|
2020-06-01 18:58:38 +02:00
|
|
|
<td>
|
|
|
|
<span class="d-block">{{cipher_count}}</span>
|
|
|
|
</td>
|
2020-06-03 17:57:03 +02:00
|
|
|
<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>
|
2020-06-01 18:58:38 +02:00
|
|
|
<td>
|
2021-01-19 17:55:21 +01:00
|
|
|
<div class="overflow-auto" style="max-height: 120px;">
|
2020-06-01 18:58:38 +02:00
|
|
|
{{#each Organizations}}
|
2021-06-19 19:22:19 +02:00
|
|
|
<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>
|
2020-06-01 18:58:38 +02:00
|
|
|
{{/each}}
|
2021-01-19 17:55:21 +01:00
|
|
|
</div>
|
2020-06-01 18:58:38 +02:00
|
|
|
</td>
|
2021-10-08 00:01:24 +02:00
|
|
|
<td class="text-end px-0 small">
|
2020-05-28 10:42:36 +02:00
|
|
|
{{#if TwoFactorEnabled}}
|
2021-10-08 00:01:24 +02:00
|
|
|
<button type="button" class="btn btn-sm btn-link p-0 border-0" onclick='remove2fa({{jsesc Id}})'>Remove all 2FA</button>
|
2020-05-28 10:42:36 +02:00
|
|
|
{{/if}}
|
2021-10-08 00:01:24 +02:00
|
|
|
<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>
|
2020-11-30 23:12:56 +01:00
|
|
|
{{#if user_enabled}}
|
2021-10-08 00:01:24 +02:00
|
|
|
<button type="button" class="btn btn-sm btn-link p-0 border-0" onclick='disableUser({{jsesc Id}}, {{jsesc Email}})'>Disable User</button>
|
2020-11-30 23:12:56 +01:00
|
|
|
{{else}}
|
2021-10-08 00:01:24 +02:00
|
|
|
<button type="button" class="btn btn-sm btn-link p-0 border-0" onclick='enableUser({{jsesc Id}}, {{jsesc Email}})'>Enable User</button>
|
2020-11-30 23:12:56 +01:00
|
|
|
{{/if}}
|
2020-06-01 18:58:38 +02:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
{{/each}}
|
|
|
|
</tbody>
|
|
|
|
</table>
|
2020-05-28 10:42:36 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="mt-3">
|
2020-06-01 18:58:38 +02:00
|
|
|
<button type="button" class="btn btn-sm btn-danger" onclick="updateRevisions();"
|
2020-05-28 10:42:36 +02:00
|
|
|
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>
|
|
|
|
|
2021-06-19 19:22:19 +02:00
|
|
|
<button type="button" class="btn btn-sm btn-primary float-end" onclick="reload();">Reload users</button>
|
2020-05-28 10:42:36 +02:00
|
|
|
</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>
|
|
|
|
|
2021-06-19 19:22:19 +02:00
|
|
|
<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>
|
2020-05-28 10:42:36 +02:00
|
|
|
<button type="submit" class="btn btn-primary">Invite</button>
|
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
</div>
|
2021-02-03 18:43:54 +01:00
|
|
|
|
|
|
|
<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>
|
2021-06-19 19:22:19 +02:00
|
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
2021-02-03 18:43:54 +01:00
|
|
|
</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">
|
2021-06-19 19:22:19 +02:00
|
|
|
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
2021-02-03 18:43:54 +01:00
|
|
|
<button type="submit" class="btn btn-sm btn-primary">Change Role</button>
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2020-05-28 10:42:36 +02:00
|
|
|
</main>
|
|
|
|
|
2022-01-23 23:40:59 +01:00
|
|
|
<link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
|
2022-09-06 17:14:16 +02:00
|
|
|
<script src="{{urlpath}}/vw_static/jquery-3.6.1.slim.js"></script>
|
2022-01-23 23:40:59 +01:00
|
|
|
<script src="{{urlpath}}/vw_static/datatables.js"></script>
|
2020-05-28 10:42:36 +02:00
|
|
|
<script>
|
2021-06-19 19:22:19 +02:00
|
|
|
'use strict';
|
|
|
|
|
2020-05-28 10:42:36 +02:00
|
|
|
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;
|
|
|
|
}
|
2020-11-30 23:12:56 +01:00
|
|
|
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;
|
|
|
|
}
|
2020-05-28 10:42:36 +02:00
|
|
|
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() {
|
2021-06-19 19:22:19 +02:00
|
|
|
const inv = document.getElementById("email-invite");
|
|
|
|
const data = JSON.stringify({ "email": inv.value });
|
2020-05-28 10:42:36 +02:00
|
|
|
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" },
|
|
|
|
};
|
|
|
|
|
2021-06-19 19:22:19 +02:00
|
|
|
document.querySelectorAll("[data-orgtype]").forEach(function (e) {
|
2020-05-28 10:42:36 +02:00
|
|
|
let orgtype = OrgTypes[e.dataset.orgtype];
|
|
|
|
e.style.backgroundColor = orgtype.color;
|
|
|
|
e.title = orgtype.name;
|
|
|
|
});
|
2020-09-19 22:19:55 +02:00
|
|
|
|
2021-01-19 17:55:21 +01:00
|
|
|
// 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(' ');
|
2021-06-19 19:22:19 +02:00
|
|
|
var timeParts = (undefined != dtParts[1]) ? dtParts[1].split(':') : ['00','00','00'];
|
2021-01-19 17:55:21 +01: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;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-06-19 19:22:19 +02:00
|
|
|
document.addEventListener("DOMContentLoaded", function() {
|
2020-09-19 22:19:55 +02:00
|
|
|
$('#users-table').DataTable({
|
|
|
|
"responsive": true,
|
|
|
|
"lengthMenu": [ [-1, 5, 10, 25, 50], ["All", 5, 10, 25, 50] ],
|
|
|
|
"pageLength": -1, // Default show all
|
2021-01-19 17:55:21 +01:00
|
|
|
"columnDefs": [
|
|
|
|
{ "targets": [1,2], "type": "date-iso" },
|
|
|
|
{ "targets": 6, "searchable": false, "orderable": false }
|
|
|
|
]
|
2020-09-19 22:19:55 +02:00
|
|
|
});
|
|
|
|
});
|
2021-02-03 18:43:54 +01:00
|
|
|
|
|
|
|
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.
|
2021-06-19 19:22:19 +02:00
|
|
|
userOrgTypeDialog.addEventListener('hide.bs.modal', function(){
|
2021-02-03 18:43:54 +01:00
|
|
|
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;
|
|
|
|
}
|
2021-02-27 06:48:01 +01:00
|
|
|
</script>
|