diff --git a/guacamole/src/main/webapp/app/auth/service/authenticationService.js b/guacamole/src/main/webapp/app/auth/service/authenticationService.js index 53b10c605..d05a0d38c 100644 --- a/guacamole/src/main/webapp/app/auth/service/authenticationService.js +++ b/guacamole/src/main/webapp/app/auth/service/authenticationService.js @@ -205,6 +205,10 @@ angular.module('auth').factory('authenticationService', ['$injector', else if (error.type === Error.Type.INSUFFICIENT_CREDENTIALS) $rootScope.$broadcast('guacInsufficientCredentials', parameters, error); + // Abort rendering of page if an internal error occurs + else if (error.type === Error.Type.INTERNAL_ERROR) + $rootScope.$broadcast('guacFatalPageError', error); + // Authentication failed throw error; diff --git a/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js b/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js index a0515ead1..16ff9ddc0 100644 --- a/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js +++ b/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js @@ -222,7 +222,7 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList() }); }); - }, requestService.WARN); + }, requestService.DIE); } diff --git a/guacamole/src/main/webapp/app/home/controllers/homeController.js b/guacamole/src/main/webapp/app/home/controllers/homeController.js index 1cedeadc9..6c2bd6922 100644 --- a/guacamole/src/main/webapp/app/home/controllers/homeController.js +++ b/guacamole/src/main/webapp/app/home/controllers/homeController.js @@ -127,6 +127,6 @@ angular.module('home').controller('homeController', ['$scope', '$injector', ) .then(function rootGroupsRetrieved(rootConnectionGroups) { $scope.rootConnectionGroups = rootConnectionGroups; - }, requestService.WARN); + }, requestService.DIE); }]); diff --git a/guacamole/src/main/webapp/app/index/controllers/indexController.js b/guacamole/src/main/webapp/app/index/controllers/indexController.js index 5cff9625a..15e781945 100644 --- a/guacamole/src/main/webapp/app/index/controllers/indexController.js +++ b/guacamole/src/main/webapp/app/index/controllers/indexController.js @@ -28,7 +28,15 @@ angular.module('index').controller('indexController', ['$scope', '$injector', var $window = $injector.get('$window'); var clipboardService = $injector.get('clipboardService'); var guacNotification = $injector.get('guacNotification'); - + + /** + * The error that prevents the current page from rendering at all. If no + * such error has occurred, this will be null. + * + * @type Error + */ + $scope.fatalError = null; + /** * The notification service. */ @@ -159,6 +167,7 @@ angular.module('index').controller('indexController', ['$scope', '$injector', $scope.loginHelpText = null; $scope.acceptedCredentials = {}; $scope.expectedCredentials = error.expected; + $scope.fatalError = null; }); // Prompt for remaining credentials if provided credentials were not enough @@ -168,6 +177,15 @@ angular.module('index').controller('indexController', ['$scope', '$injector', $scope.loginHelpText = error.translatableMessage; $scope.acceptedCredentials = parameters; $scope.expectedCredentials = error.expected; + $scope.fatalError = null; + }); + + // Replace absolutely all content with an error message if the page itself + // cannot be displayed due to an error + $scope.$on('guacFatalPageError', function fatalPageError(error) { + $scope.page.title = 'APP.NAME'; + $scope.page.bodyClassName = ''; + $scope.fatalError = error; }); // Update title and CSS class upon navigation @@ -181,6 +199,7 @@ angular.module('index').controller('indexController', ['$scope', '$injector', $scope.loginHelpText = null; $scope.acceptedCredentials = null; $scope.expectedCredentials = null; + $scope.fatalError = null; // Set title var title = current.$$route.title; diff --git a/guacamole/src/main/webapp/app/index/styles/fatal-page-error.css b/guacamole/src/main/webapp/app/index/styles/fatal-page-error.css new file mode 100644 index 000000000..a6e28ba1f --- /dev/null +++ b/guacamole/src/main/webapp/app/index/styles/fatal-page-error.css @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +.fatal-page-error-outer { + display: table; + height: 100%; + width: 100%; + position: fixed; + left: 0; + top: 0; + z-index: 30; +} + +.fatal-page-error-middle { + width: 100%; + text-align: center; + display: table-cell; + vertical-align: middle; +} + +.fatal-page-error { + display: inline-block; + width: 100%; + max-width: 5in; + padding: 1em; + text-align: left; +} + +.fatal-page-error h1 { + text-transform: uppercase; + padding: 0; + padding-right: 1em; +} + +.fatal-page-error h1::before { + content: ' '; + display: inline-block; + background: url('images/warning.png'); + background-repeat: no-repeat; + height: 1em; + width: 1em; + background-size: contain; + margin: 0 0.25em; + margin-bottom: -0.2em; +} diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js index b84886f10..06cbeae1c 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js @@ -287,7 +287,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i PermissionSet.hasConnectionPermission, identifier); - }, requestService.WARN); + }, requestService.DIE); // Get history date format $translate('MANAGE_CONNECTION.FORMAT_HISTORY_START').then(function historyDateFormatReceived(historyDateFormat) { diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js index 250ebc102..1d8177396 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js @@ -245,7 +245,7 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope' PermissionSet.hasConnectionPermission, identifier); - }, requestService.WARN); + }, requestService.DIE); /** * Cancels all pending edits, returning to the main list of connections diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js index 61bfbe93f..e3c9ca17d 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js @@ -277,7 +277,7 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope', PermissionSet.hasConnectionPermission, identifier); - }, requestService.WARN); + }, requestService.DIE); /** * @borrows Protocol.getNamespace diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index e4a91db73..16bb20f33 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -335,7 +335,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto }); - }, requestService.WARN); + }, requestService.DIE); /** * Returns the URL for the page which manages the user account currently diff --git a/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js b/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js index f47670a6e..9392bb0fc 100644 --- a/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js +++ b/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js @@ -348,7 +348,7 @@ angular.module('manage').directive('connectionPermissionEditor', ['$injector', }); - }, requestService.WARN); + }, requestService.DIE); /** * Updates the permissionsAdded and permissionsRemoved permission sets diff --git a/guacamole/src/main/webapp/app/manage/directives/systemPermissionEditor.js b/guacamole/src/main/webapp/app/manage/directives/systemPermissionEditor.js index ec4187256..723226365 100644 --- a/guacamole/src/main/webapp/app/manage/directives/systemPermissionEditor.js +++ b/guacamole/src/main/webapp/app/manage/directives/systemPermissionEditor.js @@ -147,7 +147,7 @@ angular.module('manage').directive('systemPermissionEditor', ['$injector', ) .then(function permissionsReceived(permissions) { $scope.permissions = permissions; - }, requestService.WARN); + }, requestService.DIE); /** * Returns whether the current user has permission to change the system diff --git a/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js b/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js index 492a867d1..5c6ad74a8 100644 --- a/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js +++ b/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js @@ -111,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); + }, requestService.DIE); /** * The available main pages for the current user. diff --git a/guacamole/src/main/webapp/app/navigation/services/userPageService.js b/guacamole/src/main/webapp/app/navigation/services/userPageService.js index bd401daa1..a9f849b56 100644 --- a/guacamole/src/main/webapp/app/navigation/services/userPageService.js +++ b/guacamole/src/main/webapp/app/navigation/services/userPageService.js @@ -172,7 +172,7 @@ angular.module('navigation').factory('userPageService', ['$injector', }) .then(function rootConnectionGroupsPermissionsRetrieved(data) { deferred.resolve(generateHomePage(data.rootGroups,data.permissionsSets)); - }, requestService.WARN); + }, requestService.DIE); return deferred.promise; @@ -336,7 +336,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); + }, requestService.DIE); return deferred.promise; @@ -417,7 +417,7 @@ angular.module('navigation').factory('userPageService', ['$injector', .then(function rootConnectionGroupsRetrieved(retrievedRootGroups) { rootGroups = retrievedRootGroups; resolveMainPages(); - }, requestService.WARN); + }, requestService.DIE); // Retrieve current permissions dataSourceService.apply( @@ -430,7 +430,7 @@ angular.module('navigation').factory('userPageService', ['$injector', .then(function permissionsRetrieved(retrievedPermissions) { permissions = retrievedPermissions; resolveMainPages(); - }, requestService.WARN); + }, requestService.DIE); return deferred.promise; diff --git a/guacamole/src/main/webapp/app/rest/services/requestService.js b/guacamole/src/main/webapp/app/rest/services/requestService.js index 9aef12486..8b13caf36 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 $rootScope = $injector.get('$rootScope'); // Required types var Error = $injector.get('Error'); @@ -115,6 +116,21 @@ angular.module('rest').factory('requestService', ['$injector', $log.warn(error.type, error.message || error.translatableMessage); }); + /** + * Promise error callback which replaces the content of the page with a + * generic error message warning that the page could not be displayed. All + * rejections are logged to the browser console as errors. This callback + * should be used in favor of @link{WARN} if REST errors will result in the + * page being unusable. + * + * @constant + * @type Function + */ + service.DIE = service.createErrorCallback(function fatalPageError(error) { + $rootScope.$broadcast('guacFatalPageError', error); + $log.error(error.type, error.message || error.translatableMessage); + }); + return service; }]); diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnectionHistory.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnectionHistory.js index c184a13b1..796edcd5c 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnectionHistory.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnectionHistory.js @@ -178,7 +178,7 @@ angular.module('settings').directive('guacSettingsConnectionHistory', [function $scope.historyEntryWrappers.push(new ConnectionHistoryEntryWrapper(historyEntry)); }); - }, requestService.WARN); + }, requestService.DIE); }; diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js index 2f7fafb00..05c86ef5d 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js @@ -415,9 +415,9 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe ) .then(function connectionGroupsReceived(rootGroups) { $scope.rootGroups = rootGroups; - }, requestService.WARN); + }, requestService.DIE); - }, requestService.WARN); // end retrieve permissions + }, requestService.DIE); // 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 dfad564e8..71e7af73e 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js @@ -178,7 +178,7 @@ angular.module('settings').directive('guacSettingsPreferences', [function guacSe value: languages[key] }; }); - }, requestService.WARN); + }, requestService.DIE); // Retrieve current permissions permissionService.getEffectivePermissions(dataSource, username) diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js index ccb75c974..5e1774d05 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js @@ -222,7 +222,7 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti // Attempt to produce wrapped list of active connections wrapAllActiveConnections(); - }, requestService.WARN); + }, requestService.DIE); // Query active sessions dataSourceService.apply( @@ -237,7 +237,7 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti // Attempt to produce wrapped list of active connections wrapAllActiveConnections(); - }, requestService.WARN); + }, requestService.DIE); // Get session date format $translate('SETTINGS_SESSIONS.FORMAT_STARTDATE').then(function sessionDateFormatReceived(retrievedSessionDateFormat) { diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js index 870a86256..bc7a60157 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js @@ -275,9 +275,9 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings }); }); - }, requestService.WARN); + }, requestService.DIE); - }, requestService.WARN); + }, requestService.DIE); }] }; diff --git a/guacamole/src/main/webapp/index.html b/guacamole/src/main/webapp/index.html index 5a53d8a23..d4df20d7f 100644 --- a/guacamole/src/main/webapp/index.html +++ b/guacamole/src/main/webapp/index.html @@ -32,26 +32,40 @@ - -
- - -
-
- +
+ + +
+ + +
+
+ +
+ +
+
+
- -
-
- + + + +
- - + +
+
+
+

+

+
+
+
diff --git a/guacamole/src/main/webapp/translations/en.json b/guacamole/src/main/webapp/translations/en.json index 92e5de00d..9fa242d09 100644 --- a/guacamole/src/main/webapp/translations/en.json +++ b/guacamole/src/main/webapp/translations/en.json @@ -31,6 +31,7 @@ "DIALOG_HEADER_ERROR" : "Error", + "ERROR_PAGE_UNAVAILABLE" : "An error has occurred and this action cannot be completed. If the problem persists, please notify your system administrator or check your system logs.", "ERROR_PASSWORD_BLANK" : "Your password cannot be blank.", "ERROR_PASSWORD_MISMATCH" : "The provided passwords do not match.",