From 5bea2ad51b5235d0a1a2b03ebc2c8e0461d08e64 Mon Sep 17 00:00:00 2001 From: "Mark M. Florida" <mflorida@gmail.com> Date: Mon, 23 May 2016 21:18:08 -0500 Subject: [PATCH] Ground-up rewrite of session timeout script; style tweaks to some admin ui elements. --- src/main/webapp/WEB-INF/tags/page/xnat.tag | 36 +- src/main/webapp/page/admin/style.css | 3 + src/main/webapp/scripts/xmodal-v1/xmodal.js | 7 +- src/main/webapp/scripts/xnat/app/timeout.js | 403 ++++++++++++++++++ src/main/webapp/scripts/xnat/ui/panel.js | 4 +- src/main/webapp/scripts/xnat/ui/templates.js | 2 - .../xnat-templates/navigations/DefaultTop.vm | 2 +- 7 files changed, 441 insertions(+), 16 deletions(-) create mode 100644 src/main/webapp/scripts/xnat/app/timeout.js diff --git a/src/main/webapp/WEB-INF/tags/page/xnat.tag b/src/main/webapp/WEB-INF/tags/page/xnat.tag index 58f74b34..683644fb 100644 --- a/src/main/webapp/WEB-INF/tags/page/xnat.tag +++ b/src/main/webapp/WEB-INF/tags/page/xnat.tag @@ -210,8 +210,6 @@ <script src="${SITE_ROOT}/scripts/xnat/spawner.js"></script> - <script src="${SITE_ROOT}/scripts/timeLeft.js"></script> - ${headBottom} </head> @@ -238,10 +236,10 @@ ${bodyTop} <b>|</b> <a id="logout_user" href="${SITE_ROOT}/app/action/LogoutUser">Logout</a> </span> - <%--<script type="text/javascript">--%> - <%--$('#timeLeftRenew').click(XNAT.app.timeout.handleOk);--%> - <%--Cookies.set('guest', 'false', {path: '/'});--%> - <%--</script>--%> + <script type="text/javascript"> + Cookies.set('guest', 'false', {path: '/'}); + </script> + <script src="${SITE_ROOT}/scripts/xnat/app/timeout.js"></script> </c:if> @@ -301,8 +299,8 @@ ${bodyTop} </ul> </li> + <pg:restricted msg="<!-- non-admin -->"> - <c:if test="${isAdmin == true}"> <!-- Sequence: 40 --> <li class="more"><a href="#adminbox">Administer</a> <ul> @@ -317,8 +315,8 @@ ${bodyTop} <li><a href="${SITE_ROOT}/app/template/XDATScreen_admin_options.vm">More...</a></li> </ul> </li> - </c:if> + </pg:restricted> <!-- Title: Tools --> <!-- Sequence: 50 --> @@ -652,13 +650,29 @@ ${bodyTop} <script src="${SITE_ROOT}/scripts/xnat/app/customPage.js"></script> <div id="view-page"> + + + + + <!-- BODY START --> + + + <jsp:doBody/> + + + <!-- BODY END --> + + + + + </div> <!-- end xnat-templates/screens/Page.vm --> @@ -706,9 +720,9 @@ ${bodyTop} </script> <%--<script src="${SITE_ROOT}/scripts/footer.js"></script>--%> -<div id="xmodal-loading" style="position:fixed;left:-9999px;top:-9999px;"> - <img src="${SITE_ROOT}/scripts/xmodal-v1/loading_bar.gif" alt="loading"> -</div> +<%--<div id="xmodal-loading" style="position:fixed;left:-9999px;top:-9999px;">--%> + <%--<img src="${SITE_ROOT}/scripts/xmodal/loading_bar.gif" alt="loading">--%> +<%--</div>--%> ${bodyBottom} diff --git a/src/main/webapp/page/admin/style.css b/src/main/webapp/page/admin/style.css index d42dea40..bf4a8842 100644 --- a/src/main/webapp/page/admin/style.css +++ b/src/main/webapp/page/admin/style.css @@ -98,6 +98,8 @@ body.xnat .panel-default { border: 1px solid #c8c8c8; } border-bottom: 1px solid #c8c8c8; } +.panel .panel-element textarea { width: 80%; font-family: Courier, monospace; font-weight: normal; } + /* ELEMENT GROUP ITEMS */ .panel .panel-element-group .group-item .element-label { width: auto; } @@ -113,6 +115,7 @@ body.xnat .panel-default { border: 1px solid #c8c8c8; } /* FORM CONTROLS */ body.xnat textarea.form-control { font-family: Courier, monospace; font-weight: normal; } + /* ========================================================== UI WIDGETS ========================================================== */ diff --git a/src/main/webapp/scripts/xmodal-v1/xmodal.js b/src/main/webapp/scripts/xmodal-v1/xmodal.js index d81ab12c..b967f12a 100644 --- a/src/main/webapp/scripts/xmodal-v1/xmodal.js +++ b/src/main/webapp/scripts/xmodal-v1/xmodal.js @@ -241,7 +241,12 @@ if (typeof jQuery == 'undefined') { var $top_modal = $(xmodal.dialog.top).last(); if (keyCode === 27) { // key 27 = 'esc' if ($top_modal.hasClass('esc')) { - xmodal.close($top_modal); + if ($top_modal.find('.buttons .cancel').length) { + $top_modal.find('.buttons .cancel').not('.disabled').trigger('click'); + } + else { + xmodal.close($top_modal); + } //$top_modal.find('.title .close').trigger('click'); } } diff --git a/src/main/webapp/scripts/xnat/app/timeout.js b/src/main/webapp/scripts/xnat/app/timeout.js new file mode 100644 index 00000000..eba1f7e3 --- /dev/null +++ b/src/main/webapp/scripts/xnat/app/timeout.js @@ -0,0 +1,403 @@ +/*! + * Session timeout warning dialog + */ + +var XNAT = getObject(XNAT); + +(function(factory){ + if (typeof define === 'function' && define.amd) { + define(factory); + } + else if (typeof exports === 'object') { + module.exports = factory(); + } + else { + return factory(); + } +}(function(){ + + var timeout, undefined; + var $timeLeftDisplay = $('#timeLeft'); + + XNAT.app = getObject(XNAT.app || {}); + + XNAT.app.timeout = timeout = + getObject(XNAT.app.timeout || {}); + + // timeout polling interval + timeout.interval = 1000; + + // when do we show the dialog? + timeout.showTime = 60; + + timeout.cancelled = false; + + function timeoutCookie(name){ + + var undefined; + + function CookieFn(){ + this.cookieExists = false; + this.name = name !== undefined ? name : this.name; + this.value = ''; + this.opts = { path: '/' }; + } + + var fn = CookieFn.prototype; + + // reset values after each call + fn.reset = function(){ + this.cookieExists = false; + this.name = name !== undefined ? name : this.name; + this.value = ''; + this.opts = { path: '/' }; + return this; + }; + + fn.exists = function(name){ + this.name = name; + this.cookieExists = !!Cookies.get(this.name); + return this.cookieExists; + }; + + fn.set = function(name, value){ + if (value === undefined) { + value = name; + name = undefined; + } + this.name = name !== undefined ? name : this.name; + this.value = value !== undefined ? value : this.value; + Cookies.set(this.name, this.value, this.opts); + //this.reset(); // reset to prevent passing of values to chained methods + return this; + }; + + // 'value' is a default value to set if the cookie doesn't exist + fn.get = function(){ + this.value = Cookies.get()[this.name]; + return this; + }; + + fn.is = function(value) { + return this.get().value.toString() === value.toString(); + }; + + fn.cookie = function(name) { + this.name = name; + this.exists(); + return this; + }; + + return new CookieFn(); + + } + + var cookie = {}; + + // store cookie value from server as a js var + cookie.SESSION_EXPIRATION_TIME = timeoutCookie('SESSION_EXPIRATION_TIME').get(); + + // is the dialog displayed? + cookie.SESSION_DIALOG_OPEN = timeoutCookie('SESSION_DIALOG_OPEN').set('false'); + + // has it been cancelled? + cookie.SESSION_DIALOG_CANCELLED = timeoutCookie('SESSION_DIALOG_CANCELLED').set('false'); + + // has the session timed out? + cookie.SESSION_TIMED_OUT = timeoutCookie('SESSION_TIMED_OUT').get(); + + // the time, in ms, that the session will end + cookie.SESSION_TIMEOUT_TIME = timeoutCookie('SESSION_TIMEOUT_TIME'); + + // has the user been redirected after timout? + cookie.SESSION_LOGOUT_REDIRECT = timeoutCookie('SESSION_LOGOUT_REDIRECT').get(); + + // the time, in ms, that the session started + cookie.SESSION_LAST_LOGIN = timeoutCookie('SESSION_LAST_LOGIN'); + + // what was the last page visited? + cookie.SESSION_LAST_PAGE = timeoutCookie('SESSION_LAST_PAGE').set(window.location.href); + + timeout.expTime = ''; + + // parse the timeout values + timeout.getValues = function(){ + var expTime = cookie.SESSION_EXPIRATION_TIME.get().value; + if (timeout.expTime && timeout.expTime === expTime) return; + timeout.expTime = expTime; // save it for next time + expTime = expTime.replace(/"/g, '').split(','); + timeout.startTime = (expTime[0].trim()*1 + 12000); + timeout.duration = expTime[1].trim()*1; + timeout.endTime = timeout.startTime + timeout.duration; + return { + startTime: timeout.startTime, + duration: timeout.duration, + endTime: timeout.endTime + } + }; + + + // set SESSION_TIMEOUT_TIME cookie + cookie.SESSION_TIMEOUT_TIME.set(timeout.endTime); + + + function parseTimestamp(time) { + time = time || timeout.endTime; + var timeLeft = time - Date.now(); + var secondsLeft = Math.floor(timeLeft / 1000); + var minutesLeft = Math.floor(secondsLeft / 60); + var secondsPart = secondsLeft % 60; + var hoursPart = Math.floor(minutesLeft / 60); + var minutesPart = minutesLeft % 60; + return { + time: time, + timeLeft: timeLeft, + secondsLeft: secondsLeft, + seconds: secondsPart, + minutes: minutesPart, + hours: hoursPart + }; + } + + + // these things need to wait for the DOM to load + $(function(){ + + // create the dialog but don't render until DOM load + // and don't show it until needed + function timeoutDialog(){ + + var z = 99999; + + var dialog = xmodal.open({ + id: 'session-timeout-warning', + classes: 'keep static', + width: 300, + height: 200, + title: false, + content: 'Your ' + XNAT.app.siteId + ' session will expire in: <br><br>' + + '<b class="mono timeout-hours"></b> hours ' + + '<b class="mono timeout-minutes"></b> minutes ' + + '<b class="mono timeout-seconds"></b> seconds.' + + '</br></br>Click "Renew" to reset session timer.', + okLabel: 'Renew', + okClose: false, + okAction: function(){ + timeout.handleOk(); + dialog.hide(); + }, + cancelClose: false, // don't destroy the dialog + cancelAction: function(){ + timeout.handleCancel(); + dialog.hide(); + } + }); + + dialog.$mask.hide().css('z-index', z-1); + dialog.$modal.hide().css('z-index', z); + + dialog.hours = dialog.$modal.find('b.timeout-hours'); + dialog.minutes = dialog.$modal.find('b.timeout-minutes'); + dialog.seconds = dialog.$modal.find('b.timeout-seconds'); + + dialog.show = function(){ + // DON'T SHOW IF ALREADY SHOWING + // set this as a js var so it's window-independend + if (timeout.dialogIsOpen) return; + // ONLY SHOW THE DIALOG IF NOT CANCELLED + if (cookie.SESSION_DIALOG_CANCELLED.is('false')) { + dialog.$mask.show(); + dialog.$modal.show(); + cookie.SESSION_DIALOG_OPEN.set('true'); + timeout.dialogIsOpen = true; + $('applet').css('visibility', 'hidden'); + } + }; + + dialog.hide = function(){ + if (timeout.dialogIsOpen) { + dialog.$modal.hide(); + dialog.$mask.hide(); + cookie.SESSION_DIALOG_OPEN.set('false'); + $('applet').css('visibility', 'visible'); + } + timeout.dialogIsOpen = false; + }; + + return dialog; + + } + + + timeout.dialog = timeoutDialog(); + + + function redirectToLogin() { + timeout.redirecting = true; + xmodal.loading.open('#redirecting'); + timeout.dialog.hide(); + cookie.SESSION_TIMEOUT_TIME.set(Date.now()); + cookie.SESSION_DIALOG_OPEN.set('false'); + cookie.SESSION_DIALOG_CANCELLED.set('false'); + cookie.SESSION_TIMED_OUT.set('true'); + cookie.SESSION_LOGOUT_REDIRECT.set('true'); + timeoutCookie('WARNING_BAR').set('OPEN'); + timeoutCookie('guest').set('true'); + // need to wait a little longer before reloading + setTimeout(function(){ + window.location.reload(); + }, 2000); + } + + + function renewSession(){ + timeout.dialog.hide(); + cookie.SESSION_EXPIRATION_TIME.get(); + timeout.getValues(); + cookie.SESSION_DIALOG_OPEN.set('false'); + cookie.SESSION_DIALOG_CANCELLED.set('false'); + cookie.SESSION_TIMED_OUT.set('false'); + cookie.SESSION_TIMEOUT_TIME.set(timeout.endTime); + cookie.SESSION_LOGOUT_REDIRECT.set('false'); + timeout.cancelled = false; + } + + + timeout.touch = function(opts){ + return XNAT.xhr.get(extend(true, { + url: XNAT.url.restUrl('/xapi/siteConfig/buildInfo') + }, opts || {} )); + }; + + + timeout.handleOk = function(){ + + var touch = timeout.touch(); + + touch.done(function(data){ + cookie.SESSION_DIALOG_CANCELLED.set('true'); + // an object is returned if session is still valid + timeout.sessionExpired = !isPlainObject(data); + if (timeout.sessionExpired) { + redirectToLogin(); + } + else { + renewSession(); + } + }); + + }; + + + // fire this once when the script loads + timeout.handleOk(); + + + timeout.handleCancel = function(){ + timeout.dialog.hide(); + timeout.cancelled = true; + cookie.SESSION_DIALOG_CANCELLED.set('true'); + cookie.SESSION_DIALOG_OPEN.set('false'); + }; + + + // check every second to see if our timeout time has been reached + timeout.check = function(){ + + timeout.getValues(); + + // redirect if TIMED_OUT cookie is true + if (!timeout.redirecting && cookie.SESSION_TIMED_OUT.is('true')) { + redirectToLogin(); + return false; + } + + // redirect if time has run out + if (timeout.endTime <= Date.now()) { + cookie.SESSION_TIMED_OUT.set('true'); + return false; + } + + // close dialog if closed from another window + if (cookie.SESSION_DIALOG_OPEN.is('false')) { + timeout.dialog.hide(); + } + + // if endTime minus showTime is less than now + if (timeout.endTime - (timeout.showTime*1000) <= Date.now()) { + //don't do anything if the dialog has already been cancelled + if (cookie.SESSION_DIALOG_CANCELLED.is('true')) { + //timeout.handleCancel(); + return false; + } + cookie.SESSION_DIALOG_CANCELLED.set('false'); + timeout.dialog.show(); + return false; + } + + return true; + + }; + + + timeout.sessionCountdown = function() { + + var timeLeft = parseTimestamp(); + + var hours = timeLeft.hours; + var mins = zeroPad(timeLeft.minutes); + var secs = zeroPad(timeLeft.seconds); + + $timeLeftDisplay.text(hours + ":" + mins + ":" + secs); + + if (cookie.SESSION_TIMED_OUT.is('true')) { + $timeLeftDisplay.text("Session Expired"); + hours = mins = secs = '--'; + } + + // Update the text in the dialog too so it's always in synch + timeout.dialog.hours.text(hours); + timeout.dialog.minutes.text(mins); + timeout.dialog.seconds.text(secs); + + }; + + + timeout.running = false; + + + timeout.init = function(){ + if (!timeout.running) { + timeout.running = true; + setInterval( + function(){ + timeout.check(); + timeout.sessionCountdown(); + }, + timeout.interval + ); + } + }; + + + // only run the timer if *not* a guest user (if an authenticated user) + if ((!!Cookies.get('guest')) && (Cookies.get('guest') === 'false')) { + timeout.init(); + } + + + // attach event handler to elements with 'renew-session' class + $('body').on('click', '#timeLeftRenew, .renew-session', function(){ + timeout.handleOk(); + }); + + + }); + + // this script has loaded + timeout.loaded = true; + + return XNAT.app.timeout = timeout; + + +})); diff --git a/src/main/webapp/scripts/xnat/ui/panel.js b/src/main/webapp/scripts/xnat/ui/panel.js index 1f3c3c43..33ef10c7 100644 --- a/src/main/webapp/scripts/xnat/ui/panel.js +++ b/src/main/webapp/scripts/xnat/ui/panel.js @@ -708,9 +708,11 @@ var XNAT = getObject(XNAT || {}); opts.value || opts.text || opts.html || ''; - + opts.element.html = doLookup(opts.element.html); + opts.element.rows = 6; + var textarea = spawn('textarea', opts.element); return XNAT.ui.template.panelDisplay(opts, textarea).spawned; }; diff --git a/src/main/webapp/scripts/xnat/ui/templates.js b/src/main/webapp/scripts/xnat/ui/templates.js index 22fea194..58609549 100644 --- a/src/main/webapp/scripts/xnat/ui/templates.js +++ b/src/main/webapp/scripts/xnat/ui/templates.js @@ -266,8 +266,6 @@ var XNAT = getObject(XNAT); element.checked = /true|checked/i.test((opts.checked || element.value).toString()); - element.checked = /true|checked/i.test((opts.checked || element.value).toString()); - // add a hidden input to capture the checkbox/radio value hiddenInput = spawn('input', { type: 'hidden', diff --git a/src/main/webapp/xnat-templates/navigations/DefaultTop.vm b/src/main/webapp/xnat-templates/navigations/DefaultTop.vm index 5b4c8152..24944310 100644 --- a/src/main/webapp/xnat-templates/navigations/DefaultTop.vm +++ b/src/main/webapp/xnat-templates/navigations/DefaultTop.vm @@ -47,7 +47,7 @@ </div> </div><!-- /user_bar --> -<script src="$content.getURI("scripts/timeLeft.js")"></script> +<script src="$content.getURI("scripts/xnat/app/timeout.js")"></script> #if ($sessionCount > 1 || $sessionIpCount > 1 ) ##If you want fewer warnings, you can eliminate $sessionCount > 1 so it will not display a warning for multiple sessions on the same IP, or increase it to $sessionCount > X where X is the maximum number of sessions you can have on the same IP before you get a warning. -- GitLab