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)); 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..9c660148f 100644 --- a/guacamole/src/main/webapp/app/notification/services/guacNotification.js +++ b/guacamole/src/main/webapp/app/notification/services/guacNotification.js @@ -23,34 +23,33 @@ /** * 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); + + /** + * 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,59 +76,10 @@ 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); }; - - /** - * 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 = ++notificationUniqueID; - service.notifications.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) { - for (var i = 0; i < service.notifications.length; i++) { - if (service.notifications[i].id === id) { - service.notifications.splice(i, 1); - return; - } - } - }; - // Hide status upon navigation $rootScope.$on('$routeChangeSuccess', function() { service.showStatus(false); 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..018ff9e57 --- /dev/null +++ b/guacamole/src/main/webapp/app/storage/services/sessionStorageFactory.js @@ -0,0 +1,134 @@ +/* + * 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. + * + * @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, destructor) { + + /** + * 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() { + + // 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 + 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/client/styles/notification-area.css b/guacamole/src/main/webapp/app/storage/storageModule.js similarity index 67% rename from guacamole/src/main/webapp/app/client/styles/notification-area.css rename to guacamole/src/main/webapp/app/storage/storageModule.js index a7c0a6f0e..f41860efe 100644 --- a/guacamole/src/main/webapp/app/client/styles/notification-area.css +++ b/guacamole/src/main/webapp/app/storage/storageModule.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * 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 @@ -20,30 +20,9 @@ * 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; -} +/** + * Module which provides generic storage services. + */ +angular.module('storage', [ + 'auth' +]); diff --git a/guacamole/src/main/webapp/index.html b/guacamole/src/main/webapp/index.html index 86d4ef8ea..95e1589d8 100644 --- a/guacamole/src/main/webapp/index.html +++ b/guacamole/src/main/webapp/index.html @@ -38,22 +38,15 @@ THE SOFTWARE.
-
+
- +
- -
-
- -
-
-