From 207e87f60f9543599a7452e01e1a8a04fa474a00 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 30 Nov 2014 02:52:50 -0800 Subject: [PATCH 01/11] GUAC-932: Remove legacy JavaScript source. --- guacamole/pom.xml | 2 - guacamole/src/main/webapp/scripts/history.js | 212 ------------------- guacamole/src/main/webapp/scripts/session.js | 171 --------------- 3 files changed, 385 deletions(-) delete mode 100644 guacamole/src/main/webapp/scripts/history.js delete mode 100644 guacamole/src/main/webapp/scripts/session.js diff --git a/guacamole/pom.xml b/guacamole/pom.xml index c07e8b308..351d5b257 100644 --- a/guacamole/pom.xml +++ b/guacamole/pom.xml @@ -142,8 +142,6 @@ lib/messageformat/messageformat.js license.txt guacamole-common-js/all.js - scripts/session.js - scripts/history.js diff --git a/guacamole/src/main/webapp/scripts/history.js b/guacamole/src/main/webapp/scripts/history.js deleted file mode 100644 index 5165e8df0..000000000 --- a/guacamole/src/main/webapp/scripts/history.js +++ /dev/null @@ -1,212 +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. - */ - -/** - * Set of thumbnails for each connection, indexed by ID. - */ -GuacamoleHistory = new (function() { - - /** - * Reference to this GuacamoleHistory. - */ - var guac_history = this; - - /** - * The number of entries to allow before removing old entries based on the - * cutoff. - */ - var IDEAL_LENGTH = 6; - - /** - * The maximum age of a history entry before it is removed, in - * milliseconds. - */ - var CUTOFF_AGE = 900000; - - var history = {}; - - function truncate() { - - // Build list of entries - var entries = []; - for (var old_id in history) - entries.push(history[old_id]); - - // Avoid history growth beyond defined number of entries - if (entries.length > IDEAL_LENGTH) { - - // Sort list - entries.sort(GuacamoleHistory.Entry.compare); - - // Remove entries until length is ideal or all are recent - var now = new Date().getTime(); - while (entries.length > IDEAL_LENGTH - && now - entries[0].accessed > CUTOFF_AGE) { - - // Remove entry - var removed = entries.shift(); - delete history[removed.id]; - - } - - } - - } - - /** - * Returns the URL for the thumbnail of the connection with the given ID, - * or undefined if no thumbnail is associated with that connection. - */ - this.get = function(id) { - return history[id] || new GuacamoleHistory.Entry(); - }; - - /** - * Updates the thumbnail and access time of the history entry for the - * connection with the given ID. - */ - this.update = function(id, thumbnail) { - - /* Do nothing if localStorage not present */ - if (!localStorage) - return; - - // Create updated entry - var entry = new GuacamoleHistory.Entry( - id, - thumbnail, - new Date().getTime() - ); - - // Store entry in history - history[id] = entry; - truncate(); - - // Save updated history, ignore inability to use localStorage - try { - localStorage.setItem("GUAC_HISTORY", JSON.stringify(history)); - } - catch (ignore) {} - - }; - - /** - * Reloads all history data. - */ - this.reload = function() { - - /* Do nothing if localStorage not present */ - if (!localStorage) - return; - - // Get old and new for comparison, ignore inability to use localStorage - var old_history = history; - try { - var new_history = JSON.parse(localStorage.getItem("GUAC_HISTORY") || "{}"); - } - catch (ignore) { - return; - } - - // Update history - history = new_history; - - // Call onchange handler as necessary - if (guac_history.onchange) { - - // Produce union of all known IDs - var known_ids = {}; - for (var new_id in new_history) known_ids[new_id] = true; - for (var old_id in old_history) known_ids[old_id] = true; - - // For each known ID - for (var id in known_ids) { - - // Get entries - var old_entry = old_history[id]; - var new_entry = new_history[id]; - - // Call handler for all changed - if (!old_entry || !new_entry - || old_entry.accessed != new_entry.accessed) - guac_history.onchange(id, old_entry, new_entry); - - } - - } // end onchange - - }; - - /** - * Event handler called whenever a history entry is changed. - * - * @event - * @param {String} id The ID of the connection whose history entry is - * changing. - * @param {GuacamoleHistory.Entry} old_entry The old value of the entry, if - * any. - * @param {GuacamoleHistory.Entry} new_entry The new value of the entry, if - * any. - */ - this.onchange = null; - - // Reload when modified - window.addEventListener("storage", guac_history.reload, false); - - // Initial load - guac_history.reload(); - -})(); - -/** - * A single entry in the indexed connection usage history. - * - * @constructor - * @param {String} id The ID of this connection. - * @param {String} thumbnail The URL of the thumbnail to use to represent this - * connection. - * @param {Number} last_access The time this connection was last accessed, in - * seconds. - */ -GuacamoleHistory.Entry = function(id, thumbnail, last_access) { - - /** - * The ID of the connection associated with this history entry. - */ - this.id = id; - - /** - * The thumbnail associated with the connection associated with this history - * entry. - */ - this.thumbnail = thumbnail; - - /** - * The time the connection associated with this entry was last accessed. - */ - this.accessed = last_access; - -}; - -GuacamoleHistory.Entry.compare = function(a, b) { - return a.accessed - b.accessed; -}; diff --git a/guacamole/src/main/webapp/scripts/session.js b/guacamole/src/main/webapp/scripts/session.js deleted file mode 100644 index 7afc9b6b2..000000000 --- a/guacamole/src/main/webapp/scripts/session.js +++ /dev/null @@ -1,171 +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. - */ - -/** - * Global storage for Guacamole pages. - */ -GuacamoleSessionStorage = (function() { - - // Retrieve storage from owner of window, if possible - var opener_storage = null; - try { - opener_storage = opener && opener.GuacamoleSessionStorage; - } - catch (e) {} - - return opener_storage; - -})() || new (function() { - - /** - * The contents of storage, as a JSON string containing name/value pairs as - * properties. - * - * @private - * @type String - */ - var stored_json = "{}"; - - /** - * Called whenever an item value changes. - * - * @callback onchange - * @param {String} name The name of the item changed. - * @param value The new item value. - */ - - /** - * All attached listeners. - * - * @type onchange[] - */ - var listeners = []; - - /** - * Notifies all listeners that an item has changed. - * - * @param {String} name The name of the item that changed. - * @param value The new item value. - */ - function __notify_changed(name, value) { - for (var i=0; i Date: Sun, 30 Nov 2014 02:34:54 -0800 Subject: [PATCH 02/11] GUAC-931, GUAC-942: Add guacHistory service, replacing legacy GuacamoleHistory object. --- .../webapp/app/home/services/guacHistory.js | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 guacamole/src/main/webapp/app/home/services/guacHistory.js diff --git a/guacamole/src/main/webapp/app/home/services/guacHistory.js b/guacamole/src/main/webapp/app/home/services/guacHistory.js new file mode 100644 index 000000000..efe862eed --- /dev/null +++ b/guacamole/src/main/webapp/app/home/services/guacHistory.js @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2014 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. + */ + +/** + * A service for creating Guacamole clients. + */ +angular.module('home').factory('guacHistory', [function guacHistory() { + + var service = {}; + + // The parameter name for getting the history from local storage + var GUAC_HISTORY_STORAGE_KEY = "GUAC_HISTORY"; + + /** + * The number of entries to allow before removing old entries based on the + * cutoff. + */ + var IDEAL_LENGTH = 6; + + /** + * A single entry in the connection history. + * + * @constructor + * @param {String} id The ID of the connection. + * + * @param {String} thumbnail + * The URL of the thumbnail to use to represent the connection. + */ + var HistoryEntry = function HistoryEntry(id, thumbnail) { + + /** + * The ID of the connection associated with this history entry. + */ + this.id = id; + + /** + * The thumbnail associated with the connection associated with this + * history entry. + */ + this.thumbnail = thumbnail; + + }; + + /** + * The top few recent connections, sorted in order of most recent access. + * + * @type HistoryEntry[] + */ + service.recentConnections = []; + + /** + * Updates the thumbnail and access time of the history entry for the + * connection with the given ID. + * + * @param {String} id + * The ID of the connection whose history entry should be updated. + * + * @param {String} thumbnail + * The URL of the thumbnail image to associate with the history entry. + */ + service.updateThumbnail = function(id, thumbnail) { + + var i; + + // Remove any existing entry for this connection + for (i=0; i < service.recentConnections.length; i++) { + if (service.recentConnections[i].id === id) { + service.recentConnections.splice(i, 1); + break; + } + } + + // Store new entry in history + service.recentConnections.unshift(new HistoryEntry( + id, + thumbnail, + new Date().getTime() + )); + + // Truncate history to ideal length + if (service.recentConnections.length > IDEAL_LENGTH) + service.recentConnections.length = IDEAL_LENGTH; + + // Save updated history, ignore inability to use localStorage + try { + if (localStorage) + localStorage.setItem(GUAC_HISTORY_STORAGE_KEY, JSON.stringify(service.recentConnections)); + } + catch (ignore) {} + + }; + + // Get stored connection history, ignore inability to use localStorage + try { + + if (localStorage) { + var storedHistory = JSON.parse(localStorage.getItem(GUAC_HISTORY_STORAGE_KEY) || "[]"); + if (storedHistory instanceof Array) + service.recentConnections = storedHistory; + + } + + } + catch (ignore) {} + + return service; + +}]); From fee75204bdab12fe4b0e21ddc156815fabf674ee Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 30 Nov 2014 03:31:46 -0800 Subject: [PATCH 03/11] GUAC-932: Remove use of localStorageUtility where possible. Use cookies for auth data. --- .../src/main/webapp/app/auth/authModule.js | 2 +- .../app/auth/service/authenticationService.js | 21 ++++++++++++------- .../main/webapp/app/client/clientModule.js | 2 +- .../app/client/directives/guacClient.js | 14 ++++++------- .../webapp/app/connection/connectionModule.js | 2 +- .../app/connection/service/connectionDAO.js | 16 +++++++------- .../connectionGroup/connectionGroupModule.js | 2 +- .../service/connectionGroupDAO.js | 16 +++++++------- .../webapp/app/permission/permissionModule.js | 2 +- .../app/permission/services/permissionDAO.js | 12 +++++------ .../main/webapp/app/user/services/userDAO.js | 14 ++++++------- .../src/main/webapp/app/user/userModule.js | 2 +- 12 files changed, 55 insertions(+), 50 deletions(-) diff --git a/guacamole/src/main/webapp/app/auth/authModule.js b/guacamole/src/main/webapp/app/auth/authModule.js index 112d34cb0..f3f565798 100644 --- a/guacamole/src/main/webapp/app/auth/authModule.js +++ b/guacamole/src/main/webapp/app/auth/authModule.js @@ -23,4 +23,4 @@ /** * The module for authentication and management of tokens. */ -angular.module('auth', ['util']); +angular.module('auth', ['ngCookies']); diff --git a/guacamole/src/main/webapp/app/auth/service/authenticationService.js b/guacamole/src/main/webapp/app/auth/service/authenticationService.js index 2c2162289..4430e7913 100644 --- a/guacamole/src/main/webapp/app/auth/service/authenticationService.js +++ b/guacamole/src/main/webapp/app/auth/service/authenticationService.js @@ -23,12 +23,13 @@ /** * A service for authenticating a user against the REST API. */ -angular.module('auth').factory('authenticationService', ['$http', '$injector', - function authenticationService($http, $injector) { +angular.module('auth').factory('authenticationService', ['$http', '$cookieStore', + function authenticationService($http, $cookieStore) { - var localStorageUtility = $injector.get("localStorageUtility"); var service = {}; - + + var AUTH_COOKIE_ID = "GUAC_AUTH"; + /** * Makes a request to authenticate a user using the token REST API endpoint, * returning a promise that can be used for processing the results of the call. @@ -49,8 +50,10 @@ angular.module('auth').factory('authenticationService', ['$http', '$injector', password: password }) }).success(function success(data, status, headers, config) { - localStorageUtility.set('authToken', data.authToken); - localStorageUtility.set('userID', data.userID); + $cookieStore.put(AUTH_COOKIE_ID, { + authToken : data.authToken, + userID : data.userID + }); }); }; @@ -73,7 +76,8 @@ angular.module('auth').factory('authenticationService', ['$http', '$injector', * @returns {String} The user ID of the current user. */ service.getCurrentUserID = function getCurrentUserID() { - return localStorageUtility.get('userID'); + var authData = $cookieStore.get(AUTH_COOKIE_ID); + return authData && authData.userID; }; /** @@ -83,7 +87,8 @@ angular.module('auth').factory('authenticationService', ['$http', '$injector', * @returns {String} The auth token associated with the current user. */ service.getCurrentToken = function getCurrentToken() { - return localStorageUtility.get('authToken'); + var authData = $cookieStore.get(AUTH_COOKIE_ID); + return authData && authData.authToken; }; return service; diff --git a/guacamole/src/main/webapp/app/client/clientModule.js b/guacamole/src/main/webapp/app/client/clientModule.js index dd27a21c1..19efc3ff9 100644 --- a/guacamole/src/main/webapp/app/client/clientModule.js +++ b/guacamole/src/main/webapp/app/client/clientModule.js @@ -23,4 +23,4 @@ /** * The module for code used to connect to a connection or balancing group. */ -angular.module('client', []); +angular.module('client', ['auth']); diff --git a/guacamole/src/main/webapp/app/client/directives/guacClient.js b/guacamole/src/main/webapp/app/client/directives/guacClient.js index 20b1114c6..59cbb7de1 100644 --- a/guacamole/src/main/webapp/app/client/directives/guacClient.js +++ b/guacamole/src/main/webapp/app/client/directives/guacClient.js @@ -122,12 +122,12 @@ angular.module('client').directive('guacClient', [function guacClient() { */ var touchPad = new Guacamole.Mouse.Touchpad(displayContainer); - var $window = $injector.get('$window'), - guacAudio = $injector.get('guacAudio'), - guacVideo = $injector.get('guacVideo'), - guacTunnelFactory = $injector.get('guacTunnelFactory'), - guacClientFactory = $injector.get('guacClientFactory'), - localStorageUtility = $injector.get('localStorageUtility'); + var $window = $injector.get('$window'), + guacAudio = $injector.get('guacAudio'), + guacVideo = $injector.get('guacVideo'), + guacTunnelFactory = $injector.get('guacTunnelFactory'), + guacClientFactory = $injector.get('guacClientFactory'), + authenticationService = $injector.get('authenticationService'); /** * Updates the scale of the attached Guacamole.Client based on current window @@ -175,7 +175,7 @@ angular.module('client').directive('guacClient', [function guacClient() { // Build base connect string var connectString = "id=" + encodeURIComponent($scope.id) - + "&authToken=" + encodeURIComponent(localStorageUtility.get('authToken')) + + "&authToken=" + encodeURIComponent(authenticationService.getCurrentToken()) + "&width=" + Math.floor(optimal_width) + "&height=" + Math.floor(optimal_height) + "&dpi=" + Math.floor(optimal_dpi) diff --git a/guacamole/src/main/webapp/app/connection/connectionModule.js b/guacamole/src/main/webapp/app/connection/connectionModule.js index 4b195de4e..7f3b0ab88 100644 --- a/guacamole/src/main/webapp/app/connection/connectionModule.js +++ b/guacamole/src/main/webapp/app/connection/connectionModule.js @@ -23,4 +23,4 @@ /** * The module for code relating to connections. */ -angular.module('connection', ['util']); +angular.module('connection', ['auth']); diff --git a/guacamole/src/main/webapp/app/connection/service/connectionDAO.js b/guacamole/src/main/webapp/app/connection/service/connectionDAO.js index 774ad88c6..94797b964 100644 --- a/guacamole/src/main/webapp/app/connection/service/connectionDAO.js +++ b/guacamole/src/main/webapp/app/connection/service/connectionDAO.js @@ -23,8 +23,8 @@ /** * The DAO for connection operations agains the REST API. */ -angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUtility', - function connectionDAO($http, localStorageUtility) { +angular.module('connection').factory('connectionDAO', ['$http', 'authenticationService', + function connectionDAO($http, authenticationService) { var service = {}; @@ -36,7 +36,7 @@ angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUti * @returns {promise} A promise for the HTTP call. */ service.getConnection = function getConnection(id) { - return $http.get("api/connection/" + id + "?token=" + localStorageUtility.get('authToken')); + return $http.get("api/connection/" + id + "?token=" + authenticationService.getCurrentToken()); }; /** @@ -55,7 +55,7 @@ angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUti if(parentID !== undefined) parentIDParam = "&parentID=" + parentID; - return $http.get("api/connection?token=" + localStorageUtility.get('authToken') + parentIDParam); + return $http.get("api/connection?token=" + authenticationService.getCurrentToken() + parentIDParam); }; /** @@ -74,7 +74,7 @@ angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUti // This is a new connection if(!connectionToSave.identifier) { - return $http.post("api/connection/?token=" + localStorageUtility.get('authToken'), connectionToSave).success( + return $http.post("api/connection/?token=" + authenticationService.getCurrentToken(), connectionToSave).success( function setConnectionID(connectionID){ // Set the identifier on the new connection connection.identifier = connectionID; @@ -83,7 +83,7 @@ angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUti } else { return $http.post( "api/connection/" + connectionToSave.identifier + - "?token=" + localStorageUtility.get('authToken'), + "?token=" + authenticationService.getCurrentToken(), connectionToSave); } }; @@ -100,7 +100,7 @@ angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUti return $http.put( "api/connection/" + connection.identifier + - "?token=" + localStorageUtility.get('authToken') + + "?token=" + authenticationService.getCurrentToken() + "&parentID=" + connection.parentIdentifier, connection); @@ -117,7 +117,7 @@ angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUti service.deleteConnection = function deleteConnection(connection) { return $http['delete']( "api/connection/" + connection.identifier + - "?token=" + localStorageUtility.get('authToken')); + "?token=" + authenticationService.getCurrentToken()); }; return service; diff --git a/guacamole/src/main/webapp/app/connectionGroup/connectionGroupModule.js b/guacamole/src/main/webapp/app/connectionGroup/connectionGroupModule.js index eff95131d..eab2c54a4 100644 --- a/guacamole/src/main/webapp/app/connectionGroup/connectionGroupModule.js +++ b/guacamole/src/main/webapp/app/connectionGroup/connectionGroupModule.js @@ -23,4 +23,4 @@ /** * The module for code relating to connection groups. */ -angular.module('connectionGroup', ['util', 'connection']); +angular.module('connectionGroup', ['auth', 'util', 'connection']); diff --git a/guacamole/src/main/webapp/app/connectionGroup/service/connectionGroupDAO.js b/guacamole/src/main/webapp/app/connectionGroup/service/connectionGroupDAO.js index 4ce767ee5..c2cceecb6 100644 --- a/guacamole/src/main/webapp/app/connectionGroup/service/connectionGroupDAO.js +++ b/guacamole/src/main/webapp/app/connectionGroup/service/connectionGroupDAO.js @@ -23,8 +23,8 @@ /** * The DAO for connection group operations agains the REST API. */ -angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'localStorageUtility', - function connectionGrouDAO($http, localStorageUtility) { +angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'authenticationService', + function connectionGrouDAO($http, authenticationService) { /** * The ID of the root connection group. @@ -49,7 +49,7 @@ angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'local if(parentID !== undefined) parentIDParam = "&parentID=" + parentID; - return $http.get("api/connectionGroup?token=" + localStorageUtility.get('authToken') + parentIDParam); + return $http.get("api/connectionGroup?token=" + authenticationService.getCurrentToken() + parentIDParam); }; /** @@ -67,7 +67,7 @@ angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'local // Use the root connection group ID if no ID is passed in connectionGroupID = connectionGroupID || ROOT_CONNECTION_GROUP_ID; - return $http.get("api/connectionGroup/" + connectionGroupID + "?token=" + localStorageUtility.get('authToken')); + return $http.get("api/connectionGroup/" + connectionGroupID + "?token=" + authenticationService.getCurrentToken()); }; /** @@ -81,7 +81,7 @@ angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'local service.saveConnectionGroup = function saveConnectionGroup(connectionGroup) { // This is a new connection group if(!connectionGroup.identifier) { - return $http.post("api/connectionGroup/?token=" + localStorageUtility.get('authToken'), connectionGroup).success( + return $http.post("api/connectionGroup/?token=" + authenticationService.getCurrentToken(), connectionGroup).success( function setConnectionGroupID(connectionGroupID){ // Set the identifier on the new connection connectionGroup.identifier = connectionGroupID; @@ -90,7 +90,7 @@ angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'local } else { return $http.post( "api/connectionGroup/" + connectionGroup.identifier + - "?token=" + localStorageUtility.get('authToken'), + "?token=" + authenticationService.getCurrentToken(), connectionGroup); } }; @@ -107,7 +107,7 @@ angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'local return $http.put( "api/connectionGroup/" + connectionGroup.identifier + - "?token=" + localStorageUtility.get('authToken') + + "?token=" + authenticationService.getCurrentToken() + "&parentID=" + connectionGroup.parentIdentifier, connectionGroup); }; @@ -123,7 +123,7 @@ angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'local service.deleteConnectionGroup = function deleteConnectionGroup(connectionGroup) { return $http['delete']( "api/connectionGroup/" + connectionGroup.identifier + - "?token=" + localStorageUtility.get('authToken')); + "?token=" + authenticationService.getCurrentToken()); }; return service; diff --git a/guacamole/src/main/webapp/app/permission/permissionModule.js b/guacamole/src/main/webapp/app/permission/permissionModule.js index e6afa2826..524f058f5 100644 --- a/guacamole/src/main/webapp/app/permission/permissionModule.js +++ b/guacamole/src/main/webapp/app/permission/permissionModule.js @@ -23,4 +23,4 @@ /** * A module for code relating to permissions. */ -angular.module('permission', []); +angular.module('permission', ['auth']); diff --git a/guacamole/src/main/webapp/app/permission/services/permissionDAO.js b/guacamole/src/main/webapp/app/permission/services/permissionDAO.js index 5a51c61bf..b26e66683 100644 --- a/guacamole/src/main/webapp/app/permission/services/permissionDAO.js +++ b/guacamole/src/main/webapp/app/permission/services/permissionDAO.js @@ -23,8 +23,8 @@ /** * The DAO for permission operations agains the REST API. */ -angular.module('permission').factory('permissionDAO', ['$http', 'localStorageUtility', - function permissionDAO($http, localStorageUtility) { +angular.module('permission').factory('permissionDAO', ['$http', 'authenticationService', + function permissionDAO($http, authenticationService) { var service = {}; @@ -37,7 +37,7 @@ angular.module('permission').factory('permissionDAO', ['$http', 'localStorageUti * @returns {promise} A promise for the HTTP call. */ service.getPermissions = function getPermissions(userID) { - return $http.get("api/permission/" + userID + "/?token=" + localStorageUtility.get('authToken')); + return $http.get("api/permission/" + userID + "/?token=" + authenticationService.getCurrentToken()); }; /** @@ -50,7 +50,7 @@ angular.module('permission').factory('permissionDAO', ['$http', 'localStorageUti * @returns {promise} A promise for the HTTP call. */ service.addPermission = function addPermission(userID, permission) { - return $http.post("api/permission/" + userID + "/?token=" + localStorageUtility.get('authToken'), permission); + return $http.post("api/permission/" + userID + "/?token=" + authenticationService.getCurrentToken(), permission); }; @@ -65,7 +65,7 @@ angular.module('permission').factory('permissionDAO', ['$http', 'localStorageUti * @returns {promise} A promise for the HTTP call. */ service.removePermission = function removePermission(userID, permission) { - return $http.post("api/permission/remove/" + userID + "/?token=" + localStorageUtility.get('authToken'), permission); + return $http.post("api/permission/remove/" + userID + "/?token=" + authenticationService.getCurrentToken(), permission); }; @@ -103,7 +103,7 @@ angular.module('permission').factory('permissionDAO', ['$http', 'localStorageUti // Make the HTTP call return $http({ method : 'PATCH', - url : "api/permission/?token=" + localStorageUtility.get('authToken'), + url : "api/permission/?token=" + authenticationService.getCurrentToken(), data : permissionPatch }); } diff --git a/guacamole/src/main/webapp/app/user/services/userDAO.js b/guacamole/src/main/webapp/app/user/services/userDAO.js index 99ffd12b8..c8ba6f376 100644 --- a/guacamole/src/main/webapp/app/user/services/userDAO.js +++ b/guacamole/src/main/webapp/app/user/services/userDAO.js @@ -23,8 +23,8 @@ /** * The DAO for connection operations agains the REST API. */ -angular.module('user').factory('userDAO', ['$http', 'localStorageUtility', - function userDAO($http, localStorageUtility) { +angular.module('user').factory('userDAO', ['$http', 'authenticationService', + function userDAO($http, authenticationService) { var service = {}; @@ -35,7 +35,7 @@ angular.module('user').factory('userDAO', ['$http', 'localStorageUtility', * @returns {promise} A promise for the HTTP call. */ service.getUsers = function getUsers() { - return $http.get("api/user?token=" + localStorageUtility.get('authToken')); + return $http.get("api/user?token=" + authenticationService.getCurrentToken()); }; /** @@ -47,7 +47,7 @@ angular.module('user').factory('userDAO', ['$http', 'localStorageUtility', * @returns {promise} A promise for the HTTP call. */ service.getUser = function getUser(userID) { - return $http.get("api/user/" + userID + "/?token=" + localStorageUtility.get('authToken')); + return $http.get("api/user/" + userID + "/?token=" + authenticationService.getCurrentToken()); }; /** @@ -61,7 +61,7 @@ angular.module('user').factory('userDAO', ['$http', 'localStorageUtility', service.deleteUser = function deleteUser(user) { return $http['delete']( "api/user/" + user.username + - "?token=" + localStorageUtility.get('authToken')); + "?token=" + authenticationService.getCurrentToken()); }; @@ -76,7 +76,7 @@ angular.module('user').factory('userDAO', ['$http', 'localStorageUtility', service.createUser = function createUser(user) { return $http.post( "api/user/" - + "?token=" + localStorageUtility.get('authToken'), + + "?token=" + authenticationService.getCurrentToken(), user ); } @@ -92,7 +92,7 @@ angular.module('user').factory('userDAO', ['$http', 'localStorageUtility', service.saveUser = function saveUser(user) { return $http.post( "api/user/" + user.username + - "?token=" + localStorageUtility.get('authToken'), + "?token=" + authenticationService.getCurrentToken(), user); }; diff --git a/guacamole/src/main/webapp/app/user/userModule.js b/guacamole/src/main/webapp/app/user/userModule.js index 4e3a5f602..abfef1c13 100644 --- a/guacamole/src/main/webapp/app/user/userModule.js +++ b/guacamole/src/main/webapp/app/user/userModule.js @@ -23,4 +23,4 @@ /** * A module for code relating to users. */ -angular.module('user', []); +angular.module('user', ['auth']); From c8991a2a02a0852def5ffb78f90206b21e0e3972 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 30 Nov 2014 04:04:56 -0800 Subject: [PATCH 04/11] GUAC-932: Remove localStorageUtility entirely. --- .../app/home/controllers/homeController.js | 18 ++-- .../app/util/services/localStorageUtility.js | 97 ------------------- .../src/main/webapp/app/util/utilModule.js | 2 +- 3 files changed, 10 insertions(+), 107 deletions(-) delete mode 100644 guacamole/src/main/webapp/app/util/services/localStorageUtility.js diff --git a/guacamole/src/main/webapp/app/home/controllers/homeController.js b/guacamole/src/main/webapp/app/home/controllers/homeController.js index d4f85318b..303ff9e5a 100644 --- a/guacamole/src/main/webapp/app/home/controllers/homeController.js +++ b/guacamole/src/main/webapp/app/home/controllers/homeController.js @@ -31,7 +31,7 @@ angular.module('home').controller('homeController', ['$scope', '$injector', // Get the dependencies commonJS style var connectionGroupService = $injector.get("connectionGroupService"); - var localStorageUtility = $injector.get("localStorageUtility"); + var guacHistory = $injector.get("guacHistory"); // All the connections and connection groups in root $scope.connectionsAndGroups = []; @@ -48,14 +48,14 @@ angular.module('home').controller('homeController', ['$scope', '$injector', connectionGroupService.getAllGroupsAndConnections($scope.connectionsAndGroups) .then(function findRecentConnections() { - // Try to parse out the recent connections from local storage - var recentConnections; - try { - recentConnections = JSON.parse(localStorageUtility.get(GUAC_HISTORY_STORAGE_KEY)); - } catch(e) { - - // The recent history is corrupted - clear it - localStorageUtility.clear(GUAC_HISTORY_STORAGE_KEY); + // TODONT: Munch the guacHistory recentConnections list into a legacy-style object + var recentConnections = {}; + for (var i=0; i < guacHistory.recentConnections.length; i++) { + var entry = guacHistory.recentConnections[i]; + recentConnections[encodeURIComponent(entry.id)] = { + id : entry.id, + thumbnail : entry.thumbnail + }; } // Figure out which recent connection entries are valid diff --git a/guacamole/src/main/webapp/app/util/services/localStorageUtility.js b/guacamole/src/main/webapp/app/util/services/localStorageUtility.js deleted file mode 100644 index 5841d14fc..000000000 --- a/guacamole/src/main/webapp/app/util/services/localStorageUtility.js +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2014 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. - */ - -/** - * A service for handling storage and retrieval of values on localStorage. - * If local storage is not available, cookies will be used as a fallback. - */ -angular.module('util').factory('localStorageUtility', ['$cookieStore', - function localStorageUtility($cookieStore) { - - var service = {}; - - // The prefix to use when storing cookies - var COOKIE_PREFIX = "guacamole.ui.localstorage."; - - // Check if we can actually use localStorage - var localStorageEnabled; - try { - window.localStorage.setItem("test", "test"); - window.localStorage.removeItem("test"); - localStorageEnabled = true; - } catch(e) { - localStorageEnabled = false; - } - - var getFunc, setFunc; - - if(localStorageEnabled) { - - // Just a passthrough to localStorage - getFunc = function getFromLocalStorage(key) { - return window.localStorage.getItem(key); - }; - - setFunc = function setOnLocalStorage(key, value) { - return window.localStorage.setItem(key, value); - }; - } - else { - - // Store the values as cookies - getFunc = function getValueFromCookie(key) { - return $cookieStore.get(COOKIE_PREFIX + key); - }; - - setFunc = function setValueOnCookie(key, value) { - return $cookieStore.put(COOKIE_PREFIX + key, value); - } - } - - /** - * Gets a value from the persistent local store. - * - * @param {string} key The key to use as an index into the map. - * - * @returns {string} The value, if found. - */ - service.get = getFunc; - - /** - * Sets a value on the persistent local store. - * - * @param {string} key The key to use as an index into the map. - * @param {string} value The value to store in the map. - */ - service.set = setFunc; - - /** - * Clear a value from the persistent local store. - * - * @param {string} key The key to clear from the map. - */ - service.clear = function clear(key) { - return service.set(key, undefined); - }; - - return service; -}]); diff --git a/guacamole/src/main/webapp/app/util/utilModule.js b/guacamole/src/main/webapp/app/util/utilModule.js index b86ffb432..89429303c 100644 --- a/guacamole/src/main/webapp/app/util/utilModule.js +++ b/guacamole/src/main/webapp/app/util/utilModule.js @@ -23,4 +23,4 @@ /** * A module for miscellaneous services and utilities that don't belong elsewhere. */ -angular.module('util', ['ngCookies']); +angular.module('util', []); From eb8e27a6f3973c201d9ff32a6380c03fad1c30a5 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 30 Nov 2014 04:05:13 -0800 Subject: [PATCH 05/11] GUAC-932: Save thumbnail upon disconnect. --- .../client/controllers/clientController.js | 10 +++---- .../app/client/directives/guacClient.js | 28 ++++++++++++++++++- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js index 66ffc9d6b..f03e0d1af 100644 --- a/guacamole/src/main/webapp/app/client/controllers/clientController.js +++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js @@ -265,9 +265,6 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', // Show status dialog when client errors occur $scope.$on('guacClientError', function clientErrorListener(event, client, status) { - // Disconnect - $scope.id = null; - // Determine translation name of error var errorName = (status in CLIENT_ERRORS) ? status.toString(16).toUpperCase() : "DEFAULT"; @@ -293,6 +290,10 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', // Show new status only if disconnected if (status === "closed") { + + // Disconnect + $scope.id = null; + $scope.showStatus({ title: "client.status.closedStatusTitle", text: "client.status.tunnelStates." + status @@ -304,9 +305,6 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', // Show status dialog when tunnel errors occur $scope.$on('guacTunnelError', function tunnelErrorListener(event, tunnel, status) { - // Disconnect - $scope.id = null; - // Determine translation name of error var errorName = (status in TUNNEL_ERRORS) ? status.toString(16).toUpperCase() : "DEFAULT"; diff --git a/guacamole/src/main/webapp/app/client/directives/guacClient.js b/guacamole/src/main/webapp/app/client/directives/guacClient.js index 59cbb7de1..1a84fe3dd 100644 --- a/guacamole/src/main/webapp/app/client/directives/guacClient.js +++ b/guacamole/src/main/webapp/app/client/directives/guacClient.js @@ -125,6 +125,7 @@ angular.module('client').directive('guacClient', [function guacClient() { var $window = $injector.get('$window'), guacAudio = $injector.get('guacAudio'), guacVideo = $injector.get('guacVideo'), + guacHistory = $injector.get('guacHistory'), guacTunnelFactory = $injector.get('guacTunnelFactory'), guacClientFactory = $injector.get('guacClientFactory'), authenticationService = $injector.get('authenticationService'); @@ -298,12 +299,37 @@ angular.module('client').directive('guacClient', [function guacClient() { */ // Connect to given ID whenever ID changes - $scope.$watch('id', function(id) { + $scope.$watch('id', function(id, previousID) { // If a client is already attached, ensure it is disconnected if (client) client.disconnect(); + // Update stored thumbnail of previous connection + if (previousID && display && display.getWidth() >= 0 && display.getHeight() >= 0) { + + // Get screenshot + var canvas = display.flatten(); + + // Calculate scale of thumbnail (max 320x240, max zoom 100%) + var scale = Math.min(320 / canvas.width, 240 / canvas.height, 1); + + // Create thumbnail canvas + var thumbnail = document.createElement("canvas"); + thumbnail.width = canvas.width*scale; + thumbnail.height = canvas.height*scale; + + // Scale screenshot to thumbnail + var context = thumbnail.getContext("2d"); + context.drawImage(canvas, + 0, 0, canvas.width, canvas.height, + 0, 0, thumbnail.width, thumbnail.height + ); + + guacHistory.updateThumbnail(previousID, thumbnail.toDataURL("image/png")); + + } + // Only proceed if a new client is attached if (!id) return; From f5e300250d6bf8cedbf50a5f585c85f7b9317274 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 30 Nov 2014 04:13:19 -0800 Subject: [PATCH 06/11] GUAC-932: Dynamic "src" attribute for img tag should be "ng-src". --- guacamole/src/main/webapp/app/home/templates/home.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guacamole/src/main/webapp/app/home/templates/home.html b/guacamole/src/main/webapp/app/home/templates/home.html index 12857509e..abbb095b0 100644 --- a/guacamole/src/main/webapp/app/home/templates/home.html +++ b/guacamole/src/main/webapp/app/home/templates/home.html @@ -61,7 +61,7 @@
- {{recentConnection.name}} + {{recentConnection.name}}
{{recentConnection.name}} From 331644ddd958cfb58332408a4bf711d7e779bb46 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 30 Nov 2014 04:19:45 -0800 Subject: [PATCH 07/11] GUAC-932: Fix dimension checking logic. --- guacamole/src/main/webapp/app/client/directives/guacClient.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guacamole/src/main/webapp/app/client/directives/guacClient.js b/guacamole/src/main/webapp/app/client/directives/guacClient.js index 1a84fe3dd..9ec3b2c39 100644 --- a/guacamole/src/main/webapp/app/client/directives/guacClient.js +++ b/guacamole/src/main/webapp/app/client/directives/guacClient.js @@ -306,7 +306,7 @@ angular.module('client').directive('guacClient', [function guacClient() { client.disconnect(); // Update stored thumbnail of previous connection - if (previousID && display && display.getWidth() >= 0 && display.getHeight() >= 0) { + if (previousID && display && display.getWidth() > 0 && display.getHeight() > 0) { // Get screenshot var canvas = display.flatten(); From 10171ff1ba2ba0a586ff65d584d91369b0170e1e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 30 Nov 2014 15:26:52 -0800 Subject: [PATCH 08/11] GUAC-932: Move history-related things into own module. Begin "types/" hierarchy convention for services providing constructors only. --- .../main/webapp/app/client/clientModule.js | 2 +- .../main/webapp/app/history/historyModule.js | 26 +++++++++ .../{home => history}/services/guacHistory.js | 28 +--------- .../webapp/app/history/types/HistoryEntry.js | 54 +++++++++++++++++++ .../src/main/webapp/app/home/homeModule.js | 2 +- 5 files changed, 84 insertions(+), 28 deletions(-) create mode 100644 guacamole/src/main/webapp/app/history/historyModule.js rename guacamole/src/main/webapp/app/{home => history}/services/guacHistory.js (82%) create mode 100644 guacamole/src/main/webapp/app/history/types/HistoryEntry.js diff --git a/guacamole/src/main/webapp/app/client/clientModule.js b/guacamole/src/main/webapp/app/client/clientModule.js index 19efc3ff9..dec05e580 100644 --- a/guacamole/src/main/webapp/app/client/clientModule.js +++ b/guacamole/src/main/webapp/app/client/clientModule.js @@ -23,4 +23,4 @@ /** * The module for code used to connect to a connection or balancing group. */ -angular.module('client', ['auth']); +angular.module('client', ['auth', 'history']); diff --git a/guacamole/src/main/webapp/app/history/historyModule.js b/guacamole/src/main/webapp/app/history/historyModule.js new file mode 100644 index 000000000..c81af8d8f --- /dev/null +++ b/guacamole/src/main/webapp/app/history/historyModule.js @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2014 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. + */ + +/** + * The module for code relating to connection history. + */ +angular.module('history', []); diff --git a/guacamole/src/main/webapp/app/home/services/guacHistory.js b/guacamole/src/main/webapp/app/history/services/guacHistory.js similarity index 82% rename from guacamole/src/main/webapp/app/home/services/guacHistory.js rename to guacamole/src/main/webapp/app/history/services/guacHistory.js index efe862eed..c10a0cb65 100644 --- a/guacamole/src/main/webapp/app/home/services/guacHistory.js +++ b/guacamole/src/main/webapp/app/history/services/guacHistory.js @@ -21,9 +21,9 @@ */ /** - * A service for creating Guacamole clients. + * A service for reading and manipulating the Guacamole connection history. */ -angular.module('home').factory('guacHistory', [function guacHistory() { +angular.module('history').factory('guacHistory', ['HistoryEntry', function guacHistory(HistoryEntry) { var service = {}; @@ -36,30 +36,6 @@ angular.module('home').factory('guacHistory', [function guacHistory() { */ var IDEAL_LENGTH = 6; - /** - * A single entry in the connection history. - * - * @constructor - * @param {String} id The ID of the connection. - * - * @param {String} thumbnail - * The URL of the thumbnail to use to represent the connection. - */ - var HistoryEntry = function HistoryEntry(id, thumbnail) { - - /** - * The ID of the connection associated with this history entry. - */ - this.id = id; - - /** - * The thumbnail associated with the connection associated with this - * history entry. - */ - this.thumbnail = thumbnail; - - }; - /** * The top few recent connections, sorted in order of most recent access. * diff --git a/guacamole/src/main/webapp/app/history/types/HistoryEntry.js b/guacamole/src/main/webapp/app/history/types/HistoryEntry.js new file mode 100644 index 000000000..e4bdbe038 --- /dev/null +++ b/guacamole/src/main/webapp/app/history/types/HistoryEntry.js @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 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. + */ + +/** + * Provides the HistoryEntry class used by the guacHistory service. + */ +angular.module('history').factory('HistoryEntry', [function defineHistoryEntry() { + + /** + * A single entry in the connection history. + * + * @constructor + * @param {String} id The ID of the connection. + * + * @param {String} thumbnail + * The URL of the thumbnail to use to represent the connection. + */ + var HistoryEntry = function HistoryEntry(id, thumbnail) { + + /** + * The ID of the connection associated with this history entry. + */ + this.id = id; + + /** + * The thumbnail associated with the connection associated with this + * history entry. + */ + this.thumbnail = thumbnail; + + }; + + return HistoryEntry; + +}]); diff --git a/guacamole/src/main/webapp/app/home/homeModule.js b/guacamole/src/main/webapp/app/home/homeModule.js index cbe17b62e..4f7d143a3 100644 --- a/guacamole/src/main/webapp/app/home/homeModule.js +++ b/guacamole/src/main/webapp/app/home/homeModule.js @@ -20,4 +20,4 @@ * THE SOFTWARE. */ -angular.module('home', ['connection', 'connectionGroup', 'user', 'permission']); +angular.module('home', ['connection', 'connectionGroup', 'history', 'user', 'permission']); From aab0297d4000b202d0648325bcde01ddd48489af Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 30 Nov 2014 17:50:21 -0800 Subject: [PATCH 09/11] GUAC-932: Add Notification* classes. Clean up related JS. --- .../app/index/controllers/indexController.js | 92 +++---------- .../directives/guacNotification.js | 122 ++---------------- .../templates/guacNotification.html | 18 +-- .../app/notification/types/Notification.js | 91 +++++++++++++ .../notification/types/NotificationAction.js | 76 +++++++++++ .../types/NotificationCountdown.js | 89 +++++++++++++ .../types/NotificationProgress.js | 87 +++++++++++++ guacamole/src/main/webapp/index.html | 30 +---- 8 files changed, 386 insertions(+), 219 deletions(-) create mode 100644 guacamole/src/main/webapp/app/notification/types/Notification.js create mode 100644 guacamole/src/main/webapp/app/notification/types/NotificationAction.js create mode 100644 guacamole/src/main/webapp/app/notification/types/NotificationCountdown.js create mode 100644 guacamole/src/main/webapp/app/notification/types/NotificationProgress.js diff --git a/guacamole/src/main/webapp/app/index/controllers/indexController.js b/guacamole/src/main/webapp/app/index/controllers/indexController.js index 4a5b26ce3..5854cf7ff 100644 --- a/guacamole/src/main/webapp/app/index/controllers/indexController.js +++ b/guacamole/src/main/webapp/app/index/controllers/indexController.js @@ -50,6 +50,21 @@ angular.module('index').controller('indexController', ['$scope', '$injector', } }; + /** + * The current status notification, or false if no status is currently + * shown. + * + * @type Notification|Boolean + */ + $scope.status = false; + + /** + * All currently-visible notifications. + * + * @type Notification[] + */ + $scope.notifications = []; + // Put some useful variables in the top level scope $scope.page = { title: '', @@ -59,7 +74,6 @@ angular.module('index').controller('indexController', ['$scope', '$injector', $scope.currentUserIsAdmin = false; $scope.currentUserHasUpdate = false; $scope.currentUserPermissions = null; - $scope.notifications = []; var notificationUniqueID = 0; // A promise to be fulfilled when all basic user permissions are loaded. @@ -77,41 +91,8 @@ angular.module('index').controller('indexController', ['$scope', '$injector', * notification is currently shown, no further statuses will be shown * until the current status is hidden. * - * @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 {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 {String} [status.progress.text] - * If this notification has associated progress, the text to display - * while the operation is occurring. - * - * @param {String} [status.progress.value] - * The current state of operation progress, as an arbitrary number - * which increases as the operation continues. - * - * @param {String} [status.progress.unit] - * The unit of the arbitrary status.progress.value, if that value has - * an associated unit. - * - * @param {String} [status.progress.ratio] - * If known, the current status of the operation as a value between - * 0 and 1 inclusive, where 0 is not yet started, and 1 is complete. - * - * @param {Object[]} [status.actions] - * Array of action objects which contain an action name and callback to - * be executed when that action is invoked. + * @param {Notification|Boolean|Object} status + * The status notification to show. * * @example * @@ -138,43 +119,10 @@ angular.module('index').controller('indexController', ['$scope', '$injector', /** * Adds a notification to the the list of notifications shown. * - * @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 notification. - * @param {String} [notification.className] The CSS class name to apply. + * @param {Notification|Object} notification The notification to add. * - * @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 {String} [notification.progress.text] - * If this notification has associated progress, the text to display - * while the operation is occurring. - * - * @param {String} [notification.progress.value] - * The current state of operation progress, as an arbitrary number - * which increases as the operation continues. - * - * @param {String} [notification.progress.unit] - * The unit of the arbitrary notification.progress.value, if that value - * has an associated unit. - * - * @param {String} [notification.progress.ratio] - * If known, the current status of the operation as a value between - * 0 and 1 inclusive, where 0 is not yet started, and 1 is complete. - * - * @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. + * @returns {Number} + * A unique ID for the notification that's just been added. * * @example * diff --git a/guacamole/src/main/webapp/app/notification/directives/guacNotification.js b/guacamole/src/main/webapp/app/notification/directives/guacNotification.js index f8fb8334b..e6c36ef58 100644 --- a/guacamole/src/main/webapp/app/notification/directives/guacNotification.js +++ b/guacamole/src/main/webapp/app/notification/directives/guacNotification.js @@ -31,110 +31,11 @@ angular.module('notification').directive('guacNotification', [function guacNotif scope: { /** - * The CSS class to apply to the notification. + * The notification to display. * - * @type String + * @type Notification|Object */ - className : '=', - - /** - * The title of the notification. - * - * @type String - */ - title : '=', - - /** - * The body text of the notification. - * - * @type String - */ - text : '=', - - /** - * The text to use for displaying the countdown. For the sake of - * i18n, the variable REMAINING will be applied within the - * translation string for formatting plurals, etc. - * - * @type String - * @example - * "Only {REMAINING} {REMAINING, plural, one{second} other{seconds}} remain." - */ - countdownText : '=', - - /** - * The number of seconds to wait before automatically calling the - * default callback. - * - * @type Number - */ - countdown : '=', - - /** - * The function to call when timeRemaining expires. If timeRemaining - * is not set, this does not apply. - * - * @type Function - */ - defaultCallback : '=', - - /** - * The text to use for displaying the progress. For the sake of - * i18n, the variable PROGRESS will be applied within the - * translation string for formatting plurals, etc., while the - * variable UNIT will be applied, if needed, for whatever units - * are applicable to the progress display. - * - * @type String - * @example - * "{PROGRESS} {UNIT, select, b{B} kb{KB}} uploaded." - */ - progressText : '=', - - /** - * The unit which applies to the progress indicator, if any. This - * will be substituted in the progressText string with the UNIT - * variable. - * - * @type String - */ - progressUnit : '=', - - /** - * Arbitrary value denoting how much progress has been made - * in some ongoing task that this notification represents. - * - * @type Number - */ - progress : '=', - - /** - * Value between 0 and 1 denoting what proportion of the operation - * has completed. If known, this value should be 0 if the operation - * has not started, and 1 if the operation is complete. - * - * @type Number - */ - progressRatio : '=', - - /** - * Array of name/callback pairs for each action the user is allowed - * to take once the notification is shown. - * - * @type Array - * @example - * [ - * { - * name : "Action 1 name", - * callback : actionCallback1 - * }, - * { - * name : "Action 2 text", - * callback : actionCallback2 - * } - * ] - */ - actions : '=' + notification : '=' }, @@ -142,21 +43,22 @@ angular.module('notification').directive('guacNotification', [function guacNotif controller: ['$scope', '$interval', function guacNotificationController($scope, $interval) { // Update progress bar if end known - $scope.$watch("progressRatio", function updateProgress(ratio) { + $scope.$watch("notification.progress.ratio", function updateProgress(ratio) { $scope.progressPercent = ratio * 100; }); - // Set countdown interval when associated property is set - $scope.$watch("countdown", function resetTimeRemaining(countdown) { + $scope.$watch("notification", function resetTimeRemaining(notification) { - $scope.timeRemaining = countdown; + var countdown = notification.countdown; // Clean up any existing interval if ($scope.interval) $interval.cancel($scope.interval); // Update and handle countdown, if provided - if ($scope.timeRemaining) { + if (countdown) { + + $scope.timeRemaining = countdown.remaining; $scope.interval = $interval(function updateTimeRemaining() { @@ -164,8 +66,8 @@ angular.module('notification').directive('guacNotification', [function guacNotif $scope.timeRemaining--; // Call countdown callback when time remaining expires - if ($scope.timeRemaining === 0 && $scope.defaultCallback) - $scope.defaultCallback(); + if ($scope.timeRemaining === 0) + countdown.performAction(); }, 1000, $scope.timeRemaining); @@ -184,4 +86,4 @@ angular.module('notification').directive('guacNotification', [function guacNotif }] }; -}]); \ No newline at end of file +}]); diff --git a/guacamole/src/main/webapp/app/notification/templates/guacNotification.html b/guacamole/src/main/webapp/app/notification/templates/guacNotification.html index 0691f663e..50d484fb2 100644 --- a/guacamole/src/main/webapp/app/notification/templates/guacNotification.html +++ b/guacamole/src/main/webapp/app/notification/templates/guacNotification.html @@ -1,4 +1,4 @@ -
+
-
-
{{title | translate}}
+
+
{{notification.title | translate}}
-

{{text | translate}}

+

{{notification.text | translate}}

-
{{progressText | translate:"{ PROGRESS: progress, UNIT: progressUnit }"}}
+
{{notification.progress.text | translate:"{ PROGRESS: notification.progress.value, UNIT: notification.progress.unit}"}}
-

{{countdownText | translate:"{ REMAINING: timeRemaining }"}}

+

{{notification.countdown.text | translate:"{ REMAINING: timeRemaining}"}}

-
- +
+
-
\ No newline at end of file +
diff --git a/guacamole/src/main/webapp/app/notification/types/Notification.js b/guacamole/src/main/webapp/app/notification/types/Notification.js new file mode 100644 index 000000000..dfb5ef9fa --- /dev/null +++ b/guacamole/src/main/webapp/app/notification/types/Notification.js @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2014 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. + */ + +/** + * Provides the Notification class definition. + */ +angular.module('notification').factory('Notification', [function defineNotification() { + + /** + * Creates a new Notification, initializing the properties of that + * Notification with the corresponding properties of the given template. + * + * @constructor + * @param {Notification|Object} [template={}] + * The object whose properties should be copied within the new + * Notification. + */ + var Notification = function Notification(template) { + + // Use empty object by default + template = template || {}; + + /** + * The CSS class to associate with the notification, if any. + * + * @type String + */ + this.className = template.className; + + /** + * The title of the notification. + * + * @type String + */ + this.title = template.title; + + /** + * The body text of the notification. + * + * @type String + */ + this.text = template.text; + + /** + * An array of all actions available to the user in response to this + * notification. + * + * @type NotificationAction[] + */ + this.actions = template.actions || []; + + /** + * The current progress state of the ongoing action associated with this + * notification. + * + * @type NotificationProgress + */ + this.progress = template.progress; + + /** + * The countdown and corresponding default action which applies to + * this notification, if any. + * + * @type NotificationCountdown + */ + this.countdown = template.countdown; + + }; + + return Notification; + +}]); diff --git a/guacamole/src/main/webapp/app/notification/types/NotificationAction.js b/guacamole/src/main/webapp/app/notification/types/NotificationAction.js new file mode 100644 index 000000000..d0e75cb6d --- /dev/null +++ b/guacamole/src/main/webapp/app/notification/types/NotificationAction.js @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2014 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. + */ + +/** + * Provides the NotificationAction class definition. + */ +angular.module('notification').factory('NotificationAction', [function defineNotificationAction() { + + /** + * Creates a new NotificationAction, which pairs an arbitrary callback with + * an action name. The name of this action will ultimately be presented to + * the user when the user is prompted to choose among available actions. + * + * @constructor + * @param {String} name The name of this action. + * + * @param {Function} callback + * The callback to call when the user elects to perform this action. + */ + var NotificationAction = function NotificationAction(name, callback) { + + /** + * Reference to this NotificationAction. + * + * @type NotificationAction + */ + var action = this; + + /** + * The name of this action. + * + * @type String + */ + this.name = name; + + /** + * The callback to call when this action is performed. + * + * @type Function + */ + this.callback = callback; + + /** + * Calls the callback associated with this NotificationAction, if any. + * If no callback is associated with this NotificationAction, this + * function has no effect. + */ + this.performAction = function performAction() { + if (action.callback) + action.callback(); + }; + + }; + + return NotificationAction; + +}]); diff --git a/guacamole/src/main/webapp/app/notification/types/NotificationCountdown.js b/guacamole/src/main/webapp/app/notification/types/NotificationCountdown.js new file mode 100644 index 000000000..1f834508e --- /dev/null +++ b/guacamole/src/main/webapp/app/notification/types/NotificationCountdown.js @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2014 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. + */ + +/** + * Provides the NotificationCountdown class definition. + */ +angular.module('notification').factory('NotificationCountdown', [function defineNotificationCountdown() { + + /** + * Creates a new NotificationCountdown which describes an action that + * should be performed after a specific number of seconds has elapsed. + * + * @constructor + * @param {String} text The body text of the notification countdown. + * + * @param {Number} remaining + * The number of seconds remaining in the countdown. + * + * @param {Function} [callback] + * The callback to call when the countdown elapses. + */ + var NotificationCountdown = function NotificationCountdown(text, remaining, callback) { + + /** + * Reference to this NotificationCountdown. + * + * @type NotificationCountdown + */ + var countdown = this; + + /** + * The body text of the notification countdown. For the sake of i18n, + * the variable REMAINING should be applied within the translation + * string for formatting plurals, etc. + * + * @type String + */ + this.text = text; + + /** + * The number of seconds remaining in the countdown. After this number + * of seconds elapses, the callback associated with this + * NotificationCountdown will be called. + * + * @type Number + */ + this.remaining = remaining; + + /** + * The callback to call when this countdown expires. + * + * @type Function + */ + this.callback = callback; + + /** + * Calls the callback associated with this NotificationCountdown, if any. + * If no callback is associated with this NotificationCountdown, this + * function has no effect. + */ + this.performAction = function performAction() { + if (countdown.callback) + countdown.callback(); + }; + + }; + + return NotificationCountdown; + +}]); diff --git a/guacamole/src/main/webapp/app/notification/types/NotificationProgress.js b/guacamole/src/main/webapp/app/notification/types/NotificationProgress.js new file mode 100644 index 000000000..5c3a45c0c --- /dev/null +++ b/guacamole/src/main/webapp/app/notification/types/NotificationProgress.js @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2014 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. + */ + +/** + * Provides the NotificationProgress class definition. + */ +angular.module('notification').factory('NotificationProgress', [function defineNotificationProgress() { + + /** + * Creates a new NotificationProgress which describes the current status + * of an operation, and how much of that operation remains to be performed. + * + * @constructor + * @param {String} text The text describing the operation progress. + * + * @param {Number} value + * The current state of operation progress, as an arbitrary number + * which increases as the operation continues. + * + * @param {String} [unit] + * The unit of the arbitrary value, if that value has an associated + * unit. + * + * @param {Number} [ratio] + * If known, the current status of the operation as a value between 0 + * and 1 inclusive, where 0 is not yet started, and 1 is complete. + */ + var NotificationProgress = function NotificationProgress(text, value, unit, ratio) { + + /** + * The text describing the operation progress. For the sake of i18n, + * the variable PROGRESS should be applied within the translation + * string for formatting plurals, etc., while UNIT should be used + * for the progress unit, if any. + * + * @type String + */ + this.text = text; + + /** + * The current state of operation progress, as an arbitrary number which + * increases as the operation continues. + * + * @type Number + */ + this.value = value; + + /** + * The unit of the arbitrary value, if that value has an associated + * unit. + * + * @type String + */ + this.unit = unit; + + /** + * If known, the current status of the operation as a value between 0 + * and 1 inclusive, where 0 is not yet started, and 1 is complete. + * + * @type String + */ + this.ratio = ratio; + + }; + + return NotificationProgress; + +}]); diff --git a/guacamole/src/main/webapp/index.html b/guacamole/src/main/webapp/index.html index 8206371ad..472e18ca6 100644 --- a/guacamole/src/main/webapp/index.html +++ b/guacamole/src/main/webapp/index.html @@ -37,20 +37,7 @@ THE SOFTWARE.
- - - +
@@ -60,20 +47,7 @@ THE SOFTWARE.
- - - +
From 437d389ffc2eeb94718b9d47963e8209fbc3035b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 30 Nov 2014 18:08:34 -0800 Subject: [PATCH 10/11] GUAC-932: Migrate ClientProperties to new "types/" convention. Explicitly note type elsewhere. --- .../client/controllers/clientController.js | 2 +- .../app/client/directives/guacClient.js | 27 +++++++++++++++---- .../ClientProperties.js} | 24 +++++++++++------ 3 files changed, 39 insertions(+), 14 deletions(-) rename guacamole/src/main/webapp/app/client/{services/clientProperties.js => types/ClientProperties.js} (74%) diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js index f03e0d1af..dc39fc28b 100644 --- a/guacamole/src/main/webapp/app/client/controllers/clientController.js +++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js @@ -135,7 +135,7 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', // Get DAO for reading connections and groups var connectionGroupDAO = $injector.get('connectionGroupDAO'); var connectionDAO = $injector.get('connectionDAO'); - var ClientProperties = $injector.get('clientProperties'); + var ClientProperties = $injector.get('ClientProperties'); // Client settings and state $scope.clientProperties = new ClientProperties(); diff --git a/guacamole/src/main/webapp/app/client/directives/guacClient.js b/guacamole/src/main/webapp/app/client/directives/guacClient.js index 9ec3b2c39..2fb05307a 100644 --- a/guacamole/src/main/webapp/app/client/directives/guacClient.js +++ b/guacamole/src/main/webapp/app/client/directives/guacClient.js @@ -30,12 +30,29 @@ angular.module('client').directive('guacClient', [function guacClient() { restrict: 'E', replace: true, scope: { - // Parameters for controlling client state - clientProperties : '=', + + /** + * Parameters for controlling client state. + * + * @type ClientProperties|Object + */ + clientProperties : '=', - // Parameters for initially connecting - id : '=', - connectionParameters : '=' + /** + * The ID of the Guacamole connection to connect to. + * + * @type String + */ + id : '=', + + /** + * Arbitrary URL-encoded parameters to append to the connection + * string when connecting. + * + * @type String + */ + connectionParameters : '=' + }, templateUrl: 'app/client/templates/guacClient.html', controller: ['$scope', '$injector', '$element', function guacClientController($scope, $injector, $element) { diff --git a/guacamole/src/main/webapp/app/client/services/clientProperties.js b/guacamole/src/main/webapp/app/client/types/ClientProperties.js similarity index 74% rename from guacamole/src/main/webapp/app/client/services/clientProperties.js rename to guacamole/src/main/webapp/app/client/types/ClientProperties.js index a6ae11397..7c08b360e 100644 --- a/guacamole/src/main/webapp/app/client/services/clientProperties.js +++ b/guacamole/src/main/webapp/app/client/types/ClientProperties.js @@ -23,14 +23,20 @@ /** * A service for generating new guacClient properties objects. */ -angular.module('client').factory('clientProperties', [function clientProperties() { +angular.module('client').factory('ClientProperties', [function defineClientProperties() { /** * Object used for interacting with a guacClient directive. * * @constructor + * @param {ClientProperties|Object} [template={}] + * The object whose properties should be copied within the new + * ClientProperties. */ - return function() { + var ClientProperties = function ClientProperties(template) { + + // Use empty object by default + template = template || {}; /** * Whether the display should be scaled automatically to fit within the @@ -38,7 +44,7 @@ angular.module('client').factory('clientProperties', [function clientProperties( * * @type Boolean */ - this.autoFit = true; + this.autoFit = template.autoFit || true; /** * The current scale. If autoFit is true, the effect of setting this @@ -46,28 +52,28 @@ angular.module('client').factory('clientProperties', [function clientProperties( * * @type Number */ - this.scale = 1; + this.scale = template.scale || 1; /** * The minimum scale value. * * @type Number */ - this.minScale = 1; + this.minScale = template.minScale || 1; /** * The maximum scale value. * * @type Number */ - this.maxScale = 3; + this.maxScale = template.maxScale || 3; /** * Whether or not the client should listen to keyboard events. * * @type Boolean */ - this.keyboardEnabled = true; + this.keyboardEnabled = template.keyboardEnabled || true; /** * Whether translation of touch to mouse events should emulate an @@ -75,8 +81,10 @@ angular.module('client').factory('clientProperties', [function clientProperties( * * @type Boolean */ - this.emulateAbsoluteMouse = true; + this.emulateAbsoluteMouse = template.emulateAbsoluteMouse || true; }; + return ClientProperties; + }]); \ No newline at end of file From 9d3d5008393c7ca4cf003f785e972f9de99b0e20 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 8 Dec 2014 14:26:30 -0800 Subject: [PATCH 11/11] GUAC-932: Explicitly handle lack of auth data. Document return values for such a case. --- .../app/auth/service/authenticationService.js | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/guacamole/src/main/webapp/app/auth/service/authenticationService.js b/guacamole/src/main/webapp/app/auth/service/authenticationService.js index 4430e7913..aafe92d2d 100644 --- a/guacamole/src/main/webapp/app/auth/service/authenticationService.js +++ b/guacamole/src/main/webapp/app/auth/service/authenticationService.js @@ -71,24 +71,43 @@ angular.module('auth').factory('authenticationService', ['$http', '$cookieStore' }; /** - * Returns the user ID of the current user. + * Returns the user ID of the current user. If the current user is not + * logged in, this ID may not be valid. * - * @returns {String} The user ID of the current user. + * @returns {String} + * The user ID of the current user, or null if no authentication data + * is present. */ service.getCurrentUserID = function getCurrentUserID() { + + // Return user ID, if available var authData = $cookieStore.get(AUTH_COOKIE_ID); - return authData && authData.userID; + if (authData) + return authData.userID; + + // No auth data present + return null; + }; /** * Returns the auth token associated with the current user. If the current * user is not logged in, this token may not be valid. * - * @returns {String} The auth token associated with the current user. + * @returns {String} + * The auth token associated with the current user, or null if no + * authentication data is present. */ service.getCurrentToken = function getCurrentToken() { + + // Return auth token, if available var authData = $cookieStore.get(AUTH_COOKIE_ID); - return authData && authData.authToken; + if (authData) + return authData.authToken; + + // No auth data present + return null; + }; return service;