mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-926: Finalize automatic cleanup on failure.
This commit is contained in:
@@ -43,6 +43,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
|
|
||||||
// Required types
|
// Required types
|
||||||
const DirectoryPatch = $injector.get('DirectoryPatch');
|
const DirectoryPatch = $injector.get('DirectoryPatch');
|
||||||
|
const Error = $injector.get('Error');
|
||||||
const ParseError = $injector.get('ParseError');
|
const ParseError = $injector.get('ParseError');
|
||||||
const PermissionSet = $injector.get('PermissionSet');
|
const PermissionSet = $injector.get('PermissionSet');
|
||||||
const User = $injector.get('User');
|
const User = $injector.get('User');
|
||||||
@@ -118,27 +119,27 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
$scope.patchFailure = null;
|
$scope.patchFailure = null;
|
||||||
$scope.fileName = null;
|
$scope.fileName = null;
|
||||||
|
|
||||||
// Broadcast an event to clear the file upload UI
|
|
||||||
$scope.$broadcast('clearFile');
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Indicate that data is currently being loaded / processed if the the file
|
// Indicate that data is currently being loaded / processed if the the file
|
||||||
// has been provided but not yet fully uploaded, or if the the file is
|
// has been provided but not yet fully uploaded, or if the the file is
|
||||||
// fully loaded and is currently being processed.
|
// fully loaded and is currently being processed.
|
||||||
$scope.isLoading = () => (
|
$scope.isLoading = () => (
|
||||||
($scope.fileName && !$scope.dataReady) || $scope.processing);
|
($scope.fileName && !$scope.dataReady && !$scope.patchFailure)
|
||||||
|
|| $scope.processing);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create all users and user groups mentioned in the import file that don't
|
* Create all users and user groups mentioned in the import file that don't
|
||||||
* already exist in the current data source.
|
* already exist in the current data source. If either creation fails, any
|
||||||
|
* already-created entities will be cleaned up, and the returned promise
|
||||||
|
* will be rejected.
|
||||||
*
|
*
|
||||||
* @param {ParseResult} parseResult
|
* @param {ParseResult} parseResult
|
||||||
* The result of parsing the user-supplied import file.
|
* The result of parsing the user-supplied import file.
|
||||||
*
|
*
|
||||||
* @return {Object}
|
* @return {Promise.<Object>}
|
||||||
* An object containing the results of the calls to create the users
|
* A promise resolving to an object containing the results of the calls
|
||||||
* and groups.
|
* to create the users and groups.
|
||||||
*/
|
*/
|
||||||
function createUsersAndGroups(parseResult) {
|
function createUsersAndGroups(parseResult) {
|
||||||
|
|
||||||
@@ -173,9 +174,30 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
value: new UserGroup({ identifier })
|
value: new UserGroup({ identifier })
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return $q.all({
|
// First, create any required users and groups, automatically cleaning
|
||||||
userResponse: userService.patchUsers(dataSource, userPatches),
|
// up any created already-created entities if a call fails.
|
||||||
groupResponse: userGroupService.patchUserGroups(dataSource, groupPatches)
|
// NOTE: Generally we'd want to do these calls in parallel, using
|
||||||
|
// `$q.all()`. However, `$q.all()` rejects immediately if any of the
|
||||||
|
// wrapped promises reject, so the users may not be ready for cleanup
|
||||||
|
// at the time that the group promise rejects, or vice versa. While
|
||||||
|
// it would be possible to juggle promises and still do these calls
|
||||||
|
// in parallel, the code gets pretty complex, so for readability and
|
||||||
|
// simplicity, they are executed serially. The performance cost of
|
||||||
|
// doing so should be low.
|
||||||
|
return userService.patchUsers(dataSource, userPatches).then(userResponse => {
|
||||||
|
|
||||||
|
// Then, if that succeeds, create any required groups
|
||||||
|
return userGroupService.patchUserGroups(dataSource, groupPatches).then(
|
||||||
|
|
||||||
|
// If user group creation succeeds, resolve the returned promise
|
||||||
|
userGroupResponse => ({ userResponse, userGroupResponse}))
|
||||||
|
|
||||||
|
// If the group creation request fails, clean up any created users
|
||||||
|
.catch(groupFailure => {
|
||||||
|
cleanUpUsers(userResponse);
|
||||||
|
return groupFailure;
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -298,63 +320,6 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a successful response to a user group PATCH request, make another
|
|
||||||
* request to delete every created user group in the provided request.
|
|
||||||
*
|
|
||||||
* @param {DirectoryPatchResponse} creationResponse
|
|
||||||
* The response to the user group PATCH creation request.
|
|
||||||
*
|
|
||||||
* @returns {DirectoryPatchResponse}
|
|
||||||
* The response to the PATCH deletion request.
|
|
||||||
*/
|
|
||||||
function cleanUpUserGroups(creationResponse) {
|
|
||||||
|
|
||||||
return userGroupService.patchUserGroups(
|
|
||||||
$routeParams.dataSource, createDeletionPatches(creationResponse));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make requests to delete all connections, users, and/or groups from any
|
|
||||||
* provided PATCH API responses. If any responses are not provided, no
|
|
||||||
* cleanup will be attempted.
|
|
||||||
*
|
|
||||||
* @param {DirectoryPatchResponse} connectionResponse
|
|
||||||
* The response to the connection PATCH creation request.
|
|
||||||
*
|
|
||||||
* @param {DirectoryPatchResponse} userResponse
|
|
||||||
* The response to the user PATCH creation request.
|
|
||||||
*
|
|
||||||
* @param {DirectoryPatchResponse} userGroupResponse
|
|
||||||
* The response to the user group PATCH creation request.
|
|
||||||
*
|
|
||||||
* @returns {Promise.<Object>}
|
|
||||||
* A promise resolving to an object containing PATCH deletion responses
|
|
||||||
* corresponding to any provided connection, user, and/or user group
|
|
||||||
* creation responses.
|
|
||||||
*/
|
|
||||||
function cleanUpAll(connectionResponse, userResponse, userGroupResponse) {
|
|
||||||
|
|
||||||
// All cleanup requests that need to be made
|
|
||||||
const requests = {};
|
|
||||||
|
|
||||||
// If the connection response was provided, clean up connections
|
|
||||||
if (connectionResponse)
|
|
||||||
requests.connectionCleanup = cleanUpConnections(connectionResponse);
|
|
||||||
|
|
||||||
// If the user response was provided, clean up users
|
|
||||||
if (userResponse)
|
|
||||||
requests.userCleanup = cleanUpUsers(userResponse);
|
|
||||||
|
|
||||||
// If the user group response was provided, clean up user groups
|
|
||||||
if (connectionResponse)
|
|
||||||
requests.userGroupCleanup = cleanUpUserGroups(userGroupResponse);
|
|
||||||
|
|
||||||
// Return when all cleanup is complete
|
|
||||||
return $q.all(requests);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process a successfully parsed import file, creating any specified
|
* Process a successfully parsed import file, creating any specified
|
||||||
* connections, creating and granting permissions to any specified users
|
* connections, creating and granting permissions to any specified users
|
||||||
@@ -379,58 +344,63 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
|
|
||||||
// First, attempt to create the connections
|
// First, attempt to create the connections
|
||||||
connectionService.patchConnections(dataSource, parseResult.patches)
|
connectionService.patchConnections(dataSource, parseResult.patches)
|
||||||
.then(connectionResponse => {
|
.then(connectionResponse =>
|
||||||
|
|
||||||
// If connection creation is successful, create users and groups
|
// If connection creation is successful, create users and groups
|
||||||
createUsersAndGroups(parseResult).then(
|
createUsersAndGroups(parseResult).then(() =>
|
||||||
({userResponse, groupResponse}) =>
|
|
||||||
|
|
||||||
grantConnectionPermissions(parseResult, connectionResponse)
|
grantConnectionPermissions(parseResult, connectionResponse)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
||||||
// TODON'T: Delete the stuff so we can test over and over
|
$scope.processing = false;
|
||||||
cleanUpAll(connectionResponse, userResponse, groupResponse)
|
|
||||||
.then(() => {
|
|
||||||
|
|
||||||
$scope.processing = false;
|
// Display a success message if everything worked
|
||||||
|
guacNotification.showStatus({
|
||||||
|
className : 'success',
|
||||||
|
title : 'IMPORT.DIALOG_HEADER_SUCCESS',
|
||||||
|
text : {
|
||||||
|
key: 'IMPORT.CONNECTIONS_IMPORTED_SUCCESS',
|
||||||
|
variables: { NUMBER: parseResult.patches.length }
|
||||||
|
},
|
||||||
|
|
||||||
// Display a success message if everything worked
|
// Add a button to acknowledge and redirect to
|
||||||
guacNotification.showStatus({
|
// the connection listing page
|
||||||
className : 'success',
|
actions : [{
|
||||||
title : 'IMPORT.DIALOG_HEADER_SUCCESS',
|
name : 'IMPORT.ACTION_ACKNOWLEDGE',
|
||||||
text : {
|
callback : () => {
|
||||||
key: 'IMPORT.CONNECTIONS_IMPORTED_SUCCESS',
|
|
||||||
variables: { NUMBER: parseResult.patches.length }
|
|
||||||
},
|
|
||||||
|
|
||||||
// Add a button to acknowledge and redirect to
|
// Close the notification
|
||||||
// the connection listing page
|
guacNotification.showStatus(false);
|
||||||
actions : [{
|
|
||||||
name : 'IMPORT.ACTION_ACKNOWLEDGE',
|
|
||||||
callback : () => {
|
|
||||||
|
|
||||||
// Close the notification
|
// Redirect to connection list page
|
||||||
guacNotification.showStatus(false);
|
$location.url('/settings/' + dataSource + '/connections');
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}))
|
||||||
|
|
||||||
// Redirect to connection list page
|
// If an error occurs while trying to users or groups, or while trying
|
||||||
$location.url('/settings/' + dataSource + '/connections');
|
// to assign permissions to users or groups, clean up the already-created
|
||||||
}
|
// connections, displaying an error to the user along with a blank slate
|
||||||
}]
|
// so they can fix their problems and try again.
|
||||||
})
|
.catch(error => {
|
||||||
});
|
cleanUpConnections(connectionResponse);
|
||||||
}));
|
handleError(error);
|
||||||
})
|
}))
|
||||||
|
|
||||||
// If an error occured when the call to create the connections was made,
|
// If an error occured when the call to create the connections was made,
|
||||||
// skip any further processing - the user will have a chance to fix the
|
// skip any further processing - the user will have a chance to fix the
|
||||||
// problems and try again
|
// problems and try again
|
||||||
.catch(patchFailure => { $scope.patchFailure = patchFailure; });
|
.catch(patchFailure => {
|
||||||
|
$scope.processing = false;
|
||||||
|
$scope.patchFailure = patchFailure;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the provided error to the user in a dismissable dialog.
|
* Display the provided error to the user in a dismissable dialog.
|
||||||
*
|
*
|
||||||
* @argument {ParseError} error
|
* @argument {ParseError|Error} error
|
||||||
* The error to display.
|
* The error to display.
|
||||||
*/
|
*/
|
||||||
const handleError = error => {
|
const handleError = error => {
|
||||||
@@ -439,19 +409,33 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
// all upload state to allow for a fresh retry
|
// all upload state to allow for a fresh retry
|
||||||
resetUploadState();
|
resetUploadState();
|
||||||
|
|
||||||
|
let text;
|
||||||
|
|
||||||
|
// If it's a import file parsing error
|
||||||
|
if (error instanceof ParseError)
|
||||||
|
text = {
|
||||||
|
|
||||||
|
// Use the translation key if available
|
||||||
|
key: error.key || error.message,
|
||||||
|
variables: error.variables
|
||||||
|
};
|
||||||
|
|
||||||
|
// If it's a generic REST error
|
||||||
|
else if (error instanceof Error)
|
||||||
|
text = error.translatableMessage;
|
||||||
|
|
||||||
|
// If it's an unknown type, just use the message directly
|
||||||
|
else
|
||||||
|
text = { key: error };
|
||||||
|
|
||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
className : 'error',
|
className : 'error',
|
||||||
title : 'IMPORT.DIALOG_HEADER_ERROR',
|
title : 'IMPORT.DIALOG_HEADER_ERROR',
|
||||||
|
text,
|
||||||
// Use the translation key if available
|
|
||||||
text : {
|
|
||||||
key: error.key || error.message,
|
|
||||||
variables: error.variables
|
|
||||||
},
|
|
||||||
|
|
||||||
// Add a button to hide the error
|
// Add a button to hide the error
|
||||||
actions : [{
|
actions : [{
|
||||||
name : 'IMPORT.BUTTON_CLEAR',
|
name : 'IMPORT.ACTION_ACKNOWLEDGE',
|
||||||
callback : () => guacNotification.showStatus(false)
|
callback : () => guacNotification.showStatus(false)
|
||||||
}]
|
}]
|
||||||
})
|
})
|
||||||
@@ -546,6 +530,9 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Returns true if cancellation should be disabled, or false if
|
||||||
|
* cancellation should be allowed.
|
||||||
|
*
|
||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
* True if cancellation should be disabled, or false if cancellation
|
* True if cancellation should be disabled, or false if cancellation
|
||||||
* should be allowed.
|
* should be allowed.
|
||||||
|
@@ -189,7 +189,6 @@
|
|||||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
||||||
|
|
||||||
"BUTTON_CANCEL": "Cancel",
|
"BUTTON_CANCEL": "Cancel",
|
||||||
"BUTTON_CLEAR" : "Clear",
|
|
||||||
"BUTTON_IMPORT": "Import Connections",
|
"BUTTON_IMPORT": "Import Connections",
|
||||||
|
|
||||||
"CONNECTIONS_IMPORTED_SUCCESS": "{NUMBER} connections imported successfully.",
|
"CONNECTIONS_IMPORTED_SUCCESS": "{NUMBER} connections imported successfully.",
|
||||||
|
@@ -95,14 +95,14 @@ module.exports = {
|
|||||||
optimization: {
|
optimization: {
|
||||||
minimizer: [
|
minimizer: [
|
||||||
|
|
||||||
// // Minify using Google Closure Compiler
|
// Minify using Google Closure Compiler
|
||||||
// new ClosureWebpackPlugin({ mode: 'STANDARD' }, {
|
new ClosureWebpackPlugin({ mode: 'STANDARD' }, {
|
||||||
// languageIn: 'ECMASCRIPT_2020',
|
languageIn: 'ECMASCRIPT_2020',
|
||||||
// languageOut: 'ECMASCRIPT5',
|
languageOut: 'ECMASCRIPT5',
|
||||||
// compilationLevel: 'SIMPLE'
|
compilationLevel: 'SIMPLE'
|
||||||
// }),
|
}),
|
||||||
//
|
|
||||||
// new CssMinimizerPlugin()
|
new CssMinimizerPlugin()
|
||||||
|
|
||||||
],
|
],
|
||||||
splitChunks: {
|
splitChunks: {
|
||||||
|
Reference in New Issue
Block a user