Browse Source
Main changes: - Splitted up settings and users into two separate pages. - Added verified shield when the e-mail address has been verified. - Added the amount of personal items in the database to the users overview. - Added Organizations and Diagnostics pages. - Shows if DNS resolving works. - Shows if there is a posible time drift. - Shows current versions of server and web-vault. - Optimized logo-gray.png using optipng Items which can be added later: - Amount of cipher items accessible for a user, not only his personal items. - Amount of users per Org - Version update check in the diagnostics overview. - Copy/Pasteable runtime config which has sensitive data changed or removed for support questions either on the forum or github issues. - Option to delete Orgs and all its passwords (when there are no members anymore). - Etc....pull/1007/head
12 changed files with 453 additions and 183 deletions
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 1.9 KiB |
@ -0,0 +1,73 @@ |
|||
<main class="container"> |
|||
<div id="diagnostics-block" class="my-3 p-3 bg-white rounded shadow"> |
|||
<h6 class="border-bottom pb-2 mb-2">Diagnostics</h6> |
|||
|
|||
<h3>Version</h3> |
|||
<div class="row"> |
|||
<div class="col-md"> |
|||
<dl class="row"> |
|||
<dt class="col-sm-5">Server Installed</dt> |
|||
<dd class="col-sm-7"> |
|||
<span id="server-installed">{{version}}</span> |
|||
</dd> |
|||
<dt class="col-sm-5">Web Installed</dt> |
|||
<dd class="col-sm-7"> |
|||
<span id="web-installed">{{diagnostics.web_vault_version}}</span> |
|||
</dd> |
|||
</dl> |
|||
</div> |
|||
</div> |
|||
|
|||
<h3>Checks</h3> |
|||
<div class="row"> |
|||
<div class="col-md"> |
|||
<dl class="row"> |
|||
<dt class="col-sm-5">DNS (github.com) |
|||
<span class="badge badge-success d-none" id="dns-success" title="DNS Resolving works!">Ok</span> |
|||
<span class="badge badge-danger d-none" id="dns-warning" title="DNS Resolving failed. Please fix.">Error</span> |
|||
</dt> |
|||
<dd class="col-sm-7"> |
|||
<span id="dns-resolved">{{diagnostics.dns_resolved}}</span> |
|||
</dd> |
|||
|
|||
<dt class="col-sm-5">Date & Time (UTC) |
|||
<span class="badge badge-success d-none" id="time-success" title="Time offsets seem to be correct.">Ok</span> |
|||
<span class="badge badge-danger d-none" id="time-warning" title="Time offsets are too mouch at drift.">Error</span> |
|||
</dt> |
|||
<dd class="col-sm-7"> |
|||
<span id="time-server" class="d-block"><b>Server:</b> <span id="time-server-string">{{diagnostics.server_time}}</span></span> |
|||
<span id="time-browser" class="d-block"><b>Browser:</b> <span id="time-browser-string"></span></span> |
|||
</dd> |
|||
</dl> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</main> |
|||
|
|||
<script> |
|||
const d = new Date(); |
|||
const year = d.getUTCFullYear(); |
|||
const month = String((d.getUTCMonth()+1)).padStart(2, '0'); |
|||
const day = String(d.getUTCDate()).padStart(2, '0'); |
|||
const hour = String(d.getUTCHours()).padStart(2, '0'); |
|||
const minute = String(d.getUTCMinutes()).padStart(2, '0'); |
|||
const seconds = String(d.getUTCSeconds()).padStart(2, '0'); |
|||
const browserUTC = year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + seconds; |
|||
document.getElementById("time-browser-string").innerText = browserUTC; |
|||
|
|||
const serverUTC = document.getElementById("time-server-string").innerText; |
|||
const timeDrift = (Date.parse(serverUTC) - Date.parse(browserUTC)) / 1000; |
|||
if (timeDrift > 30 || timeDrift < -30) { |
|||
document.getElementById('time-warning').classList.remove('d-none'); |
|||
} else { |
|||
document.getElementById('time-success').classList.remove('d-none'); |
|||
} |
|||
|
|||
// Check if the output is a valid IP |
|||
const isValidIp = value => (/^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/.test(value) ? true : false); |
|||
if (isValidIp(document.getElementById('dns-resolved').innerText)) { |
|||
document.getElementById('dns-success').classList.remove('d-none'); |
|||
} else { |
|||
document.getElementById('dns-warning').classList.remove('d-none'); |
|||
} |
|||
</script> |
@ -0,0 +1,30 @@ |
|||
<main class="container"> |
|||
<div id="organizations-block" class="my-3 p-3 bg-white rounded shadow"> |
|||
<h6 class="border-bottom pb-2 mb-0">Organizations</h6> |
|||
|
|||
<div id="organizations-list"> |
|||
{{#each organizations}} |
|||
<div class="media pt-3"> |
|||
<img class="mr-2 rounded identicon" data-src="{{Name}}_{{BillingEmail}}"> |
|||
<div class="media-body pb-3 mb-0 small border-bottom"> |
|||
<div class="row justify-content-between"> |
|||
<div class="col"> |
|||
<strong>{{Name}}</strong> |
|||
{{#if Id}} |
|||
<span class="badge badge-success ml-2">{{Id}}</span> |
|||
{{/if}} |
|||
<span class="d-block">{{BillingEmail}}</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
{{/each}} |
|||
</div> |
|||
</div> |
|||
</main> |
|||
|
|||
<script> |
|||
document.querySelectorAll("img.identicon").forEach(function (e, i) { |
|||
e.src = identicon(e.dataset.src); |
|||
}); |
|||
</script> |
@ -0,0 +1,134 @@ |
|||
<main class="container"> |
|||
<div id="users-block" class="my-3 p-3 bg-white rounded shadow"> |
|||
<h6 class="border-bottom pb-2 mb-0">Registered Users</h6> |
|||
|
|||
<div id="users-list"> |
|||
{{#each users}} |
|||
<div class="media pt-3"> |
|||
<img class="mr-2 rounded identicon" data-src="{{Email}}"> |
|||
<div class="media-body pb-3 mb-0 small border-bottom"> |
|||
<div class="row justify-content-between"> |
|||
<div class="col"> |
|||
<strong>{{Name}}</strong> |
|||
{{#if TwoFactorEnabled}} |
|||
<span class="badge badge-success ml-2">2FA</span> |
|||
{{/if}} |
|||
{{#case _Status 1}} |
|||
<span class="badge badge-warning ml-2">Invited</span> |
|||
{{/case}} |
|||
<span class="d-block">{{Email}} |
|||
{{#if EmailVerified}} |
|||
<span class="badge badge-success ml-2">Verified</span> |
|||
{{/if}} |
|||
</span> |
|||
</div> |
|||
<div class="col"> |
|||
<strong> Personal Items: </strong> |
|||
<span class="d-block"> |
|||
{{cipher_count}} |
|||
</span> |
|||
</div> |
|||
<div class="col-4"> |
|||
<strong> Organizations: </strong> |
|||
<span class="d-block"> |
|||
{{#each Organizations}} |
|||
<span class="badge badge-primary" data-orgtype="{{Type}}">{{Name}}</span> |
|||
{{/each}} |
|||
</span> |
|||
</div> |
|||
<div class="col" style="font-size: 90%; text-align: right; padding-right: 15px"> |
|||
{{#if TwoFactorEnabled}} |
|||
<a class="mr-2" href="#" onclick='remove2fa({{jsesc Id}})'>Remove all 2FA</a> |
|||
{{/if}} |
|||
|
|||
<a class="mr-2" href="#" onclick='deauthUser({{jsesc Id}})'>Deauthorize sessions</a> |
|||
<a class="mr-2" href="#" onclick='deleteUser({{jsesc Id}}, {{jsesc Email}})'>Delete User</a> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
{{/each}} |
|||
|
|||
</div> |
|||
|
|||
<div class="mt-3"> |
|||
<button type="button" class="btn btn-sm btn-link" 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-right" 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" id="invite-form" onsubmit="inviteUser(); return false;"> |
|||
<input type="email" class="form-control w-50 mr-2" id="email-invite" placeholder="Enter email"> |
|||
<button type="submit" class="btn btn-primary">Invite</button> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
</main> |
|||
|
|||
<script> |
|||
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 updateRevisions() { |
|||
_post("{{urlpath}}/admin/users/update_revision", |
|||
"Success, clients will sync next time they connect", |
|||
"Error forcing clients to sync"); |
|||
return false; |
|||
} |
|||
function inviteUser() { |
|||
inv = document.getElementById("email-invite"); |
|||
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("img.identicon").forEach(function (e, i) { |
|||
e.src = identicon(e.dataset.src); |
|||
}); |
|||
|
|||
document.querySelectorAll("[data-orgtype]").forEach(function (e, i) { |
|||
let orgtype = OrgTypes[e.dataset.orgtype]; |
|||
e.style.backgroundColor = orgtype.color; |
|||
e.title = orgtype.name; |
|||
}); |
|||
</script> |
Loading…
Reference in new issue