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