GUACAMOLE-598: Merge display fatal error if a page is unusable/nonfunctional.

This commit is contained in:
Nick Couchman
2018-07-27 21:54:19 -04:00
21 changed files with 155 additions and 40 deletions

View File

@@ -205,6 +205,10 @@ angular.module('auth').factory('authenticationService', ['$injector',
else if (error.type === Error.Type.INSUFFICIENT_CREDENTIALS) else if (error.type === Error.Type.INSUFFICIENT_CREDENTIALS)
$rootScope.$broadcast('guacInsufficientCredentials', parameters, error); $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 // Authentication failed
throw error; throw error;

View File

@@ -222,7 +222,7 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList()
}); });
}); });
}, requestService.WARN); }, requestService.DIE);
} }

View File

@@ -127,6 +127,6 @@ angular.module('home').controller('homeController', ['$scope', '$injector',
) )
.then(function rootGroupsRetrieved(rootConnectionGroups) { .then(function rootGroupsRetrieved(rootConnectionGroups) {
$scope.rootConnectionGroups = rootConnectionGroups; $scope.rootConnectionGroups = rootConnectionGroups;
}, requestService.WARN); }, requestService.DIE);
}]); }]);

View File

@@ -29,6 +29,14 @@ angular.module('index').controller('indexController', ['$scope', '$injector',
var clipboardService = $injector.get('clipboardService'); var clipboardService = $injector.get('clipboardService');
var guacNotification = $injector.get('guacNotification'); 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. * The notification service.
*/ */
@@ -159,6 +167,7 @@ angular.module('index').controller('indexController', ['$scope', '$injector',
$scope.loginHelpText = null; $scope.loginHelpText = null;
$scope.acceptedCredentials = {}; $scope.acceptedCredentials = {};
$scope.expectedCredentials = error.expected; $scope.expectedCredentials = error.expected;
$scope.fatalError = null;
}); });
// Prompt for remaining credentials if provided credentials were not enough // 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.loginHelpText = error.translatableMessage;
$scope.acceptedCredentials = parameters; $scope.acceptedCredentials = parameters;
$scope.expectedCredentials = error.expected; $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 // Update title and CSS class upon navigation
@@ -181,6 +199,7 @@ angular.module('index').controller('indexController', ['$scope', '$injector',
$scope.loginHelpText = null; $scope.loginHelpText = null;
$scope.acceptedCredentials = null; $scope.acceptedCredentials = null;
$scope.expectedCredentials = null; $scope.expectedCredentials = null;
$scope.fatalError = null;
// Set title // Set title
var title = current.$$route.title; var title = current.$$route.title;

View File

@@ -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;
}

View File

@@ -287,7 +287,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
PermissionSet.hasConnectionPermission, PermissionSet.hasConnectionPermission,
identifier); identifier);
}, requestService.WARN); }, requestService.DIE);
// Get history date format // Get history date format
$translate('MANAGE_CONNECTION.FORMAT_HISTORY_START').then(function historyDateFormatReceived(historyDateFormat) { $translate('MANAGE_CONNECTION.FORMAT_HISTORY_START').then(function historyDateFormatReceived(historyDateFormat) {

View File

@@ -245,7 +245,7 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
PermissionSet.hasConnectionPermission, PermissionSet.hasConnectionPermission,
identifier); identifier);
}, requestService.WARN); }, requestService.DIE);
/** /**
* Cancels all pending edits, returning to the main list of connections * Cancels all pending edits, returning to the main list of connections

View File

@@ -277,7 +277,7 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
PermissionSet.hasConnectionPermission, PermissionSet.hasConnectionPermission,
identifier); identifier);
}, requestService.WARN); }, requestService.DIE);
/** /**
* @borrows Protocol.getNamespace * @borrows Protocol.getNamespace

View File

@@ -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 * Returns the URL for the page which manages the user account currently

View File

@@ -348,7 +348,7 @@ angular.module('manage').directive('connectionPermissionEditor', ['$injector',
}); });
}, requestService.WARN); }, requestService.DIE);
/** /**
* Updates the permissionsAdded and permissionsRemoved permission sets * Updates the permissionsAdded and permissionsRemoved permission sets

View File

@@ -147,7 +147,7 @@ angular.module('manage').directive('systemPermissionEditor', ['$injector',
) )
.then(function permissionsReceived(permissions) { .then(function permissionsReceived(permissions) {
$scope.permissions = permissions; $scope.permissions = permissions;
}, requestService.WARN); }, requestService.DIE);
/** /**
* Returns whether the current user has permission to change the system * Returns whether the current user has permission to change the system

View File

@@ -111,7 +111,7 @@ angular.module('navigation').directive('guacUserMenu', [function guacUserMenu()
var email = user.attributes[User.Attributes.EMAIL_ADDRESS]; var email = user.attributes[User.Attributes.EMAIL_ADDRESS];
$scope.userURL = email ? 'mailto:' + email : null; $scope.userURL = email ? 'mailto:' + email : null;
}, requestService.WARN); }, requestService.DIE);
/** /**
* The available main pages for the current user. * The available main pages for the current user.

View File

@@ -172,7 +172,7 @@ angular.module('navigation').factory('userPageService', ['$injector',
}) })
.then(function rootConnectionGroupsPermissionsRetrieved(data) { .then(function rootConnectionGroupsPermissionsRetrieved(data) {
deferred.resolve(generateHomePage(data.rootGroups,data.permissionsSets)); deferred.resolve(generateHomePage(data.rootGroups,data.permissionsSets));
}, requestService.WARN); }, requestService.DIE);
return deferred.promise; return deferred.promise;
@@ -336,7 +336,7 @@ angular.module('navigation').factory('userPageService', ['$injector',
// Resolve promise using settings pages derived from permissions // Resolve promise using settings pages derived from permissions
.then(function permissionsRetrieved(permissions) { .then(function permissionsRetrieved(permissions) {
deferred.resolve(generateSettingsPages(permissions)); deferred.resolve(generateSettingsPages(permissions));
}, requestService.WARN); }, requestService.DIE);
return deferred.promise; return deferred.promise;
@@ -417,7 +417,7 @@ angular.module('navigation').factory('userPageService', ['$injector',
.then(function rootConnectionGroupsRetrieved(retrievedRootGroups) { .then(function rootConnectionGroupsRetrieved(retrievedRootGroups) {
rootGroups = retrievedRootGroups; rootGroups = retrievedRootGroups;
resolveMainPages(); resolveMainPages();
}, requestService.WARN); }, requestService.DIE);
// Retrieve current permissions // Retrieve current permissions
dataSourceService.apply( dataSourceService.apply(
@@ -430,7 +430,7 @@ angular.module('navigation').factory('userPageService', ['$injector',
.then(function permissionsRetrieved(retrievedPermissions) { .then(function permissionsRetrieved(retrievedPermissions) {
permissions = retrievedPermissions; permissions = retrievedPermissions;
resolveMainPages(); resolveMainPages();
}, requestService.WARN); }, requestService.DIE);
return deferred.promise; return deferred.promise;

View File

@@ -25,8 +25,9 @@ angular.module('rest').factory('requestService', ['$injector',
function requestService($injector) { function requestService($injector) {
// Required services // Required services
var $http = $injector.get('$http'); var $http = $injector.get('$http');
var $log = $injector.get('$log'); var $log = $injector.get('$log');
var $rootScope = $injector.get('$rootScope');
// Required types // Required types
var Error = $injector.get('Error'); var Error = $injector.get('Error');
@@ -115,6 +116,21 @@ angular.module('rest').factory('requestService', ['$injector',
$log.warn(error.type, error.message || error.translatableMessage); $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; return service;
}]); }]);

View File

@@ -178,7 +178,7 @@ angular.module('settings').directive('guacSettingsConnectionHistory', [function
$scope.historyEntryWrappers.push(new ConnectionHistoryEntryWrapper(historyEntry)); $scope.historyEntryWrappers.push(new ConnectionHistoryEntryWrapper(historyEntry));
}); });
}, requestService.WARN); }, requestService.DIE);
}; };

View File

@@ -415,9 +415,9 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe
) )
.then(function connectionGroupsReceived(rootGroups) { .then(function connectionGroupsReceived(rootGroups) {
$scope.rootGroups = rootGroups; $scope.rootGroups = rootGroups;
}, requestService.WARN); }, requestService.DIE);
}, requestService.WARN); // end retrieve permissions }, requestService.DIE); // end retrieve permissions
}] }]
}; };

View File

@@ -178,7 +178,7 @@ angular.module('settings').directive('guacSettingsPreferences', [function guacSe
value: languages[key] value: languages[key]
}; };
}); });
}, requestService.WARN); }, requestService.DIE);
// Retrieve current permissions // Retrieve current permissions
permissionService.getEffectivePermissions(dataSource, username) permissionService.getEffectivePermissions(dataSource, username)

View File

@@ -222,7 +222,7 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti
// Attempt to produce wrapped list of active connections // Attempt to produce wrapped list of active connections
wrapAllActiveConnections(); wrapAllActiveConnections();
}, requestService.WARN); }, requestService.DIE);
// Query active sessions // Query active sessions
dataSourceService.apply( dataSourceService.apply(
@@ -237,7 +237,7 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti
// Attempt to produce wrapped list of active connections // Attempt to produce wrapped list of active connections
wrapAllActiveConnections(); wrapAllActiveConnections();
}, requestService.WARN); }, requestService.DIE);
// Get session date format // Get session date format
$translate('SETTINGS_SESSIONS.FORMAT_STARTDATE').then(function sessionDateFormatReceived(retrievedSessionDateFormat) { $translate('SETTINGS_SESSIONS.FORMAT_STARTDATE').then(function sessionDateFormatReceived(retrievedSessionDateFormat) {

View File

@@ -275,9 +275,9 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings
}); });
}); });
}, requestService.WARN); }, requestService.DIE);
}, requestService.WARN); }, requestService.DIE);
}] }]
}; };

View File

@@ -32,26 +32,40 @@
</head> </head>
<body ng-class="page.bodyClassName"> <body ng-class="page.bodyClassName">
<!-- Content for logged-in users --> <div ng-if="!fatalError">
<div ng-if="!expectedCredentials">
<!-- Global status/error dialog --> <!-- Content for logged-in users -->
<div ng-class="{shown: guacNotification.getStatus()}" class="status-outer"> <div ng-if="!expectedCredentials">
<div class="status-middle">
<guac-notification notification="guacNotification.getStatus()"></guac-notification> <!-- Global status/error dialog -->
<div ng-class="{shown: guacNotification.getStatus()}" class="status-outer">
<div class="status-middle">
<guac-notification notification="guacNotification.getStatus()"></guac-notification>
</div>
</div> </div>
<div id="content" ng-view>
</div>
</div> </div>
<div id="content" ng-view> <!-- Login screen for logged-out users -->
</div> <guac-login ng-show="expectedCredentials"
help-text="loginHelpText"
form="expectedCredentials"
values="acceptedCredentials"></guac-login>
</div> </div>
<!-- Login screen for logged-out users --> <!-- Absolute fatal error -->
<guac-login ng-show="expectedCredentials" <div ng-if="fatalError" class="fatal-page-error-outer">
help-text="loginHelpText" <div class="fatal-page-error-middle">
form="expectedCredentials" <div class="fatal-page-error">
values="acceptedCredentials"></guac-login> <h1 translate="APP.DIALOG_HEADER_ERROR"></h1>
<p translate="APP.ERROR_PAGE_UNAVAILABLE"></p>
</div>
</div>
</div>
<!-- Reformat URL for AngularJS if query parameters are present --> <!-- Reformat URL for AngularJS if query parameters are present -->
<script type="text/javascript" src="relocateParameters.js"></script> <script type="text/javascript" src="relocateParameters.js"></script>

View File

@@ -31,6 +31,7 @@
"DIALOG_HEADER_ERROR" : "Error", "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_BLANK" : "Your password cannot be blank.",
"ERROR_PASSWORD_MISMATCH" : "The provided passwords do not match.", "ERROR_PASSWORD_MISMATCH" : "The provided passwords do not match.",