diff --git a/doc/licenses/core-util-is-1.0.3/README b/doc/licenses/core-util-is-1.0.3/README index 411c1f1e9..7db659198 100644 --- a/doc/licenses/core-util-is-1.0.3/README +++ b/doc/licenses/core-util-is-1.0.3/README @@ -2,6 +2,6 @@ core-util-is (https://github.com/isaacs/core-util-is) --------------------------------------------- Version: 1.0.3 - From: 'isaacs' (https://github.com/isaacs) + From: 'Node.js contributors' License(s): MIT (bundled/core-util-is-1.0.3/LICENSE) diff --git a/doc/licenses/csv-6.2.5/README b/doc/licenses/csv-6.2.5/README index 5fb35fae4..8a8d6debb 100644 --- a/doc/licenses/csv-6.2.5/README +++ b/doc/licenses/csv-6.2.5/README @@ -2,6 +2,6 @@ node-csv (https://github.com/adaltas/node-csv) --------------------------------------------- Version: 6.2.5 - From: 'adaltas' (https://github.com/adaltas) + From: 'Adaltas' (https://github.com/adaltas) License(s): MIT (bundled/csv-6.2.5/LICENSE) diff --git a/doc/licenses/events-3.3.0/README b/doc/licenses/events-3.3.0/README index 274b952ab..b53924d9c 100644 --- a/doc/licenses/events-3.3.0/README +++ b/doc/licenses/events-3.3.0/README @@ -2,6 +2,6 @@ events (https://github.com/browserify/events) --------------------------------------------- Version: 3.3.0 - From: 'browserify' (https://github.com/browserify) + From: 'Node.js contributors, Joyent, Inc., and other Node contributors' License(s): MIT (bundled/events-3.3.0/LICENSE) diff --git a/doc/licenses/ieee754-1.2.1/README b/doc/licenses/ieee754-1.2.1/README index 06d856f16..f15f63240 100644 --- a/doc/licenses/ieee754-1.2.1/README +++ b/doc/licenses/ieee754-1.2.1/README @@ -2,6 +2,6 @@ ieee754 (https://github.com/feross/ieee754) --------------------------------------------- Version: 1.2.1 - From: 'Feross Aboukhadijeh' (https://github.com/feross) + From: 'Fair Oaks Labs, Inc' License(s): MIT (bundled/ieee754-1.2.1/LICENSE) diff --git a/doc/licenses/process-nextick-args-2.0.1/README b/doc/licenses/process-nextick-args-2.0.1/README index d7c0f6b3b..84417dcce 100644 --- a/doc/licenses/process-nextick-args-2.0.1/README +++ b/doc/licenses/process-nextick-args-2.0.1/README @@ -4,5 +4,5 @@ process-nextick-args (https://github.com/calvinmetcalf/process-nextick-args) Version: 2.0.1 From: 'Calvin Metcalf' (https://github.com/calvinmetcalf) License(s): - MIT (bundled/process-nextick-args-2.0.1/LICENSE) + MIT (bundled/process-nextick-args-2.0.1/license.md) diff --git a/doc/licenses/readable-stream-2.3.7/README b/doc/licenses/readable-stream-2.3.7/README index cb58f728b..6e09569c1 100644 --- a/doc/licenses/readable-stream-2.3.7/README +++ b/doc/licenses/readable-stream-2.3.7/README @@ -2,7 +2,7 @@ readable-stream (https://github.com/nodejs/readable-stream) --------------------------------------------- Version: 2.3.7 - From: 'Node.js' (https://github.com/nodejs) + From: 'Node.js contributors, Joyent, Inc., and other Node contributors' License(s): MIT (bundled/readable-stream-2.3.7/LICENSE) diff --git a/doc/licenses/setimmediate-1.0.5/README b/doc/licenses/setimmediate-1.0.5/README index 0fe5638ee..8bd3389be 100644 --- a/doc/licenses/setimmediate-1.0.5/README +++ b/doc/licenses/setimmediate-1.0.5/README @@ -4,5 +4,5 @@ setImmediate.js (https://github.com/YuzuJS/setImmediate) Version: 1.0.5 From: 'Yuzu (by Barnes & Noble Education)' (https://github.com/YuzuJS) License(s): - MIT (bundled/setimmediate-1.0.5/LICENSE) + MIT (bundled/setimmediate-1.0.5/LICENSE.txt) diff --git a/doc/licenses/stream-browserify-2.0.2/README b/doc/licenses/stream-browserify-2.0.2/README index 8ab34a47f..4116876af 100644 --- a/doc/licenses/stream-browserify-2.0.2/README +++ b/doc/licenses/stream-browserify-2.0.2/README @@ -2,7 +2,7 @@ stream-browserify (https://github.com/browserify/stream-browserify) --------------------------------------------- Version: 2.0.2 - From: 'browserify' (https://github.com/browserify) + From: 'James Halliday' License(s): MIT (bundled/stream-browserify-2.0.2/LICENSE) diff --git a/doc/licenses/string_decoder-1.1.1/README b/doc/licenses/string_decoder-1.1.1/README index a64b3299f..48ee14c03 100644 --- a/doc/licenses/string_decoder-1.1.1/README +++ b/doc/licenses/string_decoder-1.1.1/README @@ -2,7 +2,7 @@ string_decoder (https://github.com/nodejs/string_decoder) --------------------------------------------- Version: 1.1.1 - From: 'Node.js' (https://github.com/nodejs) + From: 'Node.js contributors, Joyent, Inc., and other Node contributors' License(s): MIT (bundled/string_decoder-1.1.1/LICENSE) diff --git a/doc/licenses/timers-browserify-2.0.12/README b/doc/licenses/timers-browserify-2.0.12/README index 0e1eb4e8a..9ed389891 100644 --- a/doc/licenses/timers-browserify-2.0.12/README +++ b/doc/licenses/timers-browserify-2.0.12/README @@ -2,7 +2,7 @@ timers-browserify (https://github.com/browserify/timers-browserify) --------------------------------------------- Version: 2.0.12 - From: 'browserify' (https://github.com/browserify) + From: 'J. Ryan Stinnett' (https://github.com/jryans) License(s): - MIT (bundled/timers-browserify-2.0.12/LICENSE) + MIT (bundled/timers-browserify-2.0.12/LICENSE.md) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/JDBCDirectory.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/JDBCDirectory.java index fcba2f678..3ed6d8a50 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/JDBCDirectory.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/JDBCDirectory.java @@ -32,6 +32,7 @@ import org.mybatis.guice.transactional.Transactional; public abstract class JDBCDirectory extends RestrictedObject implements Directory { + @Override @Transactional public void tryAtomically(AtomicDirectoryOperation operation) throws GuacamoleException { diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AtomicDirectoryOperation.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AtomicDirectoryOperation.java index 1d5e196eb..0e081f003 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AtomicDirectoryOperation.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AtomicDirectoryOperation.java @@ -23,7 +23,7 @@ import org.apache.guacamole.GuacamoleException; /** * An operation that should be attempted atomically when passed to - * {@link Directory#tryAtomically()}, if atomic operations are supported by + * {@link Directory#tryAtomically}, if atomic operations are supported by * the Directory. */ public interface AtomicDirectoryOperation { @@ -36,12 +36,12 @@ public interface AtomicDirectoryOperation { * provided directory outside this function, or of the directory invoking * this function are not guaranteed. * - * NOTE: If atomicity is required for this operation, a GuacamoleException - * may be thrown by this function before any changes are made, ensuring the - * operation will only ever be performed atomically. + *

NOTE: If atomicity is required for this operation, a + * GuacamoleException may be thrown by this function before any changes are + * made, ensuring the operation will only ever be performed atomically. * * @param atomic - * True if the provided directory is guaranteed to peform the operation + * True if the provided directory is guaranteed to perform the operation * atomically within the context of this function. * * @param directory diff --git a/guacamole/src/main/frontend/src/app/import/controllers/importConnectionsController.js b/guacamole/src/main/frontend/src/app/import/controllers/importConnectionsController.js index e1bfbed48..1ed354698 100644 --- a/guacamole/src/main/frontend/src/app/import/controllers/importConnectionsController.js +++ b/guacamole/src/main/frontend/src/app/import/controllers/importConnectionsController.js @@ -19,15 +19,49 @@ /* global _ */ +/** + * The allowed MIME type for CSV files. + * + * @type String + */ +const CSV_MIME_TYPE = 'text/csv'; + +/** + * The allowed MIME type for JSON files. + * + * @type String + */ +const JSON_MIME_TYPE = 'application/json'; + +/** + * The allowed MIME types for YAML files. + * NOTE: There is no registered MIME type for YAML files. This may result in a + * wide variety of possible browser-supplied MIME types. + * + * @type String[] + */ +const YAML_MIME_TYPES = [ + 'text/x-yaml', + 'text/yaml', + 'text/yml', + 'application/x-yaml', + 'application/x-yml', + 'application/yaml', + 'application/yml' +]; + +/* + * All file types supported for connection import. + * + * @type {String[]} + */ +const LEGAL_MIME_TYPES = [CSV_MIME_TYPE, JSON_MIME_TYPE, ...YAML_MIME_TYPES]; + /** * The controller for the connection import page. */ angular.module('import').controller('importConnectionsController', ['$scope', '$injector', function importConnectionsController($scope, $injector) { - - // The file types supported for connection import - const LEGAL_FILE_TYPES = ['csv', 'json', 'yaml']; - // Required services const $document = $injector.get('$document'); const $location = $injector.get('$location'); @@ -40,7 +74,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ const permissionService = $injector.get('permissionService'); const userService = $injector.get('userService'); const userGroupService = $injector.get('userGroupService'); - + // Required types const DirectoryPatch = $injector.get('DirectoryPatch'); const Error = $injector.get('Error'); @@ -48,7 +82,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ const PermissionSet = $injector.get('PermissionSet'); const User = $injector.get('User'); const UserGroup = $injector.get('UserGroup'); - + /** * The result of parsing the current upload, if successful. * @@ -118,7 +152,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ $scope.parseResult = null; $scope.patchFailure = null; $scope.fileName = null; - + } // Indicate that data is currently being loaded / processed if the the file @@ -191,7 +225,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ // 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); @@ -201,7 +235,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ }); }); - + } /** @@ -237,9 +271,9 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ PermissionSet.ObjectPermissionType.READ]; return permissions; }, {}) }); - + // Now that we've created all the users, grant access to each - _.forEach(parseResult.users, (connectionIndices, identifier) => + _.forEach(parseResult.users, (connectionIndices, identifier) => // Grant the permissions - note the group flag is `false` userRequests[identifier] = permissionService.patchPermissions( @@ -255,7 +289,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ false)); // Now that we've created all the groups, grant access to each - _.forEach(parseResult.groups, (connectionIndices, identifier) => + _.forEach(parseResult.groups, (connectionIndices, identifier) => // Grant the permissions - note the group flag is `true` groupRequests[identifier] = permissionService.patchPermissions( @@ -337,21 +371,21 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ // If errors were encounted during file parsing, abort further // processing - the user will have a chance to fix the errors and try // again - if (parseResult.hasErrors) + if (parseResult.hasErrors) return; const dataSource = $routeParams.dataSource; // First, attempt to create the connections connectionService.patchConnections(dataSource, parseResult.patches) - .then(connectionResponse => + .then(connectionResponse => // If connection creation is successful, create users and groups - createUsersAndGroups(parseResult).then(() => + createUsersAndGroups(parseResult).then(() => grantConnectionPermissions(parseResult, connectionResponse) .then(() => { - + $scope.processing = false; // Display a success message if everything worked @@ -359,7 +393,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ className : 'success', title : 'IMPORT.DIALOG_HEADER_SUCCESS', text : { - key: 'IMPORT.CONNECTIONS_IMPORTED_SUCCESS', + key: 'IMPORT.INFO_CONNECTIONS_IMPORTED_SUCCESS', variables: { NUMBER: parseResult.patches.length } }, @@ -388,7 +422,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ handleError(error); })) - // If an error occured when the call to create the connections was made, + // If an error occurred when the call to create the connections was made, // skip any further processing - the user will have a chance to fix the // problems and try again .catch(patchFailure => { @@ -396,7 +430,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ $scope.patchFailure = patchFailure; }); } - + /** * Display the provided error to the user in a dismissable dialog. * @@ -410,7 +444,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ resetUploadState(); let text; - + // If it's a import file parsing error if (error instanceof ParseError) text = { @@ -438,18 +472,18 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ name : 'IMPORT.ACTION_ACKNOWLEDGE', callback : () => guacNotification.showStatus(false) }] - }) + }); }; /** * Process the uploaded import file, importing the connections, granting - * connection permissions, or displaying errors to the user if there are + * connection permissions, or displaying errors to the user if there are * problems with the provided file. * * @param {String} mimeType * The MIME type of the uploaded data file. - * + * * @param {String} data * The raw string contents of the import file. */ @@ -457,19 +491,19 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ // Data processing has begun $scope.processing = true; - + // The function that will process all the raw data and return a list of // patches to be submitted to the API let processDataCallback; // Choose the appropriate parse function based on the mimetype - if (mimeType.endsWith("json")) + if (mimeType === JSON_MIME_TYPE) processDataCallback = connectionParseService.parseJSON; - else if (mimeType.endsWith("csv")) + else if (mimeType === CSV_MIME_TYPE) processDataCallback = connectionParseService.parseCSV; - else if (mimeType.endsWith("yaml")) + else if (YAML_MIME_TYPES.indexOf(mimeType) >= 0) processDataCallback = connectionParseService.parseYAML; // The file type was validated before being uploaded - this should @@ -498,12 +532,14 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ $scope.import = () => processData($scope.mimeType, $scope.fileData); /** + * Returns true if import should be disabled, or false if import should be + * allowed. + * * @return {Boolean} - * True if import should be disabled, or false if import should be - * allowed. + * True if import should be disabled, otherwise false. */ $scope.importDisabled = () => - + // Disable import if no data is ready !$scope.dataReady || @@ -524,7 +560,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ } // Clear any upload state - there's no FileReader handler to do it - else + else resetUploadState(); }; @@ -560,19 +596,19 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ // The MIME type of the provided file const mimeType = file.type; - // Check if the mimetype ends with one of the supported types, + // Check if the mimetype is one of the supported types, // e.g. "application/json" or "text/csv" - if (_.every(LEGAL_FILE_TYPES.map( - type => !mimeType.endsWith(type)))) { + if (LEGAL_MIME_TYPES.indexOf(mimeType) < 0) { // If the provided file is not one of the supported types, // display an error and abort processing handleError(new ParseError({ message: "Invalid file type: " + mimeType, - key: 'IMPORT.ERROR_INVALID_FILE_TYPE', + key: 'IMPORT.ERROR_INVALID_MIME_TYPE', variables: { TYPE: mimeType } })); return; + } $scope.fileName = file.name; @@ -592,17 +628,17 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ // If the upload was explicitly aborted, clear any upload state and // do not process the data - if ($scope.aborted) + if ($scope.aborted) resetUploadState(); else { - + // Save the uploaded data $scope.fileData = e.target.result; // Mark the data as ready $scope.dataReady = true; - + // Clear the file reader from the scope now that this file is // fully uploaded $scope.fileReader = null; @@ -610,10 +646,10 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$ } }); - // Read all the data into memory + // Read all the data into memory $scope.fileReader.readAsBinaryString(file); }; - + /** * Whether a drag/drop operation is currently in progress (the user has * dragged a file over the Guacamole connection but has not yet diff --git a/guacamole/src/main/frontend/src/app/import/directives/connectionImportErrors.js b/guacamole/src/main/frontend/src/app/import/directives/connectionImportErrors.js index dcf42695a..65b4403fe 100644 --- a/guacamole/src/main/frontend/src/app/import/directives/connectionImportErrors.js +++ b/guacamole/src/main/frontend/src/app/import/directives/connectionImportErrors.js @@ -20,7 +20,7 @@ /* global _ */ /** - * A directive that displays errors that occured during parsing of a connection + * A directive that displays errors that occurred during parsing of a connection * import file, or errors that were returned from the API during the connection * batch creation attempt. */ @@ -124,7 +124,7 @@ angular.module('import').directive('connectionImportErrors', [ * given parse result. */ const generateConnectionError = (parseResult, index) => { - + // Get the patch associated with the current row const patch = parseResult.patches[index]; @@ -140,18 +140,6 @@ angular.module('import').directive('connectionImportErrors', [ name: connection.name, protocol: connection.protocol, - // The group and parent identifiers, if any are set. Include - // both since these could be a potential source of conflict. - // TODO: Should we _really_ have both of these here? - group: connection.group, - parentIdentifier: connection.parentIdentifier, - - // Get the list of user and group identifiers from the parse - // result. There should one entry in each of these lists for - // each patch. - users: parseResult.users[index], - groups: parseResult.groups[index], - // The human-readable error messages errors: new DisplayErrorList( [ ...(parseResult.errors[index] || []) ]) @@ -159,7 +147,7 @@ angular.module('import').directive('connectionImportErrors', [ }; // If a new connection patch failure is seen, update the display list - $scope.$watch('patchFailure', async function patchFailureChanged(patchFailure) { + $scope.$watch('patchFailure', function patchFailureChanged(patchFailure) { const { parseResult } = $scope; @@ -176,8 +164,6 @@ angular.module('import').directive('connectionImportErrors', [ const connectionError = generateConnectionError(parseResult, index); // Set the error from the PATCH request, if there is one - // TODO: These generally aren't translated from the backend - - // should we even bother trying to translate them? const error = _.get(patchFailure, ['patches', index, 'error']); if (error) connectionError.errors = new DisplayErrorList([error]); @@ -187,7 +173,7 @@ angular.module('import').directive('connectionImportErrors', [ }); // If a new parse result with errors is seen, update the display list - $scope.$watch('parseResult', async function parseResultChanged(parseResult) { + $scope.$watch('parseResult', function parseResultChanged(parseResult) { // Do not process if there are no errors in the provided result if (!parseResult || !parseResult.hasErrors) @@ -226,11 +212,11 @@ angular.module('import').directive('connectionImportErrors', [ .then(translatedError => { connectionError.errors.getArray()[errorIndex] = translatedError; })); - + }); - + return connectionError; - + }); // Once all the translations have been completed, update the @@ -245,4 +231,4 @@ angular.module('import').directive('connectionImportErrors', [ return directive; -}]); \ No newline at end of file +}]); diff --git a/guacamole/src/main/frontend/src/app/import/services/connectionCSVService.js b/guacamole/src/main/frontend/src/app/import/services/connectionCSVService.js index 389749555..9ab6a46d0 100644 --- a/guacamole/src/main/frontend/src/app/import/services/connectionCSVService.js +++ b/guacamole/src/main/frontend/src/app/import/services/connectionCSVService.js @@ -30,72 +30,75 @@ const ATTRIBUTE_SUFFIX = ' (attribute)'; */ angular.module('import').factory('connectionCSVService', ['$injector', function connectionCSVService($injector) { - + // Required types const ParseError = $injector.get('ParseError'); - const ImportConnection = $injector.get('ImportConnection'); + const ImportConnection = $injector.get('ImportConnection'); const TranslatableMessage = $injector.get('TranslatableMessage'); - + // Required services const $q = $injector.get('$q'); const $routeParams = $injector.get('$routeParams'); const schemaService = $injector.get('schemaService'); - + const service = {}; - + /** * Returns a promise that resolves to a object detailing the connection * attributes for the current data source, as well as the connection * paremeters for every protocol, for the current data source. - * + * * The object that the promise will contain an "attributes" key that maps to * a set of attribute names, and a "protocolParameters" key that maps to an * object mapping protocol names to sets of parameter names for that protocol. - * + * * The intended use case for this object is to determine if there is a * connection parameter or attribute with a given name, by e.g. checking the * path `.protocolParameters[protocolName]` to see if a protocol exists, * checking the path `.protocolParameters[protocolName][fieldName]` to see * if a parameter exists for a given protocol, or checking the path * `.attributes[fieldName]` to check if a connection attribute exists. - * + * * @returns {Promise.} + * A promise that resolves to a object detailing the connection + * attributes and parameters for every protocol, for the current data + * source. */ function getFieldLookups() { - + // The current data source - the one that the connections will be // imported into const dataSource = $routeParams.dataSource; - + // Fetch connection attributes and protocols for the current data source return $q.all({ attributes : schemaService.getConnectionAttributes(dataSource), protocols : schemaService.getProtocols(dataSource) }) .then(function connectionStructureRetrieved({attributes, protocols}) { - + return { - + // Translate the forms and fields into a flat map of attribute // name to `true` boolean value attributes: attributes.reduce( (attributeMap, form) => { form.fields.forEach( - field => attributeMap[field.name] = true); + field => attributeMap[field.name] = true); return attributeMap }, {}), - + // Translate the protocol definitions into a map of protocol // name to map of field name to `true` boolean value protocolParameters: _.mapValues( protocols, protocol => protocol.connectionForms.reduce( (protocolFieldMap, form) => { form.fields.forEach( - field => protocolFieldMap[field.name] = true); + field => protocolFieldMap[field.name] = true); return protocolFieldMap; }, {})) }; - }); + }); } /** @@ -104,7 +107,7 @@ angular.module('import').factory('connectionCSVService', * escaped with backslashes, and backslashes can also be escaped using other * backslashes. * - * @param {type} rawIdentifiers + * @param {String} rawIdentifiers * The raw string value as fetched from the CSV. * * @returns {Array.} @@ -151,18 +154,18 @@ angular.module('import').factory('connectionCSVService', .filter(identifier => identifier.length); } - + /** * Given a CSV header row, create and return a promise that will resolve to * a function that can take a CSV data row and return a ImportConnection - * object. If an error occurs while parsing a particular row, the resolved + * object. If an error occurs while parsing a particular row, the resolved * function will throw a ParseError describing the failure. - * + * * The provided CSV must contain columns for name and protocol. Optionally, * the parentIdentifier of the target parent connection group, or a connection - * name path e.g. "ROOT/parent/child" may be included. Additionallty, + * name path e.g. "ROOT/parent/child" may be included. Additionallty, * connection parameters or attributes can be included. - * + * * The names of connection attributes and parameters are not guaranteed to * be mutually exclusive, so the CSV import format supports a distinguishing * suffix. A column may be explicitly declared to be a parameter using a @@ -173,39 +176,33 @@ angular.module('import').factory('connectionCSVService', * If a parameter or attribute name conflicts with the standard * "name", "protocol", "group", or "parentIdentifier" fields, the suffix is * required. - * - * This returned object will be very similar to the Connection type, with - * the exception that a human-readable "group" field may be present, in - * addition to "user" and "userGroup" fields containing arrays of user and - * user group identifiers for whom read access should be granted to this - * connection. - * + * * If a failure occurs while attempting to create the transformer function, * the promise will be rejected with a ParseError describing the failure. - * - * @returns {Promise.>} + * + * @returns {Promise.>} * A promise that will resolve to a function that translates a CSV data * row (array of strings) to a ImportConnection object. */ service.getCSVTransformer = function getCSVTransformer(headerRow) { - + // A promise that will be resolved with the transformer or rejected if // an error occurs const deferred = $q.defer(); - + getFieldLookups().then(({attributes, protocolParameters}) => { - + // All configuration required to generate a function that can // transform a row of CSV into a connection object. // NOTE: This is a single object instead of a collection of variables // to ensure that no stale references are used - e.g. when one getter // invokes another getter const transformConfig = { - + // Callbacks for required fields nameGetter: undefined, protocolGetter: undefined, - + // Callbacks for a parent group ID or group path groupGetter: undefined, parentIdentifierGetter: undefined, @@ -214,25 +211,25 @@ angular.module('import').factory('connectionCSVService', usersGetter: () => [], userGroupsGetter: () => [], - // Callbacks that will generate either connection attributes or + // Callbacks that will generate either connection attributes or // parameters. These callbacks will return a {type, name, value} // object containing the type ("parameter" or "attribute"), // the name of the attribute or parameter, and the corresponding // value. parameterOrAttributeGetters: [] - + }; - + // A set of all headers that have been seen so far. If any of these // are duplicated, the CSV is invalid. const headerSet = {}; - + // Iterate through the headers one by one headerRow.forEach((rawHeader, index) => { - + // Trim to normalize all headers const header = rawHeader.trim(); - + // Check if the header is duplicated if (headerSet[header]) { deferred.reject(new ParseError({ @@ -244,34 +241,33 @@ angular.module('import').factory('connectionCSVService', })); return; } - + // Mark that this particular header has already been seen headerSet[header] = true; - + // A callback that returns the field at the current index const fetchFieldAtIndex = row => row[index]; - // A callback that splits identifier lists by semicolon - // characters into a javascript list of identifiers - const identifierListCallback = row => + // A callback that splits raw string identifier lists by + // semicolon characters into an array of identifiers + const identifierListCallback = row => splitIdentifiers(fetchFieldAtIndex(row)); - + // Set up the name callback if (header == 'name') transformConfig.nameGetter = fetchFieldAtIndex; - + // Set up the protocol callback - else if (header == 'protocol') + else if (header == 'protocol') transformConfig.protocolGetter = fetchFieldAtIndex; - + // Set up the group callback else if (header == 'group') transformConfig.groupGetter = fetchFieldAtIndex; - + // Set up the group parent ID callback else if (header == 'parentIdentifier') - transformConfig.parentIdentifierGetter = ( - identifierListCallback); + transformConfig.parentIdentifierGetter = fetchFieldAtIndex; // Set the user identifiers callback else if (header == 'users') @@ -283,7 +279,7 @@ angular.module('import').factory('connectionCSVService', transformConfig.userGroupsGetter = ( identifierListCallback); - // At this point, any other header might refer to a connection + // At this point, any other header might refer to a connection // parameter or to an attribute // A field may be explicitly specified as a parameter @@ -293,7 +289,7 @@ angular.module('import').factory('connectionCSVService', const parameterName = header.replace(PARAMETER_SUFFIX); transformConfig.parameterOrAttributeGetters.push( row => ({ - type: 'parameter', + type: 'parameters', name: parameterName, value: fetchFieldAtIndex(row) }) @@ -307,35 +303,35 @@ angular.module('import').factory('connectionCSVService', const attributeName = header.replace(ATTRIBUTE_SUFFIX); transformConfig.parameterOrAttributeGetters.push( row => ({ - type: 'attribute', + type: 'attributes', name: parameterName, value: fetchFieldAtIndex(row) }) ); } - + // The field is ambiguous, either an attribute or parameter, // so the getter will have to determine this for every row - else + else transformConfig.parameterOrAttributeGetters.push(row => { - + // The name is just the value of the current header const name = header; - + // The value is at the index that matches the position // of the header const value = fetchFieldAtIndex(row); - + // The protocol may determine whether a field is // a parameter or an attribute (or both) const protocol = transformConfig.protocolGetter(row); - + // Determine if the field refers to an attribute or a // parameter (or both, which is an error) const isAttribute = !!attributes[name]; const isParameter = !!_.get( protocolParameters, [protocol, name]); - + // If there is both an attribute and a protocol-specific // parameter with the provided name, it's impossible to // figure out which this should be @@ -345,7 +341,7 @@ angular.module('import').factory('connectionCSVService', key: 'IMPORT.ERROR_AMBIGUOUS_CSV_HEADER', variables: { HEADER: header } }); - + // It's neither an attribute or a parameter else if (!isAttribute && !isParameter) throw new ParseError({ @@ -353,10 +349,10 @@ angular.module('import').factory('connectionCSVService', key: 'IMPORT.ERROR_INVALID_CSV_HEADER', variables: { HEADER: header } }); - + // Choose the appropriate type const type = isAttribute ? 'attributes' : 'parameters'; - + return { type, name, value }; }); }); @@ -367,14 +363,14 @@ angular.module('import').factory('connectionCSVService', usersGetter, userGroupsGetter, parameterOrAttributeGetters } = transformConfig; - + // Fail if the name wasn't provided - if (!nameGetter) + if (!nameGetter) return deferred.reject(new ParseError({ message: 'The connection name must be provided', key: 'IMPORT.ERROR_REQUIRED_NAME' })); - + // Fail if the protocol wasn't provided if (!protocolGetter) return deferred.reject(new ParseError({ @@ -382,16 +378,9 @@ angular.module('import').factory('connectionCSVService', key: 'IMPORT.ERROR_REQUIRED_PROTOCOL' })); - // If both are specified, the parent group is ambigious - if (parentIdentifierGetter && groupGetter) - throw new ParseError({ - message: 'Only one of group or parentIdentifier can be set', - key: 'IMPORT.ERROR_AMBIGUOUS_PARENT_GROUP' - }); - // The function to transform a CSV row into a connection object deferred.resolve(function transformCSVRow(row) { - + // Get name and protocol const name = nameGetter(row); const protocol = protocolGetter(row); @@ -399,47 +388,45 @@ angular.module('import').factory('connectionCSVService', // Get any users or user groups who should be granted access const users = usersGetter(row); const groups = userGroupsGetter(row); - + // Get the parent group ID and/or group path const group = groupGetter && groupGetter(row); const parentIdentifier = ( parentIdentifierGetter && parentIdentifierGetter(row)); - + return new ImportConnection({ - + // Fields that are not protocol-specific - ...{ - name, - protocol, - parentIdentifier, - group, - users, - groups - }, - + name, + protocol, + parentIdentifier, + group, + users, + groups, + // Fields that might potentially be either attributes or // parameters, depending on the protocol ...parameterOrAttributeGetters.reduce((values, getter) => { - + // Determine the type, name, and value const { type, name, value } = getter(row); - + // Set the value and continue on to the next attribute // or parameter values[type][name] = value; return values; - + }, {parameters: {}, attributes: {}}) - + }); - + }); - + }); - + return deferred.promise; }; - + return service; - -}]); \ No newline at end of file + +}]); diff --git a/guacamole/src/main/frontend/src/app/import/services/connectionParseService.js b/guacamole/src/main/frontend/src/app/import/services/connectionParseService.js index dd9f49593..ad90145d5 100644 --- a/guacamole/src/main/frontend/src/app/import/services/connectionParseService.js +++ b/guacamole/src/main/frontend/src/app/import/services/connectionParseService.js @@ -35,7 +35,7 @@ angular.module('import').factory('connectionParseService', const ParseError = $injector.get('ParseError'); const ParseResult = $injector.get('ParseResult'); const TranslatableMessage = $injector.get('TranslatableMessage'); - + // Required services const $q = $injector.get('$q'); const $routeParams = $injector.get('$routeParams'); @@ -44,18 +44,18 @@ angular.module('import').factory('connectionParseService', const connectionGroupService = $injector.get('connectionGroupService'); const service = {}; - + /** * Perform basic checks, common to all file types - namely that the parsed - * data is an array, and contains at least one connection entry. Returns an + * data is an array, and contains at least one connection entry. Returns an * error if any of these basic checks fails. - * - * returns {ParseError} + * + * @returns {ParseError} * An error describing the parsing failure, if one of the basic checks * fails. */ function performBasicChecks(parsedData) { - + // Make sure that the file data parses to an array (connection list) if (!(parsedData instanceof Array)) return new ParseError({ @@ -71,64 +71,67 @@ angular.module('import').factory('connectionParseService', key: 'IMPORT.ERROR_EMPTY_FILE' }); } - - + + /** * Returns a promise that resolves to an object mapping potential groups * that might be encountered in an imported connection to group identifiers. - * + * * The idea is that a user-provided import file might directly specify a * parentIdentifier, or it might specify a named group path like "ROOT", - * "ROOT/parent", or "ROOT/parent/child". This object resolved by the - * promise returned from this function will map all of the above to the + * "ROOT/parent", or "ROOT/parent/child". This object resolved by the + * promise returned from this function will map all of the above to the * identifier of the appropriate group, if defined. - * + * * @returns {Promise.} + * A promise that resolves to an object mapping groups to group + * identifiers. */ function getGroupLookups() { - + // The current data source - defines all the groups that the connections // might be imported into const dataSource = $routeParams.dataSource; - + const deferredGroupLookups = $q.defer(); - + connectionGroupService.getConnectionGroupTree(dataSource).then( rootGroup => { - + const groupLookup = {}; - + // Add the specified group to the lookup, appending all specified // prefixes, and then recursively call saveLookups for all children // of the group, appending to the prefix for each level - function saveLookups(prefix, group) { - + const saveLookups = (prefix, group) => { + // To get the path for the current group, add the name const currentPath = prefix + group.name; - + // Add the current path to the lookup groupLookup[currentPath] = group.identifier; - + // Add each child group to the lookup const nextPrefix = currentPath + "/"; _.forEach(group.childConnectionGroups, childGroup => saveLookups(nextPrefix, childGroup)); + } - + // Start at the root group saveLookups("", rootGroup); - + // Resolve with the now fully-populated lookups deferredGroupLookups.resolve(groupLookup); - + }); - + return deferredGroupLookups.promise; } /** * Returns a promise that will resolve to a transformer function that will - * take an object that may contain a "group" field, replacing it if present + * take an object that may contain a "group" field, replacing it if present * with a "parentIdentifier". If both a "group" and "parentIdentifier" field * are present on the provided object, or if no group exists at the specified * path, the function will throw a ParseError describing the failure. @@ -189,7 +192,7 @@ angular.module('import').factory('connectionParseService', * parsing all provided connection data. */ function parseConnectionData(connectionData, transformFunctions) { - + // Check that the provided connection data array is not empty const checkError = performBasicChecks(connectionData); if (checkError) { @@ -198,6 +201,7 @@ angular.module('import').factory('connectionParseService', return deferred.promise; } + // Get the group transformer to apply to each connection return getGroupTransformer().then(groupTransformer => connectionData.reduce((parseResult, data, index) => { @@ -261,7 +265,7 @@ angular.module('import').factory('connectionParseService', value: connection })); - // If there are any errors for this connection fail the whole batch + // If there are any errors for this connection, fail the whole batch if (connectionErrors.length) parseResult.hasErrors = true; @@ -284,14 +288,14 @@ angular.module('import').factory('connectionParseService', * parsing all provided connection data. */ service.parseCSV = function parseCSV(csvData) { - + // Convert to an array of arrays, one per CSV row (including the header) // NOTE: skip_empty_lines is required, or a trailing newline will error let parsedData; try { parsedData = parseCSVData(csvData, {skip_empty_lines: true}); - } - + } + // If the CSV parser throws an error, reject with that error. No // translation key will be available here. catch(error) { @@ -346,7 +350,7 @@ angular.module('import').factory('connectionParseService', deferred.reject(new ParseError({ message: error.message })); return deferred.promise; } - + // Produce a ParseResult return parseConnectionData(connectionData); }; diff --git a/guacamole/src/main/frontend/src/app/import/styles/help.css b/guacamole/src/main/frontend/src/app/import/styles/help.css index a73b43afa..9cbdb5b18 100644 --- a/guacamole/src/main/frontend/src/app/import/styles/help.css +++ b/guacamole/src/main/frontend/src/app/import/styles/help.css @@ -55,5 +55,5 @@ padding-top: 1em; width: fit-content; margin-left: 1em; - -} \ No newline at end of file + +} diff --git a/guacamole/src/main/frontend/src/app/import/styles/import.css b/guacamole/src/main/frontend/src/app/import/styles/import.css index 656571c63..15d08d6d7 100644 --- a/guacamole/src/main/frontend/src/app/import/styles/import.css +++ b/guacamole/src/main/frontend/src/app/import/styles/import.css @@ -27,15 +27,21 @@ } .import .errors table { + width: 100%; + } .import .errors .error-message { + color: red; + } .import .errors .error-message ul { + margin: 0px; + } .file-upload-container { @@ -56,13 +62,17 @@ } .file-upload-container.file-selected { + display: flex; flex-direction: row; gap: 100px; + } .file-upload-container .clear { + margin: 0; + } .file-upload-container .upload-header { diff --git a/guacamole/src/main/frontend/src/app/import/templates/connectionErrors.html b/guacamole/src/main/frontend/src/app/import/templates/connectionErrors.html index eee23c260..f521d1996 100644 --- a/guacamole/src/main/frontend/src/app/import/templates/connectionErrors.html +++ b/guacamole/src/main/frontend/src/app/import/templates/connectionErrors.html @@ -5,7 +5,7 @@ placeholder="'IMPORT.FIELD_PLACEHOLDER_FILTER' | translate" properties="filteredErrorProperties"> - + diff --git a/guacamole/src/main/frontend/src/app/import/templates/connectionImport.html b/guacamole/src/main/frontend/src/app/import/templates/connectionImport.html index bab6485c7..ae937a200 100644 --- a/guacamole/src/main/frontend/src/app/import/templates/connectionImport.html +++ b/guacamole/src/main/frontend/src/app/import/templates/connectionImport.html @@ -1,34 +1,34 @@ -
+
-

{{'IMPORT.HEADER' | translate}}

+

{{'IMPORT.SECTION_HEADER_CONNECTION_IMPORT' | translate}}

{{fileName}}
- +
- {{'IMPORT.UPLOAD_FILE_TYPES' | translate}} + {{'IMPORT.HELP_UPLOAD_FILE_TYPES' | translate}} {{'IMPORT.UPLOAD_HELP_LINK' | translate}} + class="file-help-link">{{'IMPORT.ACTION_VIEW_FORMAT_HELP' | translate}}
-
{{'IMPORT.UPLOAD_DROP_TITLE' | translate}}
+
{{'IMPORT.HELP_UPLOAD_DROP_TITLE' | translate}}
- {{'IMPORT.UPLOAD_BROWSE_LINK' | translate}} + {{'IMPORT.ACTION_BROWSE' | translate}}
{{fileName}}
@@ -38,13 +38,13 @@
-
diff --git a/guacamole/src/main/frontend/src/app/import/templates/connectionImportFileHelp.html b/guacamole/src/main/frontend/src/app/import/templates/connectionImportFileHelp.html index 8e88d235a..d3abda1db 100644 --- a/guacamole/src/main/frontend/src/app/import/templates/connectionImportFileHelp.html +++ b/guacamole/src/main/frontend/src/app/import/templates/connectionImportFileHelp.html @@ -1,90 +1,26 @@ -
+
-

{{'IMPORT.HELP_HEADER' | translate}}

+

{{'IMPORT.SECTION_HEADER_HELP_CONNECTION_IMPORT_FILE' | translate}}

{{'IMPORT.HELP_FILE_TYPE_HEADER' | translate}}

{{'IMPORT.HELP_FILE_TYPE_DESCRIPTION' | translate}}

-

{{'IMPORT.HELP_CSV_HEADER' | translate}}

+

{{'IMPORT.SECTION_HEADER_CSV' | translate}}

{{'IMPORT.HELP_CSV_DESCRIPTION' | translate}}

{{'IMPORT.HELP_CSV_MORE_DETAILS' | translate}}

-
name,protocol,hostname,group,users,groups,guacd-encryption (attribute)
-conn1,vnc,conn1.web.com,ROOT,guac user 1;guac user 2,Connection 1 Users,none
-conn2,rdp,conn2.web.com,ROOT/Parent Group,guac user 1,,ssl
-conn3,ssh,conn3.web.com,ROOT/Parent Group/Child Group,guac user 2;guac user 3,,
-conn4,kubernetes,,,,,
+
{{'IMPORT.HELP_CSV_EXAMPLE' | translate }}
-

{{'IMPORT.HELP_JSON_HEADER' | translate}}

+

{{'IMPORT.SECTION_HEADER_JSON' | translate}}

{{'IMPORT.HELP_JSON_DESCRIPTION' | translate}}

{{'IMPORT.HELP_JSON_MORE_DETAILS' | translate}}

-
[
-  {
-    "name": "conn1",
-    "protocol": "vnc",
-    "parameters": { "hostname": "conn1.web.com" },
-    "parentIdentifier": "ROOT",
-    "users": [ "guac user 1", "guac user 2" ],
-    "groups": [ "Connection 1 Users" ],
-    "attributes": { "guacd-encryption": "none" }
-  },
-  {
-    "name": "conn2",
-    "protocol": "rdp",
-    "parameters": { "hostname": "conn2.web.com" },
-    "group": "ROOT/Parent Group",
-    "users": [ "guac user 1" ],
-    "attributes": { "guacd-encryption": "none" }
-  },
-  {
-    "name": "conn3",
-    "protocol": "ssh",
-    "parameters": { "hostname": "conn3.web.com" },
-    "group": "ROOT/Parent Group/Child Group",
-    "users": [ "guac user 2", "guac user 3" ]
-  },
-  {
-    "name": "conn4",
-    "protocol": "kubernetes"
-  }
-]
+
{{'IMPORT.HELP_JSON_EXAMPLE' | translate }}
-

{{'IMPORT.HELP_YAML_HEADER' | translate}}

+

{{'IMPORT.SECTION_HEADER_YAML' | translate}}

{{'IMPORT.HELP_YAML_DESCRIPTION' | translate}}

-
---
-  - name: conn1
-    protocol: vnc
-    parameters:
-      hostname: conn1.web.com
-    group: ROOT
-    users:
-    - guac user 1
-    - guac user 2
-    groups:
-    - Connection 1 Users
-    attributes:
-      guacd-encryption: none
-  - name: conn2
-    protocol: rdp
-    parameters:
-      hostname: conn2.web.com
-    group: ROOT/Parent Group
-    users:
-    - guac user 1
-    attributes:
-      guacd-encryption: none
-  - name: conn3
-    protocol: ssh
-    parameters:
-      hostname: conn3.web.com
-    group: ROOT/Parent Group/Child Group
-    users:
-    - guac user 2
-    - guac user 3
-  - name: conn4
-    protocol: kubernetes
+
{{'IMPORT.HELP_YAML_EXAMPLE' | translate}}
  1. {{'IMPORT.HELP_SEMICOLON_FOOTNOTE' | translate}}
  2. diff --git a/guacamole/src/main/frontend/src/app/import/types/DisplayErrorList.js b/guacamole/src/main/frontend/src/app/import/types/DisplayErrorList.js index d775c5734..26ee65d82 100644 --- a/guacamole/src/main/frontend/src/app/import/types/DisplayErrorList.js +++ b/guacamole/src/main/frontend/src/app/import/types/DisplayErrorList.js @@ -33,25 +33,33 @@ angular.module('import').factory('DisplayErrorList', [ */ const DisplayErrorList = function DisplayErrorList(messages) { - // Use empty message list by default + /** + * The error messages that should be prepared for display. + * + * @type {String[]} + */ this.messages = messages || []; - // The single String message composed of all messages concatenated - // together. This will be used for filtering / sorting, and should only - // be calculated once. - this.cachedMessage = null; + /** + * The single String message composed of all messages concatenated + * together. This will be used for filtering / sorting, and should only + * be calculated once, when toString() is called. + * + * @type {String} + */ + this.concatenatedMessage = null; }; /** * Return a sortable / filterable representation of all the error messages - * wrapped by this DisplayErrorList. - * + * wrapped by this DisplayErrorList. + * * NOTE: Once this method is called, any changes to the underlying array * will have no effect. This is to ensure that repeated calls to toString() * by sorting / filtering UI code will not regenerate the concatenated * message every time. - * + * * @returns {String} * A sortable / filterable representation of the error messages wrapped * by this DisplayErrorList @@ -80,4 +88,4 @@ angular.module('import').factory('DisplayErrorList', [ return DisplayErrorList; -}]); \ No newline at end of file +}]); diff --git a/guacamole/src/main/frontend/src/app/import/types/ImportConnection.js b/guacamole/src/main/frontend/src/app/import/types/ImportConnection.js index 5639ba1c6..1c1f5b81d 100644 --- a/guacamole/src/main/frontend/src/app/import/types/ImportConnection.js +++ b/guacamole/src/main/frontend/src/app/import/types/ImportConnection.js @@ -71,9 +71,7 @@ angular.module('import').factory('ImportConnection', [ /** * Connection configuration parameters, as dictated by the protocol in - * use, arranged as name/value pairs. This information may not be - * available until directly queried. If this information is - * unavailable, this property will be null or undefined. + * use, arranged as name/value pairs. * * @type Object. */ diff --git a/guacamole/src/main/frontend/src/app/import/types/ImportConnectionError.js b/guacamole/src/main/frontend/src/app/import/types/ImportConnectionError.js index 4d4fd3661..bd77f8c25 100644 --- a/guacamole/src/main/frontend/src/app/import/types/ImportConnectionError.js +++ b/guacamole/src/main/frontend/src/app/import/types/ImportConnectionError.js @@ -27,13 +27,15 @@ angular.module('import').factory('ImportConnectionError', ['$injector', const DisplayErrorList = $injector.get('DisplayErrorList'); /** - * A representation of a connection to be imported, as parsed from an + * A representation of the errors associated with a connection to be + * imported, along with some basic information connection information to + * identify the connection having the error, as returned from a parsed * user-supplied import file. * * @constructor - * @param {ImportConnection|Object} [template={}] + * @param {ImportConnectionError|Object} [template={}] * The object whose properties should be copied within the new - * Connection. + * ImportConnectionError. */ const ImportConnectionError = function ImportConnectionError(template) { @@ -46,22 +48,6 @@ angular.module('import').factory('ImportConnectionError', ['$injector', */ this.rowNumber = template.rowNumber; - /** - * The unique identifier of the connection group that contains this - * connection. - * - * @type String - */ - this.parentIdentifier = template.parentIdentifier; - - /** - * The path to the connection group that contains this connection, - * written as e.g. "ROOT/parent/child/group". - * - * @type String - */ - this.group = template.group; - /** * The human-readable name of this connection, which is not necessarily * unique. @@ -78,22 +64,6 @@ angular.module('import').factory('ImportConnectionError', ['$injector', */ this.protocol = template.protocol; - /** - * The identifiers of all users who should be granted read access to - * this connection. - * - * @type String[] - */ - this.users = template.users || []; - - /** - * The identifiers of all user groups who should be granted read access - * to this connection. - * - * @type String[] - */ - this.groups = template.groups || []; - /** * The error messages associated with this particular connection, if any. * @@ -105,4 +75,4 @@ angular.module('import').factory('ImportConnectionError', ['$injector', return ImportConnectionError; -}]); \ No newline at end of file +}]); diff --git a/guacamole/src/main/frontend/src/app/import/types/ParseResult.js b/guacamole/src/main/frontend/src/app/import/types/ParseResult.js index 1a5abd14d..6e28f20f3 100644 --- a/guacamole/src/main/frontend/src/app/import/types/ParseResult.js +++ b/guacamole/src/main/frontend/src/app/import/types/ParseResult.js @@ -26,7 +26,7 @@ angular.module('import').factory('ParseResult', [function defineParseResult() { * The result of parsing a connection import file - containing a list of * API patches ready to be submitted to the PATCH REST API for batch * connection creation, a set of users and user groups to grant access to - * each connection, and any errors that may have occured while parsing + * each connection, and any errors that may have occurred while parsing * each connection. * * @constructor @@ -49,7 +49,7 @@ angular.module('import').factory('ParseResult', [function defineParseResult() { /** * An object whose keys are the user identifiers of users specified - * in the batch import. and the keys are an array of indices of + * in the batch import, and whose values are an array of indices of * connections to which those users should be granted access. * * @type {Object.} @@ -68,7 +68,7 @@ angular.module('import').factory('ParseResult', [function defineParseResult() { /** * An array of errors encountered while parsing the corresponding * connection (at the same array index). Each connection should have a - * an array of errors. If empty, no errors occured for this connection. + * an array of errors. If empty, no errors occurred for this connection. * * @type {ParseError[][]} */ @@ -85,5 +85,5 @@ angular.module('import').factory('ParseResult', [function defineParseResult() { }; return ParseResult; - + }]); diff --git a/guacamole/src/main/frontend/src/app/index/config/indexRouteConfig.js b/guacamole/src/main/frontend/src/app/index/config/indexRouteConfig.js index 7caeee526..d24df9179 100644 --- a/guacamole/src/main/frontend/src/app/index/config/indexRouteConfig.js +++ b/guacamole/src/main/frontend/src/app/index/config/indexRouteConfig.js @@ -135,7 +135,7 @@ angular.module('index').config(['$routeProvider', '$locationProvider', resolve : { updateCurrentToken: updateCurrentToken } }) - // Connection import page + // Connection import file format help page .when('/import/connection/file-format-help', { title : 'APP.NAME', bodyClassName : 'settings', diff --git a/guacamole/src/main/frontend/src/app/rest/services/connectionService.js b/guacamole/src/main/frontend/src/app/rest/services/connectionService.js index c46874ea4..9d41db6da 100644 --- a/guacamole/src/main/frontend/src/app/rest/services/connectionService.js +++ b/guacamole/src/main/frontend/src/app/rest/services/connectionService.js @@ -26,9 +26,6 @@ angular.module('rest').factory('connectionService', ['$injector', // Required services var authenticationService = $injector.get('authenticationService'); var cacheService = $injector.get('cacheService'); - - // Required types - const Error = $injector.get('Error'); var service = {}; @@ -166,6 +163,10 @@ angular.module('rest').factory('connectionService', ['$injector', * connection patching process, the entire request will fail, and no * changes will be persisted. * + * @param {String} dataSource + * The identifier of the data source associated with the connections to + * be patched. + * * @param {DirectoryPatch.[]} patches * An array of patches to apply. * @@ -194,7 +195,7 @@ angular.module('rest').factory('connectionService', ['$injector', }); - } + }; /** * Makes a request to the REST API to delete a connection, diff --git a/guacamole/src/main/frontend/src/app/rest/services/userGroupService.js b/guacamole/src/main/frontend/src/app/rest/services/userGroupService.js index 987f8383a..147621878 100644 --- a/guacamole/src/main/frontend/src/app/rest/services/userGroupService.js +++ b/guacamole/src/main/frontend/src/app/rest/services/userGroupService.js @@ -199,6 +199,10 @@ angular.module('rest').factory('userGroupService', ['$injector', * connection patching process, the entire request will fail, and no * changes will be persisted. * + * @param {String} dataSource + * The identifier of the data source associated with the user groups to + * be patched. + * * @param {DirectoryPatch.[]} patches * An array of patches to apply. * @@ -221,7 +225,7 @@ angular.module('rest').factory('userGroupService', ['$injector', return patchResponse; }); - } + }; return service; diff --git a/guacamole/src/main/frontend/src/app/rest/services/userService.js b/guacamole/src/main/frontend/src/app/rest/services/userService.js index 252aead3a..c1d3c3572 100644 --- a/guacamole/src/main/frontend/src/app/rest/services/userService.js +++ b/guacamole/src/main/frontend/src/app/rest/services/userService.js @@ -245,6 +245,10 @@ angular.module('rest').factory('userService', ['$injector', * connection patching process, the entire request will fail, and no * changes will be persisted. * + * @param {String} dataSource + * The identifier of the data source associated with the users to be + * patched. + * * @param {DirectoryPatch.[]} patches * An array of patches to apply. * @@ -267,7 +271,7 @@ angular.module('rest').factory('userService', ['$injector', return patchResponse; }); - } + }; return service; diff --git a/guacamole/src/main/frontend/src/app/rest/types/DirectoryPatch.js b/guacamole/src/main/frontend/src/app/rest/types/DirectoryPatch.js index 05874d4fc..fb2a3c050 100644 --- a/guacamole/src/main/frontend/src/app/rest/types/DirectoryPatch.js +++ b/guacamole/src/main/frontend/src/app/rest/types/DirectoryPatch.js @@ -75,12 +75,12 @@ angular.module('rest').factory('DirectoryPatch', [function defineDirectoryPatch( /** * Adds the specified object to the relation. */ - ADD : "add", + ADD : 'add', /** * Removes the specified object from the relation. */ - REMOVE : "remove" + REMOVE : 'remove' }; diff --git a/guacamole/src/main/frontend/src/app/settings/templates/settingsConnections.html b/guacamole/src/main/frontend/src/app/settings/templates/settingsConnections.html index 0718307a1..004faa31c 100644 --- a/guacamole/src/main/frontend/src/app/settings/templates/settingsConnections.html +++ b/guacamole/src/main/frontend/src/app/settings/templates/settingsConnections.html @@ -11,7 +11,7 @@ {{'SETTINGS_CONNECTIONS.ACTION_IMPORT_CONNECTIONS' | translate}} + href="#/import/{{dataSource | escape}}/connection/">{{'SETTINGS_CONNECTIONS.ACTION_IMPORT' | translate}} patchOutcomes = new ArrayList<>(); @@ -457,8 +457,9 @@ public abstract class DirectoryResource