From 670ec390b5f33644d854d8bb98e9cff00db381db Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 24 Apr 2018 14:42:18 -0700 Subject: [PATCH 01/18] GUACAMOLE-526: Ignore failure to read/write clipboard. --- .../main/webapp/app/client/controllers/clientController.js | 6 +++--- .../main/webapp/app/index/controllers/indexController.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js index 8a2f935ed..e89f241ca 100644 --- a/guacamole/src/main/webapp/app/client/controllers/clientController.js +++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js @@ -447,7 +447,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams // Sync local clipboard as long as the menu is not open if (!$scope.menu.shown) - clipboardService.setLocalClipboard(data); + clipboardService.setLocalClipboard(data)['catch'](angular.noop); // Associate new clipboard data with any currently-pressed key for (var keysym in keysCurrentlyPressed) @@ -576,7 +576,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams // key was pressed (if any) as long as the menu is not open var clipboardData = clipboardDataFromKey[keysym]; if (clipboardData && !$scope.menu.shown) - clipboardService.setLocalClipboard(clipboardData); + clipboardService.setLocalClipboard(clipboardData)['catch'](angular.noop); // Deal with substitute key presses if (substituteKeysPressed[keysym]) { @@ -714,7 +714,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams // Sync with local clipboard clipboardService.getLocalClipboard().then(function clipboardRead(data) { $scope.$broadcast('guacClipboard', data); - }); + })['catch'](angular.noop); // Hide status notification guacNotification.showStatus(false); diff --git a/guacamole/src/main/webapp/app/index/controllers/indexController.js b/guacamole/src/main/webapp/app/index/controllers/indexController.js index ed142333e..c83e2096b 100644 --- a/guacamole/src/main/webapp/app/index/controllers/indexController.js +++ b/guacamole/src/main/webapp/app/index/controllers/indexController.js @@ -137,7 +137,7 @@ angular.module('index').controller('indexController', ['$scope', '$injector', var checkClipboard = function checkClipboard() { clipboardService.getLocalClipboard().then(function clipboardRead(data) { $scope.$broadcast('guacClipboard', data); - }); + })['catch'](angular.noop); }; // Attempt to read the clipboard if it may have changed From 01e19c19dcd9882128f14b2c6286dea3a62878c3 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 24 Apr 2018 14:43:54 -0700 Subject: [PATCH 02/18] GUACAMOLE-526: Maintain full correct chain of promises for clipboard read attempts, including the "finally" used for cleanup. The "finally" handler creates a new promise with a potentially unhandled rejection otherwise. --- .../clipboard/services/clipboardService.js | 137 +++++++++--------- 1 file changed, 68 insertions(+), 69 deletions(-) diff --git a/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js b/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js index 920006e5a..9000c1848 100644 --- a/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js +++ b/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js @@ -415,81 +415,79 @@ angular.module('clipboard').factory('clipboardService', ['$injector', var deferred = $q.defer(); - // Mark read attempt as in progress - pendingRead = deferred.promise; + // Track the originally-focused element prior to changing focus + var originalElement = document.activeElement; + + /** + * Attempts to paste the clipboard contents into the + * currently-focused element. The promise related to the current + * attempt to read the clipboard will be resolved or rejected + * depending on whether the attempt to paste succeeds. + */ + var performPaste = function performPaste() { + + // Attempt paste local clipboard into clipboard DOM element + if (document.execCommand('paste')) { + + // If the pasted data is a single image, resolve with a blob + // containing that image + var currentImage = service.getImageContent(clipboardContent); + if (currentImage) { + + // Convert the image's data URL into a blob + var blob = service.parseDataURL(currentImage); + if (blob) { + deferred.resolve(new ClipboardData({ + type : blob.type, + data : blob + })); + } + + // Reject if conversion fails + else + deferred.reject(); + + } // end if clipboard is an image + + // Otherwise, assume the clipboard contains plain text + else + deferred.resolve(new ClipboardData({ + type : 'text/plain', + data : clipboardContent.value + })); + + } + + // Otherwise, reading from the clipboard has failed + else + deferred.reject(); + + }; + + // Mark read attempt as in progress, cleaning up event listener and + // selection once the paste attempt has completed + pendingRead = deferred.promise['finally'](function cleanupReadAttempt() { + + // Do not use future changes in focus + clipboardContent.removeEventListener('focus', performPaste); + + // Unfocus the clipboard DOM event to avoid mobile keyboard opening, + // restoring whichever element was originally focused + clipboardContent.blur(); + originalElement.focus(); + popSelection(); + + // No read is pending any longer + pendingRead = null; + + }); // Wait for the next event queue run before attempting to read // clipboard data (in case the copy/cut has not yet completed) $window.setTimeout(function deferredClipboardRead() { - // Track the originally-focused element prior to changing focus - var originalElement = document.activeElement; pushSelection(); - /** - * Attempts to paste the clipboard contents into the - * currently-focused element. The promise related to the current - * attempt to read the clipboard will be resolved or rejected - * depending on whether the attempt to paste succeeds. - */ - var performPaste = function performPaste() { - - // Attempt paste local clipboard into clipboard DOM element - if (document.execCommand('paste')) { - - // If the pasted data is a single image, resolve with a blob - // containing that image - var currentImage = service.getImageContent(clipboardContent); - if (currentImage) { - - // Convert the image's data URL into a blob - var blob = service.parseDataURL(currentImage); - if (blob) { - deferred.resolve(new ClipboardData({ - type : blob.type, - data : blob - })); - } - - // Reject if conversion fails - else - deferred.reject(); - - } // end if clipboard is an image - - // Otherwise, assume the clipboard contains plain text - else - deferred.resolve(new ClipboardData({ - type : 'text/plain', - data : clipboardContent.value - })); - - } - - // Otherwise, reading from the clipboard has failed - else - deferred.reject(); - - }; - - // Clean up event listener and selection once the paste attempt has - // completed - deferred.promise['finally'](function cleanupReadAttempt() { - - // Do not use future changes in focus - clipboardContent.removeEventListener('focus', performPaste); - - // Unfocus the clipboard DOM event to avoid mobile keyboard opening, - // restoring whichever element was originally focused - clipboardContent.blur(); - originalElement.focus(); - popSelection(); - - // No read is pending any longer - pendingRead = null; - - }); - // Ensure clipboard element is blurred (and that the "focus" event // will fire) clipboardContent.blur(); @@ -506,7 +504,8 @@ angular.module('clipboard').factory('clipboardService', ['$injector', }, CLIPBOARD_READ_DELAY); - return deferred.promise; + return pendingRead; + }; return service; From 5be303666a226a53e956f8984054993c9b7a2dd4 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 24 Apr 2018 15:22:31 -0700 Subject: [PATCH 03/18] GUACAMOLE-526: Remove unnecessary use of $q within authenticationService. Rely on requestService. --- .../src/main/webapp/app/auth/authModule.js | 1 + .../app/auth/service/authenticationService.js | 62 +++++-------------- 2 files changed, 16 insertions(+), 47 deletions(-) diff --git a/guacamole/src/main/webapp/app/auth/authModule.js b/guacamole/src/main/webapp/app/auth/authModule.js index 7faaf87f7..84c3e3317 100644 --- a/guacamole/src/main/webapp/app/auth/authModule.js +++ b/guacamole/src/main/webapp/app/auth/authModule.js @@ -21,5 +21,6 @@ * The module for authentication and management of tokens. */ angular.module('auth', [ + 'rest', 'storage' ]); diff --git a/guacamole/src/main/webapp/app/auth/service/authenticationService.js b/guacamole/src/main/webapp/app/auth/service/authenticationService.js index 7f74feaf9..f4a24f985 100644 --- a/guacamole/src/main/webapp/app/auth/service/authenticationService.js +++ b/guacamole/src/main/webapp/app/auth/service/authenticationService.js @@ -46,10 +46,9 @@ angular.module('auth').factory('authenticationService', ['$injector', var Error = $injector.get('Error'); // Required services - var $http = $injector.get('$http'); - var $q = $injector.get('$q'); var $rootScope = $injector.get('$rootScope'); var localStorageService = $injector.get('localStorageService'); + var requestService = $injector.get('requestService'); var service = {}; @@ -155,27 +154,8 @@ angular.module('auth').factory('authenticationService', ['$injector', */ service.authenticate = function authenticate(parameters) { - var authenticationProcess = $q.defer(); - - /** - * Stores the given authentication data within the browser and marks - * the authentication process as completed. - * - * @param {Object} data - * The authentication data returned by the token REST endpoint. - */ - var completeAuthentication = function completeAuthentication(data) { - - // Store auth data - setAuthenticationResult(new AuthenticationResult(data)); - - // Process is complete - authenticationProcess.resolve(); - - }; - // Attempt authentication - $http({ + return requestService({ method: 'POST', url: 'api/tokens', headers: { @@ -185,44 +165,32 @@ angular.module('auth').factory('authenticationService', ['$injector', }) // If authentication succeeds, handle received auth data - .then(function authenticationSuccessful(response) { - var data = response.data; + .then(function authenticationSuccessful(data) { var currentToken = service.getCurrentToken(); + setAuthenticationResult(new AuthenticationResult(data)); // If a new token was received, ensure the old token is invalidated, // if any, and notify listeners of the new token if (data.authToken !== currentToken) { - // If an old token existed, explicitly logout first + // If an old token existed, request that the token be revoked if (currentToken) { - service.logout() - ['finally'](function logoutComplete() { - completeAuthentication(data); - $rootScope.$broadcast('guacLogin', data.authToken); - }); + service.logout().catch(angular.noop) } - // Otherwise, simply complete authentication and notify of login - else { - completeAuthentication(data); - $rootScope.$broadcast('guacLogin', data.authToken); - } + // Notify of login and new token + $rootScope.$broadcast('guacLogin', data.authToken); } - // Otherwise, just finish the auth process - else - completeAuthentication(data); + // Authentication was successful + return data; }) // If authentication fails, propogate failure to returned promise - ['catch'](function authenticationFailed(response) { - - // Ensure error object exists, even if the error response is not - // coming from the authentication REST endpoint - var error = new Error(response.data); + ['catch'](function authenticationFailed(error) { // Request credentials if provided credentials were invalid if (error.type === Error.Type.INVALID_CREDENTIALS) @@ -232,10 +200,10 @@ angular.module('auth').factory('authenticationService', ['$injector', else if (error.type === Error.Type.INSUFFICIENT_CREDENTIALS) $rootScope.$broadcast('guacInsufficientCredentials', parameters, error); - authenticationProcess.reject(error); - }); + // Authentication failed + throw error; - return authenticationProcess.promise; + }); }; @@ -317,7 +285,7 @@ angular.module('auth').factory('authenticationService', ['$injector', $rootScope.$broadcast('guacLogout', token); // Delete old token - return $http({ + return requestService({ method: 'DELETE', url: 'api/tokens/' + token }); From 6cd07052d8adcca16dc7762aaf54a8a9040016f7 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 24 Apr 2018 15:26:22 -0700 Subject: [PATCH 04/18] GUACAMOLE-526: Remove unused injections of $q. --- .../src/main/webapp/app/rest/services/connectionGroupService.js | 1 - guacamole/src/main/webapp/app/rest/services/permissionService.js | 1 - guacamole/src/main/webapp/app/rest/services/userService.js | 1 - 3 files changed, 3 deletions(-) diff --git a/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js b/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js index b7e78e3ed..c6d68be29 100644 --- a/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js +++ b/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js @@ -25,7 +25,6 @@ angular.module('rest').factory('connectionGroupService', ['$injector', // Required services var requestService = $injector.get('requestService'); - var $q = $injector.get('$q'); var authenticationService = $injector.get('authenticationService'); var cacheService = $injector.get('cacheService'); diff --git a/guacamole/src/main/webapp/app/rest/services/permissionService.js b/guacamole/src/main/webapp/app/rest/services/permissionService.js index ec0a0d536..6d3dfdf5e 100644 --- a/guacamole/src/main/webapp/app/rest/services/permissionService.js +++ b/guacamole/src/main/webapp/app/rest/services/permissionService.js @@ -25,7 +25,6 @@ angular.module('rest').factory('permissionService', ['$injector', // Required services var requestService = $injector.get('requestService'); - var $q = $injector.get('$q'); var authenticationService = $injector.get('authenticationService'); var cacheService = $injector.get('cacheService'); diff --git a/guacamole/src/main/webapp/app/rest/services/userService.js b/guacamole/src/main/webapp/app/rest/services/userService.js index bbf5fcf4b..f05ab312e 100644 --- a/guacamole/src/main/webapp/app/rest/services/userService.js +++ b/guacamole/src/main/webapp/app/rest/services/userService.js @@ -25,7 +25,6 @@ angular.module('rest').factory('userService', ['$injector', // Required services var requestService = $injector.get('requestService'); - var $q = $injector.get('$q'); var authenticationService = $injector.get('authenticationService'); var cacheService = $injector.get('cacheService'); From bba01bdbc45c74d4b8aafc59223bd16cf2bf5392 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 24 Apr 2018 15:27:18 -0700 Subject: [PATCH 05/18] GUACAMOLE-526: Explicitly fail routing if attempting to update the auth token fails. --- .../src/main/webapp/app/index/config/indexRouteConfig.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js b/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js index d66226694..47bc48e55 100644 --- a/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js +++ b/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js @@ -102,6 +102,10 @@ angular.module('index').config(['$routeProvider', '$locationProvider', route.resolve(); }); + }) + + ['catch'](function tokenUpdateFailed() { + route.reject(); }); // Return promise that will resolve only if the requested page is the From 73eb25f3118d36d4b80f5ac58a01b15b77980e00 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 24 Apr 2018 15:30:58 -0700 Subject: [PATCH 06/18] GUACAMOLE-526: Remove unused (and wrong!) getAllActiveConnections() function. Remove now-unnecessary injection of $q. --- .../rest/services/activeConnectionService.js | 63 ------------------- 1 file changed, 63 deletions(-) diff --git a/guacamole/src/main/webapp/app/rest/services/activeConnectionService.js b/guacamole/src/main/webapp/app/rest/services/activeConnectionService.js index b548c86e3..5354bc1c8 100644 --- a/guacamole/src/main/webapp/app/rest/services/activeConnectionService.js +++ b/guacamole/src/main/webapp/app/rest/services/activeConnectionService.js @@ -25,7 +25,6 @@ angular.module('rest').factory('activeConnectionService', ['$injector', // Required services var requestService = $injector.get('requestService'); - var $q = $injector.get('$q'); var authenticationService = $injector.get('authenticationService'); var service = {}; @@ -66,68 +65,6 @@ angular.module('rest').factory('activeConnectionService', ['$injector', }; - /** - * Returns a promise which resolves with all active connections accessible - * by the current user, as a map of @link{ActiveConnection} maps, as would - * be returned by getActiveConnections(), grouped by the identifier of - * their corresponding data source. All given data sources are queried. If - * an error occurs while retrieving any ActiveConnection map, the promise - * will be rejected. - * - * @param {String[]} dataSources - * The unique identifier of the data sources containing the active - * connections to be retrieved. These identifiers correspond to - * AuthenticationProviders within the Guacamole web application. - * - * @param {String[]} [permissionTypes] - * The set of permissions to filter with. A user must have one or more - * of these permissions for an active connection to appear in the - * result. If null, no filtering will be performed. Valid values are - * listed within PermissionSet.ObjectType. - * - * @returns {Promise.>>} - * A promise which resolves with all active connections available to - * the current user, as a map of ActiveConnection maps, as would be - * returned by getActiveConnections(), grouped by the identifier of - * their corresponding data source. - */ - service.getAllActiveConnections = function getAllActiveConnections(dataSources, permissionTypes) { - - var deferred = $q.defer(); - - var activeConnectionRequests = []; - var activeConnectionMaps = {}; - - // Retrieve all active connections from all data sources - angular.forEach(dataSources, function retrieveActiveConnections(dataSource) { - activeConnectionRequests.push( - service.getActiveConnections(dataSource, permissionTypes) - .then(function activeConnectionsRetrieved(activeConnections) { - activeConnectionMaps[dataSource] = activeConnections; - }) - ); - }); - - // Resolve when all requests are completed - $q.all(activeConnectionRequests) - .then( - - // All requests completed successfully - function allActiveConnectionsRetrieved() { - deferred.resolve(userArrays); - }, - - // At least one request failed - function activeConnectionRetrievalFailed(e) { - deferred.reject(e); - } - - ); - - return deferred.promise; - - }; - /** * Makes a request to the REST API to delete the active connections having * the given identifiers, effectively disconnecting them, returning a From f6f66eec0a9b503b2cb594d6d226cc1ade9dc15c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 26 Apr 2018 19:03:53 -0700 Subject: [PATCH 07/18] GUACAMOLE-526: Ignore failure to load OSK layout. --- guacamole/src/main/webapp/app/osk/directives/guacOsk.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guacamole/src/main/webapp/app/osk/directives/guacOsk.js b/guacamole/src/main/webapp/app/osk/directives/guacOsk.js index 968e86e00..2236ac852 100644 --- a/guacamole/src/main/webapp/app/osk/directives/guacOsk.js +++ b/guacamole/src/main/webapp/app/osk/directives/guacOsk.js @@ -113,7 +113,7 @@ angular.module('osk').directive('guacOsk', [function guacOsk() { $rootScope.$broadcast('guacSyntheticKeyup', keysym); }; - }); + }, angular.noop); } From 2d03387b6bb4a44bbf95a903cf98693f7edcb71c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 26 Apr 2018 19:20:43 -0700 Subject: [PATCH 08/18] GUACAMOLE-526: Add missing semicolon. --- guacamole/src/main/webapp/app/rest/services/requestService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guacamole/src/main/webapp/app/rest/services/requestService.js b/guacamole/src/main/webapp/app/rest/services/requestService.js index e06b0d7a5..be6e1e1ab 100644 --- a/guacamole/src/main/webapp/app/rest/services/requestService.js +++ b/guacamole/src/main/webapp/app/rest/services/requestService.js @@ -41,7 +41,7 @@ angular.module('rest').factory('requestService', ['$q', '$http', 'Error', function success(request) { return request.data; }, function failure(request) { throw new Error(request.data); } ); - } + }; return wrappedHttpCall; }]); From 8c9735d1e7da1a33928b70b4be6659dbf4bc711a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 26 Apr 2018 20:57:18 -0700 Subject: [PATCH 09/18] GUACAMOLE-526: Wrap HTTP response in Error object only if it's an actual HTTP response object. --- .../app/rest/services/requestService.js | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/guacamole/src/main/webapp/app/rest/services/requestService.js b/guacamole/src/main/webapp/app/rest/services/requestService.js index be6e1e1ab..f17ce251a 100644 --- a/guacamole/src/main/webapp/app/rest/services/requestService.js +++ b/guacamole/src/main/webapp/app/rest/services/requestService.js @@ -21,27 +21,47 @@ * Service for converting $http promises that pass the entire response into * promises that pass only the data from that response. */ -angular.module('rest').factory('requestService', ['$q', '$http', 'Error', - function requestService($q, $http, Error) { +angular.module('rest').factory('requestService', ['$injector', + function requestService($injector) { + + // Required services + var $http = $injector.get('$http'); + + // Required types + var Error = $injector.get('Error'); /** * Given a configuration object formatted for the $http service, returns - * a promise that will resolve or reject with only the data from the $http - * response. + * a promise that will resolve or reject with the data from the HTTP + * response. If the promise is rejected due to the HTTP response indicating + * failure, the promise will be rejected strictly with an instance of an + * @link{Error} object. * * @param {Object} object * Configuration object for $http service call. * - * @returns {Promise} - * A promise that will resolve or reject with the data from the response - * to the $http call. + * @returns {Promise.} + * A promise that will resolve with the data from the HTTP response for + * the underlying $http call if successful, or reject with an @link{Error} + * describing the failure. */ - var wrappedHttpCall = function wrappedHttpCall(object) { + var service = function wrapHttpServiceCall(object) { return $http(object).then( - function success(request) { return request.data; }, - function failure(request) { throw new Error(request.data); } + function success(response) { return response.data; }, + function failure(response) { + + // Wrap true error responses from $http within REST Error objects + if (response.data) + throw new Error(response.data); + + // The value provided is not actually a response object from + // the $http service + throw response; + + } ); }; - return wrappedHttpCall; + return service; + }]); From c30b7b0d8030e6b91f102f12df160c63a72b76c5 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 26 Apr 2018 21:11:51 -0700 Subject: [PATCH 10/18] GUACAMOLE-526: Add convenience function for generating promise callbacks which handle only REST errors. --- .../app/rest/services/requestService.js | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/guacamole/src/main/webapp/app/rest/services/requestService.js b/guacamole/src/main/webapp/app/rest/services/requestService.js index f17ce251a..b2f709b14 100644 --- a/guacamole/src/main/webapp/app/rest/services/requestService.js +++ b/guacamole/src/main/webapp/app/rest/services/requestService.js @@ -26,6 +26,7 @@ angular.module('rest').factory('requestService', ['$injector', // Required services var $http = $injector.get('$http'); + var $log = $injector.get('$log'); // Required types var Error = $injector.get('Error'); @@ -62,6 +63,34 @@ angular.module('rest').factory('requestService', ['$injector', ); }; - return service; + /** + * Creates a promise error callback which invokes the given callback only + * if the promise was rejected with a REST @link{Error} object. If the + * promise is rejected without an @link{Error} object, such as when a + * JavaScript error occurs within a callback earlier in the promise chain, + * the rejection is logged without invoking the given callback. + * + * @param {Function} callback + * The callback to invoke if the promise is rejected with an + * @link{Error} object. + * + * @returns {Function} + * A function which can be provided as the error callback for a + * promise. + */ + service.createErrorCallback = function createErrorCallback(callback) { + return (function generatedErrorCallback(error) { + + // Invoke given callback ONLY if due to a legitimate REST error + if (error instanceof Error) + return callback(error); + + // Log all other errors + $log.error(error); + + }); + }; + + return service; }]); From cc6ade49171627cd89075ab91cde9317196989c2 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 26 Apr 2018 21:19:22 -0700 Subject: [PATCH 11/18] GUACAMOLE-526: Add convenience callbacks for ignoring or (quietly) warning of REST errors. --- .../app/rest/services/requestService.js | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/guacamole/src/main/webapp/app/rest/services/requestService.js b/guacamole/src/main/webapp/app/rest/services/requestService.js index b2f709b14..9aef12486 100644 --- a/guacamole/src/main/webapp/app/rest/services/requestService.js +++ b/guacamole/src/main/webapp/app/rest/services/requestService.js @@ -91,6 +91,30 @@ angular.module('rest').factory('requestService', ['$injector', }); }; - return service; + /** + * Promise error callback which ignores all rejections due to REST errors, + * but logs all other rejections, such as those due to JavaScript errors. + * This callback should be used in favor of angular.noop in cases where + * a REST response is being handled but REST errors should be ignored. + * + * @constant + * @type Function + */ + service.IGNORE = service.createErrorCallback(angular.noop); + + /** + * Promise error callback which logs all rejections due to REST errors as + * warnings to the browser console, and logs all other rejections as + * errors. This callback should be used in favor of angular.noop or + * @link{IGNORE} if REST errors are simply not expected. + * + * @constant + * @type Function + */ + service.WARN = service.createErrorCallback(function warnRequestFailed(error) { + $log.warn(error.type, error.message || error.translatableMessage); + }); + + return service; }]); From 1e5e9b607b56e8cf0fd15b9b58337baa6c0652aa Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 26 Apr 2018 21:20:05 -0700 Subject: [PATCH 12/18] GUACAMOLE-526: Add convenience function for displaying modal notifications for REST errors. --- .../notification/services/guacNotification.js | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/guacamole/src/main/webapp/app/notification/services/guacNotification.js b/guacamole/src/main/webapp/app/notification/services/guacNotification.js index c3d4a2bee..a1b8d1016 100644 --- a/guacamole/src/main/webapp/app/notification/services/guacNotification.js +++ b/guacamole/src/main/webapp/app/notification/services/guacNotification.js @@ -37,6 +37,19 @@ angular.module('notification').factory('guacNotification', ['$injector', */ var storedStatus = sessionStorageFactory.create(false); + /** + * An action to be provided along with the object sent to showStatus which + * closes the currently-shown status dialog. + * + * @type NotificationAction + */ + service.ACKNOWLEDGE_ACTION = { + name : 'APP.ACTION_ACKNOWLEDGE', + callback : function acknowledgeCallback() { + service.showStatus(false); + } + }; + /** * Retrieves the current status notification, which may simply be false if * no status is currently shown. @@ -79,6 +92,28 @@ angular.module('notification').factory('guacNotification', ['$injector', storedStatus(status); }; + /** + * Shows the given REST error response as a modal status. If a status + * notification is already currently shown, this function will have no + * effect. + * + * @param {Error} error + * The error object returned from the failed REST request. + * + * @example + * + * someService.updateObject(object) + * ['catch'](guacNotification.showRequestError); + */ + service.showRequestError = function showRequestError(error) { + service.showStatus({ + className : 'error', + title : 'APP.DIALOG_HEADER_ERROR', + text : error.translatableMessage, + actions : [ service.ACKNOWLEDGE_ACTION ] + }); + }; + // Hide status upon navigation $rootScope.$on('$routeChangeSuccess', function() { service.showStatus(false); From f6d5e5662b643e0521a3069c14639ca03a9a0714 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 26 Apr 2018 21:20:31 -0700 Subject: [PATCH 13/18] GUACAMOLE-526: Add convenience callback for displaying modal notifications strictly for REST errors, while logging all other promise rejections. --- .../src/main/webapp/app/rest/restModule.js | 5 ++++- .../webapp/app/rest/services/requestService.js | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/guacamole/src/main/webapp/app/rest/restModule.js b/guacamole/src/main/webapp/app/rest/restModule.js index 81b64b53d..6672507dc 100644 --- a/guacamole/src/main/webapp/app/rest/restModule.js +++ b/guacamole/src/main/webapp/app/rest/restModule.js @@ -21,4 +21,7 @@ * The module for code relating to communication with the REST API of the * Guacamole web application. */ -angular.module('rest', ['auth']); +angular.module('rest', [ + 'auth', + 'notification' +]); diff --git a/guacamole/src/main/webapp/app/rest/services/requestService.js b/guacamole/src/main/webapp/app/rest/services/requestService.js index 9aef12486..a27ccc3bb 100644 --- a/guacamole/src/main/webapp/app/rest/services/requestService.js +++ b/guacamole/src/main/webapp/app/rest/services/requestService.js @@ -25,8 +25,9 @@ angular.module('rest').factory('requestService', ['$injector', function requestService($injector) { // Required services - var $http = $injector.get('$http'); - var $log = $injector.get('$log'); + var $http = $injector.get('$http'); + var $log = $injector.get('$log'); + var guacNotification = $injector.get('guacNotification'); // Required types var Error = $injector.get('Error'); @@ -115,6 +116,19 @@ angular.module('rest').factory('requestService', ['$injector', $log.warn(error.type, error.message || error.translatableMessage); }); + /** + * Promise error callback which displays a modal notification for all + * rejections due to REST errors. The message displayed to the user within + * the notification is provided by the contents of the @link{Error} object + * within the REST response. All other rejections, such as those due to + * JavaScript errors, are logged to the browser console without displaying + * any notification. + * + * @constant + * @type Function + */ + service.SHOW_NOTIFICATION = service.createErrorCallback(guacNotification.showRequestError); + return service; }]); From 266b445c217984fdb59048c09b736098776f6a1f Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 26 Apr 2018 22:15:17 -0700 Subject: [PATCH 14/18] GUACAMOLE-526: Handle rejections for absolutely all promises. --- .../app/auth/service/authenticationService.js | 4 +- .../client/controllers/clientController.js | 12 ++-- .../app/client/directives/guacFileBrowser.js | 2 +- .../webapp/app/client/types/ManagedClient.js | 7 ++- .../app/client/types/ManagedFileUpload.js | 7 ++- .../webapp/app/form/directives/formField.js | 5 +- .../app/groupList/directives/guacGroupList.js | 3 +- .../app/home/controllers/homeController.js | 3 +- .../app/index/controllers/indexController.js | 2 +- .../app/list/directives/guacUserItem.js | 2 +- .../main/webapp/app/login/directives/login.js | 5 +- .../controllers/manageConnectionController.js | 57 +++++-------------- .../manageConnectionGroupController.js | 45 +++------------ .../manageSharingProfileController.js | 53 ++++------------- .../controllers/manageUserController.js | 56 +++++------------- .../app/navigation/directives/guacUserMenu.js | 7 ++- .../navigation/services/userPageService.js | 15 ++--- .../app/rest/services/dataSourceService.js | 13 +++-- .../guacSettingsConnectionHistory.js | 7 ++- .../directives/guacSettingsConnections.js | 17 +----- .../directives/guacSettingsPreferences.js | 21 ++----- .../directives/guacSettingsSessions.js | 31 ++-------- .../settings/directives/guacSettingsUsers.js | 20 ++----- 23 files changed, 118 insertions(+), 276 deletions(-) diff --git a/guacamole/src/main/webapp/app/auth/service/authenticationService.js b/guacamole/src/main/webapp/app/auth/service/authenticationService.js index f4a24f985..a5c3ee45c 100644 --- a/guacamole/src/main/webapp/app/auth/service/authenticationService.js +++ b/guacamole/src/main/webapp/app/auth/service/authenticationService.js @@ -190,7 +190,7 @@ angular.module('auth').factory('authenticationService', ['$injector', }) // If authentication fails, propogate failure to returned promise - ['catch'](function authenticationFailed(error) { + ['catch'](requestService.createErrorCallback(function authenticationFailed(error) { // Request credentials if provided credentials were invalid if (error.type === Error.Type.INVALID_CREDENTIALS) @@ -203,7 +203,7 @@ angular.module('auth').factory('authenticationService', ['$injector', // Authentication failed throw error; - }); + })); }; diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js index e89f241ca..cf4ba5b16 100644 --- a/guacamole/src/main/webapp/app/client/controllers/clientController.js +++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js @@ -37,6 +37,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams var guacNotification = $injector.get('guacNotification'); var iconService = $injector.get('iconService'); var preferenceService = $injector.get('preferenceService'); + var requestService = $injector.get('requestService'); var tunnelService = $injector.get('tunnelService'); var userPageService = $injector.get('userPageService'); @@ -161,7 +162,9 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams name : "CLIENT.ACTION_LOGOUT", className : "logout button", callback : function logoutCallback() { - authenticationService.logout()['finally'](function logoutComplete() { + authenticationService.logout() + ['catch'](requestService.IGNORE) + ['finally'](function logoutComplete() { $location.url('/'); }); } @@ -188,7 +191,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams }; } - }); + }, requestService.WARN); /** * Action which replaces the current client with a newly-connected client. @@ -466,7 +469,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams tunnelService.getSharingProfiles(uuid) .then(function sharingProfilesRetrieved(sharingProfiles) { $scope.sharingProfiles = sharingProfiles; - }); + }, requestService.WARN); }); @@ -614,6 +617,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams // Re-authenticate to verify auth status at end of connection authenticationService.updateCurrentToken($location.search()) + ['catch'](requestService.IGNORE) // Show the requested status once the authentication check has finished ['finally'](function authenticationCheckComplete() { @@ -714,7 +718,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams // Sync with local clipboard clipboardService.getLocalClipboard().then(function clipboardRead(data) { $scope.$broadcast('guacClipboard', data); - })['catch'](angular.noop); + }, angular.noop); // Hide status notification guacNotification.showStatus(false); diff --git a/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js b/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js index 5789d5fdb..fe1a5e249 100644 --- a/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js +++ b/guacamole/src/main/webapp/app/client/directives/guacFileBrowser.js @@ -272,7 +272,7 @@ angular.module('client').directive('guacFileBrowser', [function guacFileBrowser( }); - }); // end retrieve file template + }, angular.noop); // end retrieve file template // Refresh file browser when any upload completes $scope.$on('guacUploadComplete', function uploadComplete(event, filename) { diff --git a/guacamole/src/main/webapp/app/client/types/ManagedClient.js b/guacamole/src/main/webapp/app/client/types/ManagedClient.js index 32acd8d17..e9984450a 100644 --- a/guacamole/src/main/webapp/app/client/types/ManagedClient.js +++ b/guacamole/src/main/webapp/app/client/types/ManagedClient.js @@ -42,6 +42,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', var authenticationService = $injector.get('authenticationService'); var connectionGroupService = $injector.get('connectionGroupService'); var connectionService = $injector.get('connectionService'); + var requestService = $injector.get('requestService'); var tunnelService = $injector.get('tunnelService'); var guacAudio = $injector.get('guacAudio'); var guacHistory = $injector.get('guacHistory'); @@ -514,7 +515,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', connectionService.getConnection(clientIdentifier.dataSource, clientIdentifier.id) .then(function connectionRetrieved(connection) { managedClient.name = managedClient.title = connection.name; - }); + }, requestService.WARN); } // If using a connection group, pull connection name @@ -522,7 +523,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', connectionGroupService.getConnectionGroup(clientIdentifier.dataSource, clientIdentifier.id) .then(function connectionGroupRetrieved(group) { managedClient.name = managedClient.title = group.name; - }); + }, requestService.WARN); } return managedClient; @@ -634,7 +635,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', credentialRequest.then(function sharingCredentialsReceived(sharingCredentials) { client.shareLinks[sharingProfile.identifier] = ManagedShareLink.getInstance(sharingProfile, sharingCredentials); - }); + }, requestService.WARN); return credentialRequest; diff --git a/guacamole/src/main/webapp/app/client/types/ManagedFileUpload.js b/guacamole/src/main/webapp/app/client/types/ManagedFileUpload.js index 82bface4d..56587fcd0 100644 --- a/guacamole/src/main/webapp/app/client/types/ManagedFileUpload.js +++ b/guacamole/src/main/webapp/app/client/types/ManagedFileUpload.js @@ -28,7 +28,8 @@ angular.module('client').factory('ManagedFileUpload', ['$rootScope', '$injector' var ManagedFileTransferState = $injector.get('ManagedFileTransferState'); // Required services - var tunnelService = $injector.get('tunnelService'); + var requestService = $injector.get('requestService'); + var tunnelService = $injector.get('tunnelService'); /** * Object which serves as a surrogate interface, encapsulating a Guacamole @@ -171,7 +172,7 @@ angular.module('client').factory('ManagedFileUpload', ['$rootScope', '$injector' }, // Notify if upload fails - function uploadFailed(error) { + requestService.createErrorCallback(function uploadFailed(error) { // Use provide status code if the error is coming from the stream if (error.type === Error.Type.STREAM_ERROR) @@ -185,7 +186,7 @@ angular.module('client').factory('ManagedFileUpload', ['$rootScope', '$injector' ManagedFileTransferState.StreamState.ERROR, Guacamole.Status.Code.INTERNAL_ERROR); - }); + })); // Ignore all further acks stream.onack = null; diff --git a/guacamole/src/main/webapp/app/form/directives/formField.js b/guacamole/src/main/webapp/app/form/directives/formField.js index 31be836ba..15bde947b 100644 --- a/guacamole/src/main/webapp/app/form/directives/formField.js +++ b/guacamole/src/main/webapp/app/form/directives/formField.js @@ -60,6 +60,7 @@ angular.module('form').directive('guacFormField', [function formField() { controller: ['$scope', '$injector', '$element', function formFieldController($scope, $injector, $element) { // Required services + var $log = $injector.get('$log'); var formService = $injector.get('formService'); var translationStringService = $injector.get('translationStringService'); @@ -116,7 +117,9 @@ angular.module('form').directive('guacFormField', [function formField() { // Append field content if (field) { formService.insertFieldElement(fieldContent[0], - field.type, $scope); + field.type, $scope)['catch'](function fieldCreationFailed() { + $log.warn('Failed to retrieve field with type "' + field.type + '"'); + }); } }); diff --git a/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js b/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js index 97a4f22d1..a0515ead1 100644 --- a/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js +++ b/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js @@ -94,6 +94,7 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList() // Required services var activeConnectionService = $injector.get('activeConnectionService'); var dataSourceService = $injector.get('dataSourceService'); + var requestService = $injector.get('requestService'); // Required types var GroupListItem = $injector.get('GroupListItem'); @@ -221,7 +222,7 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList() }); }); - }); + }, requestService.WARN); } diff --git a/guacamole/src/main/webapp/app/home/controllers/homeController.js b/guacamole/src/main/webapp/app/home/controllers/homeController.js index 150ac4ead..f0e753d5a 100644 --- a/guacamole/src/main/webapp/app/home/controllers/homeController.js +++ b/guacamole/src/main/webapp/app/home/controllers/homeController.js @@ -32,6 +32,7 @@ angular.module('home').controller('homeController', ['$scope', '$injector', var authenticationService = $injector.get('authenticationService'); var connectionGroupService = $injector.get('connectionGroupService'); var dataSourceService = $injector.get('dataSourceService'); + var requestService = $injector.get('requestService'); /** * Map of data source identifier to the root connection group of that data @@ -126,6 +127,6 @@ angular.module('home').controller('homeController', ['$scope', '$injector', ) .then(function rootGroupsRetrieved(rootConnectionGroups) { $scope.rootConnectionGroups = rootConnectionGroups; - }); + }, requestService.WARN); }]); diff --git a/guacamole/src/main/webapp/app/index/controllers/indexController.js b/guacamole/src/main/webapp/app/index/controllers/indexController.js index c83e2096b..5cff9625a 100644 --- a/guacamole/src/main/webapp/app/index/controllers/indexController.js +++ b/guacamole/src/main/webapp/app/index/controllers/indexController.js @@ -137,7 +137,7 @@ angular.module('index').controller('indexController', ['$scope', '$injector', var checkClipboard = function checkClipboard() { clipboardService.getLocalClipboard().then(function clipboardRead(data) { $scope.$broadcast('guacClipboard', data); - })['catch'](angular.noop); + }, angular.noop); }; // Attempt to read the clipboard if it may have changed diff --git a/guacamole/src/main/webapp/app/list/directives/guacUserItem.js b/guacamole/src/main/webapp/app/list/directives/guacUserItem.js index 5c5d26d06..6d13cec90 100644 --- a/guacamole/src/main/webapp/app/list/directives/guacUserItem.js +++ b/guacamole/src/main/webapp/app/list/directives/guacUserItem.js @@ -77,7 +77,7 @@ angular.module('list').directive('guacUserItem', [function guacUserItem() { $translate('LIST.TEXT_ANONYMOUS_USER') .then(function retrieveAnonymousDisplayName(anonymousDisplayName) { $scope.displayName = anonymousDisplayName; - }); + }, angular.noop); } // For all other users, use the username verbatim diff --git a/guacamole/src/main/webapp/app/login/directives/login.js b/guacamole/src/main/webapp/app/login/directives/login.js index 51c366f95..562e3972e 100644 --- a/guacamole/src/main/webapp/app/login/directives/login.js +++ b/guacamole/src/main/webapp/app/login/directives/login.js @@ -68,6 +68,7 @@ angular.module('login').directive('guacLogin', [function guacLogin() { // Required services var $route = $injector.get('$route'); var authenticationService = $injector.get('authenticationService'); + var requestService = $injector.get('requestService'); /** * A description of the error that occurred during login, if any. @@ -153,7 +154,7 @@ angular.module('login').directive('guacLogin', [function guacLogin() { }) // Reset upon failure - ['catch'](function loginFailed(error) { + ['catch'](requestService.createErrorCallback(function loginFailed(error) { // Clear out passwords if the credentials were rejected for any reason if (error.type !== Error.Type.INSUFFICIENT_CREDENTIALS) { @@ -178,7 +179,7 @@ angular.module('login').directive('guacLogin', [function guacLogin() { }); } - }); + })); }; diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js index 2fd815622..66e2aebdc 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js @@ -38,21 +38,10 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i var connectionService = $injector.get('connectionService'); var connectionGroupService = $injector.get('connectionGroupService'); var permissionService = $injector.get('permissionService'); + var requestService = $injector.get('requestService'); var schemaService = $injector.get('schemaService'); var translationStringService = $injector.get('translationStringService'); - /** - * An action to be provided along with the object sent to showStatus which - * closes the currently-shown status dialog. - */ - var ACKNOWLEDGE_ACTION = { - name : "MANAGE_CONNECTION.ACTION_ACKNOWLEDGE", - // Handle action - callback : function acknowledgeCallback() { - guacNotification.showStatus(false); - } - }; - /** * The unique identifier of the data source containing the connection being * edited. @@ -186,7 +175,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i schemaService.getConnectionAttributes($scope.selectedDataSource) .then(function attributesReceived(attributes) { $scope.attributes = attributes; - }); + }, requestService.WARN); // Pull connection group hierarchy connectionGroupService.getConnectionGroupTree( @@ -196,7 +185,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i ) .then(function connectionGroupReceived(rootGroup) { $scope.rootGroup = rootGroup; - }); + }, requestService.WARN); // Query the user's permissions for the current connection permissionService.getEffectivePermissions($scope.selectedDataSource, authenticationService.getCurrentUsername()) @@ -226,18 +215,18 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i ) ); - }); + }, requestService.WARN); // Get protocol metadata schemaService.getProtocols($scope.selectedDataSource) .then(function protocolsReceived(protocols) { $scope.protocols = protocols; - }); + }, requestService.WARN); // Get history date format $translate('MANAGE_CONNECTION.FORMAT_HISTORY_START').then(function historyDateFormatReceived(historyDateFormat) { $scope.historyDateFormat = historyDateFormat; - }); + }, angular.noop); // If we are editing an existing connection, pull its data if (identifier) { @@ -246,7 +235,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i connectionService.getConnection($scope.selectedDataSource, identifier) .then(function connectionRetrieved(connection) { $scope.connection = connection; - }); + }, requestService.WARN); // Pull connection history connectionService.getConnectionHistory($scope.selectedDataSource, identifier) @@ -258,13 +247,13 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i $scope.historyEntryWrappers.push(new HistoryEntryWrapper(historyEntry)); }); - }); + }, requestService.WARN); // Pull connection parameters connectionService.getConnectionParameters($scope.selectedDataSource, identifier) .then(function parametersReceived(parameters) { $scope.parameters = parameters; - }); + }, requestService.WARN); } // If we are cloning an existing connection, pull its data instead @@ -277,7 +266,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i // Clear the identifier field because this connection is new delete $scope.connection.identifier; - }); + }, requestService.WARN); // Do not pull connection history $scope.historyEntryWrappers = []; @@ -286,7 +275,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i connectionService.getConnectionParameters($scope.selectedDataSource, cloneSourceIdentifier) .then(function parametersReceived(parameters) { $scope.parameters = parameters; - }); + }, requestService.WARN); } // If we are creating a new connection, populate skeleton connection data @@ -390,17 +379,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i connectionService.saveConnection($scope.selectedDataSource, $scope.connection) .then(function savedConnection() { $location.url('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections'); - }) - - // Notify of any errors - .error(function connectionSaveFailed(error) { - guacNotification.showStatus({ - 'className' : 'error', - 'title' : 'MANAGE_CONNECTION.DIALOG_HEADER_ERROR', - 'text' : error.translatableMessage, - 'actions' : [ ACKNOWLEDGE_ACTION ] - }); - }); + }, requestService.SHOW_NOTIFICATION); }; @@ -440,17 +419,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i connectionService.deleteConnection($scope.selectedDataSource, $scope.connection) .then(function deletedConnection() { $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections'); - }) - - // Notify of any errors - .error(function connectionDeletionFailed(error) { - guacNotification.showStatus({ - 'className' : 'error', - 'title' : 'MANAGE_CONNECTION.DIALOG_HEADER_ERROR', - 'text' : error.translatableMessage, - 'actions' : [ ACKNOWLEDGE_ACTION ] - }); - }); + }, requestService.SHOW_NOTIFICATION); }; diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js index 6cdda80eb..cab4b18f5 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js @@ -34,20 +34,9 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope' var connectionGroupService = $injector.get('connectionGroupService'); var guacNotification = $injector.get('guacNotification'); var permissionService = $injector.get('permissionService'); + var requestService = $injector.get('requestService'); var schemaService = $injector.get('schemaService'); - /** - * An action to be provided along with the object sent to showStatus which - * closes the currently-shown status dialog. - */ - var ACKNOWLEDGE_ACTION = { - name : "MANAGE_CONNECTION_GROUP.ACTION_ACKNOWLEDGE", - // Handle action - callback : function acknowledgeCallback() { - guacNotification.showStatus(false); - } - }; - /** * The unique identifier of the data source containing the connection group * being edited. @@ -131,7 +120,7 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope' schemaService.getConnectionGroupAttributes($scope.selectedDataSource) .then(function attributesReceived(attributes) { $scope.attributes = attributes; - }); + }, requestService.WARN); // Query the user's permissions for the current connection group permissionService.getEffectivePermissions($scope.selectedDataSource, authenticationService.getCurrentUsername()) @@ -152,7 +141,7 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope' || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.DELETE, identifier) ); - }); + }, requestService.WARN); // Pull connection group hierarchy @@ -163,14 +152,14 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope' ) .then(function connectionGroupReceived(rootGroup) { $scope.rootGroup = rootGroup; - }); + }, requestService.WARN); // If we are editing an existing connection group, pull its data if (identifier) { connectionGroupService.getConnectionGroup($scope.selectedDataSource, identifier) .then(function connectionGroupReceived(connectionGroup) { $scope.connectionGroup = connectionGroup; - }); + }, requestService.WARN); } // If we are creating a new connection group, populate skeleton connection group data @@ -232,17 +221,7 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope' connectionGroupService.saveConnectionGroup($scope.selectedDataSource, $scope.connectionGroup) .then(function savedConnectionGroup() { $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections'); - }) - - // Notify of any errors - ['catch'](function connectionGroupSaveFailed(error) { - guacNotification.showStatus({ - 'className' : 'error', - 'title' : 'MANAGE_CONNECTION_GROUP.DIALOG_HEADER_ERROR', - 'text' : error.translatableMessage, - 'actions' : [ ACKNOWLEDGE_ACTION ] - }); - }); + }, requestService.SHOW_NOTIFICATION); }; @@ -282,17 +261,7 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope' connectionGroupService.deleteConnectionGroup($scope.selectedDataSource, $scope.connectionGroup) .then(function deletedConnectionGroup() { $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections'); - }) - - // Notify of any errors - ['catch'](function connectionGroupDeletionFailed(error) { - guacNotification.showStatus({ - 'className' : 'error', - 'title' : 'MANAGE_CONNECTION_GROUP.DIALOG_HEADER_ERROR', - 'text' : error.translatableMessage, - 'actions' : [ ACKNOWLEDGE_ACTION ] - }); - }); + }, requestService.SHOW_NOTIFICATION); }; diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js index 1adead846..886f54c22 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js @@ -34,22 +34,11 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope', var connectionService = $injector.get('connectionService'); var guacNotification = $injector.get('guacNotification'); var permissionService = $injector.get('permissionService'); + var requestService = $injector.get('requestService'); var schemaService = $injector.get('schemaService'); var sharingProfileService = $injector.get('sharingProfileService'); var translationStringService = $injector.get('translationStringService'); - /** - * An action which can be provided along with the object sent to showStatus - * to allow the user to acknowledge (and close) the currently-shown status - * dialog. - */ - var ACKNOWLEDGE_ACTION = { - name : "MANAGE_SHARING_PROFILE.ACTION_ACKNOWLEDGE", - callback : function acknowledgeCallback() { - guacNotification.showStatus(false); - } - }; - /** * An action to be provided along with the object sent to showStatus which * closes the currently-shown status dialog, effectively canceling the @@ -172,7 +161,7 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope', schemaService.getSharingProfileAttributes($scope.selectedDataSource) .then(function attributesReceived(attributes) { $scope.attributes = attributes; - }); + }, requestService.WARN); // Query the user's permissions for the current sharing profile permissionService.getEffectivePermissions($scope.selectedDataSource, authenticationService.getCurrentUsername()) @@ -208,13 +197,13 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope', ) ); - }); + }, requestService.WARN); // Get protocol metadata schemaService.getProtocols($scope.selectedDataSource) .then(function protocolsReceived(protocols) { $scope.protocols = protocols; - }); + }, requestService.WARN); // If we are editing an existing sharing profile, pull its data if (identifier) { @@ -223,13 +212,13 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope', sharingProfileService.getSharingProfile($scope.selectedDataSource, identifier) .then(function sharingProfileRetrieved(sharingProfile) { $scope.sharingProfile = sharingProfile; - }); + }, requestService.WARN); // Pull sharing profile parameters sharingProfileService.getSharingProfileParameters($scope.selectedDataSource, identifier) .then(function parametersReceived(parameters) { $scope.parameters = parameters; - }); + }, requestService.WARN); } @@ -246,13 +235,13 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope', // Clear the identifier field because this sharing profile is new delete $scope.sharingProfile.identifier; - }); + }, requestService.WARN); // Pull sharing profile parameters from cloned sharing profile sharingProfileService.getSharingProfileParameters($scope.selectedDataSource, cloneSourceIdentifier) .then(function parametersReceived(parameters) { $scope.parameters = parameters; - }); + }, requestService.WARN); } @@ -276,7 +265,7 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope', connectionService.getConnection($scope.selectedDataSource, identifier) .then(function connectionRetrieved(connection) { $scope.primaryConnection = connection; - }); + }, requestService.WARN); }); @@ -353,17 +342,7 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope', sharingProfileService.saveSharingProfile($scope.selectedDataSource, $scope.sharingProfile) .then(function savedSharingProfile() { $location.url('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections'); - }) - - // Notify of any errors - ['catch'](function sharingProfileSaveFailed(error) { - guacNotification.showStatus({ - 'className' : 'error', - 'title' : 'MANAGE_SHARING_PROFILE.DIALOG_HEADER_ERROR', - 'text' : error.translatableMessage, - 'actions' : [ ACKNOWLEDGE_ACTION ] - }); - }); + }, requestService.SHOW_NOTIFICATION); }; @@ -391,17 +370,7 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope', sharingProfileService.deleteSharingProfile($scope.selectedDataSource, $scope.sharingProfile) .then(function deletedSharingProfile() { $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections'); - }) - - // Notify of any errors - ['catch'](function sharingProfileDeletionFailed(error) { - guacNotification.showStatus({ - 'className' : 'error', - 'title' : 'MANAGE_SHARING_PROFILE.DIALOG_HEADER_ERROR', - 'text' : error.translatableMessage, - 'actions' : [ ACKNOWLEDGE_ACTION ] - }); - }); + }, requestService.SHOW_NOTIFICATION); }; diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index 76a837630..d0b6be429 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -39,6 +39,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto var dataSourceService = $injector.get('dataSourceService'); var guacNotification = $injector.get('guacNotification'); var permissionService = $injector.get('permissionService'); + var requestService = $injector.get('requestService'); var schemaService = $injector.get('schemaService'); var translationStringService = $injector.get('translationStringService'); var userService = $injector.get('userService'); @@ -531,7 +532,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto // Pull user attribute schema schemaService.getUserAttributes(selectedDataSource).then(function attributesReceived(attributes) { $scope.attributes = attributes; - }); + }, requestService.WARN); // Pull user data and permissions if we are editing an existing user if (username) { @@ -550,7 +551,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto 'username' : username }); - }); + }, requestService.WARN); // The current user will be associated with username of the existing // user in the retrieved permission set @@ -562,9 +563,9 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto }) // If permissions cannot be retrieved, use empty permissions - ['catch'](function permissionRetrievalFailed() { + ['catch'](requestService.createErrorCallback(function permissionRetrievalFailed() { $scope.permissionFlags = new PermissionFlagSet(); - }); + })); } // If we are cloning an existing user, pull his/her data instead @@ -577,7 +578,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto $scope.users = {}; $scope.user = users[selectedDataSource]; - }); + }, requestService.WARN); // The current user will be associated with cloneSourceUsername in the // retrieved permission set @@ -591,9 +592,9 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto }) // If permissions cannot be retrieved, use empty permissions - ['catch'](function permissionRetrievalFailed() { + ['catch'](requestService.createErrorCallback(function permissionRetrievalFailed() { $scope.permissionFlags = new PermissionFlagSet(); - }); + })); } // Use skeleton data if we are creating a new user @@ -676,7 +677,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto $scope.rootGroups[dataSource] = GroupListItem.fromConnectionGroup(dataSource, rootGroup); }); - }); + }, requestService.WARN); // Query the user's permissions for the current user dataSourceService.apply( @@ -686,7 +687,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto ) .then(function permissionsReceived(permissions) { $scope.permissions = permissions; - }); + }, requestService.WARN); // Update default expanded state whenever connection groups and associated // permissions change @@ -1140,30 +1141,9 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto permissionService.patchPermissions(selectedDataSource, $scope.user.username, permissionsAdded, permissionsRemoved) .then(function patchedUserPermissions() { $location.url('/settings/users'); - }) + }, requestService.SHOW_NOTIFICATION); - // Notify of any errors - ['catch'](function userPermissionsPatchFailed(error) { - guacNotification.showStatus({ - 'className' : 'error', - 'title' : 'MANAGE_USER.DIALOG_HEADER_ERROR', - 'text' : error.translatableMessage, - 'values' : error.translationValues, - 'actions' : [ ACKNOWLEDGE_ACTION ] - }); - }); - - }) - - // Notify of any errors - .error(function userSaveFailed(error) { - guacNotification.showStatus({ - 'className' : 'error', - 'title' : 'MANAGE_USER.DIALOG_HEADER_ERROR', - 'text' : error.translatableMessage, - 'actions' : [ ACKNOWLEDGE_ACTION ] - }); - }); + }, requestService.SHOW_NOTIFICATION); }; @@ -1203,17 +1183,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto userService.deleteUser(selectedDataSource, $scope.user) .then(function deletedUser() { $location.path('/settings/users'); - }) - - // Notify of any errors - ['catch'](function userDeletionFailed(error) { - guacNotification.showStatus({ - 'className' : 'error', - 'title' : 'MANAGE_USER.DIALOG_HEADER_ERROR', - 'text' : error.translatableMessage, - 'actions' : [ ACKNOWLEDGE_ACTION ] - }); - }); + }, requestService.SHOW_NOTIFICATION); }; diff --git a/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js b/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js index 13de858e1..492a867d1 100644 --- a/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js +++ b/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js @@ -50,6 +50,7 @@ angular.module('navigation').directive('guacUserMenu', [function guacUserMenu() var $location = $injector.get('$location'); var $route = $injector.get('$route'); var authenticationService = $injector.get('authenticationService'); + var requestService = $injector.get('requestService'); var userService = $injector.get('userService'); var userPageService = $injector.get('userPageService'); @@ -110,7 +111,7 @@ angular.module('navigation').directive('guacUserMenu', [function guacUserMenu() var email = user.attributes[User.Attributes.EMAIL_ADDRESS]; $scope.userURL = email ? 'mailto:' + email : null; - }); + }, requestService.WARN); /** * The available main pages for the current user. @@ -141,7 +142,9 @@ angular.module('navigation').directive('guacUserMenu', [function guacUserMenu() * after logout completes. */ $scope.logout = function logout() { - authenticationService.logout()['finally'](function logoutComplete() { + authenticationService.logout() + ['catch'](requestService.IGNORE) + ['finally'](function logoutComplete() { if ($location.path() !== '/') $location.url('/'); else diff --git a/guacamole/src/main/webapp/app/navigation/services/userPageService.js b/guacamole/src/main/webapp/app/navigation/services/userPageService.js index 24d340bd5..4d1e61246 100644 --- a/guacamole/src/main/webapp/app/navigation/services/userPageService.js +++ b/guacamole/src/main/webapp/app/navigation/services/userPageService.js @@ -35,6 +35,7 @@ angular.module('navigation').factory('userPageService', ['$injector', var connectionGroupService = $injector.get('connectionGroupService'); var dataSourceService = $injector.get('dataSourceService'); var permissionService = $injector.get('permissionService'); + var requestService = $injector.get('requestService'); var translationStringService = $injector.get('translationStringService'); var service = {}; @@ -142,7 +143,7 @@ angular.module('navigation').factory('userPageService', ['$injector', /** * Returns a promise which resolves with an appropriate home page for the - * current user. + * current user. The promise will not be rejected. * * @returns {Promise.} * A promise which resolves with the user's default home page. @@ -169,7 +170,7 @@ angular.module('navigation').factory('userPageService', ['$injector', }) .then(function rootConnectionGroupsPermissionsRetrieved(data) { deferred.resolve(generateHomePage(data.rootGroups,data.permissionsSets)); - }); + }, requestService.WARN); return deferred.promise; @@ -317,7 +318,7 @@ angular.module('navigation').factory('userPageService', ['$injector', /** * Returns a promise which resolves to an array of all settings pages that * the current user can visit. This can include any of the various manage - * pages. + * pages. The promise will not be rejected. * * @returns {Promise.} * A promise which resolves to an array of all settings pages that the @@ -337,7 +338,7 @@ angular.module('navigation').factory('userPageService', ['$injector', // Resolve promise using settings pages derived from permissions .then(function permissionsRetrieved(permissions) { deferred.resolve(generateSettingsPages(permissions)); - }); + }, requestService.WARN); return deferred.promise; @@ -387,7 +388,7 @@ angular.module('navigation').factory('userPageService', ['$injector', * Returns a promise which resolves to an array of all main pages that the * current user can visit. This can include the home page, manage pages, * etc. In the case that there are no applicable pages of this sort, it may - * return a client page. + * return a client page. The promise will not be rejected. * * @returns {Promise.} * A promise which resolves to an array of all main pages that the @@ -418,7 +419,7 @@ angular.module('navigation').factory('userPageService', ['$injector', .then(function rootConnectionGroupsRetrieved(retrievedRootGroups) { rootGroups = retrievedRootGroups; resolveMainPages(); - }); + }, requestService.WARN); // Retrieve current permissions dataSourceService.apply( @@ -431,7 +432,7 @@ angular.module('navigation').factory('userPageService', ['$injector', .then(function permissionsRetrieved(retrievedPermissions) { permissions = retrievedPermissions; resolveMainPages(); - }); + }, requestService.WARN); return deferred.promise; diff --git a/guacamole/src/main/webapp/app/rest/services/dataSourceService.js b/guacamole/src/main/webapp/app/rest/services/dataSourceService.js index 783aa10d2..640539477 100644 --- a/guacamole/src/main/webapp/app/rest/services/dataSourceService.js +++ b/guacamole/src/main/webapp/app/rest/services/dataSourceService.js @@ -27,7 +27,8 @@ angular.module('rest').factory('dataSourceService', ['$injector', var Error = $injector.get('Error'); // Required services - var $q = $injector.get('$q'); + var $q = $injector.get('$q'); + var requestService = $injector.get('requestService'); // Service containing all caches var service = {}; @@ -92,7 +93,7 @@ angular.module('rest').factory('dataSourceService', ['$injector', }, // Fail on any errors (except "NOT FOUND") - function immediateRequestFailed(error) { + requestService.createErrorCallback(function immediateRequestFailed(error) { if (error.type === Error.Type.NOT_FOUND) deferredRequest.resolve(); @@ -101,7 +102,7 @@ angular.module('rest').factory('dataSourceService', ['$injector', else deferredRequest.reject(error); - }); + })); }); @@ -111,9 +112,9 @@ angular.module('rest').factory('dataSourceService', ['$injector', }, // Reject if at least one request fails - function requestFailed(response) { - deferred.reject(response); - }); + requestService.createErrorCallback(function requestFailed(error) { + deferred.reject(error); + })); return deferred.promise; diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnectionHistory.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnectionHistory.js index 36da4ad5f..c184a13b1 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnectionHistory.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnectionHistory.js @@ -44,6 +44,7 @@ angular.module('settings').directive('guacSettingsConnectionHistory', [function var $translate = $injector.get('$translate'); var csvService = $injector.get('csvService'); var historyService = $injector.get('historyService'); + var requestService = $injector.get('requestService'); /** * The identifier of the currently-selected data source. @@ -95,7 +96,7 @@ angular.module('settings').directive('guacSettingsConnectionHistory', [function // Store received date format $scope.dateFormat = retrievedDateFormat; - }); + }, angular.noop); /** * Returns true if the connection history records have been loaded, @@ -177,7 +178,7 @@ angular.module('settings').directive('guacSettingsConnectionHistory', [function $scope.historyEntryWrappers.push(new ConnectionHistoryEntryWrapper(historyEntry)); }); - }); + }, requestService.WARN); }; @@ -227,7 +228,7 @@ angular.module('settings').directive('guacSettingsConnectionHistory', [function // Save the result saveAs(csvService.toBlob(records), translations['SETTINGS_CONNECTION_HISTORY.FILENAME_HISTORY_CSV']); - }); + }, angular.noop); }; diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js index dd9f5c851..2f7fafb00 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js @@ -46,6 +46,7 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe var dataSourceService = $injector.get('dataSourceService'); var guacNotification = $injector.get('guacNotification'); var permissionService = $injector.get('permissionService'); + var requestService = $injector.get('requestService'); /** * The identifier of the current user. @@ -54,18 +55,6 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe */ var currentUsername = authenticationService.getCurrentUsername(); - /** - * An action to be provided along with the object sent to - * showStatus which closes the currently-shown status dialog. - */ - var ACKNOWLEDGE_ACTION = { - name : "SETTINGS_CONNECTIONS.ACTION_ACKNOWLEDGE", - // Handle action - callback : function acknowledgeCallback() { - guacNotification.showStatus(false); - } - }; - /** * The identifier of the currently-selected data source. * @@ -426,9 +415,9 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe ) .then(function connectionGroupsReceived(rootGroups) { $scope.rootGroups = rootGroups; - }); + }, requestService.WARN); - }); // end retrieve permissions + }, requestService.WARN); // end retrieve permissions }] }; diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js index 12039b191..6cfd440d2 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js @@ -42,6 +42,7 @@ angular.module('settings').directive('guacSettingsPreferences', [function guacSe var languageService = $injector.get('languageService'); var permissionService = $injector.get('permissionService'); var preferenceService = $injector.get('preferenceService'); + var requestService = $injector.get('requestService'); var userService = $injector.get('userService'); /** @@ -164,17 +165,7 @@ angular.module('settings').directive('guacSettingsPreferences', [function guacSe }, actions : [ ACKNOWLEDGE_ACTION ] }); - }) - - // Notify of any errors - ['catch'](function passwordUpdateFailed(error) { - guacNotification.showStatus({ - className : 'error', - title : 'SETTINGS_PREFERENCES.DIALOG_HEADER_ERROR', - text : error.translatableMessage, - actions : [ ACKNOWLEDGE_ACTION ] - }); - }); + }, requestService.SHOW_NOTIFICATION); }; @@ -186,8 +177,8 @@ angular.module('settings').directive('guacSettingsPreferences', [function guacSe key: key, value: languages[key] }; - }) - }); + }); + }, requestService.WARN); // Retrieve current permissions permissionService.getEffectivePermissions(dataSource, username) @@ -198,9 +189,9 @@ angular.module('settings').directive('guacSettingsPreferences', [function guacSe PermissionSet.ObjectPermissionType.UPDATE, username); }) - ['catch'](function permissionsFailed(error) { + ['catch'](requestService.createErrorCallback(function permissionsFailed(error) { $scope.canChangePassword = false; - }); + })); /** * Returns whether critical data has completed being loaded. diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js index a8c13c437..8563d52a5 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js @@ -47,6 +47,7 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti var connectionGroupService = $injector.get('connectionGroupService'); var dataSourceService = $injector.get('dataSourceService'); var guacNotification = $injector.get('guacNotification'); + var requestService = $injector.get('requestService'); /** * The identifiers of all data sources accessible by the current @@ -219,7 +220,7 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti // Attempt to produce wrapped list of active connections wrapAllActiveConnections(); - }); + }, requestService.WARN); // Query active sessions dataSourceService.apply( @@ -234,7 +235,7 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti // Attempt to produce wrapped list of active connections wrapAllActiveConnections(); - }); + }, requestService.WARN); // Get session date format $translate('SETTINGS_SESSIONS.FORMAT_STARTDATE').then(function sessionDateFormatReceived(retrievedSessionDateFormat) { @@ -245,7 +246,7 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti // Attempt to produce wrapped list of active connections wrapAllActiveConnections(); - }); + }, angular.noop); /** * Returns whether critical data has completed being loaded. @@ -258,18 +259,6 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti return $scope.wrappers !== null; }; - /** - * An action to be provided along with the object sent to - * showStatus which closes the currently-shown status dialog. - */ - var ACKNOWLEDGE_ACTION = { - name : "SETTINGS_SESSIONS.ACTION_ACKNOWLEDGE", - // Handle action - callback : function acknowledgeCallback() { - guacNotification.showStatus(false); - } - }; - /** * An action to be provided along with the object sent to * showStatus which closes the currently-shown status dialog. @@ -327,17 +316,7 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti // Clear selection allSelectedWrappers = {}; - }, - - // Notify of any errors - function activeConnectionDeletionFailed(error) { - guacNotification.showStatus({ - 'className' : 'error', - 'title' : 'SETTINGS_SESSIONS.DIALOG_HEADER_ERROR', - 'text' : error.translatableMessage, - 'actions' : [ ACKNOWLEDGE_ACTION ] - }); - }); + }, requestService.SHOW_NOTIFICATION); }; diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js index c14339990..870a86256 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js @@ -43,25 +43,13 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings var $translate = $injector.get('$translate'); var authenticationService = $injector.get('authenticationService'); var dataSourceService = $injector.get('dataSourceService'); - var guacNotification = $injector.get('guacNotification'); var permissionService = $injector.get('permissionService'); + var requestService = $injector.get('requestService'); var userService = $injector.get('userService'); // Identifier of the current user var currentUsername = authenticationService.getCurrentUsername(); - /** - * An action to be provided along with the object sent to - * showStatus which closes the currently-shown status dialog. - */ - var ACKNOWLEDGE_ACTION = { - name : "SETTINGS_USERS.ACTION_ACKNOWLEDGE", - // Handle action - callback : function acknowledgeCallback() { - guacNotification.showStatus(false); - } - }; - /** * The identifiers of all data sources accessible by the current * user. @@ -129,7 +117,7 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings // Store received date format $scope.dateFormat = retrievedDateFormat; - }); + }, angular.noop); /** * Returns whether critical data has completed being loaded. @@ -287,9 +275,9 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings }); }); - }); + }, requestService.WARN); - }); + }, requestService.WARN); }] }; From ae6b5fc8bbc67822308c5aaf038bb2456c6d21f4 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 26 Apr 2018 22:28:18 -0700 Subject: [PATCH 15/18] GUACAMOLE-526: Move handling of request error notification to guacNotification, resolving circular dependency. --- .../controllers/manageConnectionController.js | 4 ++-- .../manageConnectionGroupController.js | 4 ++-- .../manageSharingProfileController.js | 4 ++-- .../controllers/manageUserController.js | 6 ++--- .../app/notification/notificationModule.js | 1 + .../notification/services/guacNotification.js | 23 +++++++++---------- .../src/main/webapp/app/rest/restModule.js | 3 +-- .../app/rest/services/requestService.js | 18 ++------------- .../directives/guacSettingsPreferences.js | 2 +- .../directives/guacSettingsSessions.js | 2 +- 10 files changed, 26 insertions(+), 41 deletions(-) diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js index 66e2aebdc..2bbd999f5 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js @@ -379,7 +379,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i connectionService.saveConnection($scope.selectedDataSource, $scope.connection) .then(function savedConnection() { $location.url('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections'); - }, requestService.SHOW_NOTIFICATION); + }, guacNotification.SHOW_REQUEST_ERROR); }; @@ -419,7 +419,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i connectionService.deleteConnection($scope.selectedDataSource, $scope.connection) .then(function deletedConnection() { $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections'); - }, requestService.SHOW_NOTIFICATION); + }, guacNotification.SHOW_REQUEST_ERROR); }; diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js index cab4b18f5..de29aff12 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js @@ -221,7 +221,7 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope' connectionGroupService.saveConnectionGroup($scope.selectedDataSource, $scope.connectionGroup) .then(function savedConnectionGroup() { $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections'); - }, requestService.SHOW_NOTIFICATION); + }, guacNotification.SHOW_REQUEST_ERROR); }; @@ -261,7 +261,7 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope' connectionGroupService.deleteConnectionGroup($scope.selectedDataSource, $scope.connectionGroup) .then(function deletedConnectionGroup() { $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections'); - }, requestService.SHOW_NOTIFICATION); + }, guacNotification.SHOW_REQUEST_ERROR); }; diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js index 886f54c22..5f2d6bd67 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js @@ -342,7 +342,7 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope', sharingProfileService.saveSharingProfile($scope.selectedDataSource, $scope.sharingProfile) .then(function savedSharingProfile() { $location.url('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections'); - }, requestService.SHOW_NOTIFICATION); + }, guacNotification.SHOW_REQUEST_ERROR); }; @@ -370,7 +370,7 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope', sharingProfileService.deleteSharingProfile($scope.selectedDataSource, $scope.sharingProfile) .then(function deletedSharingProfile() { $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections'); - }, requestService.SHOW_NOTIFICATION); + }, guacNotification.SHOW_REQUEST_ERROR); }; diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index d0b6be429..eae141b26 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -1141,9 +1141,9 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto permissionService.patchPermissions(selectedDataSource, $scope.user.username, permissionsAdded, permissionsRemoved) .then(function patchedUserPermissions() { $location.url('/settings/users'); - }, requestService.SHOW_NOTIFICATION); + }, guacNotification.SHOW_REQUEST_ERROR); - }, requestService.SHOW_NOTIFICATION); + }, guacNotification.SHOW_REQUEST_ERROR); }; @@ -1183,7 +1183,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto userService.deleteUser(selectedDataSource, $scope.user) .then(function deletedUser() { $location.path('/settings/users'); - }, requestService.SHOW_NOTIFICATION); + }, guacNotification.SHOW_REQUEST_ERROR); }; diff --git a/guacamole/src/main/webapp/app/notification/notificationModule.js b/guacamole/src/main/webapp/app/notification/notificationModule.js index 9a4eb0c15..15a2a4628 100644 --- a/guacamole/src/main/webapp/app/notification/notificationModule.js +++ b/guacamole/src/main/webapp/app/notification/notificationModule.js @@ -21,5 +21,6 @@ * The module for code used to display arbitrary notifications. */ angular.module('notification', [ + 'rest', 'storage' ]); diff --git a/guacamole/src/main/webapp/app/notification/services/guacNotification.js b/guacamole/src/main/webapp/app/notification/services/guacNotification.js index a1b8d1016..b52f42146 100644 --- a/guacamole/src/main/webapp/app/notification/services/guacNotification.js +++ b/guacamole/src/main/webapp/app/notification/services/guacNotification.js @@ -25,6 +25,7 @@ angular.module('notification').factory('guacNotification', ['$injector', // Required services var $rootScope = $injector.get('$rootScope'); + var requestService = $injector.get('requestService'); var sessionStorageFactory = $injector.get('sessionStorageFactory'); var service = {}; @@ -93,26 +94,24 @@ angular.module('notification').factory('guacNotification', ['$injector', }; /** - * Shows the given REST error response as a modal status. If a status - * notification is already currently shown, this function will have no - * effect. + * Promise error callback which displays a modal notification for all + * rejections due to REST errors. The message displayed to the user within + * the notification is provided by the contents of the @link{Error} object + * within the REST response. All other rejections, such as those due to + * JavaScript errors, are logged to the browser console without displaying + * any notification. * - * @param {Error} error - * The error object returned from the failed REST request. - * - * @example - * - * someService.updateObject(object) - * ['catch'](guacNotification.showRequestError); + * @constant + * @type Function */ - service.showRequestError = function showRequestError(error) { + service.SHOW_REQUEST_ERROR = requestService.createErrorCallback(function showRequestError(error) { service.showStatus({ className : 'error', title : 'APP.DIALOG_HEADER_ERROR', text : error.translatableMessage, actions : [ service.ACKNOWLEDGE_ACTION ] }); - }; + }); // Hide status upon navigation $rootScope.$on('$routeChangeSuccess', function() { diff --git a/guacamole/src/main/webapp/app/rest/restModule.js b/guacamole/src/main/webapp/app/rest/restModule.js index 6672507dc..f409e9545 100644 --- a/guacamole/src/main/webapp/app/rest/restModule.js +++ b/guacamole/src/main/webapp/app/rest/restModule.js @@ -22,6 +22,5 @@ * Guacamole web application. */ angular.module('rest', [ - 'auth', - 'notification' + 'auth' ]); diff --git a/guacamole/src/main/webapp/app/rest/services/requestService.js b/guacamole/src/main/webapp/app/rest/services/requestService.js index a27ccc3bb..9aef12486 100644 --- a/guacamole/src/main/webapp/app/rest/services/requestService.js +++ b/guacamole/src/main/webapp/app/rest/services/requestService.js @@ -25,9 +25,8 @@ angular.module('rest').factory('requestService', ['$injector', function requestService($injector) { // Required services - var $http = $injector.get('$http'); - var $log = $injector.get('$log'); - var guacNotification = $injector.get('guacNotification'); + var $http = $injector.get('$http'); + var $log = $injector.get('$log'); // Required types var Error = $injector.get('Error'); @@ -116,19 +115,6 @@ angular.module('rest').factory('requestService', ['$injector', $log.warn(error.type, error.message || error.translatableMessage); }); - /** - * Promise error callback which displays a modal notification for all - * rejections due to REST errors. The message displayed to the user within - * the notification is provided by the contents of the @link{Error} object - * within the REST response. All other rejections, such as those due to - * JavaScript errors, are logged to the browser console without displaying - * any notification. - * - * @constant - * @type Function - */ - service.SHOW_NOTIFICATION = service.createErrorCallback(guacNotification.showRequestError); - return service; }]); diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js index 6cfd440d2..dfad564e8 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js @@ -165,7 +165,7 @@ angular.module('settings').directive('guacSettingsPreferences', [function guacSe }, actions : [ ACKNOWLEDGE_ACTION ] }); - }, requestService.SHOW_NOTIFICATION); + }, guacNotification.SHOW_REQUEST_ERROR); }; diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js index 8563d52a5..67776f0ab 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js @@ -316,7 +316,7 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti // Clear selection allSelectedWrappers = {}; - }, requestService.SHOW_NOTIFICATION); + }, guacNotification.SHOW_REQUEST_ERROR); }; From f8bd8d8ec97d2f9d112bafe25b4f5340d5d5cc8c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 27 Apr 2018 00:02:22 -0700 Subject: [PATCH 16/18] GUACAMOLE-526: Remove call to old-style $http success(). --- guacamole/src/main/webapp/app/rest/services/userService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guacamole/src/main/webapp/app/rest/services/userService.js b/guacamole/src/main/webapp/app/rest/services/userService.js index f05ab312e..b04420521 100644 --- a/guacamole/src/main/webapp/app/rest/services/userService.js +++ b/guacamole/src/main/webapp/app/rest/services/userService.js @@ -264,7 +264,7 @@ angular.module('rest').factory('userService', ['$injector', }) // Clear the cache - .success(function passwordChanged(){ + .then(function passwordChanged(){ cacheService.users.removeAll(); }); From 2ff980dedebd51e0a8f966f25012cf03563ec8b4 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 27 Apr 2018 00:05:09 -0700 Subject: [PATCH 17/18] GUACAMOLE-526: Remove debug console.log() from toArrayFilter(). --- guacamole/src/main/webapp/app/index/filters/arrayFilter.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/guacamole/src/main/webapp/app/index/filters/arrayFilter.js b/guacamole/src/main/webapp/app/index/filters/arrayFilter.js index f0429c859..d3a5df25e 100644 --- a/guacamole/src/main/webapp/app/index/filters/arrayFilter.js +++ b/guacamole/src/main/webapp/app/index/filters/arrayFilter.js @@ -25,9 +25,6 @@ angular.module('index').filter('toArray', [function toArrayFactory() { return function toArrayFiter(input) { - - console.log(input) - // If no object is available, just return an empty array if (!input) { return []; From 0d63d6cef442b35e3892305500bffb88b81c16da Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 27 Apr 2018 00:12:36 -0700 Subject: [PATCH 18/18] GUACAMOLE-526: Only pull primary connection for sharing profile once identifier is known. --- .../controllers/manageSharingProfileController.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js index 5f2d6bd67..7414e653a 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js @@ -261,11 +261,12 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope', $scope.$watch('sharingProfile.primaryConnectionIdentifier', function retrievePrimaryConnection(identifier) { - // Pull data from existing sharing profile - connectionService.getConnection($scope.selectedDataSource, identifier) - .then(function connectionRetrieved(connection) { - $scope.primaryConnection = connection; - }, requestService.WARN); + if (identifier) { + connectionService.getConnection($scope.selectedDataSource, identifier) + .then(function connectionRetrieved(connection) { + $scope.primaryConnection = connection; + }, requestService.WARN); + } });