mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-05 20:57:40 +00:00
GUACAMOLE-926: Clean up unneeded code, trailing whitespace; fix bugs, styling, comments and licenses.
This commit is contained in:
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -32,6 +32,7 @@ import org.mybatis.guice.transactional.Transactional;
|
||||
public abstract class JDBCDirectory<ObjectType extends Identifiable>
|
||||
extends RestrictedObject implements Directory<ObjectType> {
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void tryAtomically(AtomicDirectoryOperation<ObjectType> operation)
|
||||
throws GuacamoleException {
|
||||
|
@@ -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<ObjectType extends Identifiable> {
|
||||
@@ -36,12 +36,12 @@ public interface AtomicDirectoryOperation<ObjectType extends Identifiable> {
|
||||
* 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.
|
||||
* <p>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
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
||||
}]);
|
||||
}]);
|
||||
|
@@ -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.<Object>}
|
||||
* 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.<String>}
|
||||
@@ -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.<Function.<String[], Object>>}
|
||||
*
|
||||
* @returns {Promise.<Function.<String[], ImportConnection>>}
|
||||
* 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;
|
||||
|
||||
}]);
|
||||
|
||||
}]);
|
||||
|
@@ -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.<Object>}
|
||||
* 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);
|
||||
};
|
||||
|
@@ -55,5 +55,5 @@
|
||||
padding-top: 1em;
|
||||
width: fit-content;
|
||||
margin-left: 1em;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -5,7 +5,7 @@
|
||||
placeholder="'IMPORT.FIELD_PLACEHOLDER_FILTER' | translate"
|
||||
properties="filteredErrorProperties"></guac-filter>
|
||||
|
||||
<!-- List of current u -->
|
||||
<!-- List of connection import errors -->
|
||||
<table class="sorted">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@@ -1,34 +1,34 @@
|
||||
<div class="settings-view import">
|
||||
<div class="settings-view view import">
|
||||
|
||||
<div class="header">
|
||||
<h2>{{'IMPORT.HEADER' | translate}}</h2>
|
||||
<h2>{{'IMPORT.SECTION_HEADER_CONNECTION_IMPORT' | translate}}</h2>
|
||||
<guac-user-menu></guac-user-menu>
|
||||
</div>
|
||||
|
||||
<div ng-show="fileName" class="file-upload-container file-selected">
|
||||
<div class="file-name"> {{fileName}} </div>
|
||||
<button class="danger clear" ng-click="cancel()">
|
||||
{{'IMPORT.BUTTON_CLEAR' | translate}}
|
||||
{{'IMPORT.ACTION_CLEAR' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div ng-show="!fileName" class="file-upload-container">
|
||||
|
||||
<div class="upload-header">
|
||||
<span class="file-options">{{'IMPORT.UPLOAD_FILE_TYPES' | translate}}</span>
|
||||
<span class="file-options">{{'IMPORT.HELP_UPLOAD_FILE_TYPES' | translate}}</span>
|
||||
<a
|
||||
href="#/import/connection/file-format-help" target="_blank"
|
||||
class="file-help-link">{{'IMPORT.UPLOAD_HELP_LINK' | translate}}
|
||||
class="file-help-link">{{'IMPORT.ACTION_VIEW_FORMAT_HELP' | translate}}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="drop-target" ng-class="{ 'drop-pending': dropPending, 'file-present': fileName}">
|
||||
|
||||
<div class="title">{{'IMPORT.UPLOAD_DROP_TITLE' | translate}}</div>
|
||||
<div class="title">{{'IMPORT.HELP_UPLOAD_DROP_TITLE' | translate}}</div>
|
||||
|
||||
<input type="file" class="file-upload-input"/>
|
||||
<a ng-click="openFileBrowser()" class="browse-link">
|
||||
{{'IMPORT.UPLOAD_BROWSE_LINK' | translate}}
|
||||
{{'IMPORT.ACTION_BROWSE' | translate}}
|
||||
</a>
|
||||
|
||||
<div class="file-name"> {{fileName}} </div>
|
||||
@@ -38,13 +38,13 @@
|
||||
</div>
|
||||
|
||||
<div class="import-buttons">
|
||||
<button
|
||||
ng-click="import()" ng-disabled="importDisabled()" class="import">
|
||||
{{'IMPORT.BUTTON_IMPORT' | translate}}
|
||||
<button
|
||||
ng-click="import()" ng-disabled="importDisabled()" class="save import">
|
||||
{{'IMPORT.ACTION_IMPORT' | translate}}
|
||||
</button>
|
||||
<button
|
||||
ng-click="cancel()" ng-disabled="cancelDisabled()" class="cancel">
|
||||
{{'IMPORT.BUTTON_CANCEL' | translate}}
|
||||
{{'IMPORT.ACTION_CANCEL' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
@@ -1,90 +1,26 @@
|
||||
<div class="import help">
|
||||
<div class="import view help">
|
||||
|
||||
<div class="header">
|
||||
<h2>{{'IMPORT.HELP_HEADER' | translate}}</h2>
|
||||
<h2>{{'IMPORT.SECTION_HEADER_HELP_CONNECTION_IMPORT_FILE' | translate}}</h2>
|
||||
<guac-user-menu></guac-user-menu>
|
||||
</div>
|
||||
|
||||
<h2>{{'IMPORT.HELP_FILE_TYPE_HEADER' | translate}}</h2>
|
||||
<p>{{'IMPORT.HELP_FILE_TYPE_DESCRIPTION' | translate}}</p>
|
||||
|
||||
<h2>{{'IMPORT.HELP_CSV_HEADER' | translate}}</h2>
|
||||
<h2>{{'IMPORT.SECTION_HEADER_CSV' | translate}}</h2>
|
||||
<p>{{'IMPORT.HELP_CSV_DESCRIPTION' | translate}}</p>
|
||||
<p>{{'IMPORT.HELP_CSV_MORE_DETAILS' | translate}}</p>
|
||||
<pre>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,,,,,</pre>
|
||||
<pre>{{'IMPORT.HELP_CSV_EXAMPLE' | translate }}</pre>
|
||||
|
||||
<h2>{{'IMPORT.HELP_JSON_HEADER' | translate}}</h2>
|
||||
<h2>{{'IMPORT.SECTION_HEADER_JSON' | translate}}</h2>
|
||||
<p>{{'IMPORT.HELP_JSON_DESCRIPTION' | translate}}</p>
|
||||
<p>{{'IMPORT.HELP_JSON_MORE_DETAILS' | translate}}</p>
|
||||
<pre>[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]</pre>
|
||||
<pre>{{'IMPORT.HELP_JSON_EXAMPLE' | translate }}</pre>
|
||||
|
||||
<h2>{{'IMPORT.HELP_YAML_HEADER' | translate}}</h2>
|
||||
<h2>{{'IMPORT.SECTION_HEADER_YAML' | translate}}</h2>
|
||||
<p>{{'IMPORT.HELP_YAML_DESCRIPTION' | translate}}</p>
|
||||
<pre>---
|
||||
- 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</pre>
|
||||
<pre>{{'IMPORT.HELP_YAML_EXAMPLE' | translate}}</pre>
|
||||
|
||||
<ol class="footnotes">
|
||||
<li>{{'IMPORT.HELP_SEMICOLON_FOOTNOTE' | translate}}</li>
|
||||
|
@@ -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;
|
||||
|
||||
}]);
|
||||
}]);
|
||||
|
@@ -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.<String, String>
|
||||
*/
|
||||
|
@@ -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;
|
||||
|
||||
}]);
|
||||
}]);
|
||||
|
@@ -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.<String, Integer[]>}
|
||||
@@ -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;
|
||||
|
||||
|
||||
}]);
|
||||
|
@@ -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',
|
||||
|
@@ -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.<Connection>[]} 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,
|
||||
|
@@ -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.<UserGroup>[]} patches
|
||||
* An array of patches to apply.
|
||||
*
|
||||
@@ -221,7 +225,7 @@ angular.module('rest').factory('userGroupService', ['$injector',
|
||||
return patchResponse;
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
|
@@ -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.<User>[]} patches
|
||||
* An array of patches to apply.
|
||||
*
|
||||
@@ -267,7 +271,7 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
return patchResponse;
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
|
@@ -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'
|
||||
|
||||
};
|
||||
|
||||
|
@@ -11,7 +11,7 @@
|
||||
|
||||
<a class="import-connections button"
|
||||
ng-show="canCreateConnections()"
|
||||
href="#/import/{{dataSource | escape}}/connection/">{{'SETTINGS_CONNECTIONS.ACTION_IMPORT_CONNECTIONS' | translate}}</a>
|
||||
href="#/import/{{dataSource | escape}}/connection/">{{'SETTINGS_CONNECTIONS.ACTION_IMPORT' | translate}}</a>
|
||||
|
||||
<a class="add-connection button"
|
||||
ng-show="canCreateConnections()"
|
||||
|
@@ -5,15 +5,17 @@
|
||||
"APP" : {
|
||||
|
||||
"NAME" : "Apache Guacamole",
|
||||
"VERSION" : "1.5.0",
|
||||
"VERSION" : "${project.version}",
|
||||
|
||||
"ACTION_ACKNOWLEDGE" : "OK",
|
||||
"ACTION_CANCEL" : "Cancel",
|
||||
"ACTION_CLEAR" : "Clear",
|
||||
"ACTION_CLONE" : "Clone",
|
||||
"ACTION_CONTINUE" : "Continue",
|
||||
"ACTION_DELETE" : "Delete",
|
||||
"ACTION_DELETE_SESSIONS" : "Kill Sessions",
|
||||
"ACTION_DOWNLOAD" : "Download",
|
||||
"ACTION_IMPORT" : "Import",
|
||||
"ACTION_LOGIN" : "Login",
|
||||
"ACTION_LOGIN_AGAIN" : "Re-login",
|
||||
"ACTION_LOGOUT" : "Logout",
|
||||
@@ -60,8 +62,8 @@
|
||||
|
||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
||||
"ACTION_CANCEL" : "@:APP.ACTION_CANCEL",
|
||||
"ACTION_CLEAR_CLIENT_MESSAGES" : "Clear",
|
||||
"ACTION_CLEAR_COMPLETED_TRANSFERS" : "Clear",
|
||||
"ACTION_CLEAR_CLIENT_MESSAGES" : "@:APP.ACTION_CLEAR",
|
||||
"ACTION_CLEAR_COMPLETED_TRANSFERS" : "@:APP.ACTION_CLEAR",
|
||||
"ACTION_CONTINUE" : "@:APP.ACTION_CONTINUE",
|
||||
"ACTION_DISCONNECT" : "Disconnect",
|
||||
"ACTION_LOGOUT" : "@:APP.ACTION_LOGOUT",
|
||||
@@ -185,75 +187,60 @@
|
||||
},
|
||||
|
||||
"IMPORT": {
|
||||
|
||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
||||
|
||||
"BUTTON_CANCEL": "Cancel",
|
||||
"BUTTON_IMPORT": "Import Connections",
|
||||
"ACTION_ACKNOWLEDGE": "@:APP.ACTION_ACKNOWLEDGE",
|
||||
"ACTION_BROWSE": "Browse for File",
|
||||
"ACTION_CANCEL": "@:APP.ACTION_CANCEL",
|
||||
"ACTION_CLEAR": "@:APP.ACTION_CLEAR",
|
||||
"ACTION_VIEW_FORMAT_HELP": "View Format Tips",
|
||||
"ACTION_IMPORT": "@:APP.ACTION_IMPORT",
|
||||
"ACTION_IMPORT_CONNECTIONS": "Import Connections",
|
||||
|
||||
"CONNECTIONS_IMPORTED_SUCCESS": "{NUMBER} connections imported successfully.",
|
||||
|
||||
"DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR",
|
||||
"DIALOG_HEADER_ERROR": "@:APP.DIALOG_HEADER_ERROR",
|
||||
"DIALOG_HEADER_SUCCESS": "Success",
|
||||
|
||||
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",
|
||||
|
||||
"HEADER": "Connection Import",
|
||||
|
||||
"HELP_HEADER": "Connection Import File Format",
|
||||
|
||||
"HELP_FILE_TYPE_HEADER": "File Types",
|
||||
"HELP_FILE_TYPE_DESCRIPTION" : "Three file types are supported for connection import: CSV, JSON, and YAML. The same data may be specified by each file type. This must include the connection name and protocol. Optionally, a connection group location, a list of users and/or user groups to grant access, connection parameters, or connection protocols may also be specified. Any users or user groups that do not exist in the current data source will be automatically created.",
|
||||
|
||||
"HELP_CSV_HEADER": "CSV Format",
|
||||
"HELP_CSV_DESCRIPTION": "A connection import CSV file has one connection record per row. Each column will specify a connection field. At minimum the connection name and protocol must be specified.",
|
||||
"HELP_CSV_MORE_DETAILS": "The CSV header for each row specifies the connection field. The connection group ID that the connection should be imported into may be directly specified with \"parentIdentifier\", or the path to the parent group may be specified using \"group\" as shown below. In most cases, there should be no conflict between fields, but if needed, an \" (attribute)\" or \" (parameter)\" suffix may be added to disambiguate. Lists of user or user group identifiers must be semicolon-seperated.¹",
|
||||
|
||||
"HELP_JSON_HEADER": "JSON Format",
|
||||
"HELP_JSON_DESCRIPTION": "A connection import JSON file is a list of connection objects. At minimum the connection name and protocol must be specified in each connection object.",
|
||||
"HELP_JSON_MORE_DETAILS": "The connection group ID that the connection should be imported into may be directly specified with a \"parentIdentifier\" field, or the path to the parent group may be specified using a \"group\" field as shown below. An array of user and user group identifiers to grant access to may be specified per connection.",
|
||||
|
||||
"HELP_YAML_HEADER": "YAML Format",
|
||||
"HELP_YAML_DESCRIPTION": "A connection import YAML file is a list of connection objects with exactly the same structure as the JSON format.",
|
||||
|
||||
"HELP_SEMICOLON_FOOTNOTE": "If present, semicolons can be escaped with a backslash, e.g. \"first\\\\;last\"",
|
||||
|
||||
"ERROR_AMBIGUOUS_CSV_HEADER":
|
||||
"Ambiguous CSV Header \"{HEADER}\" could be either a connection attribute or parameter",
|
||||
"ERROR_ARRAY_REQUIRED":
|
||||
"The provided file must contain a list of connections",
|
||||
"ERROR_DUPLICATE_CSV_HEADER":
|
||||
"Duplicate CSV Header: {HEADER}",
|
||||
"ERROR_AMBIGUOUS_CSV_HEADER": "Ambiguous CSV Header \"{HEADER}\" could be either a connection attribute or parameter",
|
||||
"ERROR_AMBIGUOUS_PARENT_GROUP": "Both group and parentIdentifier may be not specified at the same time",
|
||||
"ERROR_ARRAY_REQUIRED": "The provided file must contain a list of connections",
|
||||
"ERROR_DUPLICATE_CSV_HEADER": "Duplicate CSV Header: {HEADER}",
|
||||
"ERROR_EMPTY_FILE": "The provided file is empty",
|
||||
"ERROR_INVALID_CSV_HEADER":
|
||||
"Invalid CSV Header \"{HEADER}\" is neither an attribute or parameter",
|
||||
"ERROR_INVALID_GROUP": "No group matching \"{GROUP}\" found",
|
||||
"ERROR_INVALID_FILE_TYPE":
|
||||
"Unsupported file type: \"{TYPE}\"",
|
||||
"ERROR_INVALID_USER_IDENTIFIERS":
|
||||
"Users not found: {IDENTIFIER_LIST}",
|
||||
"ERROR_INVALID_USER_GROUP_IDENTIFIERS":
|
||||
"User Groups not found: {IDENTIFIER_LIST}",
|
||||
"ERROR_NO_FILE_SUPPLIED": "Please select a file to import",
|
||||
"ERROR_AMBIGUOUS_PARENT_GROUP":
|
||||
"Both group and parentIdentifier may be not specified at the same time",
|
||||
"ERROR_REQUIRED_PROTOCOL":
|
||||
"No connection protocol found in the provided file",
|
||||
"ERROR_REQUIRED_NAME":
|
||||
"No connection name found in the provided file",
|
||||
|
||||
"ERROR_FILE_SINGLE_ONLY": "Please upload only a single file at a time",
|
||||
"ERROR_INVALID_CSV_HEADER": "Invalid CSV Header \"{HEADER}\" is neither an attribute or parameter",
|
||||
"ERROR_INVALID_FILE_TYPE": "Unsupported file type: \"{TYPE}\"",
|
||||
"ERROR_INVALID_GROUP": "No group matching \"{GROUP}\" found",
|
||||
"ERROR_INVALID_USER_GROUP_IDENTIFIERS": "User Groups not found: {IDENTIFIER_LIST}",
|
||||
"ERROR_INVALID_USER_IDENTIFIERS": "Users not found: {IDENTIFIER_LIST}",
|
||||
"ERROR_NO_FILE_SUPPLIED": "Please select a file to import",
|
||||
"ERROR_REQUIRED_NAME": "No connection name found in the provided file",
|
||||
"ERROR_REQUIRED_PROTOCOL": "No connection protocol found in the provided file",
|
||||
|
||||
"TABLE_HEADER_NAME" : "Name",
|
||||
"TABLE_HEADER_PROTOCOL" : "Protocol",
|
||||
"TABLE_HEADER_ERRORS" : "Errors",
|
||||
"TABLE_HEADER_ROW_NUMBER": "Row #",
|
||||
"FIELD_PLACEHOLDER_FILTER": "@:APP.FIELD_PLACEHOLDER_FILTER",
|
||||
|
||||
"UPLOAD_FILE_TYPES": "CSV, JSON, or YAML",
|
||||
"UPLOAD_HELP_LINK": "View Format Tips",
|
||||
"UPLOAD_DROP_TITLE": "Drop a File Here",
|
||||
"UPLOAD_BROWSE_LINK": "Browse for File"
|
||||
"HELP_CSV_DESCRIPTION": "A connection import CSV file has one connection record per row. Each column will specify a connection field. At minimum the connection name and protocol must be specified.",
|
||||
"HELP_CSV_EXAMPLE": "name,protocol,hostname,group,users,groups,guacd-encryption (attribute)\nconn1,vnc,conn1.web.com,ROOT,guac user 1;guac user 2,Connection 1 Users,none\nconn2,rdp,conn2.web.com,ROOT/Parent Group,guac user 1,,ssl\nconn3,ssh,conn3.web.com,ROOT/Parent Group/Child Group,guac user 2;guac user 3,,\nconn4,kubernetes,,,,,",
|
||||
"HELP_CSV_MORE_DETAILS": "The CSV header for each row specifies the connection field. The connection group ID that the connection should be imported into may be directly specified with \"parentIdentifier\", or the path to the parent group may be specified using \"group\" as shown below. In most cases, there should be no conflict between fields, but if needed, an \" (attribute)\" or \" (parameter)\" suffix may be added to disambiguate. Lists of user or user group identifiers must be semicolon-seperated.¹",
|
||||
"HELP_FILE_TYPE_DESCRIPTION": "Three file types are supported for connection import: CSV, JSON, and YAML. The same data may be specified by each file type. This must include the connection name and protocol. Optionally, a connection group location, a list of users and/or user groups to grant access, connection parameters, or connection protocols may also be specified. Any users or user groups that do not exist in the current data source will be automatically created.",
|
||||
"HELP_FILE_TYPE_HEADER": "File Types",
|
||||
"HELP_JSON_DESCRIPTION": "A connection import JSON file is a list of connection objects. At minimum the connection name and protocol must be specified in each connection object.",
|
||||
"HELP_JSON_EXAMPLE": "[\n \\{\n \"name\": \"conn1\",\n \"protocol\": \"vnc\",\n \"parameters\": \\{ \"hostname\": \"conn1.web.com\" \\},\n \"parentIdentifier\": \"ROOT\",\n \"users\": [ \"guac user 1\", \"guac user 2\" ],\n \"groups\": [ \"Connection 1 Users\" ],\n \"attributes\": \\{ \"guacd-encryption\": \"none\" \\}\n \\},\n \\{\n \"name\": \"conn2\",\n \"protocol\": \"rdp\",\n \"parameters\": \\{ \"hostname\": \"conn2.web.com\" \\},\n \"group\": \"ROOT/Parent Group\",\n \"users\": [ \"guac user 1\" ],\n \"attributes\": \\{ \"guacd-encryption\": \"none\" \\}\n \\},\n \\{\n \"name\": \"conn3\",\n \"protocol\": \"ssh\",\n \"parameters\": \\{ \"hostname\": \"conn3.web.com\" \\},\n \"group\": \"ROOT/Parent Group/Child Group\",\n \"users\": [ \"guac user 2\", \"guac user 3\" ]\n \\},\n \\{\n \"name\": \"conn4\",\n \"protocol\": \"kubernetes\"\n \\}\n]",
|
||||
"HELP_JSON_MORE_DETAILS": "The connection group ID that the connection should be imported into may be directly specified with a \"parentIdentifier\" field, or the path to the parent group may be specified using a \"group\" field as shown below. An array of user and user group identifiers to grant access to may be specified per connection.",
|
||||
"HELP_SEMICOLON_FOOTNOTE": "If present, semicolons can be escaped with a backslash, e.g. \"first\\\\;last\"",
|
||||
"HELP_UPLOAD_DROP_TITLE": "Drop a File Here",
|
||||
"HELP_UPLOAD_FILE_TYPES": "CSV, JSON, or YAML",
|
||||
"HELP_YAML_DESCRIPTION": "A connection import YAML file is a list of connection objects with exactly the same structure as the JSON format.",
|
||||
"HELP_YAML_EXAMPLE": "---\n - name: conn1\n protocol: vnc\n parameters:\n hostname: conn1.web.com\n group: ROOT\n users:\n - guac user 1\n - guac user 2\n groups:\n - Connection 1 Users\n attributes:\n guacd-encryption: none\n - name: conn2\n protocol: rdp\n parameters:\n hostname: conn2.web.com\n group: ROOT/Parent Group\n users:\n - guac user 1\n attributes:\n guacd-encryption: none\n - name: conn3\n protocol: ssh\n parameters:\n hostname: conn3.web.com\n group: ROOT/Parent Group/Child Group\n users:\n - guac user 2\n - guac user 3\n - name: conn4\n protocol: kubernetes",
|
||||
"INFO_CONNECTIONS_IMPORTED_SUCCESS": "{NUMBER} {NUMBER, plural, one{connection} other{connections}} imported successfully.",
|
||||
|
||||
"SECTION_HEADER_CONNECTION_IMPORT": "Connection Import",
|
||||
"SECTION_HEADER_HELP_CONNECTION_IMPORT_FILE": "Connection Import File Format",
|
||||
"SECTION_HEADER_CSV": "CSV Format",
|
||||
"SECTION_HEADER_JSON": "JSON Format",
|
||||
"SECTION_HEADER_YAML": "YAML Format",
|
||||
|
||||
"TABLE_HEADER_ERRORS": "Errors",
|
||||
"TABLE_HEADER_NAME": "Name",
|
||||
"TABLE_HEADER_PROTOCOL": "Protocol",
|
||||
"TABLE_HEADER_ROW_NUMBER": "Row #"
|
||||
},
|
||||
|
||||
"DATA_SOURCE_DEFAULT" : {
|
||||
@@ -978,7 +965,7 @@
|
||||
"SETTINGS_CONNECTIONS" : {
|
||||
|
||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
||||
"ACTION_IMPORT_CONNECTIONS" : "Import",
|
||||
"ACTION_IMPORT" : "@:APP.ACTION_IMPORT",
|
||||
"ACTION_NEW_CONNECTION" : "New Connection",
|
||||
"ACTION_NEW_CONNECTION_GROUP" : "New Group",
|
||||
"ACTION_NEW_SHARING_PROFILE" : "New Sharing Profile",
|
||||
|
@@ -22,7 +22,6 @@ package org.apache.guacamole.rest;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.guacamole.GuacamoleClientException;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleResourceNotFoundException;
|
||||
|
@@ -441,7 +441,7 @@ public abstract class DirectoryResource<InternalType extends Identifiable, Exter
|
||||
|
||||
// An outcome for each patch included in the request. This list
|
||||
// may include both success and failure responses, though the
|
||||
// presense of any failure would indicated that the entire
|
||||
// presence of any failure would indicated that the entire
|
||||
// request has failed and no changes have been made.
|
||||
List<APIPatchOutcome> patchOutcomes = new ArrayList<>();
|
||||
|
||||
@@ -457,8 +457,9 @@ public abstract class DirectoryResource<InternalType extends Identifiable, Exter
|
||||
// endpoint requires that operations be performed atomically.
|
||||
if (!atomic)
|
||||
throw new GuacamoleUnsupportedException(
|
||||
"Atomic operations are not supported. " +
|
||||
"The patch cannot be executed.");
|
||||
"The extension providing this directory does not " +
|
||||
"support Atomic Operations. The patch cannot be " +
|
||||
"executed.");
|
||||
|
||||
// Keep a list of all objects that have been successfully
|
||||
// added or removed
|
||||
@@ -482,7 +483,9 @@ public abstract class DirectoryResource<InternalType extends Identifiable, Exter
|
||||
if (!path.startsWith("/"))
|
||||
throw new GuacamoleClientException("Patch paths must start with \"/\".");
|
||||
|
||||
if(patch.getOp() == APIPatch.Operation.add) {
|
||||
APIPatch.Operation op = patch.getOp();
|
||||
|
||||
if (op == APIPatch.Operation.add) {
|
||||
|
||||
// Filter/sanitize object contents
|
||||
InternalType internal = filterAndTranslate(patch.getValue());
|
||||
@@ -497,7 +500,7 @@ public abstract class DirectoryResource<InternalType extends Identifiable, Exter
|
||||
|
||||
// Add a success outcome describing the object creation
|
||||
APIPatchOutcome response = new APIPatchOutcome(
|
||||
patch.getOp(), internal.getIdentifier(), path);
|
||||
op, internal.getIdentifier(), path);
|
||||
patchOutcomes.add(response);
|
||||
creationSuccesses.add(response);
|
||||
|
||||
@@ -515,7 +518,7 @@ public abstract class DirectoryResource<InternalType extends Identifiable, Exter
|
||||
*/
|
||||
if (e instanceof GuacamoleException)
|
||||
patchOutcomes.add(new APIPatchError(
|
||||
patch.getOp(), null, path,
|
||||
op, null, path,
|
||||
((GuacamoleException) e).getMessage()));
|
||||
|
||||
// If an unexpected failure occurs, fall through to the
|
||||
@@ -527,7 +530,7 @@ public abstract class DirectoryResource<InternalType extends Identifiable, Exter
|
||||
}
|
||||
|
||||
// Append each identifier to the list, to be removed atomically
|
||||
else if (patch.getOp() == APIPatch.Operation.remove) {
|
||||
else if (op == APIPatch.Operation.remove) {
|
||||
|
||||
String identifier = path.substring(1);
|
||||
|
||||
@@ -541,7 +544,7 @@ public abstract class DirectoryResource<InternalType extends Identifiable, Exter
|
||||
|
||||
// Add a success outcome describing the object removal
|
||||
APIPatchOutcome response = new APIPatchOutcome(
|
||||
patch.getOp(), identifier, path);
|
||||
op, identifier, path);
|
||||
patchOutcomes.add(response);
|
||||
creationSuccesses.add(response);
|
||||
}
|
||||
@@ -557,7 +560,7 @@ public abstract class DirectoryResource<InternalType extends Identifiable, Exter
|
||||
*/
|
||||
if (e instanceof GuacamoleException)
|
||||
patchOutcomes.add(new APIPatchError(
|
||||
patch.getOp(), identifier, path,
|
||||
op, identifier, path,
|
||||
((GuacamoleException) e).getMessage()));
|
||||
|
||||
// If an unexpected failure occurs, fall through to the
|
||||
@@ -569,8 +572,7 @@ public abstract class DirectoryResource<InternalType extends Identifiable, Exter
|
||||
|
||||
else {
|
||||
throw new GuacamoleUnsupportedException(
|
||||
"Unsupported patch operation \""
|
||||
+ patch.getOp() + "\". "
|
||||
"Unsupported patch operation \"" + op + "\". "
|
||||
+ "Only add and remove are supported.");
|
||||
}
|
||||
|
||||
|
@@ -31,7 +31,6 @@ import org.apache.guacamole.rest.jsonpatch.APIPatch.Operation;
|
||||
* the user who submitted the Patch request. Rather than including the full
|
||||
* contents of the value, only the identifier is included, allowing the user to
|
||||
* determine the identifier of any newly-created objects as part of the request.
|
||||
*
|
||||
*/
|
||||
public class APIPatchOutcome {
|
||||
|
||||
@@ -56,8 +55,13 @@ public class APIPatchOutcome {
|
||||
* patch API request.
|
||||
*
|
||||
* @param op
|
||||
* The requested operation for the patch corresponding to this outcome.
|
||||
*
|
||||
* @param identifier
|
||||
* The identifier for the value in patch corresponding to this outcome.
|
||||
*
|
||||
* @param path
|
||||
* The path for the patch corresponding to this outcome.
|
||||
*/
|
||||
public APIPatchOutcome(Operation op, String identifier, String path) {
|
||||
this.op = op;
|
||||
|
Reference in New Issue
Block a user