mirror of
https://github.com/Luzifer/vault-otp-ui.git
synced 2024-11-09 08:40:05 +00:00
Add error handling to frontend
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
6d730a849c
commit
72dcb026e9
4 changed files with 91 additions and 10 deletions
|
@ -1,12 +1,14 @@
|
||||||
currentTimeout = 0
|
currentTimeout = 0
|
||||||
clipboard = undefined
|
clipboard = undefined
|
||||||
|
|
||||||
|
# document-ready function to start Javascript processing
|
||||||
$ ->
|
$ ->
|
||||||
if $('body').hasClass 'state-signedin'
|
if $('body').hasClass 'state-signedin'
|
||||||
initializeApplication()
|
initializeApplication()
|
||||||
|
|
||||||
|
# createOTPItem generates code entries from the JSON objects passed from the backend
|
||||||
createOTPItem = (item) ->
|
createOTPItem = (item) ->
|
||||||
tpl = $('#otp-item').html()
|
tpl = $('#tpl-otp-item').html()
|
||||||
|
|
||||||
otpItem = $(tpl)
|
otpItem = $(tpl)
|
||||||
otpItem.find('.badge').text item.code.replace(/^(.{3})(.{3})$/, '$1 $2')
|
otpItem.find('.badge').text item.code.replace(/^(.{3})(.{3})$/, '$1 $2')
|
||||||
|
@ -15,15 +17,46 @@ createOTPItem = (item) ->
|
||||||
|
|
||||||
otpItem.appendTo $('#keylist')
|
otpItem.appendTo $('#keylist')
|
||||||
|
|
||||||
|
# createAlert adds a colored message at the top of the list
|
||||||
|
# type = success / info / warning / danger
|
||||||
|
createAlert = (type, keyword, message, timeout) ->
|
||||||
|
tpl = $('#tpl-message').html()
|
||||||
|
|
||||||
|
alrt = $(tpl)
|
||||||
|
alrt.addClass "alert-#{type}"
|
||||||
|
alrt.find('.keyword').text keyword
|
||||||
|
alrt.find('.message').text message
|
||||||
|
|
||||||
|
alrt.appendTo $('#messagecontainer')
|
||||||
|
|
||||||
|
if timeout > 0
|
||||||
|
delay timeout, () ->
|
||||||
|
alrt.remove()
|
||||||
|
|
||||||
|
# delay is a convenience wrapper to swap parameters of setTimeout
|
||||||
delay = (delayMSecs, fkt) ->
|
delay = (delayMSecs, fkt) ->
|
||||||
window.setTimeout fkt, delayMSecs
|
window.setTimeout fkt, delayMSecs
|
||||||
|
|
||||||
|
# fetchCodes contacts the backend to receive JSON containing current codes
|
||||||
fetchCodes = () ->
|
fetchCodes = () ->
|
||||||
$.ajax
|
$.ajax
|
||||||
url: 'codes.json',
|
url: 'codes.json',
|
||||||
success: updateCodes,
|
success: updateCodes,
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
|
error: () ->
|
||||||
|
createAlert 'danger', 'Oops.', 'Server could not be contacted. Maybe you (or the server) are offline? I will retry in a few seconds.', 5000
|
||||||
|
delay 5000, fetchCodes
|
||||||
|
statusCode:
|
||||||
|
401: () ->
|
||||||
|
window.location.reload()
|
||||||
|
500: () ->
|
||||||
|
createAlert 'danger', 'Oops.', 'The server responded with an internal error. I will retry in a few seconds.', 2000
|
||||||
|
delay 2000, fetchCodes
|
||||||
|
|
||||||
|
# filterChange is called when changing the filter field and matches the
|
||||||
|
# titles of all shown entries. Those not matching the given regular expression
|
||||||
|
# will be hidden. The filterChange function is also called after a successful
|
||||||
|
# refresh of the shown codes to re-apply
|
||||||
filterChange = () ->
|
filterChange = () ->
|
||||||
filter = $('#filter').val().toLowerCase()
|
filter = $('#filter').val().toLowerCase()
|
||||||
$('.otp-item').each (idx, el) ->
|
$('.otp-item').each (idx, el) ->
|
||||||
|
@ -32,25 +65,33 @@ filterChange = () ->
|
||||||
else
|
else
|
||||||
$(el).show()
|
$(el).show()
|
||||||
|
|
||||||
|
# initializeApplication initializes some basic events and starts the first
|
||||||
|
# polling for codes
|
||||||
initializeApplication = () ->
|
initializeApplication = () ->
|
||||||
$('#keylist').empty()
|
$('#keylist').empty()
|
||||||
$('#filter').bind 'keyup', filterChange
|
$('#filter').bind 'keyup', filterChange
|
||||||
tick 500, refreshTimerProgress
|
tick 500, refreshTimerProgress
|
||||||
fetchCodes()
|
fetchCodes()
|
||||||
|
|
||||||
# refreshTimerProgress updates the top progressbar to display the
|
# refreshTimerProgress updates the top progressbar to display the
|
||||||
# remaining time until the one-time-passwords changes
|
# remaining time until the one-time-passwords changes
|
||||||
refreshTimerProgress = () ->
|
refreshTimerProgress = () ->
|
||||||
secondsLeft = timeLeft()
|
secondsLeft = timeLeft()
|
||||||
$('#timer').css 'width', "#{secondsLeft / 30 * 100}%"
|
$('#timer').css 'width', "#{secondsLeft / 30 * 100}%"
|
||||||
|
|
||||||
|
# tick is a convenience wrapper to swap parameters of setInterval
|
||||||
tick = (delay, fkt) ->
|
tick = (delay, fkt) ->
|
||||||
window.setInterval fkt, delay
|
window.setInterval fkt, delay
|
||||||
|
|
||||||
|
# timeLeft calculates the remaining time until codes get invalid
|
||||||
timeLeft = () ->
|
timeLeft = () ->
|
||||||
now = new Date().getTime()
|
now = new Date().getTime()
|
||||||
(currentTimeout - now) / 1000
|
(currentTimeout - now) / 1000
|
||||||
|
|
||||||
|
# updateCodes is being called when the backend delivered codes. The codes
|
||||||
|
# are then rendered and the clipboard methods are re-bound. Afterwards the
|
||||||
|
# next fetchCodes call is timed to that moment when the codes are getting
|
||||||
|
# invalid
|
||||||
updateCodes = (data) ->
|
updateCodes = (data) ->
|
||||||
currentTimeout = new Date(data.next_wrap).getTime()
|
currentTimeout = new Date(data.next_wrap).getTime()
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Generated by CoffeeScript 1.12.4
|
// Generated by CoffeeScript 1.12.4
|
||||||
(function() {
|
(function() {
|
||||||
var clipboard, createOTPItem, currentTimeout, delay, fetchCodes, filterChange, initializeApplication, refreshTimerProgress, tick, timeLeft, updateCodes;
|
var clipboard, createAlert, createOTPItem, currentTimeout, delay, fetchCodes, filterChange, initializeApplication, refreshTimerProgress, tick, timeLeft, updateCodes;
|
||||||
|
|
||||||
currentTimeout = 0;
|
currentTimeout = 0;
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
createOTPItem = function(item) {
|
createOTPItem = function(item) {
|
||||||
var otpItem, tpl;
|
var otpItem, tpl;
|
||||||
tpl = $('#otp-item').html();
|
tpl = $('#tpl-otp-item').html();
|
||||||
otpItem = $(tpl);
|
otpItem = $(tpl);
|
||||||
otpItem.find('.badge').text(item.code.replace(/^(.{3})(.{3})$/, '$1 $2'));
|
otpItem.find('.badge').text(item.code.replace(/^(.{3})(.{3})$/, '$1 $2'));
|
||||||
otpItem.find('.title').text(item.name);
|
otpItem.find('.title').text(item.name);
|
||||||
|
@ -22,6 +22,21 @@
|
||||||
return otpItem.appendTo($('#keylist'));
|
return otpItem.appendTo($('#keylist'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
createAlert = function(type, keyword, message, timeout) {
|
||||||
|
var alrt, tpl;
|
||||||
|
tpl = $('#tpl-message').html();
|
||||||
|
alrt = $(tpl);
|
||||||
|
alrt.addClass("alert-" + type);
|
||||||
|
alrt.find('.keyword').text(keyword);
|
||||||
|
alrt.find('.message').text(message);
|
||||||
|
alrt.appendTo($('#messagecontainer'));
|
||||||
|
if (timeout > 0) {
|
||||||
|
return delay(timeout, function() {
|
||||||
|
return alrt.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
delay = function(delayMSecs, fkt) {
|
delay = function(delayMSecs, fkt) {
|
||||||
return window.setTimeout(fkt, delayMSecs);
|
return window.setTimeout(fkt, delayMSecs);
|
||||||
};
|
};
|
||||||
|
@ -30,7 +45,20 @@
|
||||||
return $.ajax({
|
return $.ajax({
|
||||||
url: 'codes.json',
|
url: 'codes.json',
|
||||||
success: updateCodes,
|
success: updateCodes,
|
||||||
dataType: 'json'
|
dataType: 'json',
|
||||||
|
error: function() {
|
||||||
|
createAlert('danger', 'Oops.', 'Server could not be contacted. Maybe you (or the server) are offline? I will retry in a few seconds.', 5000);
|
||||||
|
return delay(5000, fetchCodes);
|
||||||
|
},
|
||||||
|
statusCode: {
|
||||||
|
401: function() {
|
||||||
|
return window.location.reload();
|
||||||
|
},
|
||||||
|
500: function() {
|
||||||
|
createAlert('danger', 'Oops.', 'The server responded with an internal error. I will retry in a few seconds.', 2000);
|
||||||
|
return delay(2000, fetchCodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
14
index.html
14
index.html
|
@ -19,6 +19,7 @@
|
||||||
body { font-size: 16px; padding-top: 90px; }
|
body { font-size: 16px; padding-top: 90px; }
|
||||||
i { margin-right: 0.4em; }
|
i { margin-right: 0.4em; }
|
||||||
#templates { display: none; }
|
#templates { display: none; }
|
||||||
|
.alert { background-image: none; }
|
||||||
.badge { background-color: #e2e2e2; color: #555; font-size: 15px; font-weight: bold; margin-top: 3px; }
|
.badge { background-color: #e2e2e2; color: #555; font-size: 15px; font-weight: bold; margin-top: 3px; }
|
||||||
.center { text-align: center; }
|
.center { text-align: center; }
|
||||||
.jumbotron h2 { text-align: center; }
|
.jumbotron h2 { text-align: center; }
|
||||||
|
@ -71,6 +72,11 @@
|
||||||
<div id="application">
|
<div id="application">
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2 col-lg-8 col-lg-offset-2" id="messagecontainer">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3">
|
<div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3">
|
||||||
<div class="list-group" id="keylist">
|
<div class="list-group" id="keylist">
|
||||||
|
@ -107,13 +113,19 @@
|
||||||
</div> <!-- /#login -->
|
</div> <!-- /#login -->
|
||||||
|
|
||||||
<div id="templates">
|
<div id="templates">
|
||||||
<div id="otp-item">
|
<div id="tpl-otp-item">
|
||||||
<a href="#" class="list-group-item otp-item">
|
<a href="#" class="list-group-item otp-item">
|
||||||
<span class="badge">145 369</span>
|
<span class="badge">145 369</span>
|
||||||
<i class="fa"></i>
|
<i class="fa"></i>
|
||||||
<span class="title">Some Site</span>
|
<span class="title">Some Site</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="tpl-message">
|
||||||
|
<div class="alert alert-dismissible" role="alert">
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<strong class="keyword">Warning!</strong> <span class="message"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
|
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
|
||||||
|
|
Loading…
Reference in a new issue