1
0
Fork 0
mirror of https://github.com/dani-garcia/vaultwarden.git synced 2025-06-22 19:22:51 +00:00

Updated the admin interface

Mostly updated the admin interface, also some small other items.

- Added more diagnostic information to (hopefully) decrease issue
  reporting, or at least solve them quicker.
- Added an option to generate a support string which can be used to
  copy/paste on the forum or during the creation of an issue. It will
try to hide the sensitive information automatically.
- Changed the `Created At` and `Last Active` info to be in a column and
  able to sort them in the users overview.
- Some small layout changes.
- Updated javascript and css files to the latest versions available.
- Decreased the png file sizes using `oxipng`
- Updated target='_blank' links to have rel='noreferrer' to prevent
  javascript window.opener modifications.
This commit is contained in:
BlackDex 2021-01-19 17:55:21 +01:00
parent 37cc0c34cf
commit 235ff44736
18 changed files with 383 additions and 108 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

View file

@ -1,6 +1,6 @@
/*!
* Native JavaScript for Bootstrap v3.0.10 (https://thednp.github.io/bootstrap.native/)
* Copyright 2015-2020 © dnp_theme
* Native JavaScript for Bootstrap v3.0.15 (https://thednp.github.io/bootstrap.native/)
* Copyright 2015-2021 © dnp_theme
* Licensed under MIT (https://github.com/thednp/bootstrap.native/blob/master/LICENSE)
*/
(function (global, factory) {
@ -15,10 +15,14 @@
var transitionDuration = 'webkitTransition' in document.head.style ? 'webkitTransitionDuration' : 'transitionDuration';
var transitionProperty = 'webkitTransition' in document.head.style ? 'webkitTransitionProperty' : 'transitionProperty';
function getElementTransitionDuration(element) {
var duration = supportTransition ? parseFloat(getComputedStyle(element)[transitionDuration]) : 0;
duration = typeof duration === 'number' && !isNaN(duration) ? duration * 1000 : 0;
return duration;
var computedStyle = getComputedStyle(element),
property = computedStyle[transitionProperty],
duration = supportTransition && property && property !== 'none'
? parseFloat(computedStyle[transitionDuration]) : 0;
return !isNaN(duration) ? duration * 1000 : 0;
}
function emulateTransitionEnd(element,handler){
@ -35,9 +39,15 @@
return selector instanceof Element ? selector : lookUp.querySelector(selector);
}
function bootstrapCustomEvent(eventName, componentName, related) {
function bootstrapCustomEvent(eventName, componentName, eventProperties) {
var OriginalCustomEvent = new CustomEvent( eventName + '.bs.' + componentName, {cancelable: true});
OriginalCustomEvent.relatedTarget = related;
if (typeof eventProperties !== 'undefined') {
Object.keys(eventProperties).forEach(function (key) {
Object.defineProperty(OriginalCustomEvent, key, {
value: eventProperties[key]
});
});
}
return OriginalCustomEvent;
}
@ -352,7 +362,7 @@
};
self.slideTo = function (next) {
if (vars.isSliding) { return; }
var activeItem = self.getActiveIndex(), orientation;
var activeItem = self.getActiveIndex(), orientation, eventProperties;
if ( activeItem === next ) {
return;
} else if ( (activeItem < next ) || (activeItem === 0 && next === slides.length -1 ) ) {
@ -363,8 +373,9 @@
if ( next < 0 ) { next = slides.length - 1; }
else if ( next >= slides.length ){ next = 0; }
orientation = vars.direction === 'left' ? 'next' : 'prev';
slideCustomEvent = bootstrapCustomEvent('slide', 'carousel', slides[next]);
slidCustomEvent = bootstrapCustomEvent('slid', 'carousel', slides[next]);
eventProperties = { relatedTarget: slides[next], direction: vars.direction, from: activeItem, to: next };
slideCustomEvent = bootstrapCustomEvent('slide', 'carousel', eventProperties);
slidCustomEvent = bootstrapCustomEvent('slid', 'carousel', eventProperties);
dispatchCustomEvent.call(element, slideCustomEvent);
if (slideCustomEvent.defaultPrevented) { return; }
vars.index = next;
@ -615,7 +626,7 @@
}
}
self.show = function () {
showCustomEvent = bootstrapCustomEvent('show', 'dropdown', relatedTarget);
showCustomEvent = bootstrapCustomEvent('show', 'dropdown', { relatedTarget: relatedTarget });
dispatchCustomEvent.call(parent, showCustomEvent);
if ( showCustomEvent.defaultPrevented ) { return; }
menu.classList.add('show');
@ -626,12 +637,12 @@
setTimeout(function () {
setFocus( menu.getElementsByTagName('INPUT')[0] || element );
toggleDismiss();
shownCustomEvent = bootstrapCustomEvent( 'shown', 'dropdown', relatedTarget);
shownCustomEvent = bootstrapCustomEvent('shown', 'dropdown', { relatedTarget: relatedTarget });
dispatchCustomEvent.call(parent, shownCustomEvent);
},1);
};
self.hide = function () {
hideCustomEvent = bootstrapCustomEvent('hide', 'dropdown', relatedTarget);
hideCustomEvent = bootstrapCustomEvent('hide', 'dropdown', { relatedTarget: relatedTarget });
dispatchCustomEvent.call(parent, hideCustomEvent);
if ( hideCustomEvent.defaultPrevented ) { return; }
menu.classList.remove('show');
@ -643,7 +654,7 @@
setTimeout(function () {
element.Dropdown && element.addEventListener('click',clickHandler,false);
},1);
hiddenCustomEvent = bootstrapCustomEvent('hidden', 'dropdown', relatedTarget);
hiddenCustomEvent = bootstrapCustomEvent('hidden', 'dropdown', { relatedTarget: relatedTarget });
dispatchCustomEvent.call(parent, hiddenCustomEvent);
};
self.toggle = function () {
@ -749,7 +760,7 @@
setFocus(modal);
modal.isAnimating = false;
toggleEvents(1);
shownCustomEvent = bootstrapCustomEvent('shown', 'modal', relatedTarget);
shownCustomEvent = bootstrapCustomEvent('shown', 'modal', { relatedTarget: relatedTarget });
dispatchCustomEvent.call(modal, shownCustomEvent);
}
function triggerHide(force) {
@ -804,7 +815,7 @@
};
self.show = function () {
if (modal.classList.contains('show') && !!modal.isAnimating ) {return}
showCustomEvent = bootstrapCustomEvent('show', 'modal', relatedTarget);
showCustomEvent = bootstrapCustomEvent('show', 'modal', { relatedTarget: relatedTarget });
dispatchCustomEvent.call(modal, showCustomEvent);
if ( showCustomEvent.defaultPrevented ) { return; }
modal.isAnimating = true;
@ -1193,7 +1204,7 @@
if (dropLink && !dropLink.classList.contains('active') ) {
dropLink.classList.add('active');
}
dispatchCustomEvent.call(element, bootstrapCustomEvent( 'activate', 'scrollspy', vars.items[index]));
dispatchCustomEvent.call(element, bootstrapCustomEvent( 'activate', 'scrollspy', { relatedTarget: vars.items[index] }));
} else if ( isActive && !inside ) {
item.classList.remove('active');
if (dropLink && dropLink.classList.contains('active') && !item.parentNode.getElementsByClassName('active').length ) {
@ -1278,7 +1289,7 @@
} else {
tabs.isAnimating = false;
}
shownCustomEvent = bootstrapCustomEvent('shown', 'tab', activeTab);
shownCustomEvent = bootstrapCustomEvent('shown', 'tab', { relatedTarget: activeTab });
dispatchCustomEvent.call(next, shownCustomEvent);
}
function triggerHide() {
@ -1287,8 +1298,8 @@
nextContent.style.float = 'left';
containerHeight = activeContent.scrollHeight;
}
showCustomEvent = bootstrapCustomEvent('show', 'tab', activeTab);
hiddenCustomEvent = bootstrapCustomEvent('hidden', 'tab', next);
showCustomEvent = bootstrapCustomEvent('show', 'tab', { relatedTarget: activeTab });
hiddenCustomEvent = bootstrapCustomEvent('hidden', 'tab', { relatedTarget: next });
dispatchCustomEvent.call(next, showCustomEvent);
if ( showCustomEvent.defaultPrevented ) { return; }
nextContent.classList.add('active');
@ -1331,7 +1342,7 @@
nextContent = queryElement(next.getAttribute('href'));
activeTab = getActiveTab();
activeContent = getActiveContent();
hideCustomEvent = bootstrapCustomEvent( 'hide', 'tab', next);
hideCustomEvent = bootstrapCustomEvent( 'hide', 'tab', { relatedTarget: next });
dispatchCustomEvent.call(activeTab, hideCustomEvent);
if (hideCustomEvent.defaultPrevented) { return; }
tabs.isAnimating = true;
@ -1637,7 +1648,7 @@
}
}
var version = "3.0.10";
var version = "3.0.15";
var index = {
Alert: Alert,

View file

@ -1,10 +1,10 @@
/*!
* Bootstrap v4.5.2 (https://getbootstrap.com/)
* Bootstrap v4.5.3 (https://getbootstrap.com/)
* Copyright 2011-2020 The Bootstrap Authors
* Copyright 2011-2020 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root {
:root {
--blue: #007bff;
--indigo: #6610f2;
--purple: #6f42c1;
@ -216,6 +216,7 @@ caption {
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
label {
@ -3750,6 +3751,8 @@ input[type="button"].btn-block {
display: block;
min-height: 1.5rem;
padding-left: 1.5rem;
-webkit-print-color-adjust: exact;
color-adjust: exact;
}
.custom-control-inline {
@ -5289,6 +5292,7 @@ a.badge-dark:focus, a.badge-dark.focus {
position: absolute;
top: 0;
right: 0;
z-index: 2;
padding: 0.75rem 1.25rem;
color: inherit;
}
@ -10163,7 +10167,7 @@ a.text-dark:hover, a.text-dark:focus {
.text-break {
word-break: break-word !important;
overflow-wrap: break-word !important;
word-wrap: break-word !important;
}
.text-reset {
@ -10256,3 +10260,4 @@ a.text-dark:hover, a.text-dark:focus {
border-color: #dee2e6;
}
}
/*# sourceMappingURL=bootstrap.css.map */

View file

@ -4,12 +4,13 @@
*
* To rebuild or modify this file with the latest versions of the included
* software please visit:
* https://datatables.net/download/#bs4/dt-1.10.22
* https://datatables.net/download/#bs4/dt-1.10.23
*
* Included libraries:
* DataTables 1.10.22
* DataTables 1.10.23
*/
@charset "UTF-8";
table.dataTable {
clear: both;
margin-top: 6px !important;
@ -114,7 +115,7 @@ table.dataTable > thead .sorting_desc:before,
table.dataTable > thead .sorting_asc_disabled:before,
table.dataTable > thead .sorting_desc_disabled:before {
right: 1em;
content: "\2191";
content: "";
}
table.dataTable > thead .sorting:after,
table.dataTable > thead .sorting_asc:after,
@ -122,7 +123,7 @@ table.dataTable > thead .sorting_desc:after,
table.dataTable > thead .sorting_asc_disabled:after,
table.dataTable > thead .sorting_desc_disabled:after {
right: 0.5em;
content: "\2193";
content: "";
}
table.dataTable > thead .sorting_asc:before,
table.dataTable > thead .sorting_desc:after {
@ -165,9 +166,9 @@ div.dataTables_scrollFoot > .dataTables_scrollFootInner > table {
@media screen and (max-width: 767px) {
div.dataTables_wrapper div.dataTables_length,
div.dataTables_wrapper div.dataTables_filter,
div.dataTables_wrapper div.dataTables_info,
div.dataTables_wrapper div.dataTables_paginate {
div.dataTables_wrapper div.dataTables_filter,
div.dataTables_wrapper div.dataTables_info,
div.dataTables_wrapper div.dataTables_paginate {
text-align: center;
}
div.dataTables_wrapper div.dataTables_paginate ul.pagination {
@ -213,10 +214,10 @@ div.dataTables_scrollHead table.table-bordered {
div.table-responsive > div.dataTables_wrapper > div.row {
margin: 0;
}
div.table-responsive > div.dataTables_wrapper > div.row > div[class^="col-"]:first-child {
div.table-responsive > div.dataTables_wrapper > div.row > div[class^=col-]:first-child {
padding-left: 0;
}
div.table-responsive > div.dataTables_wrapper > div.row > div[class^="col-"]:last-child {
div.table-responsive > div.dataTables_wrapper > div.row > div[class^=col-]:last-child {
padding-right: 0;
}

View file

@ -4,20 +4,20 @@
*
* To rebuild or modify this file with the latest versions of the included
* software please visit:
* https://datatables.net/download/#bs4/dt-1.10.22
* https://datatables.net/download/#bs4/dt-1.10.23
*
* Included libraries:
* DataTables 1.10.22
* DataTables 1.10.23
*/
/*! DataTables 1.10.22
/*! DataTables 1.10.23
* ©2008-2020 SpryMedia Ltd - datatables.net/license
*/
/**
* @summary DataTables
* @description Paginate, search and order HTML tables
* @version 1.10.22
* @version 1.10.23
* @file jquery.dataTables.js
* @author SpryMedia Ltd
* @contact www.datatables.net
@ -2775,7 +2775,7 @@
for ( var i=0, iLen=a.length-1 ; i<iLen ; i++ )
{
// Protect against prototype pollution
if (a[i] === '__proto__') {
if (a[i] === '__proto__' || a[i] === 'constructor') {
throw new Error('Cannot set prototype values');
}
@ -3157,7 +3157,7 @@
cells.push( nTd );
// Need to create the HTML if new, or if a rendering function is defined
if ( create || ((!nTrIn || oCol.mRender || oCol.mData !== i) &&
if ( create || ((oCol.mRender || oCol.mData !== i) &&
(!$.isPlainObject(oCol.mData) || oCol.mData._ !== i+'.display')
)) {
nTd.innerHTML = _fnGetCellData( oSettings, iRow, i, 'display' );
@ -3189,10 +3189,6 @@
_fnCallbackFire( oSettings, 'aoRowCreatedCallback', null, [nTr, rowData, iRow, cells] );
}
// Remove once webkit bug 131819 and Chromium bug 365619 have been resolved
// and deployed
row.nTr.setAttribute( 'role', 'row' );
}
@ -9546,7 +9542,7 @@
* @type string
* @default Version number
*/
DataTable.version = "1.10.22";
DataTable.version = "1.10.23";
/**
* Private data store, containing all of the settings objects that are
@ -13970,7 +13966,7 @@
*
* @type string
*/
build:"bs4/dt-1.10.22",
build:"bs4/dt-1.10.23",
/**

View file

@ -4,6 +4,7 @@
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="robots" content="noindex,nofollow" />
<link rel="icon" type="image/png" href="{{urlpath}}/bwrs_static/shield-white.png">
<title>Bitwarden_rs Admin Panel</title>
<link rel="stylesheet" href="{{urlpath}}/bwrs_static/bootstrap.css" />
<style>
@ -73,7 +74,7 @@
<body class="bg-light">
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4 shadow fixed-top">
<div class="container">
<div class="container-xl">
<a class="navbar-brand" href="{{urlpath}}/admin"><img class="pr-1" src="{{urlpath}}/bwrs_static/shield-white.png">Bitwarden_rs Admin</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse"
aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
@ -96,7 +97,7 @@
</li>
{{/if}}
<li class="nav-item">
<a class="nav-link" href="{{urlpath}}/">Vault</a>
<a class="nav-link" href="{{urlpath}}/" target="_blank" rel="noreferrer">Vault</a>
</li>
</ul>

View file

@ -1,4 +1,4 @@
<main class="container">
<main class="container-xl">
<div id="diagnostics-block" class="my-3 p-3 bg-white rounded shadow">
<h6 class="border-bottom pb-2 mb-2">Diagnostics</h6>
@ -15,7 +15,7 @@
<span id="server-installed">{{version}}</span>
</dd>
<dt class="col-sm-5">Server Latest
<span class="badge badge-danger d-none" id="server-failed" title="Unable to determine latest version.">Unknown</span>
<span class="badge badge-secondary d-none" id="server-failed" title="Unable to determine latest version.">Unknown</span>
</dt>
<dd class="col-sm-7">
<span id="server-latest">{{diagnostics.latest_release}}<span id="server-latest-commit" class="d-none">-{{diagnostics.latest_commit}}</span></span>
@ -28,7 +28,7 @@
<span id="web-installed">{{diagnostics.web_vault_version}}</span>
</dd>
<dt class="col-sm-5">Web Latest
<span class="badge badge-danger d-none" id="web-failed" title="Unable to determine latest version.">Unknown</span>
<span class="badge badge-secondary d-none" id="web-failed" title="Unable to determine latest version.">Unknown</span>
</dt>
<dd class="col-sm-7">
<span id="web-latest">{{diagnostics.latest_web_build}}</span>
@ -41,6 +41,40 @@
<div class="row">
<div class="col-md">
<dl class="row">
<dt class="col-sm-5">Running within Docker</dt>
<dd class="col-sm-7">
{{#if diagnostics.running_within_docker}}
<span id="running-docker" class="d-block"><b>Yes</b></span>
{{/if}}
{{#unless diagnostics.running_within_docker}}
<span id="running-docker" class="d-block"><b>No</b></span>
{{/unless}}
</dd>
<dt class="col-sm-5">Uses a proxy</dt>
<dd class="col-sm-7">
{{#if diagnostics.uses_proxy}}
<span id="running-docker" class="d-block"><b>Yes</b></span>
{{/if}}
{{#unless diagnostics.uses_proxy}}
<span id="running-docker" class="d-block"><b>No</b></span>
{{/unless}}
</dd>
<dt class="col-sm-5">Internet access
{{#if diagnostics.has_http_access}}
<span class="badge badge-success" id="internet-success" title="We have internet access!">Ok</span>
{{/if}}
{{#unless diagnostics.has_http_access}}
<span class="badge badge-danger" id="internet-warning" title="There seems to be no internet access. Please fix.">Error</span>
{{/unless}}
</dt>
<dd class="col-sm-7">
{{#if diagnostics.has_http_access}}
<span id="running-docker" class="d-block"><b>Yes</b></span>
{{/if}}
{{#unless diagnostics.has_http_access}}
<span id="running-docker" class="d-block"><b>No</b></span>
{{/unless}}
</dd>
<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>
@ -57,6 +91,44 @@
<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>
<dt class="col-sm-5">Domain configuration
<span class="badge badge-success d-none" id="domain-success" title="Domain variable seems to be correct.">Ok</span>
<span class="badge badge-danger d-none" id="domain-warning" title="Domain variable is not configured correctly.&#013;&#010;Some features may not work as expected!">Error</span>
</dt>
<dd class="col-sm-7">
<span id="domain-server" class="d-block"><b>Server:</b> <span id="domain-server-string">{{diagnostics.admin_url}}</span></span>
<span id="domain-browser" class="d-block"><b>Browser:</b> <span id="domain-browser-string"></span></span>
</dd>
</dl>
</div>
</div>
<h3>Support</h3>
<div class="row">
<div class="col-md">
<dl class="row">
<dd class="col-sm-12">
If you need support please check the following links first before you create a new issue:
<a href="https://bitwardenrs.discourse.group/" target="_blank" rel="noreferrer">Bitwarden_RS Forum</a>
| <a href="https://github.com/dani-garcia/bitwarden_rs/discussions" target="_blank" rel="noreferrer">Github Discussions</a>
</dd>
</dl>
<dl class="row">
<dd class="col-sm-12">
You can use the button below to pre-generate a string which you can copy/paste on either the Forum or when Creating a new issue at Github.<br>
We try to hide the most sensitive values from the generated support string by default, but please verify if there is nothing in there which you want to hide!<br>
</dd>
</dl>
<dl class="row">
<dt class="col-sm-3">
<button type="button" id="gen-support" class="btn btn-primary" onclick="generateSupportString(); return false;">Generate Support String</button>
<br><br>
<button type="button" id="copy-support" class="btn btn-info d-none" onclick="copyToClipboard(); return false;">Copy To Clipboard</button>
</dt>
<dd class="col-sm-9">
<pre id="support-string" class="pre-scrollable d-none" style="width: 100%; height: 16em; size: 0.6em; border: 1px solid; padding: 4px;"></pre>
</dd>
</dl>
</div>
</div>
@ -64,7 +136,12 @@
</main>
<script>
dnsCheck = false;
timeCheck = false;
domainCheck = false;
(() => {
// ================================
// Date & Time Check
const d = new Date();
const year = d.getUTCFullYear();
const month = String(d.getUTCMonth()+1).padStart(2, '0');
@ -81,16 +158,21 @@
document.getElementById('time-warning').classList.remove('d-none');
} else {
document.getElementById('time-success').classList.remove('d-none');
timeCheck = true;
}
// ================================
// 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');
dnsCheck = true;
} else {
document.getElementById('dns-warning').classList.remove('d-none');
}
// ================================
// Version check for both bitwarden_rs and web-vault
let serverInstalled = document.getElementById('server-installed').innerText;
let serverLatest = document.getElementById('server-latest').innerText;
let serverLatestCommit = document.getElementById('server-latest-commit').innerText.replace('-', '');
@ -146,5 +228,56 @@
}
}
}
// ================================
// Check valid DOMAIN configuration
document.getElementById('domain-browser-string').innerText = location.href.toLowerCase();
if (document.getElementById('domain-server-string').innerText.toLowerCase() == location.href.toLowerCase()) {
document.getElementById('domain-success').classList.remove('d-none');
domainCheck = true;
} else {
document.getElementById('domain-warning').classList.remove('d-none');
}
})();
// ================================
// Generate support string to be pasted on github or the forum
async function generateSupportString() {
supportString = "### Your environment (Generated via diagnostics page)\n";
supportString += "* Bitwarden_rs version: v{{ version }}\n";
supportString += "* Web-vault version: v{{ diagnostics.web_vault_version }}\n";
supportString += "* Running within Docker: {{ diagnostics.running_within_docker }}\n";
supportString += "* Internet access: {{ diagnostics.has_http_access }}\n";
supportString += "* Uses a proxy: {{ diagnostics.uses_proxy }}\n";
supportString += "* DNS Check: " + dnsCheck + "\n";
supportString += "* Time Check: " + timeCheck + "\n";
supportString += "* Domain Configuration Check: " + domainCheck + "\n";
supportString += "* Database type: {{ diagnostics.db_type }}\n";
{{#case diagnostics.db_type "MySQL" "PostgreSQL"}}
supportString += "* Database version: [PLEASE PROVIDE DATABASE VERSION]\n";
{{/case}}
jsonResponse = await fetch('{{urlpath}}/admin/diagnostics/config');
configJson = await jsonResponse.json();
supportString += "\n### Config (Generated via diagnostics page)\n```json\n" + JSON.stringify(configJson, undefined, 2) + "\n```\n";
document.getElementById('support-string').innerText = supportString;
document.getElementById('support-string').classList.remove('d-none');
document.getElementById('copy-support').classList.remove('d-none');
}
function copyToClipboard() {
const str = document.getElementById('support-string').innerText;
const el = document.createElement('textarea');
el.value = str;
el.setAttribute('readonly', '');
el.style.position = 'absolute';
el.style.left = '-9999px';
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
}
</script>

View file

@ -1,4 +1,4 @@
<main class="container">
<main class="container-xl">
{{#if error}}
<div class="align-items-center p-3 mb-3 text-white-50 bg-warning rounded shadow">
<div>

View file

@ -1,4 +1,4 @@
<main class="container">
<main class="container-xl">
<div id="organizations-block" class="my-3 p-3 bg-white rounded shadow">
<h6 class="border-bottom pb-2 mb-3">Organizations</h6>

View file

@ -1,4 +1,4 @@
<main class="container">
<main class="container-xl">
<div id="config-block" class="align-items-center p-3 mb-3 bg-secondary rounded shadow">
<div>
<h6 class="text-white mb-3">Configuration</h6>

View file

@ -1,4 +1,4 @@
<main class="container">
<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>
@ -7,10 +7,12 @@
<thead>
<tr>
<th>User</th>
<th style="width:60px; min-width: 60px;">Items</th>
<th style="width:65px; min-width: 65px;">Created at</th>
<th style="width:70px; min-width: 65px;">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: 140px; min-width: 140px;">Actions</th>
<th style="width: 120px; min-width: 120px;">Actions</th>
</tr>
</thead>
<tbody>
@ -21,8 +23,6 @@
<div class="float-left">
<strong>{{Name}}</strong>
<span class="d-block">{{Email}}</span>
<span class="d-block">Created at: {{created_at}}</span>
<span class="d-block">Last active: {{last_active}}</span>
<span class="d-block">
{{#unless user_enabled}}
<span class="badge badge-danger mr-2" title="User is disabled">Disabled</span>
@ -39,6 +39,12 @@
</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>
@ -49,9 +55,11 @@
{{/if}}
</td>
<td>
<div class="overflow-auto" style="max-height: 120px;">
{{#each Organizations}}
<span class="badge badge-primary" data-orgtype="{{Type}}">{{Name}}</span>
{{/each}}
</div>
</td>
<td style="font-size: 90%; text-align: right; padding-right: 15px">
{{#if TwoFactorEnabled}}
@ -173,18 +181,43 @@
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(event) {
$('#users-table').DataTable({
"responsive": true,
"lengthMenu": [ [-1, 5, 10, 25, 50], ["All", 5, 10, 25, 50] ],
"pageLength": -1, // Default show all
"columns": [
null, // Userdata
null, // Items
null, // Attachments
null, // Organizations
{ "searchable": false, "orderable": false }, // Actions
],
"columnDefs": [
{ "targets": [1,2], "type": "date-iso" },
{ "targets": 6, "searchable": false, "orderable": false }
]
});
});
</script>