mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +00:00
GUACAMOLE-926: Create new file upload UI.
This commit is contained in:
@@ -42,6 +42,70 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
const User = $injector.get('User');
|
const User = $injector.get('User');
|
||||||
const UserGroup = $injector.get('UserGroup');
|
const UserGroup = $injector.get('UserGroup');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any error that may have occured during import file parsing.
|
||||||
|
*
|
||||||
|
* @type {ParseError}
|
||||||
|
*/
|
||||||
|
$scope.error = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the file is fully uploaded and ready to be processed, or false
|
||||||
|
* otherwise.
|
||||||
|
*
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
$scope.dataReady = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the file upload has been aborted mid-upload, or false otherwise.
|
||||||
|
*/
|
||||||
|
$scope.aborted = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if fully-uploaded data is being processed, or false otherwise.
|
||||||
|
*/
|
||||||
|
$scope.processing = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The MIME type of the uploaded file, if any.
|
||||||
|
*
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
$scope.mimeType = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The raw string contents of the uploaded file, if any.
|
||||||
|
*
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
$scope.fileData = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file reader currently being used to upload the file, if any. If
|
||||||
|
* null, no file upload is currently in progress.
|
||||||
|
*
|
||||||
|
* @type {FileReader}
|
||||||
|
*/
|
||||||
|
$scope.fileReader = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all file upload state.
|
||||||
|
*/
|
||||||
|
function resetUploadState() {
|
||||||
|
|
||||||
|
$scope.aborted = false;
|
||||||
|
$scope.dataReady = false;
|
||||||
|
$scope.processing = false;
|
||||||
|
$scope.fileData = null;
|
||||||
|
$scope.mimeType = null;
|
||||||
|
$scope.fileReader = null;
|
||||||
|
|
||||||
|
// Broadcast an event to clear the file upload UI
|
||||||
|
$scope.$broadcast('clearFile');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a successful response to an import PATCH request, make another
|
* Given a successful response to an import PATCH request, make another
|
||||||
* request to delete every created connection in the provided request, i.e.
|
* request to delete every created connection in the provided request, i.e.
|
||||||
@@ -227,20 +291,35 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
|
|
||||||
// TODON'T: Delete connections so we can test over and over
|
// TODON'T: Delete connections so we can test over and over
|
||||||
cleanUpConnections(response);
|
cleanUpConnections(response);
|
||||||
})
|
|
||||||
|
|
||||||
|
resetUploadState();
|
||||||
|
})
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set any caught error message to the scope for display
|
/**
|
||||||
|
* Set any caught error message to the scope for display.
|
||||||
|
*
|
||||||
|
* @argument {ParseError} error
|
||||||
|
* The error to display.
|
||||||
|
*/
|
||||||
const handleError = error => {
|
const handleError = error => {
|
||||||
|
|
||||||
|
// Any error indicates that processing of the file has failed, so clear
|
||||||
|
// all upload state to allow for a fresh retry
|
||||||
|
resetUploadState();
|
||||||
|
|
||||||
|
// Set the error for display
|
||||||
console.error(error);
|
console.error(error);
|
||||||
$scope.error = error;
|
$scope.error = error;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the current error
|
/**
|
||||||
|
* Clear the current displayed error.
|
||||||
|
*/
|
||||||
const clearError = () => delete $scope.error;
|
const clearError = () => delete $scope.error;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -255,39 +334,35 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
* The raw string contents of the import file.
|
* The raw string contents of the import file.
|
||||||
*/
|
*/
|
||||||
function processData(mimeType, data) {
|
function processData(mimeType, data) {
|
||||||
|
|
||||||
|
// Data processing has begun
|
||||||
|
$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;
|
||||||
|
|
||||||
// Parse the data based on the provided mimetype
|
// Choose the appropriate parse function based on the mimetype
|
||||||
switch(mimeType) {
|
if (mimeType.endsWith("json"))
|
||||||
|
processDataCallback = connectionParseService.parseJSON;
|
||||||
|
|
||||||
case "application/json":
|
else if (mimeType.endsWith("csv"))
|
||||||
case "text/json":
|
processDataCallback = connectionParseService.parseCSV;
|
||||||
processDataCallback = connectionParseService.parseJSON;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "text/csv":
|
else if (mimeType.endsWith("yaml"))
|
||||||
processDataCallback = connectionParseService.parseCSV;
|
processDataCallback = connectionParseService.parseYAML;
|
||||||
break;
|
|
||||||
|
|
||||||
case "application/yaml":
|
// We don't expect this to happen - the file upload directive should
|
||||||
case "application/x-yaml":
|
// have already have filtered out any invalid file types
|
||||||
case "text/yaml":
|
else {
|
||||||
case "text/x-yaml":
|
handleError(new ParseError({
|
||||||
processDataCallback = connectionParseService.parseYAML;
|
message: 'Invalid file type: ' + type,
|
||||||
break;
|
key: 'CONNECTION_IMPORT.INVALID_FILE_TYPE',
|
||||||
|
variables: { TYPE: type }
|
||||||
default:
|
}));
|
||||||
handleError(new ParseError({
|
return;
|
||||||
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
|
// Make the call to process the data into a series of patches
|
||||||
processDataCallback(data)
|
processDataCallback(data)
|
||||||
|
|
||||||
@@ -298,30 +373,109 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
|||||||
.catch(handleError);
|
.catch(handleError);
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.upload = function() {
|
/**
|
||||||
|
* Process the uploaded import data. Only usuable if the upload is fully
|
||||||
|
* complete.
|
||||||
|
*/
|
||||||
|
$scope.import = () => processData($scope.mimeType, $scope.fileData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Boolean}
|
||||||
|
* True if import should be disabled, or false if cancellation
|
||||||
|
* should be allowed.
|
||||||
|
*/
|
||||||
|
$scope.importDisabled = () =>
|
||||||
|
|
||||||
|
// Disable import if no data is ready
|
||||||
|
!$scope.dataReady ||
|
||||||
|
|
||||||
|
// Disable import if the file is currently being processed
|
||||||
|
$scope.processing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel any in-progress upload, or clear any uploaded-but
|
||||||
|
*/
|
||||||
|
$scope.cancel = function() {
|
||||||
|
|
||||||
|
// Clear any error message
|
||||||
|
clearError();
|
||||||
|
|
||||||
|
// If the upload is in progress, stop it now; the FileReader will
|
||||||
|
// reset the upload state when it stops
|
||||||
|
if ($scope.fileReader) {
|
||||||
|
$scope.aborted = true;
|
||||||
|
$scope.fileReader.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear any upload state - there's no FileReader handler to do it
|
||||||
|
else
|
||||||
|
resetUploadState();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Boolean}
|
||||||
|
* True if cancellation should be disabled, or false if cancellation
|
||||||
|
* should be allowed.
|
||||||
|
*/
|
||||||
|
$scope.cancelDisabled = () =>
|
||||||
|
|
||||||
|
// Disable cancellation if the import has already been cancelled
|
||||||
|
$scope.aborted ||
|
||||||
|
|
||||||
|
// Disable cancellation if the file is currently being processed
|
||||||
|
$scope.processing ||
|
||||||
|
|
||||||
|
// Disable cancellation if no data is ready or being uploaded
|
||||||
|
!($scope.fileReader || $scope.dataReady);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a provided File upload, reading all data onto the scope for
|
||||||
|
* import processing, should the user request an import.
|
||||||
|
*
|
||||||
|
* @argument {File} file
|
||||||
|
* The file to upload onto the scope for further processing.
|
||||||
|
*/
|
||||||
|
$scope.handleFile = function(file) {
|
||||||
|
|
||||||
// Clear any error message from the previous upload attempt
|
// Clear any error message from the previous upload attempt
|
||||||
clearError();
|
clearError();
|
||||||
|
|
||||||
const files = angular.element('#file')[0].files;
|
// Initialize upload state
|
||||||
|
$scope.aborted = false;
|
||||||
|
$scope.dataReady = false;
|
||||||
|
$scope.processing = false;
|
||||||
|
$scope.uploadStarted = true;
|
||||||
|
|
||||||
if (files.length <= 0) {
|
// Save the MIME type to the scope
|
||||||
handleError(new ParseError({
|
$scope.mimeType = file.type;
|
||||||
message: 'No file supplied',
|
|
||||||
key: 'CONNECTION_IMPORT.ERROR_NO_FILE_SUPPLIED'
|
|
||||||
}));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The file that the user uploaded
|
// Save the file to the scope when ready
|
||||||
const file = files[0];
|
$scope.fileReader = new FileReader();
|
||||||
|
$scope.fileReader.onloadend = (e => {
|
||||||
|
|
||||||
// Call processData when the data is ready
|
// If the upload was explicitly aborted, clear any upload state and
|
||||||
const reader = new FileReader();
|
// do not process the data
|
||||||
reader.onloadend = (e => processData(file.type, e.target.result));
|
if ($scope.aborted)
|
||||||
|
resetUploadState();
|
||||||
|
|
||||||
// Read all the data into memory and call processData when done
|
else {
|
||||||
reader.readAsBinaryString(file);
|
|
||||||
|
// Save the uploaded data
|
||||||
|
$scope.fileData = e.target.result;
|
||||||
|
|
||||||
|
// Mark the data as ready
|
||||||
|
$scope.dataReady = true;
|
||||||
|
|
||||||
|
// Clear the file reader from the scope now that this file is
|
||||||
|
// fully uploaded
|
||||||
|
$scope.fileReader = null;
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Read all the data into memory
|
||||||
|
$scope.fileReader.readAsBinaryString(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
@@ -0,0 +1,253 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global _ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A directive that allows for file upload, either through drag-and-drop or
|
||||||
|
* a file browser.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All legal import file types. Any file not belonging to one of these types
|
||||||
|
* must be rejected.
|
||||||
|
*/
|
||||||
|
const LEGAL_FILE_TYPES = ["csv", "json", "yaml"];
|
||||||
|
|
||||||
|
angular.module('import').directive('connectionImportFileUpload', [
|
||||||
|
function connectionImportFileUpload() {
|
||||||
|
|
||||||
|
const directive = {
|
||||||
|
restrict: 'E',
|
||||||
|
replace: true,
|
||||||
|
templateUrl: 'app/import/templates/connectionImportFileUpload.html',
|
||||||
|
scope: {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function to invoke when a file is provided to the file upload
|
||||||
|
* UI, either by dragging and dropping, or by navigating using the
|
||||||
|
* file browser. The function will be called with 2 arguments - the
|
||||||
|
* mime type, and the raw string contents of the file.
|
||||||
|
*
|
||||||
|
* @type function
|
||||||
|
*/
|
||||||
|
onFile : '&',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
directive.controller = ['$scope', '$injector', '$element',
|
||||||
|
function fileUploadController($scope, $injector, $element) {
|
||||||
|
|
||||||
|
// Required services
|
||||||
|
const $timeout = $injector.get('$timeout');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether a drag/drop operation is currently in progress (the user has
|
||||||
|
* dragged a file over the Guacamole connection but has not yet
|
||||||
|
* dropped it).
|
||||||
|
*
|
||||||
|
* @type boolean
|
||||||
|
*/
|
||||||
|
$scope.dropPending = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The error associated with the file upload, if any. An object of the
|
||||||
|
* form { key, variables }, or null if no error has occured.
|
||||||
|
*/
|
||||||
|
$scope.error = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the file that's currently being uploaded, or has yet to
|
||||||
|
* be imported, if any.
|
||||||
|
*/
|
||||||
|
$scope.fileName = null;
|
||||||
|
|
||||||
|
// Clear the file if instructed to do so by the parent
|
||||||
|
$scope.$on('clearFile', () => delete $scope.fileName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear any displayed error message.
|
||||||
|
*/
|
||||||
|
const clearError = () => $scope.error = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an error for display using the provided translation key and
|
||||||
|
* translation variables.
|
||||||
|
*
|
||||||
|
* @param {String} key
|
||||||
|
* The translation key.
|
||||||
|
*
|
||||||
|
* @param {Object.<String, String>} variables
|
||||||
|
* The variables to subsitute into the message, if any.
|
||||||
|
*/
|
||||||
|
const setError = (key, variables) => $scope.error = { key, variables };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The location where files can be dragged-and-dropped to.
|
||||||
|
*
|
||||||
|
* @type Element
|
||||||
|
*/
|
||||||
|
const dropTarget = $element.find('.drop-target')[0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a visual indication that dropping the file currently
|
||||||
|
* being dragged is possible. Further propagation and default behavior
|
||||||
|
* of the given event is automatically prevented.
|
||||||
|
*
|
||||||
|
* @param {Event} e
|
||||||
|
* The event related to the in-progress drag/drop operation.
|
||||||
|
*/
|
||||||
|
const notifyDragStart = function notifyDragStart(e) {
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
$scope.$apply(() => {
|
||||||
|
$scope.dropPending = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the visual indication that dropping the file currently
|
||||||
|
* being dragged is possible. Further propagation and default behavior
|
||||||
|
* of the given event is automatically prevented.
|
||||||
|
*
|
||||||
|
* @param {Event} e
|
||||||
|
* The event related to the end of the former drag/drop operation.
|
||||||
|
*/
|
||||||
|
const notifyDragEnd = function notifyDragEnd(e) {
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
$scope.$apply(() => {
|
||||||
|
$scope.dropPending = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add listeners to the drop target to ensure that the visual state
|
||||||
|
// stays up to date
|
||||||
|
dropTarget.addEventListener('dragenter', notifyDragStart, false);
|
||||||
|
dropTarget.addEventListener('dragover', notifyDragStart, false);
|
||||||
|
dropTarget.addEventListener('dragleave', notifyDragEnd, false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a user-supplied file, validate that the file type is correct,
|
||||||
|
* and invoke the onFile callback provided to this directive if so.
|
||||||
|
*
|
||||||
|
* @param {File} file
|
||||||
|
* The user-supplied file.
|
||||||
|
*/
|
||||||
|
function handleFile(file) {
|
||||||
|
|
||||||
|
// Clear any error from a previous attempted file upload
|
||||||
|
clearError();
|
||||||
|
|
||||||
|
// The MIME type of the provided file
|
||||||
|
const mimeType = file.type;
|
||||||
|
|
||||||
|
// Check if the mimetype ends with one of the supported types,
|
||||||
|
// e.g. "application/json" or "text/csv"
|
||||||
|
if (_.every(LEGAL_FILE_TYPES.map(
|
||||||
|
type => !mimeType.endsWith(type)))) {
|
||||||
|
|
||||||
|
// If the provided file is not one of the supported types,
|
||||||
|
// display an error and abort processing
|
||||||
|
setError('CONNECTION_IMPORT.ERROR_INVALID_FILE_TYPE',
|
||||||
|
{ TYPE: mimeType });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.fileName = file.name;
|
||||||
|
|
||||||
|
// Invoke the provided file callback using the file
|
||||||
|
$scope.onFile({ file });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop target event listener that will be invoked if the user drops
|
||||||
|
* anything onto the drop target. If a valid file is provided, the
|
||||||
|
* onFile callback provided to this directive will be called; otherwise
|
||||||
|
* an error will be displayed, if appropriate.
|
||||||
|
*
|
||||||
|
* @param {Event} e
|
||||||
|
* The drop event that triggered this handler.
|
||||||
|
*/
|
||||||
|
dropTarget.addEventListener('drop', function(e) {
|
||||||
|
|
||||||
|
notifyDragEnd(e);
|
||||||
|
|
||||||
|
const files = e.dataTransfer.files;
|
||||||
|
|
||||||
|
// Ignore any non-files that are dragged into the drop area
|
||||||
|
if (files.length < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (files.length > 2) {
|
||||||
|
|
||||||
|
// If more than one file was provided, print an error explaining
|
||||||
|
// that only a single file is allowed and abort processing
|
||||||
|
setError('CONNECTION_IMPORT.ERROR_FILE_SINGLE_ONLY');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFile(files[0]);
|
||||||
|
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hidden file input used to create a file browser.
|
||||||
|
*
|
||||||
|
* @type Element
|
||||||
|
*/
|
||||||
|
const fileUploadInput = $element.find('.file-upload-input')[0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that will click on the hidden file input to open a file
|
||||||
|
* browser to allow the user to select a file for upload.
|
||||||
|
*/
|
||||||
|
$scope.openFileBrowser = () =>
|
||||||
|
$timeout(() => fileUploadInput.click(), 0, false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler that will be invoked when a user selectes a file in the
|
||||||
|
* file browser. After some error checking, the file will be passed to
|
||||||
|
* the onFile callback provided to this directive.
|
||||||
|
*
|
||||||
|
* @param {Event} e
|
||||||
|
* The event that was triggered when the user selected a file in
|
||||||
|
* their file browser.
|
||||||
|
*/
|
||||||
|
fileUploadInput.onchange = e => {
|
||||||
|
|
||||||
|
// Process the uploaded file
|
||||||
|
handleFile(e.target.files[0]);
|
||||||
|
|
||||||
|
// Clear the value to ensure that the change event will be fired
|
||||||
|
// if the user selects the same file again
|
||||||
|
fileUploadInput.value = null;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}];
|
||||||
|
return directive;
|
||||||
|
|
||||||
|
}]);
|
@@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.file-upload-container {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24px 24px 24px;
|
||||||
|
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
border: 1px solid rgba(0,0,0,.25);
|
||||||
|
box-shadow: 1px 1px 2px rgb(0 0 0 / 25%);
|
||||||
|
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload-container .upload-header {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 500px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload-container .file-error {
|
||||||
|
|
||||||
|
color: red;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload-container .file-options {
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload-container .file-upload-input {
|
||||||
|
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload-container .drop-target {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
|
||||||
|
width: 500px;
|
||||||
|
height: 200px;
|
||||||
|
|
||||||
|
background: rgba(0,0,0,.04);
|
||||||
|
border: 1px solid black;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload-container .drop-target.file-present {
|
||||||
|
|
||||||
|
background: rgba(0,0,0,.15);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.file-upload-container .drop-target .file-name {
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.5em;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload-container .drop-target.drop-pending {
|
||||||
|
|
||||||
|
background: #3161a9;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload-container .drop-target.drop-pending > * {
|
||||||
|
|
||||||
|
opacity: 0.5;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload-container .drop-target .title {
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.25em;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload-container .drop-target .browse-link {
|
||||||
|
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
}
|
@@ -19,4 +19,13 @@
|
|||||||
|
|
||||||
.import .parseError {
|
.import .parseError {
|
||||||
color: red;
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.import .import-buttons {
|
||||||
|
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
}
|
}
|
@@ -5,9 +5,19 @@
|
|||||||
<guac-user-menu></guac-user-menu>
|
<guac-user-menu></guac-user-menu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="file" id="file" name="file"/>
|
<connection-import-file-upload on-file="handleFile(file)"></connection-import-file-upload>
|
||||||
<button ng-click="upload()">Add</button>
|
|
||||||
|
<div class="import-buttons">
|
||||||
|
<button
|
||||||
|
ng-click="import()" ng-disabled="importDisabled()" class="import">
|
||||||
|
{{'CONNECTION_IMPORT.BUTTON_IMPORT' | translate}}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
ng-click="cancel()" ng-disabled="cancelDisabled()" class="cancel">
|
||||||
|
{{'CONNECTION_IMPORT.BUTTON_CANCEL' | translate}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- The translatable error message, if one is set -->
|
<!-- The translatable error message, if one is set -->
|
||||||
<p
|
<p
|
||||||
class="parseError" ng-show="error.key"
|
class="parseError" ng-show="error.key"
|
||||||
|
@@ -0,0 +1,30 @@
|
|||||||
|
<div class="file-upload-container">
|
||||||
|
|
||||||
|
<div class="upload-header">
|
||||||
|
<span class="file-options">{{'CONNECTION_IMPORT.UPLOAD_FILE_TYPES' | translate}}</span>
|
||||||
|
<a
|
||||||
|
href="#/import/upload-help"
|
||||||
|
class="file-help-link">{{'CONNECTION_IMPORT.UPLOAD_HELP_LINK' | translate}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="drop-target" ng-class="{ 'drop-pending': dropPending, 'file-present': fileName}">
|
||||||
|
|
||||||
|
<div ng-show="!fileName" class="title">{{'CONNECTION_IMPORT.UPLOAD_DROP_TITLE' | translate}}</div>
|
||||||
|
|
||||||
|
<input type="file" class="file-upload-input"/>
|
||||||
|
<a ng-show="!fileName" ng-click="openFileBrowser()" class="browse-link">
|
||||||
|
{{'CONNECTION_IMPORT.UPLOAD_BROWSE_LINK' | translate}}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div ng-show="fileName" class="file-name"> {{fileName}} </div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- The translatable error message regarding the provided file(s), if any -->
|
||||||
|
<p
|
||||||
|
class="file-error" ng-show="error.key"
|
||||||
|
translate="{{error.key}}" translate-values="{{error.variables}}"
|
||||||
|
></p>
|
||||||
|
|
||||||
|
</div>
|
@@ -186,6 +186,9 @@
|
|||||||
|
|
||||||
"CONNECTION_IMPORT": {
|
"CONNECTION_IMPORT": {
|
||||||
|
|
||||||
|
"BUTTON_CANCEL": "Cancel",
|
||||||
|
"BUTTON_IMPORT": "Import Connections",
|
||||||
|
|
||||||
"HEADER": "Connection Import",
|
"HEADER": "Connection Import",
|
||||||
|
|
||||||
"ERROR_AMBIGUOUS_CSV_HEADER":
|
"ERROR_AMBIGUOUS_CSV_HEADER":
|
||||||
@@ -199,7 +202,7 @@
|
|||||||
"Invalid CSV Header \"{HEADER}\" is neither an attribute or parameter",
|
"Invalid CSV Header \"{HEADER}\" is neither an attribute or parameter",
|
||||||
"ERROR_INVALID_GROUP": "No group matching \"{GROUP}\" found",
|
"ERROR_INVALID_GROUP": "No group matching \"{GROUP}\" found",
|
||||||
"ERROR_INVALID_FILE_TYPE":
|
"ERROR_INVALID_FILE_TYPE":
|
||||||
"Invalid import file type \"{TYPE}\"",
|
"Unsupported file type: \"{TYPE}\"",
|
||||||
"ERROR_INVALID_USER_IDENTIFIERS":
|
"ERROR_INVALID_USER_IDENTIFIERS":
|
||||||
"Users not found: {IDENTIFIER_LIST}",
|
"Users not found: {IDENTIFIER_LIST}",
|
||||||
"ERROR_INVALID_USER_GROUP_IDENTIFIERS":
|
"ERROR_INVALID_USER_GROUP_IDENTIFIERS":
|
||||||
@@ -210,7 +213,14 @@
|
|||||||
"ERROR_REQUIRED_PROTOCOL":
|
"ERROR_REQUIRED_PROTOCOL":
|
||||||
"No connection protocol found in the provided file",
|
"No connection protocol found in the provided file",
|
||||||
"ERROR_REQUIRED_NAME":
|
"ERROR_REQUIRED_NAME":
|
||||||
"No connection name found in the provided file"
|
"No connection name found in the provided file",
|
||||||
|
|
||||||
|
"UPLOAD_FILE_TYPES": "CSV, JSON, or YAML",
|
||||||
|
"UPLOAD_HELP_LINK": "View Format Tips",
|
||||||
|
"UPLOAD_DROP_TITLE": "Drop a File Here",
|
||||||
|
"UPLOAD_BROWSE_LINK": "Browse for File",
|
||||||
|
|
||||||
|
"ERROR_FILE_SINGLE_ONLY": "Please upload only a single file at a time"
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@@ -95,14 +95,14 @@ module.exports = {
|
|||||||
optimization: {
|
optimization: {
|
||||||
minimizer: [
|
minimizer: [
|
||||||
|
|
||||||
// Minify using Google Closure Compiler
|
// // Minify using Google Closure Compiler
|
||||||
new ClosureWebpackPlugin({ mode: 'STANDARD' }, {
|
// new ClosureWebpackPlugin({ mode: 'STANDARD' }, {
|
||||||
languageIn: 'ECMASCRIPT_2020',
|
// languageIn: 'ECMASCRIPT_2020',
|
||||||
languageOut: 'ECMASCRIPT5',
|
// languageOut: 'ECMASCRIPT5',
|
||||||
compilationLevel: 'SIMPLE'
|
// compilationLevel: 'SIMPLE'
|
||||||
}),
|
// }),
|
||||||
|
//
|
||||||
new CssMinimizerPlugin()
|
// new CssMinimizerPlugin()
|
||||||
|
|
||||||
],
|
],
|
||||||
splitChunks: {
|
splitChunks: {
|
||||||
|
Reference in New Issue
Block a user