mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +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
|
Version: 1.0.3
|
||||||
From: 'isaacs' (https://github.com/isaacs)
|
From: 'Node.js contributors'
|
||||||
License(s):
|
License(s):
|
||||||
MIT (bundled/core-util-is-1.0.3/LICENSE)
|
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
|
Version: 6.2.5
|
||||||
From: 'adaltas' (https://github.com/adaltas)
|
From: 'Adaltas' (https://github.com/adaltas)
|
||||||
License(s):
|
License(s):
|
||||||
MIT (bundled/csv-6.2.5/LICENSE)
|
MIT (bundled/csv-6.2.5/LICENSE)
|
||||||
|
@@ -2,6 +2,6 @@ events (https://github.com/browserify/events)
|
|||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
|
||||||
Version: 3.3.0
|
Version: 3.3.0
|
||||||
From: 'browserify' (https://github.com/browserify)
|
From: 'Node.js contributors, Joyent, Inc., and other Node contributors'
|
||||||
License(s):
|
License(s):
|
||||||
MIT (bundled/events-3.3.0/LICENSE)
|
MIT (bundled/events-3.3.0/LICENSE)
|
||||||
|
@@ -2,6 +2,6 @@ ieee754 (https://github.com/feross/ieee754)
|
|||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
|
||||||
Version: 1.2.1
|
Version: 1.2.1
|
||||||
From: 'Feross Aboukhadijeh' (https://github.com/feross)
|
From: 'Fair Oaks Labs, Inc'
|
||||||
License(s):
|
License(s):
|
||||||
MIT (bundled/ieee754-1.2.1/LICENSE)
|
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
|
Version: 2.0.1
|
||||||
From: 'Calvin Metcalf' (https://github.com/calvinmetcalf)
|
From: 'Calvin Metcalf' (https://github.com/calvinmetcalf)
|
||||||
License(s):
|
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
|
Version: 2.3.7
|
||||||
From: 'Node.js' (https://github.com/nodejs)
|
From: 'Node.js contributors, Joyent, Inc., and other Node contributors'
|
||||||
License(s):
|
License(s):
|
||||||
MIT (bundled/readable-stream-2.3.7/LICENSE)
|
MIT (bundled/readable-stream-2.3.7/LICENSE)
|
||||||
|
|
||||||
|
@@ -4,5 +4,5 @@ setImmediate.js (https://github.com/YuzuJS/setImmediate)
|
|||||||
Version: 1.0.5
|
Version: 1.0.5
|
||||||
From: 'Yuzu (by Barnes & Noble Education)' (https://github.com/YuzuJS)
|
From: 'Yuzu (by Barnes & Noble Education)' (https://github.com/YuzuJS)
|
||||||
License(s):
|
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
|
Version: 2.0.2
|
||||||
From: 'browserify' (https://github.com/browserify)
|
From: 'James Halliday'
|
||||||
License(s):
|
License(s):
|
||||||
MIT (bundled/stream-browserify-2.0.2/LICENSE)
|
MIT (bundled/stream-browserify-2.0.2/LICENSE)
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@ string_decoder (https://github.com/nodejs/string_decoder)
|
|||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
|
||||||
Version: 1.1.1
|
Version: 1.1.1
|
||||||
From: 'Node.js' (https://github.com/nodejs)
|
From: 'Node.js contributors, Joyent, Inc., and other Node contributors'
|
||||||
License(s):
|
License(s):
|
||||||
MIT (bundled/string_decoder-1.1.1/LICENSE)
|
MIT (bundled/string_decoder-1.1.1/LICENSE)
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@ timers-browserify (https://github.com/browserify/timers-browserify)
|
|||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
|
||||||
Version: 2.0.12
|
Version: 2.0.12
|
||||||
From: 'browserify' (https://github.com/browserify)
|
From: 'J. Ryan Stinnett' (https://github.com/jryans)
|
||||||
License(s):
|
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>
|
public abstract class JDBCDirectory<ObjectType extends Identifiable>
|
||||||
extends RestrictedObject implements Directory<ObjectType> {
|
extends RestrictedObject implements Directory<ObjectType> {
|
||||||
|
|
||||||
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void tryAtomically(AtomicDirectoryOperation<ObjectType> operation)
|
public void tryAtomically(AtomicDirectoryOperation<ObjectType> operation)
|
||||||
throws GuacamoleException {
|
throws GuacamoleException {
|
||||||
|
@@ -23,7 +23,7 @@ import org.apache.guacamole.GuacamoleException;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* An operation that should be attempted atomically when passed to
|
* 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.
|
* the Directory.
|
||||||
*/
|
*/
|
||||||
public interface AtomicDirectoryOperation<ObjectType extends Identifiable> {
|
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
|
* provided directory outside this function, or of the directory invoking
|
||||||
* this function are not guaranteed.
|
* this function are not guaranteed.
|
||||||
*
|
*
|
||||||
* NOTE: If atomicity is required for this operation, a GuacamoleException
|
* <p>NOTE: If atomicity is required for this operation, a
|
||||||
* may be thrown by this function before any changes are made, ensuring the
|
* GuacamoleException may be thrown by this function before any changes are
|
||||||
* operation will only ever be performed atomically.
|
* made, ensuring the operation will only ever be performed atomically.
|
||||||
*
|
*
|
||||||
* @param atomic
|
* @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.
|
* atomically within the context of this function.
|
||||||
*
|
*
|
||||||
* @param directory
|
* @param directory
|
||||||
|
@@ -19,15 +19,49 @@
|
|||||||
|
|
||||||
/* global _ */
|
/* 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.
|
* The controller for the connection import page.
|
||||||
*/
|
*/
|
||||||
angular.module('import').controller('importConnectionsController', ['$scope', '$injector',
|
angular.module('import').controller('importConnectionsController', ['$scope', '$injector',
|
||||||
function importConnectionsController($scope, $injector) {
|
function importConnectionsController($scope, $injector) {
|
||||||
|
|
||||||
// The file types supported for connection import
|
|
||||||
const LEGAL_FILE_TYPES = ['csv', 'json', 'yaml'];
|
|
||||||
|
|
||||||
// Required services
|
// Required services
|
||||||
const $document = $injector.get('$document');
|
const $document = $injector.get('$document');
|
||||||
const $location = $injector.get('$location');
|
const $location = $injector.get('$location');
|
||||||
@@ -40,7 +74,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
const permissionService = $injector.get('permissionService');
|
const permissionService = $injector.get('permissionService');
|
||||||
const userService = $injector.get('userService');
|
const userService = $injector.get('userService');
|
||||||
const userGroupService = $injector.get('userGroupService');
|
const userGroupService = $injector.get('userGroupService');
|
||||||
|
|
||||||
// Required types
|
// Required types
|
||||||
const DirectoryPatch = $injector.get('DirectoryPatch');
|
const DirectoryPatch = $injector.get('DirectoryPatch');
|
||||||
const Error = $injector.get('Error');
|
const Error = $injector.get('Error');
|
||||||
@@ -48,7 +82,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
const PermissionSet = $injector.get('PermissionSet');
|
const PermissionSet = $injector.get('PermissionSet');
|
||||||
const User = $injector.get('User');
|
const User = $injector.get('User');
|
||||||
const UserGroup = $injector.get('UserGroup');
|
const UserGroup = $injector.get('UserGroup');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The result of parsing the current upload, if successful.
|
* The result of parsing the current upload, if successful.
|
||||||
*
|
*
|
||||||
@@ -118,7 +152,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
$scope.parseResult = null;
|
$scope.parseResult = null;
|
||||||
$scope.patchFailure = null;
|
$scope.patchFailure = null;
|
||||||
$scope.fileName = null;
|
$scope.fileName = null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Indicate that data is currently being loaded / processed if the the file
|
// 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
|
// If user group creation succeeds, resolve the returned promise
|
||||||
userGroupResponse => ({ userResponse, userGroupResponse}))
|
userGroupResponse => ({ userResponse, userGroupResponse}))
|
||||||
|
|
||||||
// If the group creation request fails, clean up any created users
|
// If the group creation request fails, clean up any created users
|
||||||
.catch(groupFailure => {
|
.catch(groupFailure => {
|
||||||
cleanUpUsers(userResponse);
|
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];
|
PermissionSet.ObjectPermissionType.READ];
|
||||||
return permissions;
|
return permissions;
|
||||||
}, {}) });
|
}, {}) });
|
||||||
|
|
||||||
// Now that we've created all the users, grant access to each
|
// 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`
|
// Grant the permissions - note the group flag is `false`
|
||||||
userRequests[identifier] = permissionService.patchPermissions(
|
userRequests[identifier] = permissionService.patchPermissions(
|
||||||
@@ -255,7 +289,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
false));
|
false));
|
||||||
|
|
||||||
// Now that we've created all the groups, grant access to each
|
// 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`
|
// Grant the permissions - note the group flag is `true`
|
||||||
groupRequests[identifier] = permissionService.patchPermissions(
|
groupRequests[identifier] = permissionService.patchPermissions(
|
||||||
@@ -337,21 +371,21 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
// If errors were encounted during file parsing, abort further
|
// If errors were encounted during file parsing, abort further
|
||||||
// processing - the user will have a chance to fix the errors and try
|
// processing - the user will have a chance to fix the errors and try
|
||||||
// again
|
// again
|
||||||
if (parseResult.hasErrors)
|
if (parseResult.hasErrors)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const dataSource = $routeParams.dataSource;
|
const dataSource = $routeParams.dataSource;
|
||||||
|
|
||||||
// First, attempt to create the connections
|
// First, attempt to create the connections
|
||||||
connectionService.patchConnections(dataSource, parseResult.patches)
|
connectionService.patchConnections(dataSource, parseResult.patches)
|
||||||
.then(connectionResponse =>
|
.then(connectionResponse =>
|
||||||
|
|
||||||
// If connection creation is successful, create users and groups
|
// If connection creation is successful, create users and groups
|
||||||
createUsersAndGroups(parseResult).then(() =>
|
createUsersAndGroups(parseResult).then(() =>
|
||||||
|
|
||||||
grantConnectionPermissions(parseResult, connectionResponse)
|
grantConnectionPermissions(parseResult, connectionResponse)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
||||||
$scope.processing = false;
|
$scope.processing = false;
|
||||||
|
|
||||||
// Display a success message if everything worked
|
// Display a success message if everything worked
|
||||||
@@ -359,7 +393,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
className : 'success',
|
className : 'success',
|
||||||
title : 'IMPORT.DIALOG_HEADER_SUCCESS',
|
title : 'IMPORT.DIALOG_HEADER_SUCCESS',
|
||||||
text : {
|
text : {
|
||||||
key: 'IMPORT.CONNECTIONS_IMPORTED_SUCCESS',
|
key: 'IMPORT.INFO_CONNECTIONS_IMPORTED_SUCCESS',
|
||||||
variables: { NUMBER: parseResult.patches.length }
|
variables: { NUMBER: parseResult.patches.length }
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -388,7 +422,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
handleError(error);
|
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
|
// skip any further processing - the user will have a chance to fix the
|
||||||
// problems and try again
|
// problems and try again
|
||||||
.catch(patchFailure => {
|
.catch(patchFailure => {
|
||||||
@@ -396,7 +430,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
$scope.patchFailure = patchFailure;
|
$scope.patchFailure = patchFailure;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the provided error to the user in a dismissable dialog.
|
* Display the provided error to the user in a dismissable dialog.
|
||||||
*
|
*
|
||||||
@@ -410,7 +444,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
resetUploadState();
|
resetUploadState();
|
||||||
|
|
||||||
let text;
|
let text;
|
||||||
|
|
||||||
// If it's a import file parsing error
|
// If it's a import file parsing error
|
||||||
if (error instanceof ParseError)
|
if (error instanceof ParseError)
|
||||||
text = {
|
text = {
|
||||||
@@ -438,18 +472,18 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
name : 'IMPORT.ACTION_ACKNOWLEDGE',
|
name : 'IMPORT.ACTION_ACKNOWLEDGE',
|
||||||
callback : () => guacNotification.showStatus(false)
|
callback : () => guacNotification.showStatus(false)
|
||||||
}]
|
}]
|
||||||
})
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process the uploaded import file, importing the connections, granting
|
* 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.
|
* problems with the provided file.
|
||||||
*
|
*
|
||||||
* @param {String} mimeType
|
* @param {String} mimeType
|
||||||
* The MIME type of the uploaded data file.
|
* The MIME type of the uploaded data file.
|
||||||
*
|
*
|
||||||
* @param {String} data
|
* @param {String} data
|
||||||
* The raw string contents of the import file.
|
* The raw string contents of the import file.
|
||||||
*/
|
*/
|
||||||
@@ -457,19 +491,19 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
|
|
||||||
// Data processing has begun
|
// Data processing has begun
|
||||||
$scope.processing = true;
|
$scope.processing = true;
|
||||||
|
|
||||||
// The function that will process all the raw data and return a list of
|
// The function that will process all the raw data and return a list of
|
||||||
// patches to be submitted to the API
|
// patches to be submitted to the API
|
||||||
let processDataCallback;
|
let processDataCallback;
|
||||||
|
|
||||||
// Choose the appropriate parse function based on the mimetype
|
// Choose the appropriate parse function based on the mimetype
|
||||||
if (mimeType.endsWith("json"))
|
if (mimeType === JSON_MIME_TYPE)
|
||||||
processDataCallback = connectionParseService.parseJSON;
|
processDataCallback = connectionParseService.parseJSON;
|
||||||
|
|
||||||
else if (mimeType.endsWith("csv"))
|
else if (mimeType === CSV_MIME_TYPE)
|
||||||
processDataCallback = connectionParseService.parseCSV;
|
processDataCallback = connectionParseService.parseCSV;
|
||||||
|
|
||||||
else if (mimeType.endsWith("yaml"))
|
else if (YAML_MIME_TYPES.indexOf(mimeType) >= 0)
|
||||||
processDataCallback = connectionParseService.parseYAML;
|
processDataCallback = connectionParseService.parseYAML;
|
||||||
|
|
||||||
// The file type was validated before being uploaded - this should
|
// 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);
|
$scope.import = () => processData($scope.mimeType, $scope.fileData);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Returns true if import should be disabled, or false if import should be
|
||||||
|
* allowed.
|
||||||
|
*
|
||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
* True if import should be disabled, or false if import should be
|
* True if import should be disabled, otherwise false.
|
||||||
* allowed.
|
|
||||||
*/
|
*/
|
||||||
$scope.importDisabled = () =>
|
$scope.importDisabled = () =>
|
||||||
|
|
||||||
// Disable import if no data is ready
|
// Disable import if no data is ready
|
||||||
!$scope.dataReady ||
|
!$scope.dataReady ||
|
||||||
|
|
||||||
@@ -524,7 +560,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear any upload state - there's no FileReader handler to do it
|
// Clear any upload state - there's no FileReader handler to do it
|
||||||
else
|
else
|
||||||
resetUploadState();
|
resetUploadState();
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -560,19 +596,19 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
// The MIME type of the provided file
|
// The MIME type of the provided file
|
||||||
const mimeType = file.type;
|
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"
|
// e.g. "application/json" or "text/csv"
|
||||||
if (_.every(LEGAL_FILE_TYPES.map(
|
if (LEGAL_MIME_TYPES.indexOf(mimeType) < 0) {
|
||||||
type => !mimeType.endsWith(type)))) {
|
|
||||||
|
|
||||||
// If the provided file is not one of the supported types,
|
// If the provided file is not one of the supported types,
|
||||||
// display an error and abort processing
|
// display an error and abort processing
|
||||||
handleError(new ParseError({
|
handleError(new ParseError({
|
||||||
message: "Invalid file type: " + mimeType,
|
message: "Invalid file type: " + mimeType,
|
||||||
key: 'IMPORT.ERROR_INVALID_FILE_TYPE',
|
key: 'IMPORT.ERROR_INVALID_MIME_TYPE',
|
||||||
variables: { TYPE: mimeType }
|
variables: { TYPE: mimeType }
|
||||||
}));
|
}));
|
||||||
return;
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.fileName = file.name;
|
$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
|
// If the upload was explicitly aborted, clear any upload state and
|
||||||
// do not process the data
|
// do not process the data
|
||||||
if ($scope.aborted)
|
if ($scope.aborted)
|
||||||
resetUploadState();
|
resetUploadState();
|
||||||
|
|
||||||
else {
|
else {
|
||||||
|
|
||||||
// Save the uploaded data
|
// Save the uploaded data
|
||||||
$scope.fileData = e.target.result;
|
$scope.fileData = e.target.result;
|
||||||
|
|
||||||
// Mark the data as ready
|
// Mark the data as ready
|
||||||
$scope.dataReady = true;
|
$scope.dataReady = true;
|
||||||
|
|
||||||
// Clear the file reader from the scope now that this file is
|
// Clear the file reader from the scope now that this file is
|
||||||
// fully uploaded
|
// fully uploaded
|
||||||
$scope.fileReader = null;
|
$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);
|
$scope.fileReader.readAsBinaryString(file);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether a drag/drop operation is currently in progress (the user has
|
* Whether a drag/drop operation is currently in progress (the user has
|
||||||
* dragged a file over the Guacamole connection but has not yet
|
* dragged a file over the Guacamole connection but has not yet
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
/* global _ */
|
/* 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
|
* import file, or errors that were returned from the API during the connection
|
||||||
* batch creation attempt.
|
* batch creation attempt.
|
||||||
*/
|
*/
|
||||||
@@ -124,7 +124,7 @@ angular.module('import').directive('connectionImportErrors', [
|
|||||||
* given parse result.
|
* given parse result.
|
||||||
*/
|
*/
|
||||||
const generateConnectionError = (parseResult, index) => {
|
const generateConnectionError = (parseResult, index) => {
|
||||||
|
|
||||||
// Get the patch associated with the current row
|
// Get the patch associated with the current row
|
||||||
const patch = parseResult.patches[index];
|
const patch = parseResult.patches[index];
|
||||||
|
|
||||||
@@ -140,18 +140,6 @@ angular.module('import').directive('connectionImportErrors', [
|
|||||||
name: connection.name,
|
name: connection.name,
|
||||||
protocol: connection.protocol,
|
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
|
// The human-readable error messages
|
||||||
errors: new DisplayErrorList(
|
errors: new DisplayErrorList(
|
||||||
[ ...(parseResult.errors[index] || []) ])
|
[ ...(parseResult.errors[index] || []) ])
|
||||||
@@ -159,7 +147,7 @@ angular.module('import').directive('connectionImportErrors', [
|
|||||||
};
|
};
|
||||||
|
|
||||||
// If a new connection patch failure is seen, update the display list
|
// 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;
|
const { parseResult } = $scope;
|
||||||
|
|
||||||
@@ -176,8 +164,6 @@ angular.module('import').directive('connectionImportErrors', [
|
|||||||
const connectionError = generateConnectionError(parseResult, index);
|
const connectionError = generateConnectionError(parseResult, index);
|
||||||
|
|
||||||
// Set the error from the PATCH request, if there is one
|
// 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']);
|
const error = _.get(patchFailure, ['patches', index, 'error']);
|
||||||
if (error)
|
if (error)
|
||||||
connectionError.errors = new DisplayErrorList([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
|
// 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
|
// Do not process if there are no errors in the provided result
|
||||||
if (!parseResult || !parseResult.hasErrors)
|
if (!parseResult || !parseResult.hasErrors)
|
||||||
@@ -226,11 +212,11 @@ angular.module('import').directive('connectionImportErrors', [
|
|||||||
.then(translatedError => {
|
.then(translatedError => {
|
||||||
connectionError.errors.getArray()[errorIndex] = translatedError;
|
connectionError.errors.getArray()[errorIndex] = translatedError;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return connectionError;
|
return connectionError;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Once all the translations have been completed, update the
|
// Once all the translations have been completed, update the
|
||||||
@@ -245,4 +231,4 @@ angular.module('import').directive('connectionImportErrors', [
|
|||||||
|
|
||||||
return directive;
|
return directive;
|
||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
@@ -30,72 +30,75 @@ const ATTRIBUTE_SUFFIX = ' (attribute)';
|
|||||||
*/
|
*/
|
||||||
angular.module('import').factory('connectionCSVService',
|
angular.module('import').factory('connectionCSVService',
|
||||||
['$injector', function connectionCSVService($injector) {
|
['$injector', function connectionCSVService($injector) {
|
||||||
|
|
||||||
// Required types
|
// Required types
|
||||||
const ParseError = $injector.get('ParseError');
|
const ParseError = $injector.get('ParseError');
|
||||||
const ImportConnection = $injector.get('ImportConnection');
|
const ImportConnection = $injector.get('ImportConnection');
|
||||||
const TranslatableMessage = $injector.get('TranslatableMessage');
|
const TranslatableMessage = $injector.get('TranslatableMessage');
|
||||||
|
|
||||||
// Required services
|
// Required services
|
||||||
const $q = $injector.get('$q');
|
const $q = $injector.get('$q');
|
||||||
const $routeParams = $injector.get('$routeParams');
|
const $routeParams = $injector.get('$routeParams');
|
||||||
const schemaService = $injector.get('schemaService');
|
const schemaService = $injector.get('schemaService');
|
||||||
|
|
||||||
const service = {};
|
const service = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a promise that resolves to a object detailing the connection
|
* Returns a promise that resolves to a object detailing the connection
|
||||||
* attributes for the current data source, as well as the connection
|
* attributes for the current data source, as well as the connection
|
||||||
* paremeters for every protocol, for the current data source.
|
* paremeters for every protocol, for the current data source.
|
||||||
*
|
*
|
||||||
* The object that the promise will contain an "attributes" key that maps to
|
* 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
|
* 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.
|
* 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
|
* 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
|
* connection parameter or attribute with a given name, by e.g. checking the
|
||||||
* path `.protocolParameters[protocolName]` to see if a protocol exists,
|
* path `.protocolParameters[protocolName]` to see if a protocol exists,
|
||||||
* checking the path `.protocolParameters[protocolName][fieldName]` to see
|
* checking the path `.protocolParameters[protocolName][fieldName]` to see
|
||||||
* if a parameter exists for a given protocol, or checking the path
|
* if a parameter exists for a given protocol, or checking the path
|
||||||
* `.attributes[fieldName]` to check if a connection attribute exists.
|
* `.attributes[fieldName]` to check if a connection attribute exists.
|
||||||
*
|
*
|
||||||
* @returns {Promise.<Object>}
|
* @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() {
|
function getFieldLookups() {
|
||||||
|
|
||||||
// The current data source - the one that the connections will be
|
// The current data source - the one that the connections will be
|
||||||
// imported into
|
// imported into
|
||||||
const dataSource = $routeParams.dataSource;
|
const dataSource = $routeParams.dataSource;
|
||||||
|
|
||||||
// Fetch connection attributes and protocols for the current data source
|
// Fetch connection attributes and protocols for the current data source
|
||||||
return $q.all({
|
return $q.all({
|
||||||
attributes : schemaService.getConnectionAttributes(dataSource),
|
attributes : schemaService.getConnectionAttributes(dataSource),
|
||||||
protocols : schemaService.getProtocols(dataSource)
|
protocols : schemaService.getProtocols(dataSource)
|
||||||
})
|
})
|
||||||
.then(function connectionStructureRetrieved({attributes, protocols}) {
|
.then(function connectionStructureRetrieved({attributes, protocols}) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
||||||
// Translate the forms and fields into a flat map of attribute
|
// Translate the forms and fields into a flat map of attribute
|
||||||
// name to `true` boolean value
|
// name to `true` boolean value
|
||||||
attributes: attributes.reduce(
|
attributes: attributes.reduce(
|
||||||
(attributeMap, form) => {
|
(attributeMap, form) => {
|
||||||
form.fields.forEach(
|
form.fields.forEach(
|
||||||
field => attributeMap[field.name] = true);
|
field => attributeMap[field.name] = true);
|
||||||
return attributeMap
|
return attributeMap
|
||||||
}, {}),
|
}, {}),
|
||||||
|
|
||||||
// Translate the protocol definitions into a map of protocol
|
// Translate the protocol definitions into a map of protocol
|
||||||
// name to map of field name to `true` boolean value
|
// name to map of field name to `true` boolean value
|
||||||
protocolParameters: _.mapValues(
|
protocolParameters: _.mapValues(
|
||||||
protocols, protocol => protocol.connectionForms.reduce(
|
protocols, protocol => protocol.connectionForms.reduce(
|
||||||
(protocolFieldMap, form) => {
|
(protocolFieldMap, form) => {
|
||||||
form.fields.forEach(
|
form.fields.forEach(
|
||||||
field => protocolFieldMap[field.name] = true);
|
field => protocolFieldMap[field.name] = true);
|
||||||
return protocolFieldMap;
|
return protocolFieldMap;
|
||||||
}, {}))
|
}, {}))
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,7 +107,7 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
* escaped with backslashes, and backslashes can also be escaped using other
|
* escaped with backslashes, and backslashes can also be escaped using other
|
||||||
* backslashes.
|
* backslashes.
|
||||||
*
|
*
|
||||||
* @param {type} rawIdentifiers
|
* @param {String} rawIdentifiers
|
||||||
* The raw string value as fetched from the CSV.
|
* The raw string value as fetched from the CSV.
|
||||||
*
|
*
|
||||||
* @returns {Array.<String>}
|
* @returns {Array.<String>}
|
||||||
@@ -151,18 +154,18 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
.filter(identifier => identifier.length);
|
.filter(identifier => identifier.length);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a CSV header row, create and return a promise that will resolve to
|
* 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
|
* 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.
|
* function will throw a ParseError describing the failure.
|
||||||
*
|
*
|
||||||
* The provided CSV must contain columns for name and protocol. Optionally,
|
* The provided CSV must contain columns for name and protocol. Optionally,
|
||||||
* the parentIdentifier of the target parent connection group, or a connection
|
* 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.
|
* connection parameters or attributes can be included.
|
||||||
*
|
*
|
||||||
* The names of connection attributes and parameters are not guaranteed to
|
* The names of connection attributes and parameters are not guaranteed to
|
||||||
* be mutually exclusive, so the CSV import format supports a distinguishing
|
* be mutually exclusive, so the CSV import format supports a distinguishing
|
||||||
* suffix. A column may be explicitly declared to be a parameter using a
|
* 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
|
* If a parameter or attribute name conflicts with the standard
|
||||||
* "name", "protocol", "group", or "parentIdentifier" fields, the suffix is
|
* "name", "protocol", "group", or "parentIdentifier" fields, the suffix is
|
||||||
* required.
|
* 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,
|
* If a failure occurs while attempting to create the transformer function,
|
||||||
* the promise will be rejected with a ParseError describing the failure.
|
* 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
|
* A promise that will resolve to a function that translates a CSV data
|
||||||
* row (array of strings) to a ImportConnection object.
|
* row (array of strings) to a ImportConnection object.
|
||||||
*/
|
*/
|
||||||
service.getCSVTransformer = function getCSVTransformer(headerRow) {
|
service.getCSVTransformer = function getCSVTransformer(headerRow) {
|
||||||
|
|
||||||
// A promise that will be resolved with the transformer or rejected if
|
// A promise that will be resolved with the transformer or rejected if
|
||||||
// an error occurs
|
// an error occurs
|
||||||
const deferred = $q.defer();
|
const deferred = $q.defer();
|
||||||
|
|
||||||
getFieldLookups().then(({attributes, protocolParameters}) => {
|
getFieldLookups().then(({attributes, protocolParameters}) => {
|
||||||
|
|
||||||
// All configuration required to generate a function that can
|
// All configuration required to generate a function that can
|
||||||
// transform a row of CSV into a connection object.
|
// transform a row of CSV into a connection object.
|
||||||
// NOTE: This is a single object instead of a collection of variables
|
// 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
|
// to ensure that no stale references are used - e.g. when one getter
|
||||||
// invokes another getter
|
// invokes another getter
|
||||||
const transformConfig = {
|
const transformConfig = {
|
||||||
|
|
||||||
// Callbacks for required fields
|
// Callbacks for required fields
|
||||||
nameGetter: undefined,
|
nameGetter: undefined,
|
||||||
protocolGetter: undefined,
|
protocolGetter: undefined,
|
||||||
|
|
||||||
// Callbacks for a parent group ID or group path
|
// Callbacks for a parent group ID or group path
|
||||||
groupGetter: undefined,
|
groupGetter: undefined,
|
||||||
parentIdentifierGetter: undefined,
|
parentIdentifierGetter: undefined,
|
||||||
@@ -214,25 +211,25 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
usersGetter: () => [],
|
usersGetter: () => [],
|
||||||
userGroupsGetter: () => [],
|
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}
|
// parameters. These callbacks will return a {type, name, value}
|
||||||
// object containing the type ("parameter" or "attribute"),
|
// object containing the type ("parameter" or "attribute"),
|
||||||
// the name of the attribute or parameter, and the corresponding
|
// the name of the attribute or parameter, and the corresponding
|
||||||
// value.
|
// value.
|
||||||
parameterOrAttributeGetters: []
|
parameterOrAttributeGetters: []
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// A set of all headers that have been seen so far. If any of these
|
// A set of all headers that have been seen so far. If any of these
|
||||||
// are duplicated, the CSV is invalid.
|
// are duplicated, the CSV is invalid.
|
||||||
const headerSet = {};
|
const headerSet = {};
|
||||||
|
|
||||||
// Iterate through the headers one by one
|
// Iterate through the headers one by one
|
||||||
headerRow.forEach((rawHeader, index) => {
|
headerRow.forEach((rawHeader, index) => {
|
||||||
|
|
||||||
// Trim to normalize all headers
|
// Trim to normalize all headers
|
||||||
const header = rawHeader.trim();
|
const header = rawHeader.trim();
|
||||||
|
|
||||||
// Check if the header is duplicated
|
// Check if the header is duplicated
|
||||||
if (headerSet[header]) {
|
if (headerSet[header]) {
|
||||||
deferred.reject(new ParseError({
|
deferred.reject(new ParseError({
|
||||||
@@ -244,34 +241,33 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
}));
|
}));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark that this particular header has already been seen
|
// Mark that this particular header has already been seen
|
||||||
headerSet[header] = true;
|
headerSet[header] = true;
|
||||||
|
|
||||||
// A callback that returns the field at the current index
|
// A callback that returns the field at the current index
|
||||||
const fetchFieldAtIndex = row => row[index];
|
const fetchFieldAtIndex = row => row[index];
|
||||||
|
|
||||||
// A callback that splits identifier lists by semicolon
|
// A callback that splits raw string identifier lists by
|
||||||
// characters into a javascript list of identifiers
|
// semicolon characters into an array of identifiers
|
||||||
const identifierListCallback = row =>
|
const identifierListCallback = row =>
|
||||||
splitIdentifiers(fetchFieldAtIndex(row));
|
splitIdentifiers(fetchFieldAtIndex(row));
|
||||||
|
|
||||||
// Set up the name callback
|
// Set up the name callback
|
||||||
if (header == 'name')
|
if (header == 'name')
|
||||||
transformConfig.nameGetter = fetchFieldAtIndex;
|
transformConfig.nameGetter = fetchFieldAtIndex;
|
||||||
|
|
||||||
// Set up the protocol callback
|
// Set up the protocol callback
|
||||||
else if (header == 'protocol')
|
else if (header == 'protocol')
|
||||||
transformConfig.protocolGetter = fetchFieldAtIndex;
|
transformConfig.protocolGetter = fetchFieldAtIndex;
|
||||||
|
|
||||||
// Set up the group callback
|
// Set up the group callback
|
||||||
else if (header == 'group')
|
else if (header == 'group')
|
||||||
transformConfig.groupGetter = fetchFieldAtIndex;
|
transformConfig.groupGetter = fetchFieldAtIndex;
|
||||||
|
|
||||||
// Set up the group parent ID callback
|
// Set up the group parent ID callback
|
||||||
else if (header == 'parentIdentifier')
|
else if (header == 'parentIdentifier')
|
||||||
transformConfig.parentIdentifierGetter = (
|
transformConfig.parentIdentifierGetter = fetchFieldAtIndex;
|
||||||
identifierListCallback);
|
|
||||||
|
|
||||||
// Set the user identifiers callback
|
// Set the user identifiers callback
|
||||||
else if (header == 'users')
|
else if (header == 'users')
|
||||||
@@ -283,7 +279,7 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
transformConfig.userGroupsGetter = (
|
transformConfig.userGroupsGetter = (
|
||||||
identifierListCallback);
|
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
|
// parameter or to an attribute
|
||||||
|
|
||||||
// A field may be explicitly specified as a parameter
|
// A field may be explicitly specified as a parameter
|
||||||
@@ -293,7 +289,7 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
const parameterName = header.replace(PARAMETER_SUFFIX);
|
const parameterName = header.replace(PARAMETER_SUFFIX);
|
||||||
transformConfig.parameterOrAttributeGetters.push(
|
transformConfig.parameterOrAttributeGetters.push(
|
||||||
row => ({
|
row => ({
|
||||||
type: 'parameter',
|
type: 'parameters',
|
||||||
name: parameterName,
|
name: parameterName,
|
||||||
value: fetchFieldAtIndex(row)
|
value: fetchFieldAtIndex(row)
|
||||||
})
|
})
|
||||||
@@ -307,35 +303,35 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
const attributeName = header.replace(ATTRIBUTE_SUFFIX);
|
const attributeName = header.replace(ATTRIBUTE_SUFFIX);
|
||||||
transformConfig.parameterOrAttributeGetters.push(
|
transformConfig.parameterOrAttributeGetters.push(
|
||||||
row => ({
|
row => ({
|
||||||
type: 'attribute',
|
type: 'attributes',
|
||||||
name: parameterName,
|
name: parameterName,
|
||||||
value: fetchFieldAtIndex(row)
|
value: fetchFieldAtIndex(row)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The field is ambiguous, either an attribute or parameter,
|
// The field is ambiguous, either an attribute or parameter,
|
||||||
// so the getter will have to determine this for every row
|
// so the getter will have to determine this for every row
|
||||||
else
|
else
|
||||||
transformConfig.parameterOrAttributeGetters.push(row => {
|
transformConfig.parameterOrAttributeGetters.push(row => {
|
||||||
|
|
||||||
// The name is just the value of the current header
|
// The name is just the value of the current header
|
||||||
const name = header;
|
const name = header;
|
||||||
|
|
||||||
// The value is at the index that matches the position
|
// The value is at the index that matches the position
|
||||||
// of the header
|
// of the header
|
||||||
const value = fetchFieldAtIndex(row);
|
const value = fetchFieldAtIndex(row);
|
||||||
|
|
||||||
// The protocol may determine whether a field is
|
// The protocol may determine whether a field is
|
||||||
// a parameter or an attribute (or both)
|
// a parameter or an attribute (or both)
|
||||||
const protocol = transformConfig.protocolGetter(row);
|
const protocol = transformConfig.protocolGetter(row);
|
||||||
|
|
||||||
// Determine if the field refers to an attribute or a
|
// Determine if the field refers to an attribute or a
|
||||||
// parameter (or both, which is an error)
|
// parameter (or both, which is an error)
|
||||||
const isAttribute = !!attributes[name];
|
const isAttribute = !!attributes[name];
|
||||||
const isParameter = !!_.get(
|
const isParameter = !!_.get(
|
||||||
protocolParameters, [protocol, name]);
|
protocolParameters, [protocol, name]);
|
||||||
|
|
||||||
// If there is both an attribute and a protocol-specific
|
// If there is both an attribute and a protocol-specific
|
||||||
// parameter with the provided name, it's impossible to
|
// parameter with the provided name, it's impossible to
|
||||||
// figure out which this should be
|
// figure out which this should be
|
||||||
@@ -345,7 +341,7 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
key: 'IMPORT.ERROR_AMBIGUOUS_CSV_HEADER',
|
key: 'IMPORT.ERROR_AMBIGUOUS_CSV_HEADER',
|
||||||
variables: { HEADER: header }
|
variables: { HEADER: header }
|
||||||
});
|
});
|
||||||
|
|
||||||
// It's neither an attribute or a parameter
|
// It's neither an attribute or a parameter
|
||||||
else if (!isAttribute && !isParameter)
|
else if (!isAttribute && !isParameter)
|
||||||
throw new ParseError({
|
throw new ParseError({
|
||||||
@@ -353,10 +349,10 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
key: 'IMPORT.ERROR_INVALID_CSV_HEADER',
|
key: 'IMPORT.ERROR_INVALID_CSV_HEADER',
|
||||||
variables: { HEADER: header }
|
variables: { HEADER: header }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Choose the appropriate type
|
// Choose the appropriate type
|
||||||
const type = isAttribute ? 'attributes' : 'parameters';
|
const type = isAttribute ? 'attributes' : 'parameters';
|
||||||
|
|
||||||
return { type, name, value };
|
return { type, name, value };
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -367,14 +363,14 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
usersGetter, userGroupsGetter,
|
usersGetter, userGroupsGetter,
|
||||||
parameterOrAttributeGetters
|
parameterOrAttributeGetters
|
||||||
} = transformConfig;
|
} = transformConfig;
|
||||||
|
|
||||||
// Fail if the name wasn't provided
|
// Fail if the name wasn't provided
|
||||||
if (!nameGetter)
|
if (!nameGetter)
|
||||||
return deferred.reject(new ParseError({
|
return deferred.reject(new ParseError({
|
||||||
message: 'The connection name must be provided',
|
message: 'The connection name must be provided',
|
||||||
key: 'IMPORT.ERROR_REQUIRED_NAME'
|
key: 'IMPORT.ERROR_REQUIRED_NAME'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Fail if the protocol wasn't provided
|
// Fail if the protocol wasn't provided
|
||||||
if (!protocolGetter)
|
if (!protocolGetter)
|
||||||
return deferred.reject(new ParseError({
|
return deferred.reject(new ParseError({
|
||||||
@@ -382,16 +378,9 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
key: 'IMPORT.ERROR_REQUIRED_PROTOCOL'
|
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
|
// The function to transform a CSV row into a connection object
|
||||||
deferred.resolve(function transformCSVRow(row) {
|
deferred.resolve(function transformCSVRow(row) {
|
||||||
|
|
||||||
// Get name and protocol
|
// Get name and protocol
|
||||||
const name = nameGetter(row);
|
const name = nameGetter(row);
|
||||||
const protocol = protocolGetter(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
|
// Get any users or user groups who should be granted access
|
||||||
const users = usersGetter(row);
|
const users = usersGetter(row);
|
||||||
const groups = userGroupsGetter(row);
|
const groups = userGroupsGetter(row);
|
||||||
|
|
||||||
// Get the parent group ID and/or group path
|
// Get the parent group ID and/or group path
|
||||||
const group = groupGetter && groupGetter(row);
|
const group = groupGetter && groupGetter(row);
|
||||||
const parentIdentifier = (
|
const parentIdentifier = (
|
||||||
parentIdentifierGetter && parentIdentifierGetter(row));
|
parentIdentifierGetter && parentIdentifierGetter(row));
|
||||||
|
|
||||||
return new ImportConnection({
|
return new ImportConnection({
|
||||||
|
|
||||||
// Fields that are not protocol-specific
|
// Fields that are not protocol-specific
|
||||||
...{
|
name,
|
||||||
name,
|
protocol,
|
||||||
protocol,
|
parentIdentifier,
|
||||||
parentIdentifier,
|
group,
|
||||||
group,
|
users,
|
||||||
users,
|
groups,
|
||||||
groups
|
|
||||||
},
|
|
||||||
|
|
||||||
// Fields that might potentially be either attributes or
|
// Fields that might potentially be either attributes or
|
||||||
// parameters, depending on the protocol
|
// parameters, depending on the protocol
|
||||||
...parameterOrAttributeGetters.reduce((values, getter) => {
|
...parameterOrAttributeGetters.reduce((values, getter) => {
|
||||||
|
|
||||||
// Determine the type, name, and value
|
// Determine the type, name, and value
|
||||||
const { type, name, value } = getter(row);
|
const { type, name, value } = getter(row);
|
||||||
|
|
||||||
// Set the value and continue on to the next attribute
|
// Set the value and continue on to the next attribute
|
||||||
// or parameter
|
// or parameter
|
||||||
values[type][name] = value;
|
values[type][name] = value;
|
||||||
return values;
|
return values;
|
||||||
|
|
||||||
}, {parameters: {}, attributes: {}})
|
}, {parameters: {}, attributes: {}})
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
return service;
|
return service;
|
||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
@@ -35,7 +35,7 @@ angular.module('import').factory('connectionParseService',
|
|||||||
const ParseError = $injector.get('ParseError');
|
const ParseError = $injector.get('ParseError');
|
||||||
const ParseResult = $injector.get('ParseResult');
|
const ParseResult = $injector.get('ParseResult');
|
||||||
const TranslatableMessage = $injector.get('TranslatableMessage');
|
const TranslatableMessage = $injector.get('TranslatableMessage');
|
||||||
|
|
||||||
// Required services
|
// Required services
|
||||||
const $q = $injector.get('$q');
|
const $q = $injector.get('$q');
|
||||||
const $routeParams = $injector.get('$routeParams');
|
const $routeParams = $injector.get('$routeParams');
|
||||||
@@ -44,18 +44,18 @@ angular.module('import').factory('connectionParseService',
|
|||||||
const connectionGroupService = $injector.get('connectionGroupService');
|
const connectionGroupService = $injector.get('connectionGroupService');
|
||||||
|
|
||||||
const service = {};
|
const service = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform basic checks, common to all file types - namely that the parsed
|
* 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.
|
* error if any of these basic checks fails.
|
||||||
*
|
*
|
||||||
* returns {ParseError}
|
* @returns {ParseError}
|
||||||
* An error describing the parsing failure, if one of the basic checks
|
* An error describing the parsing failure, if one of the basic checks
|
||||||
* fails.
|
* fails.
|
||||||
*/
|
*/
|
||||||
function performBasicChecks(parsedData) {
|
function performBasicChecks(parsedData) {
|
||||||
|
|
||||||
// Make sure that the file data parses to an array (connection list)
|
// Make sure that the file data parses to an array (connection list)
|
||||||
if (!(parsedData instanceof Array))
|
if (!(parsedData instanceof Array))
|
||||||
return new ParseError({
|
return new ParseError({
|
||||||
@@ -71,64 +71,67 @@ angular.module('import').factory('connectionParseService',
|
|||||||
key: 'IMPORT.ERROR_EMPTY_FILE'
|
key: 'IMPORT.ERROR_EMPTY_FILE'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a promise that resolves to an object mapping potential groups
|
* Returns a promise that resolves to an object mapping potential groups
|
||||||
* that might be encountered in an imported connection to group identifiers.
|
* that might be encountered in an imported connection to group identifiers.
|
||||||
*
|
*
|
||||||
* The idea is that a user-provided import file might directly specify a
|
* The idea is that a user-provided import file might directly specify a
|
||||||
* parentIdentifier, or it might specify a named group path like "ROOT",
|
* parentIdentifier, or it might specify a named group path like "ROOT",
|
||||||
* "ROOT/parent", or "ROOT/parent/child". This object resolved by 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
|
* promise returned from this function will map all of the above to the
|
||||||
* identifier of the appropriate group, if defined.
|
* identifier of the appropriate group, if defined.
|
||||||
*
|
*
|
||||||
* @returns {Promise.<Object>}
|
* @returns {Promise.<Object>}
|
||||||
|
* A promise that resolves to an object mapping groups to group
|
||||||
|
* identifiers.
|
||||||
*/
|
*/
|
||||||
function getGroupLookups() {
|
function getGroupLookups() {
|
||||||
|
|
||||||
// The current data source - defines all the groups that the connections
|
// The current data source - defines all the groups that the connections
|
||||||
// might be imported into
|
// might be imported into
|
||||||
const dataSource = $routeParams.dataSource;
|
const dataSource = $routeParams.dataSource;
|
||||||
|
|
||||||
const deferredGroupLookups = $q.defer();
|
const deferredGroupLookups = $q.defer();
|
||||||
|
|
||||||
connectionGroupService.getConnectionGroupTree(dataSource).then(
|
connectionGroupService.getConnectionGroupTree(dataSource).then(
|
||||||
rootGroup => {
|
rootGroup => {
|
||||||
|
|
||||||
const groupLookup = {};
|
const groupLookup = {};
|
||||||
|
|
||||||
// Add the specified group to the lookup, appending all specified
|
// Add the specified group to the lookup, appending all specified
|
||||||
// prefixes, and then recursively call saveLookups for all children
|
// prefixes, and then recursively call saveLookups for all children
|
||||||
// of the group, appending to the prefix for each level
|
// 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
|
// To get the path for the current group, add the name
|
||||||
const currentPath = prefix + group.name;
|
const currentPath = prefix + group.name;
|
||||||
|
|
||||||
// Add the current path to the lookup
|
// Add the current path to the lookup
|
||||||
groupLookup[currentPath] = group.identifier;
|
groupLookup[currentPath] = group.identifier;
|
||||||
|
|
||||||
// Add each child group to the lookup
|
// Add each child group to the lookup
|
||||||
const nextPrefix = currentPath + "/";
|
const nextPrefix = currentPath + "/";
|
||||||
_.forEach(group.childConnectionGroups,
|
_.forEach(group.childConnectionGroups,
|
||||||
childGroup => saveLookups(nextPrefix, childGroup));
|
childGroup => saveLookups(nextPrefix, childGroup));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start at the root group
|
// Start at the root group
|
||||||
saveLookups("", rootGroup);
|
saveLookups("", rootGroup);
|
||||||
|
|
||||||
// Resolve with the now fully-populated lookups
|
// Resolve with the now fully-populated lookups
|
||||||
deferredGroupLookups.resolve(groupLookup);
|
deferredGroupLookups.resolve(groupLookup);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferredGroupLookups.promise;
|
return deferredGroupLookups.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a promise that will resolve to a transformer function that will
|
* 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
|
* with a "parentIdentifier". If both a "group" and "parentIdentifier" field
|
||||||
* are present on the provided object, or if no group exists at the specified
|
* are present on the provided object, or if no group exists at the specified
|
||||||
* path, the function will throw a ParseError describing the failure.
|
* path, the function will throw a ParseError describing the failure.
|
||||||
@@ -189,7 +192,7 @@ angular.module('import').factory('connectionParseService',
|
|||||||
* parsing all provided connection data.
|
* parsing all provided connection data.
|
||||||
*/
|
*/
|
||||||
function parseConnectionData(connectionData, transformFunctions) {
|
function parseConnectionData(connectionData, transformFunctions) {
|
||||||
|
|
||||||
// Check that the provided connection data array is not empty
|
// Check that the provided connection data array is not empty
|
||||||
const checkError = performBasicChecks(connectionData);
|
const checkError = performBasicChecks(connectionData);
|
||||||
if (checkError) {
|
if (checkError) {
|
||||||
@@ -198,6 +201,7 @@ angular.module('import').factory('connectionParseService',
|
|||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the group transformer to apply to each connection
|
||||||
return getGroupTransformer().then(groupTransformer =>
|
return getGroupTransformer().then(groupTransformer =>
|
||||||
connectionData.reduce((parseResult, data, index) => {
|
connectionData.reduce((parseResult, data, index) => {
|
||||||
|
|
||||||
@@ -261,7 +265,7 @@ angular.module('import').factory('connectionParseService',
|
|||||||
value: connection
|
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)
|
if (connectionErrors.length)
|
||||||
parseResult.hasErrors = true;
|
parseResult.hasErrors = true;
|
||||||
|
|
||||||
@@ -284,14 +288,14 @@ angular.module('import').factory('connectionParseService',
|
|||||||
* parsing all provided connection data.
|
* parsing all provided connection data.
|
||||||
*/
|
*/
|
||||||
service.parseCSV = function parseCSV(csvData) {
|
service.parseCSV = function parseCSV(csvData) {
|
||||||
|
|
||||||
// Convert to an array of arrays, one per CSV row (including the header)
|
// 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
|
// NOTE: skip_empty_lines is required, or a trailing newline will error
|
||||||
let parsedData;
|
let parsedData;
|
||||||
try {
|
try {
|
||||||
parsedData = parseCSVData(csvData, {skip_empty_lines: true});
|
parsedData = parseCSVData(csvData, {skip_empty_lines: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the CSV parser throws an error, reject with that error. No
|
// If the CSV parser throws an error, reject with that error. No
|
||||||
// translation key will be available here.
|
// translation key will be available here.
|
||||||
catch(error) {
|
catch(error) {
|
||||||
@@ -346,7 +350,7 @@ angular.module('import').factory('connectionParseService',
|
|||||||
deferred.reject(new ParseError({ message: error.message }));
|
deferred.reject(new ParseError({ message: error.message }));
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Produce a ParseResult
|
// Produce a ParseResult
|
||||||
return parseConnectionData(connectionData);
|
return parseConnectionData(connectionData);
|
||||||
};
|
};
|
||||||
|
@@ -55,5 +55,5 @@
|
|||||||
padding-top: 1em;
|
padding-top: 1em;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -27,15 +27,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.import .errors table {
|
.import .errors table {
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.import .errors .error-message {
|
.import .errors .error-message {
|
||||||
|
|
||||||
color: red;
|
color: red;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.import .errors .error-message ul {
|
.import .errors .error-message ul {
|
||||||
|
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-upload-container {
|
.file-upload-container {
|
||||||
@@ -56,13 +62,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-upload-container.file-selected {
|
.file-upload-container.file-selected {
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 100px;
|
gap: 100px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-upload-container .clear {
|
.file-upload-container .clear {
|
||||||
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-upload-container .upload-header {
|
.file-upload-container .upload-header {
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
placeholder="'IMPORT.FIELD_PLACEHOLDER_FILTER' | translate"
|
placeholder="'IMPORT.FIELD_PLACEHOLDER_FILTER' | translate"
|
||||||
properties="filteredErrorProperties"></guac-filter>
|
properties="filteredErrorProperties"></guac-filter>
|
||||||
|
|
||||||
<!-- List of current u -->
|
<!-- List of connection import errors -->
|
||||||
<table class="sorted">
|
<table class="sorted">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@@ -1,34 +1,34 @@
|
|||||||
<div class="settings-view import">
|
<div class="settings-view view import">
|
||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h2>{{'IMPORT.HEADER' | translate}}</h2>
|
<h2>{{'IMPORT.SECTION_HEADER_CONNECTION_IMPORT' | translate}}</h2>
|
||||||
<guac-user-menu></guac-user-menu>
|
<guac-user-menu></guac-user-menu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-show="fileName" class="file-upload-container file-selected">
|
<div ng-show="fileName" class="file-upload-container file-selected">
|
||||||
<div class="file-name"> {{fileName}} </div>
|
<div class="file-name"> {{fileName}} </div>
|
||||||
<button class="danger clear" ng-click="cancel()">
|
<button class="danger clear" ng-click="cancel()">
|
||||||
{{'IMPORT.BUTTON_CLEAR' | translate}}
|
{{'IMPORT.ACTION_CLEAR' | translate}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-show="!fileName" class="file-upload-container">
|
<div ng-show="!fileName" class="file-upload-container">
|
||||||
|
|
||||||
<div class="upload-header">
|
<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
|
<a
|
||||||
href="#/import/connection/file-format-help" target="_blank"
|
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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="drop-target" ng-class="{ 'drop-pending': dropPending, 'file-present': fileName}">
|
<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"/>
|
<input type="file" class="file-upload-input"/>
|
||||||
<a ng-click="openFileBrowser()" class="browse-link">
|
<a ng-click="openFileBrowser()" class="browse-link">
|
||||||
{{'IMPORT.UPLOAD_BROWSE_LINK' | translate}}
|
{{'IMPORT.ACTION_BROWSE' | translate}}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="file-name"> {{fileName}} </div>
|
<div class="file-name"> {{fileName}} </div>
|
||||||
@@ -38,13 +38,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="import-buttons">
|
<div class="import-buttons">
|
||||||
<button
|
<button
|
||||||
ng-click="import()" ng-disabled="importDisabled()" class="import">
|
ng-click="import()" ng-disabled="importDisabled()" class="save import">
|
||||||
{{'IMPORT.BUTTON_IMPORT' | translate}}
|
{{'IMPORT.ACTION_IMPORT' | translate}}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
ng-click="cancel()" ng-disabled="cancelDisabled()" class="cancel">
|
ng-click="cancel()" ng-disabled="cancelDisabled()" class="cancel">
|
||||||
{{'IMPORT.BUTTON_CANCEL' | translate}}
|
{{'IMPORT.ACTION_CANCEL' | translate}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -1,90 +1,26 @@
|
|||||||
<div class="import help">
|
<div class="import view help">
|
||||||
|
|
||||||
<div class="header">
|
<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>
|
<guac-user-menu></guac-user-menu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>{{'IMPORT.HELP_FILE_TYPE_HEADER' | translate}}</h2>
|
<h2>{{'IMPORT.HELP_FILE_TYPE_HEADER' | translate}}</h2>
|
||||||
<p>{{'IMPORT.HELP_FILE_TYPE_DESCRIPTION' | translate}}</p>
|
<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_DESCRIPTION' | translate}}</p>
|
||||||
<p>{{'IMPORT.HELP_CSV_MORE_DETAILS' | translate}}</p>
|
<p>{{'IMPORT.HELP_CSV_MORE_DETAILS' | translate}}</p>
|
||||||
<pre>name,protocol,hostname,group,users,groups,guacd-encryption (attribute)
|
<pre>{{'IMPORT.HELP_CSV_EXAMPLE' | translate }}</pre>
|
||||||
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>
|
|
||||||
|
|
||||||
<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_DESCRIPTION' | translate}}</p>
|
||||||
<p>{{'IMPORT.HELP_JSON_MORE_DETAILS' | translate}}</p>
|
<p>{{'IMPORT.HELP_JSON_MORE_DETAILS' | translate}}</p>
|
||||||
<pre>[
|
<pre>{{'IMPORT.HELP_JSON_EXAMPLE' | translate }}</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>
|
|
||||||
|
|
||||||
<h2>{{'IMPORT.HELP_YAML_HEADER' | translate}}</h2>
|
<h2>{{'IMPORT.SECTION_HEADER_YAML' | translate}}</h2>
|
||||||
<p>{{'IMPORT.HELP_YAML_DESCRIPTION' | translate}}</p>
|
<p>{{'IMPORT.HELP_YAML_DESCRIPTION' | translate}}</p>
|
||||||
<pre>---
|
<pre>{{'IMPORT.HELP_YAML_EXAMPLE' | translate}}</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>
|
|
||||||
|
|
||||||
<ol class="footnotes">
|
<ol class="footnotes">
|
||||||
<li>{{'IMPORT.HELP_SEMICOLON_FOOTNOTE' | translate}}</li>
|
<li>{{'IMPORT.HELP_SEMICOLON_FOOTNOTE' | translate}}</li>
|
||||||
|
@@ -33,25 +33,33 @@ angular.module('import').factory('DisplayErrorList', [
|
|||||||
*/
|
*/
|
||||||
const DisplayErrorList = function DisplayErrorList(messages) {
|
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 || [];
|
this.messages = messages || [];
|
||||||
|
|
||||||
// The single String message composed of all messages concatenated
|
/**
|
||||||
// together. This will be used for filtering / sorting, and should only
|
* The single String message composed of all messages concatenated
|
||||||
// be calculated once.
|
* together. This will be used for filtering / sorting, and should only
|
||||||
this.cachedMessage = null;
|
* be calculated once, when toString() is called.
|
||||||
|
*
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
this.concatenatedMessage = null;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a sortable / filterable representation of all the error messages
|
* 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
|
* 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()
|
* will have no effect. This is to ensure that repeated calls to toString()
|
||||||
* by sorting / filtering UI code will not regenerate the concatenated
|
* by sorting / filtering UI code will not regenerate the concatenated
|
||||||
* message every time.
|
* message every time.
|
||||||
*
|
*
|
||||||
* @returns {String}
|
* @returns {String}
|
||||||
* A sortable / filterable representation of the error messages wrapped
|
* A sortable / filterable representation of the error messages wrapped
|
||||||
* by this DisplayErrorList
|
* by this DisplayErrorList
|
||||||
@@ -80,4 +88,4 @@ angular.module('import').factory('DisplayErrorList', [
|
|||||||
|
|
||||||
return DisplayErrorList;
|
return DisplayErrorList;
|
||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
@@ -71,9 +71,7 @@ angular.module('import').factory('ImportConnection', [
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Connection configuration parameters, as dictated by the protocol in
|
* Connection configuration parameters, as dictated by the protocol in
|
||||||
* use, arranged as name/value pairs. This information may not be
|
* use, arranged as name/value pairs.
|
||||||
* available until directly queried. If this information is
|
|
||||||
* unavailable, this property will be null or undefined.
|
|
||||||
*
|
*
|
||||||
* @type Object.<String, String>
|
* @type Object.<String, String>
|
||||||
*/
|
*/
|
||||||
|
@@ -27,13 +27,15 @@ angular.module('import').factory('ImportConnectionError', ['$injector',
|
|||||||
const DisplayErrorList = $injector.get('DisplayErrorList');
|
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.
|
* user-supplied import file.
|
||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
* @param {ImportConnection|Object} [template={}]
|
* @param {ImportConnectionError|Object} [template={}]
|
||||||
* The object whose properties should be copied within the new
|
* The object whose properties should be copied within the new
|
||||||
* Connection.
|
* ImportConnectionError.
|
||||||
*/
|
*/
|
||||||
const ImportConnectionError = function ImportConnectionError(template) {
|
const ImportConnectionError = function ImportConnectionError(template) {
|
||||||
|
|
||||||
@@ -46,22 +48,6 @@ angular.module('import').factory('ImportConnectionError', ['$injector',
|
|||||||
*/
|
*/
|
||||||
this.rowNumber = template.rowNumber;
|
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
|
* The human-readable name of this connection, which is not necessarily
|
||||||
* unique.
|
* unique.
|
||||||
@@ -78,22 +64,6 @@ angular.module('import').factory('ImportConnectionError', ['$injector',
|
|||||||
*/
|
*/
|
||||||
this.protocol = template.protocol;
|
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.
|
* The error messages associated with this particular connection, if any.
|
||||||
*
|
*
|
||||||
@@ -105,4 +75,4 @@ angular.module('import').factory('ImportConnectionError', ['$injector',
|
|||||||
|
|
||||||
return ImportConnectionError;
|
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
|
* 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
|
* 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
|
* 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.
|
* each connection.
|
||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
@@ -49,7 +49,7 @@ angular.module('import').factory('ParseResult', [function defineParseResult() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* An object whose keys are the user identifiers of users specified
|
* 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.
|
* connections to which those users should be granted access.
|
||||||
*
|
*
|
||||||
* @type {Object.<String, Integer[]>}
|
* @type {Object.<String, Integer[]>}
|
||||||
@@ -68,7 +68,7 @@ angular.module('import').factory('ParseResult', [function defineParseResult() {
|
|||||||
/**
|
/**
|
||||||
* An array of errors encountered while parsing the corresponding
|
* An array of errors encountered while parsing the corresponding
|
||||||
* connection (at the same array index). Each connection should have a
|
* 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[][]}
|
* @type {ParseError[][]}
|
||||||
*/
|
*/
|
||||||
@@ -85,5 +85,5 @@ angular.module('import').factory('ParseResult', [function defineParseResult() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return ParseResult;
|
return ParseResult;
|
||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
@@ -135,7 +135,7 @@ angular.module('index').config(['$routeProvider', '$locationProvider',
|
|||||||
resolve : { updateCurrentToken: updateCurrentToken }
|
resolve : { updateCurrentToken: updateCurrentToken }
|
||||||
})
|
})
|
||||||
|
|
||||||
// Connection import page
|
// Connection import file format help page
|
||||||
.when('/import/connection/file-format-help', {
|
.when('/import/connection/file-format-help', {
|
||||||
title : 'APP.NAME',
|
title : 'APP.NAME',
|
||||||
bodyClassName : 'settings',
|
bodyClassName : 'settings',
|
||||||
|
@@ -26,9 +26,6 @@ angular.module('rest').factory('connectionService', ['$injector',
|
|||||||
// Required services
|
// Required services
|
||||||
var authenticationService = $injector.get('authenticationService');
|
var authenticationService = $injector.get('authenticationService');
|
||||||
var cacheService = $injector.get('cacheService');
|
var cacheService = $injector.get('cacheService');
|
||||||
|
|
||||||
// Required types
|
|
||||||
const Error = $injector.get('Error');
|
|
||||||
|
|
||||||
var service = {};
|
var service = {};
|
||||||
|
|
||||||
@@ -166,6 +163,10 @@ angular.module('rest').factory('connectionService', ['$injector',
|
|||||||
* connection patching process, the entire request will fail, and no
|
* connection patching process, the entire request will fail, and no
|
||||||
* changes will be persisted.
|
* changes will be persisted.
|
||||||
*
|
*
|
||||||
|
* @param {String} dataSource
|
||||||
|
* The identifier of the data source associated with the connections to
|
||||||
|
* be patched.
|
||||||
|
*
|
||||||
* @param {DirectoryPatch.<Connection>[]} patches
|
* @param {DirectoryPatch.<Connection>[]} patches
|
||||||
* An array of patches to apply.
|
* 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,
|
* 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
|
* connection patching process, the entire request will fail, and no
|
||||||
* changes will be persisted.
|
* 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
|
* @param {DirectoryPatch.<UserGroup>[]} patches
|
||||||
* An array of patches to apply.
|
* An array of patches to apply.
|
||||||
*
|
*
|
||||||
@@ -221,7 +225,7 @@ angular.module('rest').factory('userGroupService', ['$injector',
|
|||||||
return patchResponse;
|
return patchResponse;
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
};
|
||||||
|
|
||||||
return service;
|
return service;
|
||||||
|
|
||||||
|
@@ -245,6 +245,10 @@ angular.module('rest').factory('userService', ['$injector',
|
|||||||
* connection patching process, the entire request will fail, and no
|
* connection patching process, the entire request will fail, and no
|
||||||
* changes will be persisted.
|
* changes will be persisted.
|
||||||
*
|
*
|
||||||
|
* @param {String} dataSource
|
||||||
|
* The identifier of the data source associated with the users to be
|
||||||
|
* patched.
|
||||||
|
*
|
||||||
* @param {DirectoryPatch.<User>[]} patches
|
* @param {DirectoryPatch.<User>[]} patches
|
||||||
* An array of patches to apply.
|
* An array of patches to apply.
|
||||||
*
|
*
|
||||||
@@ -267,7 +271,7 @@ angular.module('rest').factory('userService', ['$injector',
|
|||||||
return patchResponse;
|
return patchResponse;
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
};
|
||||||
|
|
||||||
return service;
|
return service;
|
||||||
|
|
||||||
|
@@ -75,12 +75,12 @@ angular.module('rest').factory('DirectoryPatch', [function defineDirectoryPatch(
|
|||||||
/**
|
/**
|
||||||
* Adds the specified object to the relation.
|
* Adds the specified object to the relation.
|
||||||
*/
|
*/
|
||||||
ADD : "add",
|
ADD : 'add',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the specified object from the relation.
|
* Removes the specified object from the relation.
|
||||||
*/
|
*/
|
||||||
REMOVE : "remove"
|
REMOVE : 'remove'
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<a class="import-connections button"
|
<a class="import-connections button"
|
||||||
ng-show="canCreateConnections()"
|
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"
|
<a class="add-connection button"
|
||||||
ng-show="canCreateConnections()"
|
ng-show="canCreateConnections()"
|
||||||
|
@@ -5,15 +5,17 @@
|
|||||||
"APP" : {
|
"APP" : {
|
||||||
|
|
||||||
"NAME" : "Apache Guacamole",
|
"NAME" : "Apache Guacamole",
|
||||||
"VERSION" : "1.5.0",
|
"VERSION" : "${project.version}",
|
||||||
|
|
||||||
"ACTION_ACKNOWLEDGE" : "OK",
|
"ACTION_ACKNOWLEDGE" : "OK",
|
||||||
"ACTION_CANCEL" : "Cancel",
|
"ACTION_CANCEL" : "Cancel",
|
||||||
|
"ACTION_CLEAR" : "Clear",
|
||||||
"ACTION_CLONE" : "Clone",
|
"ACTION_CLONE" : "Clone",
|
||||||
"ACTION_CONTINUE" : "Continue",
|
"ACTION_CONTINUE" : "Continue",
|
||||||
"ACTION_DELETE" : "Delete",
|
"ACTION_DELETE" : "Delete",
|
||||||
"ACTION_DELETE_SESSIONS" : "Kill Sessions",
|
"ACTION_DELETE_SESSIONS" : "Kill Sessions",
|
||||||
"ACTION_DOWNLOAD" : "Download",
|
"ACTION_DOWNLOAD" : "Download",
|
||||||
|
"ACTION_IMPORT" : "Import",
|
||||||
"ACTION_LOGIN" : "Login",
|
"ACTION_LOGIN" : "Login",
|
||||||
"ACTION_LOGIN_AGAIN" : "Re-login",
|
"ACTION_LOGIN_AGAIN" : "Re-login",
|
||||||
"ACTION_LOGOUT" : "Logout",
|
"ACTION_LOGOUT" : "Logout",
|
||||||
@@ -60,8 +62,8 @@
|
|||||||
|
|
||||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
||||||
"ACTION_CANCEL" : "@:APP.ACTION_CANCEL",
|
"ACTION_CANCEL" : "@:APP.ACTION_CANCEL",
|
||||||
"ACTION_CLEAR_CLIENT_MESSAGES" : "Clear",
|
"ACTION_CLEAR_CLIENT_MESSAGES" : "@:APP.ACTION_CLEAR",
|
||||||
"ACTION_CLEAR_COMPLETED_TRANSFERS" : "Clear",
|
"ACTION_CLEAR_COMPLETED_TRANSFERS" : "@:APP.ACTION_CLEAR",
|
||||||
"ACTION_CONTINUE" : "@:APP.ACTION_CONTINUE",
|
"ACTION_CONTINUE" : "@:APP.ACTION_CONTINUE",
|
||||||
"ACTION_DISCONNECT" : "Disconnect",
|
"ACTION_DISCONNECT" : "Disconnect",
|
||||||
"ACTION_LOGOUT" : "@:APP.ACTION_LOGOUT",
|
"ACTION_LOGOUT" : "@:APP.ACTION_LOGOUT",
|
||||||
@@ -185,75 +187,60 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"IMPORT": {
|
"IMPORT": {
|
||||||
|
|
||||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
|
||||||
|
|
||||||
"BUTTON_CANCEL": "Cancel",
|
"ACTION_ACKNOWLEDGE": "@:APP.ACTION_ACKNOWLEDGE",
|
||||||
"BUTTON_IMPORT": "Import Connections",
|
"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",
|
"DIALOG_HEADER_SUCCESS": "Success",
|
||||||
|
|
||||||
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",
|
"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",
|
||||||
"HEADER": "Connection Import",
|
"ERROR_ARRAY_REQUIRED": "The provided file must contain a list of connections",
|
||||||
|
"ERROR_DUPLICATE_CSV_HEADER": "Duplicate CSV Header: {HEADER}",
|
||||||
"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_EMPTY_FILE": "The provided file is empty",
|
"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_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",
|
"FIELD_PLACEHOLDER_FILTER": "@:APP.FIELD_PLACEHOLDER_FILTER",
|
||||||
"TABLE_HEADER_PROTOCOL" : "Protocol",
|
|
||||||
"TABLE_HEADER_ERRORS" : "Errors",
|
|
||||||
"TABLE_HEADER_ROW_NUMBER": "Row #",
|
|
||||||
|
|
||||||
"UPLOAD_FILE_TYPES": "CSV, JSON, or YAML",
|
"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.",
|
||||||
"UPLOAD_HELP_LINK": "View Format Tips",
|
"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,,,,,",
|
||||||
"UPLOAD_DROP_TITLE": "Drop a File Here",
|
"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.¹",
|
||||||
"UPLOAD_BROWSE_LINK": "Browse for File"
|
"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" : {
|
"DATA_SOURCE_DEFAULT" : {
|
||||||
@@ -978,7 +965,7 @@
|
|||||||
"SETTINGS_CONNECTIONS" : {
|
"SETTINGS_CONNECTIONS" : {
|
||||||
|
|
||||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
||||||
"ACTION_IMPORT_CONNECTIONS" : "Import",
|
"ACTION_IMPORT" : "@:APP.ACTION_IMPORT",
|
||||||
"ACTION_NEW_CONNECTION" : "New Connection",
|
"ACTION_NEW_CONNECTION" : "New Connection",
|
||||||
"ACTION_NEW_CONNECTION_GROUP" : "New Group",
|
"ACTION_NEW_CONNECTION_GROUP" : "New Group",
|
||||||
"ACTION_NEW_SHARING_PROFILE" : "New Sharing Profile",
|
"ACTION_NEW_SHARING_PROFILE" : "New Sharing Profile",
|
||||||
|
@@ -22,7 +22,6 @@ package org.apache.guacamole.rest;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.guacamole.GuacamoleClientException;
|
import org.apache.guacamole.GuacamoleClientException;
|
||||||
import org.apache.guacamole.GuacamoleException;
|
import org.apache.guacamole.GuacamoleException;
|
||||||
import org.apache.guacamole.GuacamoleResourceNotFoundException;
|
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
|
// An outcome for each patch included in the request. This list
|
||||||
// may include both success and failure responses, though the
|
// 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.
|
// request has failed and no changes have been made.
|
||||||
List<APIPatchOutcome> patchOutcomes = new ArrayList<>();
|
List<APIPatchOutcome> patchOutcomes = new ArrayList<>();
|
||||||
|
|
||||||
@@ -457,8 +457,9 @@ public abstract class DirectoryResource<InternalType extends Identifiable, Exter
|
|||||||
// endpoint requires that operations be performed atomically.
|
// endpoint requires that operations be performed atomically.
|
||||||
if (!atomic)
|
if (!atomic)
|
||||||
throw new GuacamoleUnsupportedException(
|
throw new GuacamoleUnsupportedException(
|
||||||
"Atomic operations are not supported. " +
|
"The extension providing this directory does not " +
|
||||||
"The patch cannot be executed.");
|
"support Atomic Operations. The patch cannot be " +
|
||||||
|
"executed.");
|
||||||
|
|
||||||
// Keep a list of all objects that have been successfully
|
// Keep a list of all objects that have been successfully
|
||||||
// added or removed
|
// added or removed
|
||||||
@@ -482,7 +483,9 @@ public abstract class DirectoryResource<InternalType extends Identifiable, Exter
|
|||||||
if (!path.startsWith("/"))
|
if (!path.startsWith("/"))
|
||||||
throw new GuacamoleClientException("Patch paths must start with \"/\".");
|
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
|
// Filter/sanitize object contents
|
||||||
InternalType internal = filterAndTranslate(patch.getValue());
|
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
|
// Add a success outcome describing the object creation
|
||||||
APIPatchOutcome response = new APIPatchOutcome(
|
APIPatchOutcome response = new APIPatchOutcome(
|
||||||
patch.getOp(), internal.getIdentifier(), path);
|
op, internal.getIdentifier(), path);
|
||||||
patchOutcomes.add(response);
|
patchOutcomes.add(response);
|
||||||
creationSuccesses.add(response);
|
creationSuccesses.add(response);
|
||||||
|
|
||||||
@@ -515,7 +518,7 @@ public abstract class DirectoryResource<InternalType extends Identifiable, Exter
|
|||||||
*/
|
*/
|
||||||
if (e instanceof GuacamoleException)
|
if (e instanceof GuacamoleException)
|
||||||
patchOutcomes.add(new APIPatchError(
|
patchOutcomes.add(new APIPatchError(
|
||||||
patch.getOp(), null, path,
|
op, null, path,
|
||||||
((GuacamoleException) e).getMessage()));
|
((GuacamoleException) e).getMessage()));
|
||||||
|
|
||||||
// If an unexpected failure occurs, fall through to the
|
// 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
|
// 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);
|
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
|
// Add a success outcome describing the object removal
|
||||||
APIPatchOutcome response = new APIPatchOutcome(
|
APIPatchOutcome response = new APIPatchOutcome(
|
||||||
patch.getOp(), identifier, path);
|
op, identifier, path);
|
||||||
patchOutcomes.add(response);
|
patchOutcomes.add(response);
|
||||||
creationSuccesses.add(response);
|
creationSuccesses.add(response);
|
||||||
}
|
}
|
||||||
@@ -557,7 +560,7 @@ public abstract class DirectoryResource<InternalType extends Identifiable, Exter
|
|||||||
*/
|
*/
|
||||||
if (e instanceof GuacamoleException)
|
if (e instanceof GuacamoleException)
|
||||||
patchOutcomes.add(new APIPatchError(
|
patchOutcomes.add(new APIPatchError(
|
||||||
patch.getOp(), identifier, path,
|
op, identifier, path,
|
||||||
((GuacamoleException) e).getMessage()));
|
((GuacamoleException) e).getMessage()));
|
||||||
|
|
||||||
// If an unexpected failure occurs, fall through to the
|
// If an unexpected failure occurs, fall through to the
|
||||||
@@ -569,8 +572,7 @@ public abstract class DirectoryResource<InternalType extends Identifiable, Exter
|
|||||||
|
|
||||||
else {
|
else {
|
||||||
throw new GuacamoleUnsupportedException(
|
throw new GuacamoleUnsupportedException(
|
||||||
"Unsupported patch operation \""
|
"Unsupported patch operation \"" + op + "\". "
|
||||||
+ patch.getOp() + "\". "
|
|
||||||
+ "Only add and remove are supported.");
|
+ "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
|
* 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
|
* 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.
|
* determine the identifier of any newly-created objects as part of the request.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class APIPatchOutcome {
|
public class APIPatchOutcome {
|
||||||
|
|
||||||
@@ -56,8 +55,13 @@ public class APIPatchOutcome {
|
|||||||
* patch API request.
|
* patch API request.
|
||||||
*
|
*
|
||||||
* @param op
|
* @param op
|
||||||
|
* The requested operation for the patch corresponding to this outcome.
|
||||||
|
*
|
||||||
* @param identifier
|
* @param identifier
|
||||||
|
* The identifier for the value in patch corresponding to this outcome.
|
||||||
|
*
|
||||||
* @param path
|
* @param path
|
||||||
|
* The path for the patch corresponding to this outcome.
|
||||||
*/
|
*/
|
||||||
public APIPatchOutcome(Operation op, String identifier, String path) {
|
public APIPatchOutcome(Operation op, String identifier, String path) {
|
||||||
this.op = op;
|
this.op = op;
|
||||||
|
Reference in New Issue
Block a user