From e162bef02cc58c13fb0274deda2887d02f86beaf Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 23 Apr 2015 13:33:35 -0700 Subject: [PATCH 1/5] GUAC-1161: Add session-local storage. --- .../storage/services/sessionStorageFactory.js | 123 ++++++++++++++++++ .../main/webapp/app/storage/storageModule.js | 28 ++++ 2 files changed, 151 insertions(+) create mode 100644 guacamole/src/main/webapp/app/storage/services/sessionStorageFactory.js create mode 100644 guacamole/src/main/webapp/app/storage/storageModule.js diff --git a/guacamole/src/main/webapp/app/storage/services/sessionStorageFactory.js b/guacamole/src/main/webapp/app/storage/services/sessionStorageFactory.js new file mode 100644 index 000000000..b0c172d54 --- /dev/null +++ b/guacamole/src/main/webapp/app/storage/services/sessionStorageFactory.js @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * Factory for session-local storage. Creating session-local storage creates a + * getter/setter with semantics tied to the user's session. If a user is logged + * in, the storage is consistent. If the user logs out, the storage will not + * persist new values, and attempts to retrieve the existing value will result + * only in the default value. + */ +angular.module('storage').factory('sessionStorageFactory', ['$injector', function sessionStorageFactory($injector) { + + // Required services + var $rootScope = $injector.get('$rootScope'); + var authenticationService = $injector.get('authenticationService'); + + var service = {}; + + /** + * Creates session-local storage that uses the provided default value or + * getter to obtain new values as necessary. Beware that if the default is + * an object, the resulting getter provide deep copies for new values. + * + * @param {Function|*} [template] + * The default value for new users, or a getter which returns a newly- + * created default value. + * + * @returns {Function} + * A getter/setter which returns or sets the current value of the new + * session-local storage. Newly-set values will only persist of the + * user is actually logged in. + */ + service.create = function create(template) { + + /** + * Whether new values may be stored and retrieved. + * + * @type Boolean + */ + var enabled = !!authenticationService.getCurrentToken(); + + /** + * Getter which returns the default value for this storage. + * + * @type Function + */ + var getter; + + // If getter provided, use that + if (typeof template === 'function') + getter = template; + + // Otherwise, always create a deep copy + else + getter = function getCopy() { + return angular.copy(template); + }; + + /** + * The current value of this storage, or undefined if not yet set. + */ + var value = undefined; + + // Reset value and allow storage when the user is logged in + $rootScope.$on('guacLogin', function userLoggedIn() { + enabled = true; + value = undefined; + }); + + // Reset value and disallow storage when the user is logged out + $rootScope.$on('guacLogout', function userLoggedOut() { + enabled = false; + value = undefined; + }); + + // Return getter/setter for value + return function sessionLocalGetterSetter(newValue) { + + // Only actually store/retrieve values if enabled + if (enabled) { + + // Set value if provided + if (angular.isDefined(newValue)) + value = newValue; + + // Obtain new value if unset + if (!angular.isDefined(value)) + value = getter(); + + // Return current value + return value; + + } + + // Otherwise, just pretend to store/retrieve + return angular.isDefined(newValue) ? newValue : getter(); + + }; + + }; + + return service; + +}]); diff --git a/guacamole/src/main/webapp/app/storage/storageModule.js b/guacamole/src/main/webapp/app/storage/storageModule.js new file mode 100644 index 000000000..f41860efe --- /dev/null +++ b/guacamole/src/main/webapp/app/storage/storageModule.js @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * Module which provides generic storage services. + */ +angular.module('storage', [ + 'auth' +]); From afd9cd8032a85e4d32cf1dfb47230b5b6e832fd8 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 23 Apr 2015 14:28:04 -0700 Subject: [PATCH 2/5] GUAC-1161: Allow custom destructor to be defined for each session-local storage. --- .../app/storage/services/sessionStorageFactory.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/guacamole/src/main/webapp/app/storage/services/sessionStorageFactory.js b/guacamole/src/main/webapp/app/storage/services/sessionStorageFactory.js index b0c172d54..018ff9e57 100644 --- a/guacamole/src/main/webapp/app/storage/services/sessionStorageFactory.js +++ b/guacamole/src/main/webapp/app/storage/services/sessionStorageFactory.js @@ -44,12 +44,16 @@ angular.module('storage').factory('sessionStorageFactory', ['$injector', functio * The default value for new users, or a getter which returns a newly- * created default value. * + * @param {Function} [destructor] + * Function which will be called just before the stored value is + * destroyed on logout, if a value is stored. + * * @returns {Function} * A getter/setter which returns or sets the current value of the new * session-local storage. Newly-set values will only persist of the * user is actually logged in. */ - service.create = function create(template) { + service.create = function create(template, destructor) { /** * Whether new values may be stored and retrieved. @@ -88,8 +92,15 @@ angular.module('storage').factory('sessionStorageFactory', ['$injector', functio // Reset value and disallow storage when the user is logged out $rootScope.$on('guacLogout', function userLoggedOut() { + + // Call destructor before storage is teared down + if (angular.isDefined(value) && destructor) + destructor(value); + + // Destroy storage enabled = false; value = undefined; + }); // Return getter/setter for value From 592ce3b8d305a6b84060010be6e33dc0428db8b5 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 23 Apr 2015 13:57:44 -0700 Subject: [PATCH 3/5] GUAC-1161: Store notification state in session-local storage. --- .../app/notification/notificationModule.js | 4 +- .../notification/services/guacNotification.js | 85 +++++++++++++------ guacamole/src/main/webapp/index.html | 6 +- 3 files changed, 63 insertions(+), 32 deletions(-) diff --git a/guacamole/src/main/webapp/app/notification/notificationModule.js b/guacamole/src/main/webapp/app/notification/notificationModule.js index 54c64fdfb..d4008200f 100644 --- a/guacamole/src/main/webapp/app/notification/notificationModule.js +++ b/guacamole/src/main/webapp/app/notification/notificationModule.js @@ -23,4 +23,6 @@ /** * The module for code used to display arbitrary notifications. */ -angular.module('notification', []); +angular.module('notification', [ + 'storage' +]); diff --git a/guacamole/src/main/webapp/app/notification/services/guacNotification.js b/guacamole/src/main/webapp/app/notification/services/guacNotification.js index b5f5c9872..eb9b15c62 100644 --- a/guacamole/src/main/webapp/app/notification/services/guacNotification.js +++ b/guacamole/src/main/webapp/app/notification/services/guacNotification.js @@ -23,34 +23,49 @@ /** * Service for displaying notifications and modal status dialogs. */ -angular.module('notification').factory('guacNotification', ['$rootScope', - function guacNotification($rootScope) { +angular.module('notification').factory('guacNotification', ['$injector', + function guacNotification($injector) { + + // Required services + var $rootScope = $injector.get('$rootScope'); + var sessionStorageFactory = $injector.get('sessionStorageFactory'); var service = {}; /** - * The current status notification, or false if no status is currently - * shown. + * Getter/setter which retrieves or sets the current status notification, + * which may simply be false if no status is currently shown. + * + * @type Function + */ + var storedStatus = sessionStorageFactory.create(false); + + /** + * Getter/setter which retrieves or sets an array of all currently-visible + * notifications. + * + * @type Function + */ + var storedNotifications = sessionStorageFactory.create([]); + + /** + * Getter/setter which retrieves or sets the ID of the most recently shown + * notification, or 0 if no notifications have yet been shown. + * + * @type Function + */ + var storedNotificationUniqueID = sessionStorageFactory.create(0); + + /** + * Retrieves the current status notification, which may simply be false if + * no status is currently shown. * * @type Notification|Boolean */ - service.status = false; + service.getStatus = function getStatus() { + return storedStatus(); + }; - /** - * All currently-visible notifications. - * - * @type Notification[] - */ - service.notifications = []; - - /** - * The ID of the most recently shown notification, or 0 if no notifications - * have yet been shown. - * - * @type Number - */ - var notificationUniqueID = 0; - /** * Shows or hides the given notification as a modal status. If a status * notification is currently shown, no further statuses will be shown @@ -77,10 +92,20 @@ angular.module('notification').factory('guacNotification', ['$rootScope', * guacNotification.showStatus(false); */ service.showStatus = function showStatus(status) { - if (!service.status || !status) - service.status = status; + if (!storedStatus() || !status) + storedStatus(status); }; - + + /** + * Returns an array of all currently-visible notifications. + * + * @returns {Notification[]} + * An array of all currently-visible notifications. + */ + service.getNotifications = function getNotifications() { + return storedNotifications(); + }; + /** * Adds a notification to the the list of notifications shown. * @@ -104,9 +129,9 @@ angular.module('notification').factory('guacNotification', ['$rootScope', * }); */ service.addNotification = function addNotification(notification) { - var id = ++notificationUniqueID; + var id = storedNotificationUniqueID(storedNotificationUniqueID() + 1); - service.notifications.push({ + storedNotifications().push({ notification : notification, id : id }); @@ -122,12 +147,16 @@ angular.module('notification').factory('guacNotification', ['$rootScope', * from the initial call to addNotification. */ service.removeNotification = function removeNotification(id) { - for (var i = 0; i < service.notifications.length; i++) { - if (service.notifications[i].id === id) { - service.notifications.splice(i, 1); + + var notifications = storedNotifications(); + + for (var i = 0; i < notifications.length; i++) { + if (notifications[i].id === id) { + notifications.splice(i, 1); return; } } + }; // Hide status upon navigation diff --git a/guacamole/src/main/webapp/index.html b/guacamole/src/main/webapp/index.html index 86d4ef8ea..41fec33d2 100644 --- a/guacamole/src/main/webapp/index.html +++ b/guacamole/src/main/webapp/index.html @@ -38,9 +38,9 @@ THE SOFTWARE.
-
+
- +
@@ -49,7 +49,7 @@ THE SOFTWARE.
-
+
From d1be55809a608cc352ed48ceb6b2c9d371d3e486 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 23 Apr 2015 15:20:41 -0700 Subject: [PATCH 4/5] GUAC-1161: Store managed clients in session storage. --- .../app/client/services/guacClientManager.js | 61 ++++++++++++------- .../home/directives/guacRecentConnections.js | 8 ++- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/guacamole/src/main/webapp/app/client/services/guacClientManager.js b/guacamole/src/main/webapp/app/client/services/guacClientManager.js index 81dc249fb..3d74bc2ea 100644 --- a/guacamole/src/main/webapp/app/client/services/guacClientManager.js +++ b/guacamole/src/main/webapp/app/client/services/guacClientManager.js @@ -30,18 +30,34 @@ angular.module('client').factory('guacClientManager', ['$injector', var ManagedClient = $injector.get('ManagedClient'); // Required services - var $window = $injector.get('$window'); - var $rootScope = $injector.get('$rootScope'); + var $window = $injector.get('$window'); + var sessionStorageFactory = $injector.get('sessionStorageFactory'); var service = {}; /** - * Map of all active managed clients. Each key is the ID of the connection - * used by that client. + * Getter/setter which retrieves or sets the map of all active managed + * clients. Each key is the ID of the connection used by that client. * - * @type Object. + * @type Function */ - service.managedClients = {}; + var storedManagedClients = sessionStorageFactory.create({}, function destroyClientStorage() { + + // Disconnect all clients when storage is destroyed + service.clear(); + + }); + + /** + * Returns a map of all active managed clients. Each key is the ID of the + * connection used by that client. + * + * @returns {Object.} + * A map of all active managed clients. + */ + service.getManagedClients = function getManagedClients() { + return storedManagedClients(); + }; /** * Removes the existing ManagedClient associated with the connection having @@ -55,13 +71,15 @@ angular.module('client').factory('guacClientManager', ['$injector', * true if an existing client was removed, false otherwise. */ service.removeManagedClient = function replaceManagedClient(id) { - + + var managedClients = storedManagedClients(); + // Remove client if it exists - if (id in service.managedClients) { + if (id in managedClients) { // Disconnect and remove - service.managedClients[id].client.disconnect(); - delete service.managedClients[id]; + managedClients[id].client.disconnect(); + delete managedClients[id]; // A client was removed return true; @@ -96,7 +114,7 @@ angular.module('client').factory('guacClientManager', ['$injector', service.removeManagedClient(id); // Set new client - return service.managedClients[id] = ManagedClient.getInstance(id, connectionParameters); + return storedManagedClients()[id] = ManagedClient.getInstance(id, connectionParameters); }; @@ -119,12 +137,14 @@ angular.module('client').factory('guacClientManager', ['$injector', */ service.getManagedClient = function getManagedClient(id, connectionParameters) { + var managedClients = storedManagedClients(); + // Create new managed client if it doesn't already exist - if (!(id in service.managedClients)) - service.managedClients[id] = ManagedClient.getInstance(id, connectionParameters); + if (!(id in managedClients)) + managedClients[id] = ManagedClient.getInstance(id, connectionParameters); // Return existing client - return service.managedClients[id]; + return managedClients[id]; }; @@ -133,23 +153,20 @@ angular.module('client').factory('guacClientManager', ['$injector', */ service.clear = function clear() { + var managedClients = storedManagedClients(); + // Disconnect each managed client - for (var id in service.managedClients) - service.managedClients[id].client.disconnect(); + for (var id in managedClients) + managedClients[id].client.disconnect(); // Clear managed clients - service.managedClients = {}; + storedManagedClients({}); }; // Disconnect all clients when window is unloaded $window.addEventListener('unload', service.clear); - // Clear clients on logout - $rootScope.$on('guacLogout', function handleLogout() { - service.clear(); - }); - return service; }]); diff --git a/guacamole/src/main/webapp/app/home/directives/guacRecentConnections.js b/guacamole/src/main/webapp/app/home/directives/guacRecentConnections.js index 3b7f522ce..5dddd9717 100644 --- a/guacamole/src/main/webapp/app/home/directives/guacRecentConnections.js +++ b/guacamole/src/main/webapp/app/home/directives/guacRecentConnections.js @@ -137,11 +137,13 @@ angular.module('home').directive('guacRecentConnections', [function guacRecentCo if (rootGroup) addVisibleConnectionGroup(rootGroup); + var managedClients = guacClientManager.getManagedClients(); + // Add all active connections - for (var id in guacClientManager.managedClients) { + for (var id in managedClients) { // Get corresponding managed client - var client = guacClientManager.managedClients[id]; + var client = managedClients[id]; // Add active connections for clients with associated visible objects if (id in visibleObjects) { @@ -157,7 +159,7 @@ angular.module('home').directive('guacRecentConnections', [function guacRecentCo guacHistory.recentConnections.forEach(function addRecentConnection(historyEntry) { // Add recent connections for history entries with associated visible objects - if (historyEntry.id in visibleObjects && !(historyEntry.id in guacClientManager.managedClients)) { + if (historyEntry.id in visibleObjects && !(historyEntry.id in managedClients)) { var object = visibleObjects[historyEntry.id]; $scope.recentConnections.push(new RecentConnection(object.name, historyEntry)); From e486c32477fb0249c3d9df07cb346e3f6ff60f0c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 23 Apr 2015 16:18:17 -0700 Subject: [PATCH 5/5] GUAC-1161: Remove completely-unused notification area and associated functions. --- .../app/client/styles/notification-area.css | 49 ------------ .../notification/services/guacNotification.js | 79 ------------------- guacamole/src/main/webapp/index.html | 7 -- 3 files changed, 135 deletions(-) delete mode 100644 guacamole/src/main/webapp/app/client/styles/notification-area.css diff --git a/guacamole/src/main/webapp/app/client/styles/notification-area.css b/guacamole/src/main/webapp/app/client/styles/notification-area.css deleted file mode 100644 index a7c0a6f0e..000000000 --- a/guacamole/src/main/webapp/app/client/styles/notification-area.css +++ /dev/null @@ -1,49 +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%; - 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; -} - -#notificationArea .notification.error .text { - white-space: normal; - text-overflow: clip; - text-align: left; -} diff --git a/guacamole/src/main/webapp/app/notification/services/guacNotification.js b/guacamole/src/main/webapp/app/notification/services/guacNotification.js index eb9b15c62..9c660148f 100644 --- a/guacamole/src/main/webapp/app/notification/services/guacNotification.js +++ b/guacamole/src/main/webapp/app/notification/services/guacNotification.js @@ -40,22 +40,6 @@ angular.module('notification').factory('guacNotification', ['$injector', */ var storedStatus = sessionStorageFactory.create(false); - /** - * Getter/setter which retrieves or sets an array of all currently-visible - * notifications. - * - * @type Function - */ - var storedNotifications = sessionStorageFactory.create([]); - - /** - * Getter/setter which retrieves or sets the ID of the most recently shown - * notification, or 0 if no notifications have yet been shown. - * - * @type Function - */ - var storedNotificationUniqueID = sessionStorageFactory.create(0); - /** * Retrieves the current status notification, which may simply be false if * no status is currently shown. @@ -96,69 +80,6 @@ angular.module('notification').factory('guacNotification', ['$injector', storedStatus(status); }; - /** - * Returns an array of all currently-visible notifications. - * - * @returns {Notification[]} - * An array of all currently-visible notifications. - */ - service.getNotifications = function getNotifications() { - return storedNotifications(); - }; - - /** - * Adds a notification to the the list of notifications shown. - * - * @param {Notification|Object} notification - * The notification to add. - * - * @returns {Number} - * A unique ID for the notification that's just been added. - * - * @example - * - * var id = guacNotification.addNotification({ - * 'title' : 'Download', - * 'text' : 'You have a file ready for download!', - * 'actions' : { - * 'name' : 'download', - * 'callback' : function () { - * // download the file and remove the notification here - * } - * } - * }); - */ - service.addNotification = function addNotification(notification) { - var id = storedNotificationUniqueID(storedNotificationUniqueID() + 1); - - storedNotifications().push({ - notification : notification, - id : id - }); - - return id; - }; - - /** - * Remove a notification by unique ID. - * - * @param {Number} id - * The unique ID of the notification to remove. This ID is retrieved - * from the initial call to addNotification. - */ - service.removeNotification = function removeNotification(id) { - - var notifications = storedNotifications(); - - for (var i = 0; i < notifications.length; i++) { - if (notifications[i].id === id) { - notifications.splice(i, 1); - return; - } - } - - }; - // Hide status upon navigation $rootScope.$on('$routeChangeSuccess', function() { service.showStatus(false); diff --git a/guacamole/src/main/webapp/index.html b/guacamole/src/main/webapp/index.html index 41fec33d2..95e1589d8 100644 --- a/guacamole/src/main/webapp/index.html +++ b/guacamole/src/main/webapp/index.html @@ -47,13 +47,6 @@ THE SOFTWARE.
- -
-
- -
-
-