diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js index 350aa3840..2ce19d486 100644 --- a/guacamole/src/main/webapp/app/client/controllers/clientController.js +++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js @@ -51,6 +51,18 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', 0x031D: true }; + /** + * All error codes for which automatic reconnection is appropriate when a + * client error occurs. + */ + var CLIENT_AUTO_RECONNECT = { + 0x0200: true, + 0x0202: true, + 0x0203: true, + 0x0301: true, + 0x0308: true + }; + /** * All tunnel error codes handled and passed off for translation. Any error * code not present in this list will be represented by the "DEFAULT" @@ -67,7 +79,18 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', 0x0308: true, 0x031D: true }; - + + /** + * All error codes for which automatic reconnection is appropriate when a + * tunnel error occurs. + */ + var TUNNEL_AUTO_RECONNECT = { + 0x0200: true, + 0x0202: true, + 0x0203: true, + 0x0308: true + }; + /** * The reconnect action to be provided along with the object sent to * showStatus. @@ -81,6 +104,16 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', } }; + /** + * The reconnect countdown to display if an error or status warrants an + * automatic, timed reconnect. + */ + var RECONNECT_COUNTDOWN = { + text: "client.action.reconnectCountdown", + callback: RECONNECT_ACTION.callback, + remaining: 15 + }; + // Get DAO for reading connections and groups var connectionGroupDAO = $injector.get('connectionGroupDAO'); var connectionDAO = $injector.get('connectionDAO'); @@ -146,7 +179,7 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', return true; } - + $scope.$watch('menuShown', function setKeyboardEnabled(menuShown, menuShownPreviousState) { // Send clipboard data if menu is hidden @@ -218,7 +251,10 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', $scope.id = null; // Determine translation name of error - var errorName = (status in CLIENT_ERRORS) ? status.toString(16) : "DEFAULT"; + var errorName = (status in CLIENT_ERRORS) ? status.toString(16).toUpperCase() : "DEFAULT"; + + // Determine whether the reconnect countdown applies + var countdown = (status in CLIENT_AUTO_RECONNECT) ? RECONNECT_COUNTDOWN : null; // Override any existing status $scope.showStatus(false); @@ -228,6 +264,7 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', className: "error", title: "client.error.connectionErrorTitle", text: "client.error.clientErrors." + errorName, + countdown: countdown, actions: [ RECONNECT_ACTION ] }); @@ -253,7 +290,10 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', $scope.id = null; // Determine translation name of error - var errorName = (status in TUNNEL_ERRORS) ? status.toString(16) : "DEFAULT"; + var errorName = (status in TUNNEL_ERRORS) ? status.toString(16).toUpperCase() : "DEFAULT"; + + // Determine whether the reconnect countdown applies + var countdown = (status in TUNNEL_AUTO_RECONNECT) ? RECONNECT_COUNTDOWN : null; // Override any existing status $scope.showStatus(false); @@ -263,6 +303,7 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', className: "error", title: "client.error.connectionErrorTitle", text: "client.error.tunnelErrors." + errorName, + countdown: countdown, actions: [ RECONNECT_ACTION ] }); diff --git a/guacamole/src/main/webapp/app/client/styles/notification-area.css b/guacamole/src/main/webapp/app/client/styles/notification-area.css new file mode 100644 index 000000000..f3b9d7fc4 --- /dev/null +++ b/guacamole/src/main/webapp/app/client/styles/notification-area.css @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2013 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#notificationArea { + position: fixed; + right: 0.5em; + bottom: 0.5em; + max-width: 25%; + width: 2in; +} + +#notificationArea .notification { + font-size: 0.7em; + text-align: center; + width: 100%; + overflow: hidden; +} + +#notificationArea .notification .text { + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/guacamole/src/main/webapp/app/client/styles/notification.css b/guacamole/src/main/webapp/app/client/styles/notification.css deleted file mode 100644 index 9b559efd5..000000000 --- a/guacamole/src/main/webapp/app/client/styles/notification.css +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#notificationArea { - position: fixed; - right: 0.5em; - bottom: 0.5em; - max-width: 25%; - min-width: 10em; -} - -.notification { - - font-size: 0.7em; - text-align: center; - - border: 1px solid rgba(0, 0, 0, 0.125); - box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.125); - background: white; - - color: black; - - padding: 0.5em; - margin: 1em; - width: 2in; - max-width: 75%; - overflow: hidden; -} - -.notification .buttons { - margin: 0; -} - -@keyframes notification-progress { - from {background-position: 0px 0px;} - to {background-position: 64px 0px;} -} - -@-webkit-keyframes notification-progress { - from {background-position: 0px 0px;} - to {background-position: 64px 0px;} -} - -.notification .title-bar { - font-size: 1.25em; - font-weight: bold; - - text-transform: uppercase; - border-bottom: 1px solid rgba(0, 0, 0, 0.125); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.125); - background: rgba(0, 0, 0, 0.04); - - margin-bottom: 1em; -} - -.notification .text { - width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.notification.upload .progress { - float: none; - width: 80%; - margin-left: auto; - margin-right: auto; -} - -.notification.download .progress div, -.notification.upload .progress div { - position: relative; -} - -.notification.download .progress .bar, -.notification.upload .progress .bar { - background: #A3D655; - position: absolute; - top: 0; - left: 0; - height: 100%; - width: 0; - box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.5), - inset -1px -1px 0 rgba( 0, 0, 0, 0.1), - 1px 1px 0 gray; -} - -.notification.upload .progress, -.notification.download .progress { - - width: 100%; - background: #C2C2C2 url('images/progress.png'); - background-size: 16px 16px; - -moz-background-size: 16px 16px; - -webkit-background-size: 16px 16px; - -khtml-background-size: 16px 16px; - - animation-name: notification-progress; - animation-duration: 2s; - animation-timing-function: linear; - animation-iteration-count: infinite; - - -webkit-animation-name: notification-progress; - -webkit-animation-duration: 2s; - -webkit-animation-timing-function: linear; - -webkit-animation-iteration-count: infinite; - - padding: 0.25em; - min-width: 5em; - - border: 1px solid gray; - -moz-border-radius: 0.2em; - -webkit-border-radius: 0.2em; - -khtml-border-radius: 0.2em; - border-radius: 0.2em; - - text-align: center; - float: right; - - position: relative; - -} - -.notification.download .download { - background: rgb(16, 87, 153); - cursor: pointer; -} - -.notification.message { - background: #DFD; - animation: fadein 0.125s linear, fadeout 2s 3s linear; - -webkit-animation: fadein 0.125s linear, fadeout 2s 3s linear; -} - -.notification.message .caption { - vertical-align: middle; - white-space: normal; - overflow: hidden; - text-overflow: ellipsis; -} diff --git a/guacamole/src/main/webapp/app/index/controllers/indexController.js b/guacamole/src/main/webapp/app/index/controllers/indexController.js index 5dc066c19..4f7f54776 100644 --- a/guacamole/src/main/webapp/app/index/controllers/indexController.js +++ b/guacamole/src/main/webapp/app/index/controllers/indexController.js @@ -73,20 +73,30 @@ angular.module('index').controller('indexController', ['$scope', '$injector', $location.path('/login'); /** - * Shows or hides the status. If a status is currently shown, - * no further statuses will be shown until the current status is - * hidden. + * Shows or hides the given notification as a modal status. If a status + * notification is currently shown, no further statuses will be shown + * until the current status is hidden. * - * @param {Boolean|Object} status The status to show, or false to hide the - * current status. - * @param {String} [status.title] The title of the status. - * @param {String} [status.text] The body text of the status. + * @param {Object} status The status notification to show. + * @param {String} [status.title] The title of the notification. + * @param {String} [status.text] The body text of the notification. * @param {String} [status.className] The CSS class name to apply. - * @param {Object[]} [status.actions] Array of action objects which - * contain an action name and callback to - * be executed when that action is - * invoked. - * + * + * @param {String} [status.countdown.text] + * In the case that a countdown applies to the notification, the text to + * display while the countdown is active. + * + * @param {Function} [status.countdown.callback] + * The callback to call when the countdown expires. + * + * @param {String} [status.countdown.remaining] + * The number of seconds remaining before the countdown callback is + * called. + * + * @param {Object[]} [status.actions] + * Array of action objects which contain an action name and callback to + * be executed when that action is invoked. + * * @example * * // To show a status message with actions @@ -114,15 +124,26 @@ angular.module('index').controller('indexController', ['$scope', '$injector', * * @param {Object} notification The notification to add. * @param {String} [notification.title] The title of the notification. - * @param {String} [notification.text] The body text of the status modal. + * @param {String} [notification.text] The body text of the notification. * @param {String} [notification.className] The CSS class name to apply. - * @param {Object[]} [notification.actions] Array of action objects which - * contain an action name and callback to - * be executed when that action is - * invoked. + * + * @param {String} [notification.countdown.text] + * In the case that a countdown applies to the notification, the text to + * display while the countdown is active. + * + * @param {Function} [notification.countdown.callback] + * The callback to call when the countdown expires. + * + * @param {String} [notification.countdown.remaining] + * The number of seconds remaining before the countdown callback is + * called. + * + * @param {Object[]} [notification.actions] + * Array of action objects which contain an action name and callback to + * be executed when that action is invoked. + * * @returns {Number} A unique ID for the notification that's just been added. * - * * @example * * var id = $scope.addNotification({ @@ -138,7 +159,7 @@ angular.module('index').controller('indexController', ['$scope', '$injector', */ $scope.addNotification = function addNotification(notification) { var id = ++notificationUniqueID; - + $scope.notifications.push({ notification : notification, id : id diff --git a/guacamole/src/main/webapp/app/index/styles/status.css b/guacamole/src/main/webapp/app/index/styles/status.css index f15be62d5..abb077e21 100644 --- a/guacamole/src/main/webapp/app/index/styles/status.css +++ b/guacamole/src/main/webapp/app/index/styles/status.css @@ -20,16 +20,6 @@ * THE SOFTWARE. */ -.status-container { - position: fixed; - top: 0; - left: 0; - bottom: 0; - right: 0; - background: rgba(0, 0, 0, 0.5); - padding: 1em; -} - .status-outer { display: table; height: 100%; @@ -48,7 +38,7 @@ vertical-align: middle; } -.status { +.status-middle .notification { width: 75%; max-width: 5in; @@ -56,33 +46,22 @@ margin-right: auto; overflow: auto; - background: white; - border: 1px solid rgba(0, 0, 0, 0.25); - box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.25); - text-align: left; } -.status .title { - font-size: 1.25em; - font-weight: bold; - background: rgba(0, 0, 0, 0.04); - border-bottom: 1px solid rgba(0, 0, 0, 0.125); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.125); - margin: 0; - padding: 0.5em 1em; - text-transform: uppercase; -} - -.status.error { - background: #FDD; -} - -.status > * { +.status-middle .notification .body { margin: 1.25em; } +.status-middle .notification .buttons { + margin: 1em; +} + +.status-middle .notification.error { + background: #FDD; +} + /* Fade entire status area in/out based on shown status */ .status-outer { @@ -99,10 +78,10 @@ /* Hide dialog immediately based on status */ -.status { +.status-middle .notification { visibility: hidden; } -.shown .status { +.shown .status-middle .notification { visibility: visible; } diff --git a/guacamole/src/main/webapp/index.html b/guacamole/src/main/webapp/index.html index f4e61cc28..5f3ac9404 100644 --- a/guacamole/src/main/webapp/index.html +++ b/guacamole/src/main/webapp/index.html @@ -37,20 +37,17 @@ THE SOFTWARE.
-
- - -

{{status.title | translate}}

- - -

{{status.text | translate}}

- - -
- -
- -
+ + +
@@ -59,24 +56,19 @@ THE SOFTWARE.
-
- - -
-
{{wrapper.notification.title | translate}}
-
- - -

{{wrapper.notification.text}}

- -
- {{wrapper.notification.progress}} -
- -
- -
-
+
+ + + +
diff --git a/guacamole/src/main/webapp/translations/en_US.json b/guacamole/src/main/webapp/translations/en_US.json index 53635a75f..679d7c5c0 100644 --- a/guacamole/src/main/webapp/translations/en_US.json +++ b/guacamole/src/main/webapp/translations/en_US.json @@ -267,7 +267,8 @@ } }, "action" : { - "reconnect" : "Reconnect" + "reconnect" : "Reconnect", + "reconnectCountdown" : "Reconnecting in {REMAINING} {REMAINING, plural, one{second} other{seconds}}..." }, "fileTransfer" : { "title" : "File Transfer",