mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +00:00
GUACAMOLE-926: Implement logic for translating CSV rows to connection objects.
This commit is contained in:
@@ -25,42 +25,81 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
||||
|
||||
// Required services
|
||||
const connectionParseService = $injector.get('connectionParseService');
|
||||
const connectionService = $injector.get('connectionService');
|
||||
const connectionService = $injector.get('connectionService');
|
||||
|
||||
// Required types
|
||||
const ParseError = $injector.get('ParseError');
|
||||
const TranslatableMessage = $injector.get('TranslatableMessage');
|
||||
|
||||
function handleSuccess(data) {
|
||||
console.log("OMG SUCCESS: ", data)
|
||||
}
|
||||
|
||||
// Set any caught error message to the scope for display
|
||||
const handleError = error => {
|
||||
console.error(error);
|
||||
$scope.error = error;
|
||||
}
|
||||
|
||||
// Clear the current error
|
||||
const clearError = () => delete $scope.error;
|
||||
|
||||
function processData(type, data) {
|
||||
|
||||
let requestBody;
|
||||
|
||||
// The function that will process all the raw data and return a list of
|
||||
// patches to be submitted to the API
|
||||
let processDataCallback;
|
||||
|
||||
// Parse the data based on the provided mimetype
|
||||
switch(type) {
|
||||
|
||||
case "application/json":
|
||||
case "text/json":
|
||||
requestBody = connectionParseService.parseJSON(data);
|
||||
processDataCallback = connectionParseService.parseJSON;
|
||||
break;
|
||||
|
||||
case "text/csv":
|
||||
requestBody = connectionParseService.parseCSV(data);
|
||||
processDataCallback = connectionParseService.parseCSV;
|
||||
break;
|
||||
|
||||
case "application/yaml":
|
||||
case "application/x-yaml":
|
||||
case "text/yaml":
|
||||
case "text/x-yaml":
|
||||
requestBody = connectionParseService.parseYAML(data);
|
||||
processDataCallback = connectionParseService.parseYAML;
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
handleError(new ParseError({
|
||||
message: 'Invalid file type: ' + type,
|
||||
key: 'CONNECTION_IMPORT.INVALID_FILE_TYPE',
|
||||
variables: { TYPE: type }
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Make the call to process the data into a series of patches
|
||||
processDataCallback(data)
|
||||
|
||||
console.log(requestBody);
|
||||
// Send the data off to be imported if parsing is successful
|
||||
.then(handleSuccess)
|
||||
|
||||
// Display any error found while parsing the file
|
||||
.catch(handleError);
|
||||
}
|
||||
|
||||
$scope.upload = function() {
|
||||
|
||||
// Clear any error message from the previous upload attempt
|
||||
clearError();
|
||||
|
||||
const files = angular.element('#file')[0].files;
|
||||
|
||||
if (files.length <= 0) {
|
||||
console.error("TODO: This should be a proper error tho");
|
||||
handleError(new ParseError({
|
||||
message: 'No file supplied',
|
||||
key: 'CONNECTION_IMPORT.ERROR_NO_FILE_SUPPLIED'
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -19,16 +19,26 @@
|
||||
|
||||
/* global _ */
|
||||
|
||||
// A suffix that indicates that a particular header refers to a parameter
|
||||
const PARAMETER_SUFFIX = ' (parameter)';
|
||||
|
||||
// A suffix that indicates that a particular header refers to an attribute
|
||||
const ATTRIBUTE_SUFFIX = ' (attribute)';
|
||||
|
||||
/**
|
||||
* A service for parsing user-provided CSV connection data for bulk import.
|
||||
*/
|
||||
angular.module('import').factory('connectionCSVService',
|
||||
['$injector', function connectionCSVService($injector) {
|
||||
|
||||
// Required types
|
||||
const ParseError = $injector.get('ParseError');
|
||||
const TranslatableMessage = $injector.get('TranslatableMessage');
|
||||
|
||||
// Required services
|
||||
const $q = $injector.get('$q');
|
||||
const $routeParams = $injector.get('$routeParams');
|
||||
const schemaService = $injector.get('schemaService');
|
||||
const $q = $injector.get('$q');
|
||||
const $routeParams = $injector.get('$routeParams');
|
||||
const schemaService = $injector.get('schemaService');
|
||||
|
||||
const service = {};
|
||||
|
||||
@@ -88,14 +98,254 @@ angular.module('import').factory('connectionCSVService',
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 connection object.
|
||||
* If an error occurs while parsing a particular row, the resolved function
|
||||
* will throw a ParseError describing the failure.
|
||||
*
|
||||
* The provided CSV must contain columns for name and protocol. Optionally,
|
||||
* the parentIdentifier of the target parent connection group, or a connection
|
||||
* name path e.g. "ROOT/parent/child" may be included. Additionallty,
|
||||
* connection parameters or attributes can be included.
|
||||
*
|
||||
* The names of connection attributes and parameters are not guaranteed to
|
||||
* be mutually exclusive, so the CSV import format supports a distinguishing
|
||||
* suffix. A column may be explicitly declared to be a parameter using a
|
||||
* " (parameter)" suffix, or an attribute using an " (attribute)" suffix.
|
||||
* No suffix is required if the name is unique across connections and
|
||||
* attributes.
|
||||
*
|
||||
* If a parameter or attribute name conflicts with the standard
|
||||
* "name", "protocol", "group", or "parentIdentifier" fields, the suffix is
|
||||
* required.
|
||||
*
|
||||
* This returned object will be very similar to the Connection type, with
|
||||
* the exception that a human-readable "group" field may be present.
|
||||
*
|
||||
* If a failure occurs while attempting to create the transformer function,
|
||||
* the promise will be rejected with a ParseError describing the failure.
|
||||
*
|
||||
* @returns {Promise.<Function.<String[], Object>>}
|
||||
* A promise that will resolve to a function that translates a CSV data
|
||||
* row (array of strings) to a connection object.
|
||||
* row (array of strings) to a connection object.
|
||||
*/
|
||||
service.getCSVTransformer = function getCSVTransformer(headerRow) {
|
||||
|
||||
// A promise that will be resolved with the transformer or rejected if
|
||||
// an error occurs
|
||||
const deferred = $q.defer();
|
||||
|
||||
getFieldLookups().then(({attributes, protocolParameters}) => {
|
||||
|
||||
// All configuration required to generate a function that can
|
||||
// transform a row of CSV into a connection object.
|
||||
// NOTE: This is a single object instead of a collection of variables
|
||||
// to ensure that no stale references are used - e.g. when one getter
|
||||
// invokes another getter
|
||||
const transformConfig = {
|
||||
|
||||
// Callbacks for required fields
|
||||
nameGetter: undefined,
|
||||
protocolGetter: undefined,
|
||||
|
||||
// Callbacks for a parent group ID or group path
|
||||
groupGetter: _.noop,
|
||||
parentIdentifierGetter: _.noop,
|
||||
|
||||
// Callbacks that will generate either connection attributes or
|
||||
// parameters. These callbacks will return a {type, name, value}
|
||||
// object containing the type ("parameter" or "attribute"),
|
||||
// the name of the attribute or parameter, and the corresponding
|
||||
// value.
|
||||
parameterOrAttributeGetters: []
|
||||
|
||||
};
|
||||
|
||||
// A set of all headers that have been seen so far. If any of these
|
||||
// are duplicated, the CSV is invalid.
|
||||
const headerSet = {};
|
||||
|
||||
// Iterate through the headers one by one
|
||||
headerRow.forEach((rawHeader, index) => {
|
||||
|
||||
// Trim to normalize all headers
|
||||
const header = rawHeader.trim();
|
||||
|
||||
// Check if the header is duplicated
|
||||
if (headerSet[header]) {
|
||||
deferred.reject(new ParseError({
|
||||
message: 'Duplicate CSV Header: ' + header,
|
||||
translatableMessage: new TranslatableMessage({
|
||||
key: 'CONNECTION_IMPORT.ERROR_DUPLICATE_CSV_HEADER',
|
||||
variables: { HEADER: header }
|
||||
})
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark that this particular header has already been seen
|
||||
headerSet[header] = true;
|
||||
|
||||
// A callback that returns the field at the current index
|
||||
const fetchFieldAtIndex = row => row[index];
|
||||
|
||||
// Set up the name callback
|
||||
if (header == 'name')
|
||||
transformConfig.nameGetter = fetchFieldAtIndex;
|
||||
|
||||
// Set up the protocol callback
|
||||
else if (header == 'protocol')
|
||||
transformConfig.protocolGetter = fetchFieldAtIndex;
|
||||
|
||||
// Set up the group callback
|
||||
else if (header == 'group')
|
||||
transformConfig.groupGetter = fetchFieldAtIndex;
|
||||
|
||||
// Set up the group parent ID callback
|
||||
else if (header == 'parentIdentifier')
|
||||
transformConfig.parentIdentifierGetter = fetchFieldAtIndex;
|
||||
|
||||
// At this point, any other header might refer to a connection
|
||||
// parameter or to an attribute
|
||||
|
||||
// A field may be explicitly specified as a parameter
|
||||
else if (header.endsWith(PARAMETER_SUFFIX)) {
|
||||
|
||||
// Push as an explicit parameter getter
|
||||
const parameterName = header.replace(PARAMETER_SUFFIX);
|
||||
transformConfig.parameterOrAttributeGetters.push(
|
||||
row => ({
|
||||
type: 'parameter',
|
||||
name: parameterName,
|
||||
value: fetchFieldAtIndex(row)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// A field may be explicitly specified as a parameter
|
||||
else if (header.endsWith(ATTRIBUTE_SUFFIX)) {
|
||||
|
||||
// Push as an explicit attribute getter
|
||||
const attributeName = header.replace(ATTRIBUTE_SUFFIX);
|
||||
transformConfig.parameterOrAttributeGetters.push(
|
||||
row => ({
|
||||
type: 'attribute',
|
||||
name: parameterName,
|
||||
value: fetchFieldAtIndex(row)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// The field is ambiguous, either an attribute or parameter,
|
||||
// so the getter will have to determine this for every row
|
||||
else
|
||||
transformConfig.parameterOrAttributeGetters.push(row => {
|
||||
|
||||
// The name is just the value of the current header
|
||||
const name = header;
|
||||
|
||||
// The value is at the index that matches the position
|
||||
// of the header
|
||||
const value = fetchFieldAtIndex(row);
|
||||
|
||||
// The protocol may determine whether a field is
|
||||
// a parameter or an attribute (or both)
|
||||
const protocol = transformConfig.protocolGetter(row);
|
||||
|
||||
// Determine if the field refers to an attribute or a
|
||||
// parameter (or both, which is an error)
|
||||
const isAttribute = !!attributes[name];
|
||||
const isParameter = !!_.get(
|
||||
protocolParameters, [protocol, name]);
|
||||
|
||||
// If there is both an attribute and a protocol-specific
|
||||
// parameter with the provided name, it's impossible to
|
||||
// figure out which this should be
|
||||
if (isAttribute && isParameter)
|
||||
throw new ParseError({
|
||||
message: 'Ambiguous CSV Header: ' + header,
|
||||
key: 'CONNECTION_IMPORT.ERROR_AMBIGUOUS_CSV_HEADER',
|
||||
variables: { HEADER: header }
|
||||
});
|
||||
|
||||
// It's neither an attribute or a parameter
|
||||
else if (!isAttribute && !isParameter)
|
||||
throw new ParseError({
|
||||
message: 'Invalid CSV Header: ' + header,
|
||||
key: 'CONNECTION_IMPORT.ERROR_INVALID_CSV_HEADER',
|
||||
variables: { HEADER: header }
|
||||
});
|
||||
|
||||
// Choose the appropriate type
|
||||
const type = isAttribute ? 'attributes' : 'parameters';
|
||||
|
||||
return { type, name, value };
|
||||
});
|
||||
});
|
||||
|
||||
// Fail if the name wasn't provided
|
||||
if (!transformConfig.nameGetter)
|
||||
return deferred.reject(new ParseError({
|
||||
message: 'The connection name must be provided',
|
||||
key: 'CONNECTION_IMPORT.ERROR_REQUIRED_NAME'
|
||||
}));
|
||||
|
||||
// Fail if the protocol wasn't provided
|
||||
if (!transformConfig.protocolGetter)
|
||||
return deferred.reject(new ParseError({
|
||||
message: 'The connection protocol must be provided',
|
||||
key: 'CONNECTION_IMPORT.ERROR_REQUIRED_PROTOCOL'
|
||||
}));
|
||||
|
||||
// The function to transform a CSV row into a connection object
|
||||
deferred.resolve(function transformCSVRow(row) {
|
||||
|
||||
const {
|
||||
nameGetter, protocolGetter,
|
||||
parentIdentifierGetter, groupGetter,
|
||||
parameterOrAttributeGetters
|
||||
} = transformConfig;
|
||||
|
||||
// Set name and protocol
|
||||
const name = nameGetter(row);
|
||||
const protocol = protocolGetter(row);
|
||||
|
||||
// Set the parent group ID and/or group path
|
||||
const group = groupGetter && groupGetter(row);
|
||||
const parentIdentifier = (
|
||||
parentIdentifierGetter && parentIdentifierGetter(row));
|
||||
|
||||
return {
|
||||
|
||||
// Simple fields that are not protocol-specific
|
||||
...{
|
||||
name,
|
||||
protocol,
|
||||
parentIdentifier,
|
||||
group
|
||||
},
|
||||
|
||||
// Fields that might potentially be either attributes or
|
||||
// parameters, depending on the protocol
|
||||
...parameterOrAttributeGetters.reduce((values, getter) => {
|
||||
|
||||
// Determine the type, name, and value
|
||||
const { type, name, value } = getter(row);
|
||||
|
||||
// Set the value and continue on to the next attribute
|
||||
// or parameter
|
||||
values[type][name] = value;
|
||||
return values;
|
||||
|
||||
}, {parameters: {}, attributes: {}})
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
|
@@ -38,15 +38,17 @@ angular.module('import').factory('connectionParseService',
|
||||
const $q = $injector.get('$q');
|
||||
const $routeParams = $injector.get('$routeParams');
|
||||
const schemaService = $injector.get('schemaService');
|
||||
const connectionCSVService = $injector.get('connectionCSVService');
|
||||
const connectionGroupService = $injector.get('connectionGroupService');
|
||||
|
||||
const service = {};
|
||||
|
||||
/**
|
||||
* Perform basic checks, common to all file types - namely that the parsed
|
||||
* data is an array, and contains at least one connection entry.
|
||||
* data is an array, and contains at least one connection entry. Returns an
|
||||
* error if any of these basic checks fails.
|
||||
*
|
||||
* @throws {ParseError}
|
||||
* returns {ParseError}
|
||||
* An error describing the parsing failure, if one of the basic checks
|
||||
* fails.
|
||||
*/
|
||||
@@ -54,80 +56,20 @@ angular.module('import').factory('connectionParseService',
|
||||
|
||||
// Make sure that the file data parses to an array (connection list)
|
||||
if (!(parsedData instanceof Array))
|
||||
throw new ParseError({
|
||||
return new ParseError({
|
||||
message: 'Import data must be a list of connections',
|
||||
translatableMessage: new TranslatableMessage({
|
||||
key: 'SETTINGS_CONNECTION_IMPORT.ERROR_ARRAY_REQUIRED'
|
||||
})
|
||||
key: 'CONNECTION_IMPORT.ERROR_ARRAY_REQUIRED'
|
||||
});
|
||||
|
||||
// Make sure that the connection list is not empty - contains at least
|
||||
// one connection
|
||||
if (!parsedData.length)
|
||||
throw new ParseError({
|
||||
message: 'The provided CSV file is empty',
|
||||
translatableMessage: new TranslatableMessage({
|
||||
key: 'SETTINGS_CONNECTION_IMPORT.ERROR_EMPTY_FILE'
|
||||
})
|
||||
return new ParseError({
|
||||
message: 'The provided file is empty',
|
||||
key: 'CONNECTION_IMPORT.ERROR_EMPTY_FILE'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves to an object detailing the connection
|
||||
* attributes for the current data source, as well as the connection
|
||||
* paremeters for every protocol, for the current data source.
|
||||
*
|
||||
* The object that the promise will contain an "attributes" key that maps to
|
||||
* a set of attribute names, and a "protocolParameters" key that maps to an
|
||||
* object mapping protocol names to sets of parameter names for that protocol.
|
||||
*
|
||||
* The intended use case for this object is to determine if there is a
|
||||
* connection parameter or attribute with a given name, by e.g. checking the
|
||||
* path `.protocolParameters[protocolName]` to see if a protocol exists,
|
||||
* checking the path `.protocolParameters[protocolName][fieldName]` to see
|
||||
* if a parameter exists for a given protocol, or checking the path
|
||||
* `.attributes[fieldName]` to check if a connection attribute exists.
|
||||
*
|
||||
* @returns {Promise.<Object>}
|
||||
*/
|
||||
function getFieldLookups() {
|
||||
|
||||
// The current data source - the one that the connections will be
|
||||
// imported into
|
||||
const dataSource = $routeParams.dataSource;
|
||||
|
||||
// Fetch connection attributes and protocols for the current data source
|
||||
return $q.all({
|
||||
attributes : schemaService.getConnectionAttributes(dataSource),
|
||||
protocols : schemaService.getProtocols(dataSource)
|
||||
})
|
||||
.then(function connectionStructureRetrieved({attributes, protocols}) {
|
||||
|
||||
return {
|
||||
|
||||
// Translate the forms and fields into a flat map of attribute
|
||||
// name to `true` boolean value
|
||||
attributes: attributes.reduce(
|
||||
(attributeMap, form) => {
|
||||
form.fields.forEach(
|
||||
field => attributeMap[field.name] = true);
|
||||
return attributeMap
|
||||
}, {}),
|
||||
|
||||
// Translate the protocol definitions into a map of protocol
|
||||
// name to map of field name to `true` boolean value
|
||||
protocolParameters: _.mapValues(
|
||||
protocols, protocol => protocol.connectionForms.reduce(
|
||||
(protocolFieldMap, form) => {
|
||||
form.fields.forEach(
|
||||
field => protocolFieldMap[field.name] = true);
|
||||
return protocolFieldMap;
|
||||
}, {}))
|
||||
};
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves to an object mapping potential groups
|
||||
@@ -181,68 +123,9 @@ angular.module('import').factory('connectionParseService',
|
||||
|
||||
return deferredGroupLookups.promise;
|
||||
}
|
||||
|
||||
/*
|
||||
// Example Connection JSON
|
||||
{
|
||||
"attributes": {
|
||||
"failover-only": "true",
|
||||
"guacd-encryption": "none",
|
||||
"guacd-hostname": "potato",
|
||||
"guacd-port": "1234",
|
||||
"ksm-user-config-enabled": "true",
|
||||
"max-connections": "1",
|
||||
"max-connections-per-user": "1",
|
||||
"weight": "1"
|
||||
},
|
||||
"name": "Bloatato",
|
||||
"parameters": {
|
||||
"audio-servername": "heyoooooooo",
|
||||
"clipboard-encoding": "",
|
||||
"color-depth": "",
|
||||
"create-recording-path": "",
|
||||
"cursor": "remote",
|
||||
"dest-host": "pooootato",
|
||||
"dest-port": "4444",
|
||||
"disable-copy": "",
|
||||
"disable-paste": "true",
|
||||
"enable-audio": "true",
|
||||
"enable-sftp": "true",
|
||||
"force-lossless": "true",
|
||||
"hostname": "potato",
|
||||
"password": "taste",
|
||||
"port": "4321",
|
||||
"read-only": "",
|
||||
"recording-exclude-mouse": "",
|
||||
"recording-exclude-output": "",
|
||||
"recording-include-keys": "",
|
||||
"recording-name": "heyoooooo",
|
||||
"recording-path": "/path/to/goo",
|
||||
"sftp-disable-download": "",
|
||||
"sftp-disable-upload": "",
|
||||
"sftp-hostname": "what what good sir",
|
||||
"sftp-port": "",
|
||||
"sftp-private-key": "lol i'll never tell",
|
||||
"sftp-server-alive-interval": "",
|
||||
"swap-red-blue": "true",
|
||||
"username": "test",
|
||||
"wol-send-packet": "",
|
||||
"wol-udp-port": "",
|
||||
"wol-wait-time": ""
|
||||
},
|
||||
|
||||
// or a numeric identifier - we will probably want to offer a way to allow
|
||||
// them to specify a path like "ROOT/parent/child" or just "/parent/child" or
|
||||
// something like that
|
||||
// TODO: Call the
|
||||
"parentIdentifier": "ROOT",
|
||||
"protocol": "vnc"
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert a provided JSON representation of a connection list into a JSON
|
||||
* Convert a provided CSV representation of a connection list into a JSON
|
||||
* string to be submitted to the PATCH REST endpoint. The returned JSON
|
||||
* string will contain a PATCH operation to create each connection in the
|
||||
* provided list.
|
||||
@@ -259,36 +142,44 @@ angular.module('import').factory('connectionParseService',
|
||||
*/
|
||||
service.parseCSV = function parseCSV(csvData) {
|
||||
|
||||
const deferredConnections = $q.defer();
|
||||
// Convert to an array of arrays, one per CSV row (including the header)
|
||||
// NOTE: skip_empty_lines is required, or a trailing newline will error
|
||||
let parsedData;
|
||||
try {
|
||||
parsedData = parseCSVData(csvData, {skip_empty_lines: true});
|
||||
}
|
||||
|
||||
return $q.all({
|
||||
fieldLookups : getFieldLookups(),
|
||||
groupLookups : getGroupLookups()
|
||||
})
|
||||
.then(function lookupsReady({fieldLookups, groupLookups}) {
|
||||
// If the CSV parser throws an error, reject with that error. No
|
||||
// translation key will be available here.
|
||||
catch(error) {
|
||||
console.error(error);
|
||||
const deferred = $q.defer();
|
||||
deferred.reject(error);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
// Slice off the header row to get the data rows
|
||||
const connectionData = parsedData.slice(1);
|
||||
|
||||
// Check that the provided CSV is not empty (the parser always
|
||||
// returns an array)
|
||||
const checkError = performBasicChecks(connectionData);
|
||||
if (checkError) {
|
||||
const deferred = $q.defer();
|
||||
deferred.reject(checkError);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
const {attributes, protocolParameters} = fieldLookups;
|
||||
|
||||
console.log({attributes, protocolParameters}, groupLookups);
|
||||
|
||||
// Convert to an array of arrays, one per CSV row (including the header)
|
||||
const parsedData = parseCSVData(csvData);
|
||||
|
||||
// Slice off the header row to get the data rows
|
||||
const connectionData = parsedData.slice(1);
|
||||
|
||||
// Check that the provided CSV is not empty (the parser always
|
||||
// returns an array)
|
||||
performBasicChecks(connectionData);
|
||||
|
||||
// The header row - an array of string header values
|
||||
const header = parsedData[0];
|
||||
|
||||
// TODO: Connectionify this
|
||||
deferredConnections.resolve(connectionData);
|
||||
});
|
||||
// The header row - an array of string header values
|
||||
const header = parsedData[0];
|
||||
|
||||
return deferredConnections.promise;
|
||||
return connectionCSVService.getCSVTransformer(header).then(
|
||||
|
||||
// If the transformer was successfully generated, apply it to the
|
||||
// data rows
|
||||
// TODO: Also apply the group -> parentIdentifier transform
|
||||
csvTransformer => connectionData.map(csvTransformer)
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
@@ -303,7 +194,7 @@ angular.module('import').factory('connectionParseService',
|
||||
*
|
||||
* @return {Promise.<Connection[]>}
|
||||
* A promise resolving to an array of Connection objects, one for each
|
||||
* connection in the provided CSV.
|
||||
* connection in the provided YAML.
|
||||
*/
|
||||
service.parseYAML = function parseYAML(yamlData) {
|
||||
|
||||
@@ -311,7 +202,9 @@ angular.module('import').factory('connectionParseService',
|
||||
const parsedData = parseYAMLData(yamlData);
|
||||
|
||||
// Check that the data is the correct format, and not empty
|
||||
performBasicChecks(parsedData);
|
||||
const checkError = performBasicChecks(connectionData);
|
||||
if (checkError)
|
||||
return $q.defer().reject(checkError);
|
||||
|
||||
// Convert to an array of Connection objects and return
|
||||
const deferredConnections = $q.defer();
|
||||
@@ -332,7 +225,7 @@ angular.module('import').factory('connectionParseService',
|
||||
*
|
||||
* @return {Promise.<Connection[]>}
|
||||
* A promise resolving to an array of Connection objects, one for each
|
||||
* connection in the provided CSV.
|
||||
* connection in the provided JSON.
|
||||
*/
|
||||
service.parseJSON = function parseJSON(jsonData) {
|
||||
|
||||
@@ -340,7 +233,9 @@ angular.module('import').factory('connectionParseService',
|
||||
const parsedData = JSON.parse(yamlData);
|
||||
|
||||
// Check that the data is the correct format, and not empty
|
||||
performBasicChecks(parsedData);
|
||||
const checkError = performBasicChecks(connectionData);
|
||||
if (checkError)
|
||||
return $q.defer().reject(checkError);
|
||||
|
||||
// Convert to an array of Connection objects and return
|
||||
const deferredConnections = $q.defer();
|
||||
|
22
guacamole/src/main/frontend/src/app/import/styles/import.css
vendored
Normal file
22
guacamole/src/main/frontend/src/app/import/styles/import.css
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
.import .parseError {
|
||||
color: red;
|
||||
}
|
@@ -1,11 +1,23 @@
|
||||
<div class="settings-view import">
|
||||
|
||||
<div class="header">
|
||||
<h2>{{'SETTINGS_CONNECTION_IMPORT.HEADER' | translate}}</h2>
|
||||
<h2>{{'CONNECTION_IMPORT.HEADER' | translate}}</h2>
|
||||
<guac-user-menu></guac-user-menu>
|
||||
</div>
|
||||
|
||||
<input type="file" id="file" name="file"/>
|
||||
<button ng-click="upload()">Add</button>
|
||||
|
||||
<!-- The translatable error message, if one is set -->
|
||||
<p
|
||||
class="parseError" ng-show="error.key"
|
||||
translate="{{error.key}}" translate-values="{{error.variables}}"
|
||||
></p>
|
||||
|
||||
<!-- The base message, if no translatable message is available -->
|
||||
<p class="parseError" ng-show="!error.key && error.message">
|
||||
{{error.message}}
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
@@ -44,13 +44,22 @@ angular.module('import').factory('ParseError', [function defineParseError() {
|
||||
this.message = template.message;
|
||||
|
||||
/**
|
||||
* A message which can be translated using the translation service,
|
||||
* consisting of a translation key and optional set of substitution
|
||||
* variables.
|
||||
* The key associated with the translation string that used when
|
||||
* displaying this message.
|
||||
*
|
||||
* @type TranslatableMessage
|
||||
* @type String
|
||||
*/
|
||||
this.translatableMessage = template.translatableMessage;
|
||||
this.key = template.key;
|
||||
|
||||
/**
|
||||
* The object which should be passed through to the translation service
|
||||
* for the sake of variable substitution. Each property of the provided
|
||||
* object will be substituted for the variable of the same name within
|
||||
* the translation string.
|
||||
*
|
||||
* @type Object
|
||||
*/
|
||||
this.variables = template.variables;
|
||||
|
||||
};
|
||||
|
||||
|
@@ -35,6 +35,7 @@ angular.module('index', [
|
||||
'client',
|
||||
'clipboard',
|
||||
'home',
|
||||
'import',
|
||||
'login',
|
||||
'manage',
|
||||
'navigation',
|
||||
|
@@ -11,7 +11,7 @@
|
||||
|
||||
<a class="import-connections button"
|
||||
ng-show="canCreateConnections()"
|
||||
href="#/settings/{{dataSource | escape}}/import/">{{'SETTINGS_CONNECTIONS.ACTION_IMPORT_CONNECTIONS' | translate}}</a>
|
||||
href="#/import/{{dataSource | escape}}/connection/">{{'SETTINGS_CONNECTIONS.ACTION_IMPORT_CONNECTIONS' | translate}}</a>
|
||||
|
||||
<a class="add-connection button"
|
||||
ng-show="canCreateConnections()"
|
||||
|
@@ -184,6 +184,31 @@
|
||||
|
||||
},
|
||||
|
||||
"CONNECTION_IMPORT": {
|
||||
|
||||
"HEADER": "Connection Import",
|
||||
|
||||
"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_INVALID_CSV_HEADER":
|
||||
"Invalid CSV Header \"{HEADER}\" is neither an attribute or parameter",
|
||||
"ERROR_INVALID_FILE_TYPE":
|
||||
"Invalid import file type \"{TYPE}\"",
|
||||
"ERROR_NO_FILE_SUPPLIED": "Please select a file to import",
|
||||
"ERROR_REQUIRED_GROUP":
|
||||
"Either group or parentIdentifier must be specified, but not both",
|
||||
"ERROR_REQUIRED_PROTOCOL":
|
||||
"No connection protocol found in the provided file",
|
||||
"ERROR_REQUIRED_NAME":
|
||||
"No connection name found in the provided file"
|
||||
|
||||
},
|
||||
|
||||
"DATA_SOURCE_DEFAULT" : {
|
||||
"NAME" : "Default (XML)"
|
||||
},
|
||||
@@ -923,13 +948,6 @@
|
||||
|
||||
},
|
||||
|
||||
"SETTINGS_CONNECTION_IMPORT": {
|
||||
|
||||
"HEADER": "Connection Import",
|
||||
|
||||
"ERROR_ARRAY_REQUIRED": "The provided file must contain a list of connections"
|
||||
},
|
||||
|
||||
"SETTINGS_PREFERENCES" : {
|
||||
|
||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
||||
|
Reference in New Issue
Block a user