From 086d9a951679ac221357c556f93786c58b22c906 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 21 Dec 2014 16:30:04 -0800 Subject: [PATCH 01/18] GUAC-932: Fix connection parameter directive and display. --- .../connectionEditModalController.js | 7 + .../manage/directives/connectionParameter.js | 163 +++++++++++++----- .../manage/templates/connectionParameter.html | 13 +- .../manage/templates/editableConnection.html | 2 +- .../app/rest/services/protocolService.js | 10 +- 5 files changed, 138 insertions(+), 57 deletions(-) diff --git a/guacamole/src/main/webapp/app/manage/controllers/connectionEditModalController.js b/guacamole/src/main/webapp/app/manage/controllers/connectionEditModalController.js index fea764da2..fd943217c 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/connectionEditModalController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/connectionEditModalController.js @@ -36,6 +36,7 @@ angular.module('manage').controller('connectionEditModalController', ['$scope', // Copy data into a new conection object in case the user doesn't want to save $scope.connection = new Connection($scope.connection); + $scope.connection.parameters = {}; var newConnection = !$scope.connection.identifier; @@ -47,11 +48,17 @@ angular.module('manage').controller('connectionEditModalController', ['$scope', // Wrap all the history entries if (!newConnection) { + connectionService.getConnectionHistory($scope.connection.identifier).success(function wrapHistoryEntries(historyEntries) { historyEntries.forEach(function wrapHistoryEntry(historyEntry) { $scope.historyEntryWrappers.push(new HistoryEntryWrapper(historyEntry)); }); }); + + connectionService.getConnectionParameters($scope.connection.identifier).success(function setParameters(parameters) { + $scope.connection.parameters = parameters; + }); + } /** diff --git a/guacamole/src/main/webapp/app/manage/directives/connectionParameter.js b/guacamole/src/main/webapp/app/manage/directives/connectionParameter.js index f3f931017..c720ecc17 100644 --- a/guacamole/src/main/webapp/app/manage/directives/connectionParameter.js +++ b/guacamole/src/main/webapp/app/manage/directives/connectionParameter.js @@ -31,55 +31,128 @@ angular.module('manage').directive('guacConnectionParameter', [function connecti restrict: 'E', replace: true, scope: { - parameter: '=parameter', - connection: '=connection', + + /** + * The protocol this parameter is associated with. + * + * @type Protocol + */ + protocol : '=', + + /** + * The unique name of this parameter within the protocol + * definition. + * + * @type String + */ + name : '=', + + /** + * The current map of parameter names to their corresponding string + * values. + * + * @type Object. + */ + parameters : '=' + }, templateUrl: 'app/manage/templates/connectionParameter.html', - controller: ['$scope', function connectionParameterController($scope) { - $scope.connectionParameters = $scope.connection.parameters; - $scope.parameterType = $scope.parameter.type; - $scope.parameterName = $scope.parameter.name; - - // Coerce numeric strings to numbers - if ($scope.parameterType === 'NUMERIC') { - - // If a value exists, coerce it to a number - if ($scope.connectionParameters[$scope.parameterName]) - $scope.parameterValue = Number($scope.connectionParameters[$scope.parameterName]); - else - $scope.parameterValue = null; - - } - - // Coerce boolean strings to boolean values - else if ($scope.parameterType === 'BOOLEAN') { - // TODO: Use defined checked value from protocol description - $scope.parameterValue = $scope.connectionParameters[$scope.parameterName] === 'true'; - } - - // All other parameter types are represented internally as strings - else - $scope.parameterValue = $scope.connectionParameters[$scope.parameterName]; - - // Update internal representation as model is changed - $scope.$watch('parameterValue', function parameterValueChanges(value) { - - // Convert numeric values back into strings - if ($scope.parameterType === 'NUMERIC') { - if (value === null || typeof value === 'undefined') - $scope.connectionParameters[$scope.parameterName] = ''; + controller: ['$scope', '$q', function connectionParameterController($scope, $q) { + + /** + * Deferred load of the parameter definition, pending availability + * of the protocol definition as a whole. + * + * @type Deferred + */ + var parameterDefinitionAvailable = $q.defer(); + + /** + * Populates the parameter definition on the scope as + * $scope.parameter if both the parameter name and + * protocol definition are available. If either are unavailable, + * this function has no effect. + */ + var retrieveParameterDefinition = function retrieveParameterDefinition() { + + // Both name and protocol are needed to retrieve the parameter definition + if (!$scope.name || !$scope.protocol) + return; + + // Once protocol definition is available, locate parameter definition by name + $scope.protocol.parameters.forEach(function findParameter(parameter) { + if (parameter.name === $scope.name) { + $scope.parameter = parameter; + parameterDefinitionAvailable.resolve(parameter); + } + }); + + }; + + // Load parameter definition once protocol definition is available. + $scope.$watch('name', retrieveParameterDefinition); + $scope.$watch('protocol', retrieveParameterDefinition); + + // Update typed value when parameter set is changed + $scope.$watch('parameters', function setParameters(parameters) { + + // Don't bother if no parameters were provided + if (!parameters) + return; + + // Wait for parameter definition + parameterDefinitionAvailable.promise.then(function setTypedValue() { + + // Pull parameter value + var value = parameters[$scope.name]; + + // Coerce numeric strings to numbers + if ($scope.parameter.type === 'NUMERIC') + $scope.typedValue = (value ? Number(value) : null); + + // Coerce boolean strings to boolean values + else if ($scope.parameter.type === 'BOOLEAN') + $scope.typedValue = (value === $scope.parameter.value); + + // All other parameter types are represented internally as strings else - $scope.connectionParameters[$scope.parameterName] = value.toString(); - } - - // TODO: Transform BOOLEAN input fields back into strings based on protocol description - - // All other parameter types are already strings - else - $scope.connectionParameters[$scope.parameterName] = value; - + $scope.typedValue = value || ''; + + }); + }); - }] + + // Update string value in parameter set when typed value is changed + $scope.$watch('typedValue', function typedValueChanged(typedValue) { + + // Don't bother if there's nothing to set + if (!$scope.parameters) + return; + + // Wait for parameter definition + parameterDefinitionAvailable.promise.then(function setValue() { + + // Convert numeric values back into strings + if ($scope.parameter.type === 'NUMERIC') { + if (!typedValue) + $scope.parameters[$scope.name] = ''; + else + $scope.parameters[$scope.name] = typedValue.toString(); + } + + // Convert boolean values back into strings based on protocol description + else if ($scope.parameter.type === 'BOOLEAN') + $scope.parameters[$scope.name] = (typedValue ? $scope.parameter.value : ''); + + // All other parameter types are already strings + else + $scope.parameters[$scope.name] = typedValue || ''; + + }); + + }); // end watch typedValue + + }] // end controller }; }]); \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/manage/templates/connectionParameter.html b/guacamole/src/main/webapp/app/manage/templates/connectionParameter.html index 4249f3a3a..540f7e5fa 100644 --- a/guacamole/src/main/webapp/app/manage/templates/connectionParameter.html +++ b/guacamole/src/main/webapp/app/manage/templates/connectionParameter.html @@ -20,10 +20,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --> - - - - - - + + + + + + + \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/manage/templates/editableConnection.html b/guacamole/src/main/webapp/app/manage/templates/editableConnection.html index a46883c2d..213b33c02 100644 --- a/guacamole/src/main/webapp/app/manage/templates/editableConnection.html +++ b/guacamole/src/main/webapp/app/manage/templates/editableConnection.html @@ -72,7 +72,7 @@ THE SOFTWARE. {{'protocol.' + connection.protocol + '.parameters.' + parameter.name + '.label' | translate}}: - + diff --git a/guacamole/src/main/webapp/app/rest/services/protocolService.js b/guacamole/src/main/webapp/app/rest/services/protocolService.js index 1a863107a..b03acd53b 100644 --- a/guacamole/src/main/webapp/app/rest/services/protocolService.js +++ b/guacamole/src/main/webapp/app/rest/services/protocolService.js @@ -30,12 +30,12 @@ angular.module('rest').factory('protocolService', ['$http', 'authenticationServi /** * Makes a request to the REST API to get the list of protocols, returning - * a promise that provides an array of @link{Protocol} objects if - * successful. + * a promise that provides a map of @link{Protocol} objects by protocol + * name if successful. * - * @returns {Promise.} - * A promise which will resolve with an array of @link{Protocol} - * objects upon success. + * @returns {Promise.>} + * A promise which will resolve with a map of @link{Protocol} + * objects by protocol name upon success. */ service.getProtocols = function getProtocols() { From c3bdbcd013285e3da49b9ef63fd76b198c229af9 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 21 Dec 2014 20:00:38 -0800 Subject: [PATCH 02/18] GUAC-932: Display the cog when parameters are not yet loaded. --- .../manage/controllers/connectionEditModalController.js | 4 +++- .../webapp/app/manage/templates/editableConnection.html | 7 +++++-- guacamole/src/main/webapp/translations/en_US.json | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/guacamole/src/main/webapp/app/manage/controllers/connectionEditModalController.js b/guacamole/src/main/webapp/app/manage/controllers/connectionEditModalController.js index fd943217c..568450257 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/connectionEditModalController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/connectionEditModalController.js @@ -36,7 +36,6 @@ angular.module('manage').controller('connectionEditModalController', ['$scope', // Copy data into a new conection object in case the user doesn't want to save $scope.connection = new Connection($scope.connection); - $scope.connection.parameters = {}; var newConnection = !$scope.connection.identifier; @@ -60,6 +59,9 @@ angular.module('manage').controller('connectionEditModalController', ['$scope', }); } + else { + $scope.connection.parameters = {}; + } /** * Close the modal. diff --git a/guacamole/src/main/webapp/app/manage/templates/editableConnection.html b/guacamole/src/main/webapp/app/manage/templates/editableConnection.html index 213b33c02..06c5e3a50 100644 --- a/guacamole/src/main/webapp/app/manage/templates/editableConnection.html +++ b/guacamole/src/main/webapp/app/manage/templates/editableConnection.html @@ -65,14 +65,17 @@ THE SOFTWARE. -
+ +
{{'manage.edit.connection.parameters' | translate}}
+ +
{{'protocol.' + connection.protocol + '.parameters.' + parameter.name + '.label' | translate}}: - +
diff --git a/guacamole/src/main/webapp/translations/en_US.json b/guacamole/src/main/webapp/translations/en_US.json index 16d282093..2d8a236d3 100644 --- a/guacamole/src/main/webapp/translations/en_US.json +++ b/guacamole/src/main/webapp/translations/en_US.json @@ -46,6 +46,7 @@ "root" : "ROOT", "location" : "Location:", "name" : "Name:", + "parameters" : "Parameters:", "history" : { "connectionNotUsed" : "This connection has not yet been used.", "usageHistory" : "Usage History:", From 60f249aa6fd47b311ca9016ba8029be4b55f5322 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 21 Dec 2014 20:14:11 -0800 Subject: [PATCH 03/18] GUAC-932: Fix display of history. --- .../manage/controllers/connectionEditModalController.js | 4 ++-- .../webapp/app/manage/templates/editableConnection.html | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/guacamole/src/main/webapp/app/manage/controllers/connectionEditModalController.js b/guacamole/src/main/webapp/app/manage/controllers/connectionEditModalController.js index 568450257..5df9da58c 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/connectionEditModalController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/connectionEditModalController.js @@ -43,12 +43,11 @@ angular.module('manage').controller('connectionEditModalController', ['$scope', if(!$scope.connection.protocol) $scope.connection.protocol = "vnc"; - $scope.historyEntryWrappers = []; - // Wrap all the history entries if (!newConnection) { connectionService.getConnectionHistory($scope.connection.identifier).success(function wrapHistoryEntries(historyEntries) { + $scope.historyEntryWrappers = []; historyEntries.forEach(function wrapHistoryEntry(historyEntry) { $scope.historyEntryWrappers.push(new HistoryEntryWrapper(historyEntry)); }); @@ -60,6 +59,7 @@ angular.module('manage').controller('connectionEditModalController', ['$scope', } else { + $scope.historyEntryWrappers = []; $scope.connection.parameters = {}; } diff --git a/guacamole/src/main/webapp/app/manage/templates/editableConnection.html b/guacamole/src/main/webapp/app/manage/templates/editableConnection.html index 06c5e3a50..66bcfd576 100644 --- a/guacamole/src/main/webapp/app/manage/templates/editableConnection.html +++ b/guacamole/src/main/webapp/app/manage/templates/editableConnection.html @@ -84,13 +84,10 @@ THE SOFTWARE.
{{'manage.edit.connection.history.usageHistory' | translate}}
-
-

{{'manage.edit.connection.history.connectionNotUsed' | translate}}

-
- -
- +
+

{{'manage.edit.connection.history.connectionNotUsed' | translate}}

+
From 07a2a2da543838a8f5f7a1490ab64b4521079d28 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 21 Dec 2014 23:11:14 -0800 Subject: [PATCH 04/18] GUAC-932: Move connection editor to own page. --- .../app/index/config/indexRouteConfig.js | 26 ++-- .../main/webapp/app/index/styles/dialog.css | 17 --- ...oller.js => manageConnectionController.js} | 64 ++++++---- .../manage/controllers/manageController.js | 1 - .../app/manage/directives/locationChooser.js | 27 ++-- .../main/webapp/app/manage/styles/buttons.css | 2 +- .../locationChooser.css} | 29 +++-- .../app/manage/styles/manageConnection.css | 38 ++++++ .../app/manage/templates/connection.html | 4 +- .../manage/templates/editableConnection.html | 117 ------------------ .../app/manage/templates/locationChooser.html | 7 +- .../webapp/app/manage/templates/manage.html | 2 +- .../manage/templates/manageConnection.html | 107 ++++++++++++++++ .../src/main/webapp/translations/en_US.json | 5 +- 14 files changed, 246 insertions(+), 200 deletions(-) rename guacamole/src/main/webapp/app/manage/controllers/{connectionEditModalController.js => manageConnectionController.js} (67%) rename guacamole/src/main/webapp/app/manage/{services/connectionEditModal.js => styles/locationChooser.css} (71%) create mode 100644 guacamole/src/main/webapp/app/manage/styles/manageConnection.css delete mode 100644 guacamole/src/main/webapp/app/manage/templates/editableConnection.html create mode 100644 guacamole/src/main/webapp/app/manage/templates/manageConnection.html diff --git a/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js b/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js index db16fc1dc..c980705ea 100644 --- a/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js +++ b/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js @@ -29,31 +29,37 @@ angular.module('index').config(['$routeProvider', '$locationProvider', // Disable HTML5 mode (use # for routing) $locationProvider.html5Mode(false); - $routeProvider. - when('/', { + $routeProvider + .when('/', { title: 'application.title', bodyClassName: 'home', templateUrl: 'app/home/templates/home.html', controller: 'homeController' - }). - when('/manage/', { + }) + .when('/manage/', { title: 'application.title', bodyClassName: 'manage', templateUrl: 'app/manage/templates/manage.html', controller: 'manageController' - }). - when('/login/', { + }) + .when('/manage/connections/:id?', { + title: 'application.title', + bodyClassName: 'manage-connection', + templateUrl: 'app/manage/templates/manageConnection.html', + controller: 'manageConnectionController' + }) + .when('/login/', { title: 'application.title', bodyClassName: 'login', templateUrl: 'app/login/templates/login.html', controller: 'loginController' - }). - when('/client/:type/:id/:params?', { + }) + .when('/client/:type/:id/:params?', { bodyClassName: 'client', templateUrl: 'app/client/templates/client.html', controller: 'clientController' - }). - otherwise({ + }) + .otherwise({ redirectTo: '/' }); }]); diff --git a/guacamole/src/main/webapp/app/index/styles/dialog.css b/guacamole/src/main/webapp/app/index/styles/dialog.css index e514eaf5b..bad31104a 100644 --- a/guacamole/src/main/webapp/app/index/styles/dialog.css +++ b/guacamole/src/main/webapp/app/index/styles/dialog.css @@ -92,23 +92,6 @@ z-index: 1; } -.dialog .dropdown { - - position: absolute; - z-index: 2; - margin-top: -1px; - - width: 3in; - max-height: 2in; - overflow: auto; - - border: 1px solid rgba(0, 0, 0, 0.5); - background: white; - - font-size: 10pt; - -} - .dialog .footer { text-align: center; } diff --git a/guacamole/src/main/webapp/app/manage/controllers/connectionEditModalController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js similarity index 67% rename from guacamole/src/main/webapp/app/manage/controllers/connectionEditModalController.js rename to guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js index 5df9da58c..eca91e8a7 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/connectionEditModalController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js @@ -23,51 +23,65 @@ /** * The controller for the connection edit modal. */ -angular.module('manage').controller('connectionEditModalController', ['$scope', '$injector', - function connectionEditModalController($scope, $injector) { - - var connectionEditModal = $injector.get('connectionEditModal'); +angular.module('manage').controller('manageConnectionController', ['$scope', '$injector', + function manageConnectionController($scope, $injector) { + + var $routeParams = $injector.get('$routeParams'); var connectionService = $injector.get('connectionService'); + var connectionGroupService = $injector.get('connectionGroupService'); + var protocolService = $injector.get('protocolService'); var Connection = $injector.get('Connection'); + var ConnectionGroup = $injector.get('ConnectionGroup'); + var PermissionSet = $injector.get('PermissionSet'); var HistoryEntryWrapper = $injector.get('HistoryEntryWrapper'); - + + var identifier = $routeParams.id; + // Make a copy of the old connection so that we can copy over the changes when done var oldConnection = $scope.connection; - - // Copy data into a new conection object in case the user doesn't want to save - $scope.connection = new Connection($scope.connection); - - var newConnection = !$scope.connection.identifier; - - // Set it to VNC by default - if(!$scope.connection.protocol) - $scope.connection.protocol = "vnc"; - - // Wrap all the history entries - if (!newConnection) { - connectionService.getConnectionHistory($scope.connection.identifier).success(function wrapHistoryEntries(historyEntries) { + connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, PermissionSet.ObjectPermissionType.UPDATE) + .success(function connectionGroupReceived(rootGroup) { + $scope.rootGroup = rootGroup; + $scope.loadingConnections = false; + }); + + // Get the protocol information from the server and copy it into the scope + protocolService.getProtocols().success(function fetchProtocols(protocols) { + $scope.protocols = protocols; + }); + + // Wrap all the history entries + if (identifier) { + + // Copy data into a new conection object in case the user doesn't want to save + connectionService.getConnection(identifier).success(function connectionRetrieved(connection) { + $scope.connection = connection; + }); + + connectionService.getConnectionHistory(identifier).success(function wrapHistoryEntries(historyEntries) { $scope.historyEntryWrappers = []; historyEntries.forEach(function wrapHistoryEntry(historyEntry) { $scope.historyEntryWrappers.push(new HistoryEntryWrapper(historyEntry)); }); }); - connectionService.getConnectionParameters($scope.connection.identifier).success(function setParameters(parameters) { - $scope.connection.parameters = parameters; + connectionService.getConnectionParameters(identifier).success(function setParameters(parameters) { + $scope.parameters = parameters; }); } else { + $scope.connection = new Connection({ protocol: 'vnc' }); $scope.historyEntryWrappers = []; - $scope.connection.parameters = {}; + $scope.parameters = {}; } /** * Close the modal. */ $scope.close = function close() { - connectionEditModal.deactivate(); + //connectionEditModal.deactivate(); }; /** @@ -95,7 +109,7 @@ angular.module('manage').controller('connectionEditModalController', ['$scope', } // Close the modal - connectionEditModal.deactivate(); + //connectionEditModal.deactivate(); }); }; @@ -108,7 +122,7 @@ angular.module('manage').controller('connectionEditModalController', ['$scope', var newConnection = !$scope.connection.identifier; if(newConnection) { // Close the modal - connectionEditModal.deactivate(); + //connectionEditModal.deactivate(); return; } @@ -119,7 +133,7 @@ angular.module('manage').controller('connectionEditModalController', ['$scope', $scope.moveItem($scope.connection, oldParentID); // Close the modal - connectionEditModal.deactivate(); + //connectionEditModal.deactivate(); }); }; diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageController.js b/guacamole/src/main/webapp/app/manage/controllers/manageController.js index 1efcde5d4..7fc5e0a08 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageController.js @@ -32,7 +32,6 @@ angular.module('manage').controller('manageController', ['$scope', '$injector', // Required services var connectionGroupService = $injector.get('connectionGroupService'); - var connectionEditModal = $injector.get('connectionEditModal'); var connectionGroupEditModal = $injector.get('connectionGroupEditModal'); var userEditModal = $injector.get('userEditModal'); var protocolService = $injector.get('protocolService'); diff --git a/guacamole/src/main/webapp/app/manage/directives/locationChooser.js b/guacamole/src/main/webapp/app/manage/directives/locationChooser.js index 9b0961bbf..9f4b912d3 100644 --- a/guacamole/src/main/webapp/app/manage/directives/locationChooser.js +++ b/guacamole/src/main/webapp/app/manage/directives/locationChooser.js @@ -72,13 +72,6 @@ angular.module('manage').directive('locationChooser', [function locationChooser( }; - // Map all known groups - mapConnectionGroups($scope.rootGroup); - - // If no value is specified, default to the root identifier - if (!$scope.value || !($scope.value in connectionGroups)) - $scope.value = $scope.rootGroup.identifier; - /** * Whether the group list menu is currently open. * @@ -92,7 +85,7 @@ angular.module('manage').directive('locationChooser', [function locationChooser( * * @type String */ - $scope.chosenConnectionGroupName = connectionGroups[$scope.value].name; + $scope.chosenConnectionGroupName = null; /** * Toggle the current state of the menu listing connection groups. @@ -125,6 +118,24 @@ angular.module('manage').directive('locationChooser', [function locationChooser( }; + $scope.$watch('rootGroup', function setRootGroup(rootGroup) { + + connectionGroups = {}; + + if (!rootGroup) + return; + + // Map all known groups + mapConnectionGroups(rootGroup); + + // If no value is specified, default to the root identifier + if (!$scope.value || !($scope.value in connectionGroups)) + $scope.value = rootGroup.identifier; + + $scope.chosenConnectionGroupName = connectionGroups[$scope.value].name; + + }); + }] }; diff --git a/guacamole/src/main/webapp/app/manage/styles/buttons.css b/guacamole/src/main/webapp/app/manage/styles/buttons.css index 20f2ce3f0..ca5c5665a 100644 --- a/guacamole/src/main/webapp/app/manage/styles/buttons.css +++ b/guacamole/src/main/webapp/app/manage/styles/buttons.css @@ -31,7 +31,7 @@ button.add-user { } -button.add-connection { +a.button.add-connection { background-image: url('images/action-icons/guac-monitor-add.png'); background-repeat: no-repeat; diff --git a/guacamole/src/main/webapp/app/manage/services/connectionEditModal.js b/guacamole/src/main/webapp/app/manage/styles/locationChooser.css similarity index 71% rename from guacamole/src/main/webapp/app/manage/services/connectionEditModal.js rename to guacamole/src/main/webapp/app/manage/styles/locationChooser.css index cc251798a..836142270 100644 --- a/guacamole/src/main/webapp/app/manage/services/connectionEditModal.js +++ b/guacamole/src/main/webapp/app/manage/styles/locationChooser.css @@ -20,16 +20,19 @@ * THE SOFTWARE. */ -/** - * A modal for editing a connection. - */ -angular.module('manage').factory('connectionEditModal', ['btfModal', - function connectionEditModal(btfModal) { - - // Create the modal object to be used later to actually create the modal - return btfModal({ - controller: 'connectionEditModalController', - controllerAs: 'modal', - templateUrl: 'app/manage/templates/editableConnection.html', - }); -}]); +.location-chooser .dropdown { + + position: absolute; + z-index: 2; + margin-top: -1px; + + width: 3in; + max-height: 2in; + overflow: auto; + + border: 1px solid rgba(0, 0, 0, 0.5); + background: white; + + font-size: 10pt; + +} diff --git a/guacamole/src/main/webapp/app/manage/styles/manageConnection.css b/guacamole/src/main/webapp/app/manage/styles/manageConnection.css new file mode 100644 index 000000000..8df3ede5c --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/styles/manageConnection.css @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +.manage-connection .info table, +.manage-connection .parameters table { + margin: 1em; +} + +.manage-connection .info table th, +.manage-connection .parameters table th { + text-align: left; + font-weight: normal; + padding-right: 1em; +} + +.manage-connection .action-buttons { + text-align: center; + margin-bottom: 1em; +} diff --git a/guacamole/src/main/webapp/app/manage/templates/connection.html b/guacamole/src/main/webapp/app/manage/templates/connection.html index 578c8b654..b92988048 100644 --- a/guacamole/src/main/webapp/app/manage/templates/connection.html +++ b/guacamole/src/main/webapp/app/manage/templates/connection.html @@ -1,4 +1,4 @@ -
{{'manage.edit.connection.history.username' | translate}} {{'manage.edit.connection.history.startTime' | translate}}
- - - - - - - - - - - - - - - - - - - - - -
{{'manage.edit.connection.name' | translate}}
{{'manage.edit.connection.location' | translate}} - -
{{'manage.edit.connection.protocol' | translate}} - -
-
- - -
{{'manage.edit.connection.parameters' | translate}}
- -
- - - - - - - -
{{'protocol.' + connection.protocol + '.parameters.' + parameter.name + '.label' | translate}}: - -
-
- - -
{{'manage.edit.connection.history.usageHistory' | translate}}
- - -
-

{{'manage.edit.connection.history.connectionNotUsed' | translate}}

- - - - - - - - - - - - - -
{{'manage.edit.connection.history.username' | translate}}{{'manage.edit.connection.history.startTime' | translate}}{{'manage.edit.connection.history.duration' | translate}}
{{wrapper.entry.username}}{{wrapper.entry.startDate | date:'short'}}{{wrapper.durationText | translate:"{VALUE: wrapper.duration.value, UNIT: wrapper.duration.unit}"}}
-
- - - - - - - - - \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/manage/templates/locationChooser.html b/guacamole/src/main/webapp/app/manage/templates/locationChooser.html index 8f7b793d5..967e9a36b 100644 --- a/guacamole/src/main/webapp/app/manage/templates/locationChooser.html +++ b/guacamole/src/main/webapp/app/manage/templates/locationChooser.html @@ -1,4 +1,4 @@ -
+
- +
{{chosenConnectionGroupName}}
- + +
diff --git a/guacamole/src/main/webapp/app/manage/templates/manage.html b/guacamole/src/main/webapp/app/manage/templates/manage.html index 9a428e58d..231b02dda 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manage.html +++ b/guacamole/src/main/webapp/app/manage/templates/manage.html @@ -57,7 +57,7 @@ THE SOFTWARE.
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageConnection.html b/guacamole/src/main/webapp/app/manage/templates/manageConnection.html new file mode 100644 index 000000000..80255b148 --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/templates/manageConnection.html @@ -0,0 +1,107 @@ + + + + + +

{{'manage.edit.connection.title' | translate}}

+ + +
+ + + + + + + + + + + + + + + + + + + + + + +
{{'manage.edit.connection.name' | translate}}
{{'manage.edit.connection.location' | translate}} + +
{{'manage.edit.connection.protocol' | translate}} + +
+
+ + +

{{'manage.edit.connection.parameters' | translate}}

+ +
+ + + + + + + +
{{'protocol.' + connection.protocol + '.parameters.' + parameter.name + '.label' | translate}}: + +
+
+ + +
+ + + +
+ + +

{{'manage.edit.connection.history.usageHistory' | translate}}

+ + +
+

{{'manage.edit.connection.history.connectionNotUsed' | translate}}

+ + + + + + + + + + + + + + + +
{{'manage.edit.connection.history.username' | translate}}{{'manage.edit.connection.history.startTime' | translate}}{{'manage.edit.connection.history.duration' | translate}}
{{wrapper.entry.username}}{{wrapper.entry.startDate | date:'short'}}{{wrapper.durationText | translate:"{VALUE: wrapper.duration.value, UNIT: wrapper.duration.unit}"}}
+
diff --git a/guacamole/src/main/webapp/translations/en_US.json b/guacamole/src/main/webapp/translations/en_US.json index 2d8a236d3..9fa497063 100644 --- a/guacamole/src/main/webapp/translations/en_US.json +++ b/guacamole/src/main/webapp/translations/en_US.json @@ -39,6 +39,7 @@ "newGroup" : "New Group", "edit": { "connection": { + "title" : "Edit Connection", "cancel" : "Cancel", "save" : "Save", "delete" : "Delete", @@ -46,10 +47,10 @@ "root" : "ROOT", "location" : "Location:", "name" : "Name:", - "parameters" : "Parameters:", + "parameters" : "Parameters", "history" : { "connectionNotUsed" : "This connection has not yet been used.", - "usageHistory" : "Usage History:", + "usageHistory" : "Usage History", "username" : "Username", "startTime" : "Start Time", "duration" : "Duration", From 8b53797b302512251ecc7eba0e704f22d25ab020 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 22 Dec 2014 03:19:24 -0800 Subject: [PATCH 05/18] GUAC-932: Fully-working connection editor. Initial migration of users and connection groups to own edit pages. Add support for CSS classes on notification actions. --- .../app/index/config/indexRouteConfig.js | 14 +- .../connection.css => index/styles/lists.css} | 8 +- .../controllers/manageConnectionController.js | 198 ++++++++++++------ ....js => manageConnectionGroupController.js} | 48 +++-- .../manage/controllers/manageController.js | 184 +++++----------- ...lController.js => manageUserController.js} | 30 ++- .../main/webapp/app/manage/styles/buttons.css | 2 +- .../{manageConnection.css => forms.css} | 8 +- .../app/manage/templates/connectionGroup.html | 4 +- .../templates/editableConnectionGroup.html | 79 ------- .../app/manage/templates/editableUser.html | 141 ------------- .../webapp/app/manage/templates/manage.html | 10 +- .../manage/templates/manageConnection.html | 10 +- .../templates/manageConnectionGroup.html | 65 ++++++ .../app/manage/templates/manageUser.html | 125 +++++++++++ .../templates/guacNotification.html | 2 +- .../notification/types/NotificationAction.js | 12 +- .../src/main/webapp/translations/en_US.json | 10 + 18 files changed, 477 insertions(+), 473 deletions(-) rename guacamole/src/main/webapp/app/{home/styles/connection.css => index/styles/lists.css} (97%) rename guacamole/src/main/webapp/app/manage/controllers/{connectionGroupEditModalController.js => manageConnectionGroupController.js} (73%) rename guacamole/src/main/webapp/app/manage/controllers/{userEditModalController.js => manageUserController.js} (91%) rename guacamole/src/main/webapp/app/manage/styles/{manageConnection.css => forms.css} (87%) delete mode 100644 guacamole/src/main/webapp/app/manage/templates/editableConnectionGroup.html delete mode 100644 guacamole/src/main/webapp/app/manage/templates/editableUser.html create mode 100644 guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html create mode 100644 guacamole/src/main/webapp/app/manage/templates/manageUser.html diff --git a/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js b/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js index c980705ea..cf2434586 100644 --- a/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js +++ b/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js @@ -44,10 +44,22 @@ angular.module('index').config(['$routeProvider', '$locationProvider', }) .when('/manage/connections/:id?', { title: 'application.title', - bodyClassName: 'manage-connection', + bodyClassName: 'manage', templateUrl: 'app/manage/templates/manageConnection.html', controller: 'manageConnectionController' }) + .when('/manage/connectionGroups/:id?', { + title: 'application.title', + bodyClassName: 'manage', + templateUrl: 'app/manage/templates/manageConnectionGroup.html', + controller: 'manageConnectionGroupController' + }) + .when('/manage/users/:id', { + title: 'application.title', + bodyClassName: 'manage', + templateUrl: 'app/manage/templates/manageUser.html', + controller: 'manageUserController' + }) .when('/login/', { title: 'application.title', bodyClassName: 'login', diff --git a/guacamole/src/main/webapp/app/home/styles/connection.css b/guacamole/src/main/webapp/app/index/styles/lists.css similarity index 97% rename from guacamole/src/main/webapp/app/home/styles/connection.css rename to guacamole/src/main/webapp/app/index/styles/lists.css index 9d659d20f..ad0a1d542 100644 --- a/guacamole/src/main/webapp/app/home/styles/connection.css +++ b/guacamole/src/main/webapp/app/index/styles/lists.css @@ -20,33 +20,33 @@ * THE SOFTWARE. */ +.user, .group, .connection { cursor: pointer; } +.user a, .connection a, .group a { text-decoration:none; color: black; } +.user a:hover, .connection a:hover, .group a:hover { text-decoration:none; color: black; } +.user a:visited, .connection a:visited, .group a:visited { text-decoration:none; color: black; } -.group .connection .bears { - display: none; -} - .connection:hover { background: #CDA; } diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js index eca91e8a7..e6396c631 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js @@ -26,115 +26,181 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$injector', function manageConnectionController($scope, $injector) { - var $routeParams = $injector.get('$routeParams'); - var connectionService = $injector.get('connectionService'); - var connectionGroupService = $injector.get('connectionGroupService'); - var protocolService = $injector.get('protocolService'); - var Connection = $injector.get('Connection'); - var ConnectionGroup = $injector.get('ConnectionGroup'); - var PermissionSet = $injector.get('PermissionSet'); - var HistoryEntryWrapper = $injector.get('HistoryEntryWrapper'); - + // Required types + var Connection = $injector.get('Connection'); + var ConnectionGroup = $injector.get('ConnectionGroup'); + var HistoryEntryWrapper = $injector.get('HistoryEntryWrapper'); + var PermissionSet = $injector.get('PermissionSet'); + + // Required services + var $location = $injector.get('$location'); + var $routeParams = $injector.get('$routeParams'); + var connectionService = $injector.get('connectionService'); + var connectionGroupService = $injector.get('connectionGroupService'); + var protocolService = $injector.get('protocolService'); + + /** + * An action to be provided along with the object sent to showStatus which + * closes the currently-shown status dialog. + */ + var ACKNOWLEDGE_ACTION = { + name : "manage.error.action.acknowledge", + // Handle action + callback : function acknowledgeCallback() { + $scope.showStatus(false); + } + }; + + /** + * The identifier of the connection being edited. If a new connection is + * being created, this will not be defined. + * + * @type String + */ var identifier = $routeParams.id; - // Make a copy of the old connection so that we can copy over the changes when done - var oldConnection = $scope.connection; - + // Pull connection group hierarchy connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, PermissionSet.ObjectPermissionType.UPDATE) .success(function connectionGroupReceived(rootGroup) { $scope.rootGroup = rootGroup; $scope.loadingConnections = false; }); - // Get the protocol information from the server and copy it into the scope - protocolService.getProtocols().success(function fetchProtocols(protocols) { + // Get protocol metadata + protocolService.getProtocols().success(function protocolsReceived(protocols) { $scope.protocols = protocols; }); - // Wrap all the history entries + // If we are editing an existing connection, pull its data if (identifier) { - // Copy data into a new conection object in case the user doesn't want to save + // Pull data from existing connection connectionService.getConnection(identifier).success(function connectionRetrieved(connection) { $scope.connection = connection; }); - - connectionService.getConnectionHistory(identifier).success(function wrapHistoryEntries(historyEntries) { + + // Pull connection history + connectionService.getConnectionHistory(identifier).success(function historyReceived(historyEntries) { + + // Wrap all history entries for sake of display $scope.historyEntryWrappers = []; historyEntries.forEach(function wrapHistoryEntry(historyEntry) { $scope.historyEntryWrappers.push(new HistoryEntryWrapper(historyEntry)); }); + }); - connectionService.getConnectionParameters(identifier).success(function setParameters(parameters) { + // Pull connection parameters + connectionService.getConnectionParameters(identifier).success(function parametersReceived(parameters) { $scope.parameters = parameters; }); } + + // If we are creating a new connection, populate skeleton connection data else { $scope.connection = new Connection({ protocol: 'vnc' }); $scope.historyEntryWrappers = []; $scope.parameters = {}; } - + /** - * Close the modal. + * Cancels all pending edits, returning to the management page. */ - $scope.close = function close() { - //connectionEditModal.deactivate(); + $scope.cancel = function cancel() { + $location.path('/manage/'); }; - + /** - * Save the connection and close the modal. + * Saves the connection, creating a new connection or updating the existing + * connection. */ - $scope.save = function save() { - connectionService.saveConnection($scope.connection).success(function successfullyUpdatedConnection() { - - var oldParentID = oldConnection.parentIdentifier; - var newParentID = $scope.connection.parentIdentifier; - - // Copy the data back to the original model - angular.extend(oldConnection, $scope.connection); - - // We have to move this connection - if(oldParentID !== newParentID) - - // New connections are created by default in root - don't try to move it if it's already there. - if(newConnection && newParentID === $scope.rootGroup.identifier) { - $scope.moveItem($scope.connection, oldParentID, newParentID); - } else { - connectionService.moveConnection($scope.connection).then(function moveConnection() { - $scope.moveItem($scope.connection, oldParentID, newParentID); - }); - } - - // Close the modal - //connectionEditModal.deactivate(); + $scope.saveConnection = function saveConnection() { + + $scope.connection.parameters = $scope.parameters; + + // Save the connection + connectionService.saveConnection($scope.connection) + .success(function savedConnection() { + $location.path('/manage/'); + }) + + // Notify of any errors + .error(function connectionSaveFailed(error) { + $scope.showStatus({ + 'className' : 'error', + 'title' : 'manage.error.title', + 'text' : error.message, + 'actions' : [ ACKNOWLEDGE_ACTION ] + }); }); + + }; /** - * Delete the connection and close the modal. + * An action to be provided along with the object sent to showStatus which + * closes the currently-shown status dialog. */ - $scope['delete'] = function deleteConnection() { - - // Nothing to delete if the connection is new - var newConnection = !$scope.connection.identifier; - if(newConnection) { - // Close the modal - //connectionEditModal.deactivate(); - return; + var DELETE_ACTION = { + name : "manage.edit.connection.delete", + className : "danger", + // Handle action + callback : function deleteCallback() { + deleteConnectionImmediately(); + $scope.showStatus(false); } - - connectionService.deleteConnection($scope.connection).success(function successfullyDeletedConnection() { - var oldParentID = oldConnection.parentIdentifier; - - // We have to remove this connection from the heirarchy - $scope.moveItem($scope.connection, oldParentID); - - // Close the modal - //connectionEditModal.deactivate(); + }; + + /** + * An action to be provided along with the object sent to showStatus which + * closes the currently-shown status dialog. + */ + var CANCEL_ACTION = { + name : "manage.edit.connection.cancel", + // Handle action + callback : function cancelCallback() { + $scope.showStatus(false); + } + }; + + /** + * Immediately deletes the current connection, without prompting the user + * for confirmation. + */ + var deleteConnectionImmediately = function deleteConnectionImmediately() { + + // Delete the connection + connectionService.deleteConnection($scope.connection) + .success(function deletedConnection() { + $location.path('/manage/'); + }) + + // Notify of any errors + .error(function connectionDeletionFailed(error) { + $scope.showStatus({ + 'className' : 'error', + 'title' : 'manage.error.title', + 'text' : error.message, + 'actions' : [ ACKNOWLEDGE_ACTION ] + }); }); + + }; + + /** + * Deletes the connection, prompting the user first to confirm that + * deletion is desired. + */ + $scope.deleteConnection = function deleteConnection() { + + // Confirm deletion request + $scope.showStatus({ + 'title' : 'manage.edit.connection.confirmDelete.title', + 'text' : 'manage.edit.connection.confirmDelete.text', + 'actions' : [ DELETE_ACTION, CANCEL_ACTION] + }); + }; }]); diff --git a/guacamole/src/main/webapp/app/manage/controllers/connectionGroupEditModalController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js similarity index 73% rename from guacamole/src/main/webapp/app/manage/controllers/connectionGroupEditModalController.js rename to guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js index 68ba74f51..87520c03d 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/connectionGroupEditModalController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js @@ -23,35 +23,47 @@ /** * The controller for the connection group edit modal. */ -angular.module('manage').controller('connectionGroupEditModalController', ['$scope', '$injector', - function connectionEditModalController($scope, $injector) { +angular.module('manage').controller('manageConnectionGroupController', ['$scope', '$injector', + function manageConnectionGroupController($scope, $injector) { - var connectionGroupEditModal = $injector.get('connectionGroupEditModal'); - var connectionGroupService = $injector.get('connectionGroupService'); - - // Make a copy of the old connection group so that we can copy over the changes when done - var oldConnectionGroup = $scope.connectionGroup; + // Required types + var ConnectionGroup = $injector.get('ConnectionGroup'); + var PermissionSet = $injector.get('PermissionSet'); + + // Required services + var connectionGroupService = $injector.get('connectionGroupService'); + var $routeParams = $injector.get('$routeParams'); // Copy data into a new conection group object in case the user doesn't want to save - $scope.connectionGroup = angular.copy($scope.connectionGroup); - - var newConnectionGroup = !$scope.connectionGroup.identifier; - + var identifier = $routeParams.id; + + // Pull connection group data + if (identifier) { + connectionGroupService.getConnectionGroup(identifier).success(function connectionGroupReceived(connectionGroup) { + $scope.connectionGroup = connectionGroup; + }); + } + + else + $scope.connectionGroup = new ConnectionGroup(); + + connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, PermissionSet.ObjectPermissionType.UPDATE) + .success(function connectionGroupReceived(rootGroup) { + $scope.rootGroup = rootGroup; + $scope.loadingConnections = false; + }); + $scope.types = [ { label: "organizational", - value: "ORGANIZATIONAL" + value: ConnectionGroup.Type.ORGANIZATIONAL }, { - label: "balancing", - value: "BALANCING" + label : "balancing", + value : ConnectionGroup.Type.BALANCING } ]; - // Set it to organizational by default - if(!$scope.connectionGroup.type) - $scope.connectionGroup.type = $scope.types[0].value; - /** * Close the modal. */ diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageController.js b/guacamole/src/main/webapp/app/manage/controllers/manageController.js index 7fc5e0a08..80af55931 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageController.js @@ -27,146 +27,78 @@ angular.module('manage').controller('manageController', ['$scope', '$injector', function manageController($scope, $injector) { // Required types - var PermissionSet = $injector.get('PermissionSet'); var ConnectionGroup = $injector.get('ConnectionGroup'); + var PermissionSet = $injector.get('PermissionSet'); + var User = $injector.get('User'); // Required services - var connectionGroupService = $injector.get('connectionGroupService'); - var connectionGroupEditModal = $injector.get('connectionGroupEditModal'); - var userEditModal = $injector.get('userEditModal'); - var protocolService = $injector.get('protocolService'); - var userService = $injector.get('userService'); - - // Set status to loading until we have all the connections, groups, and users have loaded - $scope.loadingUsers = true; - $scope.loadingConnections = true; - - $scope.basicPermissionsLoaded.then(function basicPermissionsHaveBeenLoaded() { + var connectionGroupService = $injector.get('connectionGroupService'); + var userService = $injector.get('userService'); - // Retrieve all users for whom we have UPDATE permission - connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, PermissionSet.ObjectPermissionType.UPDATE) - .success(function connectionGroupReceived(rootGroup) { - $scope.rootGroup = rootGroup; - $scope.loadingConnections = false; - }); - - // Retrieve all users for whom we have UPDATE permission - userService.getUsers(PermissionSet.ObjectPermissionType.UPDATE) - .success(function usersReceived(users) { - $scope.users = users; - $scope.loadingUsers = false; - }); - - }); - - $scope.protocols = {}; - - // Get the protocol information from the server and copy it into the scope - protocolService.getProtocols().success(function fetchProtocols(protocols) { - $scope.protocols = protocols; - }); - - // Expose object edit functions to group list template - $scope.groupListContext = { - - /** - * Open a modal to edit the given connection. - * - * @param {Connection} connection - * The connection to edit. - */ - editConnection : function editConnection(connection) { - connectionEditModal.activate({ - connection : connection, - protocols : $scope.protocols, - rootGroup : $scope.rootGroup - }); - }, - - /** - * Open a modal to edit the given connection group. - * - * @param {ConnectionGroup} connectionGroup - * The connection group to edit. - */ - editConnectionGroup : function editConnectionGroup(connectionGroup) { - connectionGroupEditModal.activate({ - connectionGroup : connectionGroup, - rootGroup : $scope.rootGroup - }); + /** + * An action to be provided along with the object sent to showStatus which + * closes the currently-shown status dialog. + */ + var ACKNOWLEDGE_ACTION = { + name : "manage.error.action.acknowledge", + // Handle action + callback : function acknowledgeCallback() { + $scope.showStatus(false); } + }; - }; - /** - * Open a modal to create a new connection. + * The name of the new user to create, if any, when user creation is + * requested via newUser(). + * + * @type String */ - $scope.newConnection = function newConnection() { - connectionEditModal.activate( - { - connection : {}, - protocols : $scope.protocols, - rootGroup : $scope.rootGroup - }); - }; - - /** - * Open a modal to create a new connection group. - */ - $scope.newConnectionGroup = function newConnectionGroup() { - connectionGroupEditModal.activate( - { - connectionGroup : {}, - rootGroup : $scope.rootGroup - }); - }; - - // Remove the user from the current list of users - function removeUser(user) { - for(var i = 0; i < $scope.users.length; i++) { - if($scope.users[i].username === user.username) { - $scope.users.splice(i, 1); - break; - } - } - } - - /** - * Open a modal to edit the user. - * - * @param {object} user The user to edit. - */ - $scope.editUser = function editUser(user) { - userEditModal.activate( - { - user : user, - rootGroup : $scope.rootGroup, - removeUser : removeUser - }); - }; - $scope.newUsername = ""; + // Retrieve all users for whom we have UPDATE permission + connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, PermissionSet.ObjectPermissionType.UPDATE) + .success(function connectionGroupReceived(rootGroup) { + $scope.rootGroup = rootGroup; + }); + + // Retrieve all users for whom we have UPDATE permission + userService.getUsers(PermissionSet.ObjectPermissionType.UPDATE) + .success(function usersReceived(users) { + $scope.users = users; + }); + /** - * Open a modal to edit the user. - * - * @param {object} user The user to edit. + * Creates a new user having the username specified in the user creation + * interface. */ $scope.newUser = function newUser() { - if($scope.newUsername) { - var newUser = { - username: $scope.newUsername - }; - - userService.createUser(newUser).success(function addUserToList() { - $scope.users.push(newUser); + + // Create user skeleton + var user = new User({ + username: $scope.newUsername || '' + }); + + // Create specified user + userService.createUser(user) + + // Add user to visible list upon success + .success(function userCreated() { + $scope.users.push(user); + }) + + // Notify of any errors + .error(function userCreationFailed(error) { + $scope.showStatus({ + 'className' : 'error', + 'title' : 'manage.error.title', + 'text' : error.message, + 'actions' : [ ACKNOWLEDGE_ACTION ] }); - - $scope.newUsername = ""; - } + }); + + // Reset username + $scope.newUsername = ""; + }; }]); - - - diff --git a/guacamole/src/main/webapp/app/manage/controllers/userEditModalController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js similarity index 91% rename from guacamole/src/main/webapp/app/manage/controllers/userEditModalController.js rename to guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index 3d7cadd6c..c62cb38fe 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/userEditModalController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -23,18 +23,20 @@ /** * The controller for the connection edit modal. */ -angular.module('manage').controller('userEditModalController', ['$scope', '$injector', - function userEditModalController($scope, $injector) { +angular.module('manage').controller('manageUserController', ['$scope', '$injector', + function manageUserController($scope, $injector) { - var userEditModal = $injector.get('userEditModal'); + // Required services + var $routeParams = $injector.get('$routeParams'); var userService = $injector.get('userService'); var permissionService = $injector.get('permissionService'); - // Make a copy of the old user so that we can copy over the changes when done - var oldUser = $scope.user; - - // Copy data into a new conection object in case the user doesn't want to save - $scope.user = angular.copy($scope.user); + var identifier = $routeParams.id; + + // Pull user data + userService.getUser(identifier).success(function userReceived(user) { + $scope.user = user; + }); /** * Close the modal. @@ -205,7 +207,7 @@ angular.module('manage').controller('userEditModalController', ['$scope', '$inje originalSystemPermissions; // Get the permissions for the user we are editing - permissionService.getPermissions($scope.user.username).success(function gotPermissions(permissions) { + permissionService.getPermissions(identifier).success(function gotPermissions(permissions) { $scope.permissions = permissions; // Figure out if the user has any system level permissions @@ -247,16 +249,8 @@ angular.module('manage').controller('userEditModalController', ['$scope', '$inje // Close the modal userEditModal.deactivate(); }); - } - - /** - * Toggle the open/closed status of the connectionGroup. - * - * @param {object} connectionGroup The connection group to toggle. - */ - $scope.toggleExpanded = function toggleExpanded(connectionGroup) { - connectionGroup.expanded = !connectionGroup.expanded; }; + }]); diff --git a/guacamole/src/main/webapp/app/manage/styles/buttons.css b/guacamole/src/main/webapp/app/manage/styles/buttons.css index ca5c5665a..0e4925855 100644 --- a/guacamole/src/main/webapp/app/manage/styles/buttons.css +++ b/guacamole/src/main/webapp/app/manage/styles/buttons.css @@ -42,7 +42,7 @@ a.button.add-connection { } -button.add-connection-group { +a.button.add-connection-group { background-image: url('images/action-icons/guac-group-add.png'); background-repeat: no-repeat; diff --git a/guacamole/src/main/webapp/app/manage/styles/manageConnection.css b/guacamole/src/main/webapp/app/manage/styles/forms.css similarity index 87% rename from guacamole/src/main/webapp/app/manage/styles/manageConnection.css rename to guacamole/src/main/webapp/app/manage/styles/forms.css index 8df3ede5c..656d86a20 100644 --- a/guacamole/src/main/webapp/app/manage/styles/manageConnection.css +++ b/guacamole/src/main/webapp/app/manage/styles/forms.css @@ -20,19 +20,17 @@ * THE SOFTWARE. */ -.manage-connection .info table, -.manage-connection .parameters table { +.manage .properties table { margin: 1em; } -.manage-connection .info table th, -.manage-connection .parameters table th { +.manage .properties table th { text-align: left; font-weight: normal; padding-right: 1em; } -.manage-connection .action-buttons { +.manage .action-buttons { text-align: center; margin-bottom: 1em; } diff --git a/guacamole/src/main/webapp/app/manage/templates/connectionGroup.html b/guacamole/src/main/webapp/app/manage/templates/connectionGroup.html index 93acc8148..499c53daa 100644 --- a/guacamole/src/main/webapp/app/manage/templates/connectionGroup.html +++ b/guacamole/src/main/webapp/app/manage/templates/connectionGroup.html @@ -1,4 +1,4 @@ - + {{item.name}} - + diff --git a/guacamole/src/main/webapp/app/manage/templates/editableConnectionGroup.html b/guacamole/src/main/webapp/app/manage/templates/editableConnectionGroup.html deleted file mode 100644 index f10a348db..000000000 --- a/guacamole/src/main/webapp/app/manage/templates/editableConnectionGroup.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - -
-
- - -
-

{{connectionGroup.name}}

-
- - -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - -
{{'manage.edit.connectionGroup.name' | translate}}
{{'manage.edit.connectionGroup.location' | translate}} - -
{{'manage.edit.connectionGroup.type.label' | translate}} - -
-
-
-
-
-
- - - -
-
\ No newline at end of file diff --git a/guacamole/src/main/webapp/app/manage/templates/editableUser.html b/guacamole/src/main/webapp/app/manage/templates/editableUser.html deleted file mode 100644 index c85e13c5e..000000000 --- a/guacamole/src/main/webapp/app/manage/templates/editableUser.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - -
-
-
-

{{user.username}}

-
- -
-
-
-
- - -
{{'manage.edit.user.properties' | translate}}
- -
- - - - - - - - - - - - -
{{'manage.edit.user.password' | translate}}
{{'manage.edit.user.passwordMatch' | translate}}
-
- - -
{{'manage.edit.user.permissions' | translate}}
- -
- - - - - - - - - - - - - - - - - - - - - - - - -
{{'manage.edit.user.administerSystem' | translate}}
{{'manage.edit.user.createUser' | translate}}
{{'manage.edit.user.createConnection' | translate}}
{{'manage.edit.user.createConnectionGroup' | translate}}
-
- - -
{{'manage.edit.user.connections' | translate}}
-
-
-
-
-
-
-
-
-
-
-
- - - -
-
\ No newline at end of file diff --git a/guacamole/src/main/webapp/app/manage/templates/manage.html b/guacamole/src/main/webapp/app/manage/templates/manage.html index 231b02dda..55b6abed5 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manage.html +++ b/guacamole/src/main/webapp/app/manage/templates/manage.html @@ -40,11 +40,11 @@ THE SOFTWARE.
-
-
+
+
- {{user.username}} + {{user.username}}
@@ -58,11 +58,11 @@ THE SOFTWARE. -
+
{{'manage.edit.connection.title' | translate}} -
+
@@ -62,7 +62,7 @@ THE SOFTWARE.

{{'manage.edit.connection.parameters' | translate}}

-
+
@@ -77,9 +77,9 @@ THE SOFTWARE.
- - - + + +
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html b/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html new file mode 100644 index 000000000..336ee2a9f --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html @@ -0,0 +1,65 @@ + + + + + +

{{connectionGroup.name}}

+
+
+ + + + + + + + + + + + + + + + + + + + + +
{{'manage.edit.connectionGroup.name' | translate}}
{{'manage.edit.connectionGroup.location' | translate}} + +
{{'manage.edit.connectionGroup.type.label' | translate}} + +
+
+ + +
+ + + +
\ No newline at end of file diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUser.html b/guacamole/src/main/webapp/app/manage/templates/manageUser.html new file mode 100644 index 000000000..dc14babb2 --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/templates/manageUser.html @@ -0,0 +1,125 @@ + + + + + + + + +

{{user.username}}

+
+ + + + + + + + + + + +
{{'manage.edit.user.password' | translate}}
{{'manage.edit.user.passwordMatch' | translate}}
+
+ + +

{{'manage.edit.user.permissions' | translate}}

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
{{'manage.edit.user.administerSystem' | translate}}
{{'manage.edit.user.createUser' | translate}}
{{'manage.edit.user.createConnection' | translate}}
{{'manage.edit.user.createConnectionGroup' | translate}}
+
+ + +

{{'manage.edit.user.connections' | translate}}

+
+
+
+
+
+
+
+ + +
+ + + +
\ No newline at end of file diff --git a/guacamole/src/main/webapp/app/notification/templates/guacNotification.html b/guacamole/src/main/webapp/app/notification/templates/guacNotification.html index 50d484fb2..a31cff250 100644 --- a/guacamole/src/main/webapp/app/notification/templates/guacNotification.html +++ b/guacamole/src/main/webapp/app/notification/templates/guacNotification.html @@ -41,7 +41,7 @@
- +
diff --git a/guacamole/src/main/webapp/app/notification/types/NotificationAction.js b/guacamole/src/main/webapp/app/notification/types/NotificationAction.js index d0e75cb6d..8a49adc52 100644 --- a/guacamole/src/main/webapp/app/notification/types/NotificationAction.js +++ b/guacamole/src/main/webapp/app/notification/types/NotificationAction.js @@ -35,8 +35,11 @@ angular.module('notification').factory('NotificationAction', [function defineNot * * @param {Function} callback * The callback to call when the user elects to perform this action. + * + * @param {String} className + * The CSS class to associate with this action, if any. */ - var NotificationAction = function NotificationAction(name, callback) { + var NotificationAction = function NotificationAction(name, callback, className) { /** * Reference to this NotificationAction. @@ -45,6 +48,13 @@ angular.module('notification').factory('NotificationAction', [function defineNot */ var action = this; + /** + * The CSS class associated with this action. + * + * @type String + */ + this.className = className; + /** * The name of this action. * diff --git a/guacamole/src/main/webapp/translations/en_US.json b/guacamole/src/main/webapp/translations/en_US.json index 9fa497063..84637ccf2 100644 --- a/guacamole/src/main/webapp/translations/en_US.json +++ b/guacamole/src/main/webapp/translations/en_US.json @@ -43,6 +43,10 @@ "cancel" : "Cancel", "save" : "Save", "delete" : "Delete", + "confirmDelete" : { + "title" : "Delete Connection", + "text" : "Connections cannot be restored after they have been deleted. Are you sure you want to delete this connection?" + }, "protocol" : "Protocol:", "root" : "ROOT", "location" : "Location:", @@ -87,6 +91,12 @@ "createConnectionGroup" : "Create new connection groups:", "connections" : "Connections:" } + }, + "error": { + "title" : "Error", + "action": { + "acknowledge" : "OK" + } } }, "protocol": { From 73df4f4c7d8a0b22c9749548382df8c8a9a94023 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 22 Dec 2014 11:24:10 -0800 Subject: [PATCH 06/18] GUAC-932: Fully-working connection group editor. --- .../controllers/manageConnectionController.js | 9 +- .../manageConnectionGroupController.js | 177 ++++++++++++------ .../controllers/manageUserController.js | 2 +- .../manage/templates/manageConnection.html | 35 ++-- .../templates/manageConnectionGroup.html | 16 +- .../src/main/webapp/translations/en_US.json | 5 + 6 files changed, 152 insertions(+), 92 deletions(-) diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js index e6396c631..22d6c8837 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js @@ -21,7 +21,7 @@ */ /** - * The controller for the connection edit modal. + * The controller for editing or creating connections. */ angular.module('manage').controller('manageConnectionController', ['$scope', '$injector', function manageConnectionController($scope, $injector) { @@ -63,7 +63,6 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, PermissionSet.ObjectPermissionType.UPDATE) .success(function connectionGroupReceived(rootGroup) { $scope.rootGroup = rootGroup; - $scope.loadingConnections = false; }); // Get protocol metadata @@ -135,12 +134,11 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i }); }); - }; /** * An action to be provided along with the object sent to showStatus which - * closes the currently-shown status dialog. + * immediately deletes the current connection. */ var DELETE_ACTION = { name : "manage.edit.connection.delete", @@ -204,6 +202,3 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i }; }]); - - - diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js index 87520c03d..2aa6a2177 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js @@ -21,7 +21,7 @@ */ /** - * The controller for the connection group edit modal. + * The controller for editing or creating connection groups. */ angular.module('manage').controller('manageConnectionGroupController', ['$scope', '$injector', function manageConnectionGroupController($scope, $injector) { @@ -31,28 +31,53 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope' var PermissionSet = $injector.get('PermissionSet'); // Required services - var connectionGroupService = $injector.get('connectionGroupService'); + var $location = $injector.get('$location'); var $routeParams = $injector.get('$routeParams'); + var connectionGroupService = $injector.get('connectionGroupService'); - // Copy data into a new conection group object in case the user doesn't want to save + /** + * An action to be provided along with the object sent to showStatus which + * closes the currently-shown status dialog. + */ + var ACKNOWLEDGE_ACTION = { + name : "manage.error.action.acknowledge", + // Handle action + callback : function acknowledgeCallback() { + $scope.showStatus(false); + } + }; + + /** + * The identifier of the connection group being edited. If a new connection + * group is being created, this will not be defined. + * + * @type String + */ var identifier = $routeParams.id; - // Pull connection group data + // Pull connection group hierarchy + connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, PermissionSet.ObjectPermissionType.UPDATE) + .success(function connectionGroupReceived(rootGroup) { + $scope.rootGroup = rootGroup; + }); + + // If we are editing an existing connection group, pull its data if (identifier) { connectionGroupService.getConnectionGroup(identifier).success(function connectionGroupReceived(connectionGroup) { $scope.connectionGroup = connectionGroup; }); } + // If we are creating a new connection group, populate skeleton connection group data else $scope.connectionGroup = new ConnectionGroup(); - connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, PermissionSet.ObjectPermissionType.UPDATE) - .success(function connectionGroupReceived(rootGroup) { - $scope.rootGroup = rootGroup; - $scope.loadingConnections = false; - }); - + /** + * Available connection group types, as translation string / internal value + * pairs. + * + * @type Object[] + */ $scope.types = [ { label: "organizational", @@ -65,59 +90,99 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope' ]; /** - * Close the modal. + * Cancels all pending edits, returning to the management page. */ - $scope.close = function close() { - connectionGroupEditModal.deactivate(); + $scope.cancel = function cancel() { + $location.path('/manage/'); + }; + + /** + * Saves the connection group, creating a new connection group or updating + * the existing connection group. + */ + $scope.saveConnectionGroup = function saveConnectionGroup() { + + // Save the connection + connectionGroupService.saveConnectionGroup($scope.connectionGroup) + .success(function savedConnectionGroup() { + $location.path('/manage/'); + }) + + // Notify of any errors + .error(function connectionGroupSaveFailed(error) { + $scope.showStatus({ + 'className' : 'error', + 'title' : 'manage.error.title', + 'text' : error.message, + 'actions' : [ ACKNOWLEDGE_ACTION ] + }); + }); + }; /** - * Save the connection and close the modal. + * An action to be provided along with the object sent to showStatus which + * immediately deletes the current connection group. */ - $scope.save = function save() { - connectionGroupService.saveConnectionGroup($scope.connectionGroup).success(function successfullyUpdatedConnectionGroup() { - - var oldParentID = oldConnectionGroup.parentIdentifier; - var newParentID = $scope.connectionGroup.parentIdentifier; - - // Copy the data back to the original model - angular.extend(oldConnectionGroup, $scope.connectionGroup); - - // New groups are created by default in root - don't try to move it if it's already there. - if(newConnectionGroup && newParentID === $scope.rootGroup.identifier) { - $scope.moveItem($scope.connectionGroup, oldParentID, newParentID); - } else { - connectionGroupService.moveConnectionGroup($scope.connectionGroup).then(function moveConnectionGroup() { - $scope.moveItem($scope.connectionGroup, oldParentID, newParentID); - }); - } - - // Close the modal - connectionGroupEditModal.deactivate(); - }); + var DELETE_ACTION = { + name : "manage.edit.connectionGroup.delete", + className : "danger", + // Handle action + callback : function deleteCallback() { + deleteConnectionGroupImmediately(); + $scope.showStatus(false); + } }; - + /** - * Delete the connection and close the modal. + * An action to be provided along with the object sent to showStatus which + * closes the currently-shown status dialog. */ - $scope['delete'] = function deleteConnectionGroup() { - - // Nothing to delete if the connection is new - if(newConnectionGroup) - // Close the modal - connectionGroupEditModal.deactivate(); - - connectionGroupService.deleteConnectionGroup($scope.connectionGroup).success(function successfullyDeletedConnectionGroup() { - var oldParentID = oldConnectionGroup.parentIdentifier; - - // We have to remove this connection group from the heirarchy - $scope.moveItem($scope.connectionGroup, oldParentID); - - // Close the modal - connectionGroupEditModal.deactivate(); + var CANCEL_ACTION = { + name : "manage.edit.connectionGroup.cancel", + // Handle action + callback : function cancelCallback() { + $scope.showStatus(false); + } + }; + + /** + * Immediately deletes the current connection group, without prompting the + * user for confirmation. + */ + var deleteConnectionGroupImmediately = function deleteConnectionGroupImmediately() { + + // Delete the connection group + connectionGroupService.deleteConnectionGroup($scope.connectionGroup) + .success(function deletedConnectionGroup() { + $location.path('/manage/'); + }) + + // Notify of any errors + .error(function connectionGroupDeletionFailed(error) { + $scope.showStatus({ + 'className' : 'error', + 'title' : 'manage.error.title', + 'text' : error.message, + 'actions' : [ ACKNOWLEDGE_ACTION ] + }); }); - } + + }; + + /** + * Deletes the connection group, prompting the user first to confirm that + * deletion is desired. + */ + $scope.deleteConnectionGroup = function deleteConnectionGroup() { + + // Confirm deletion request + $scope.showStatus({ + 'title' : 'manage.edit.connectionGroup.confirmDelete.title', + 'text' : 'manage.edit.connectionGroup.confirmDelete.text', + 'actions' : [ DELETE_ACTION, CANCEL_ACTION] + }); + + }; + }]); - - - diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index c62cb38fe..bf1a6cfdf 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -21,7 +21,7 @@ */ /** - * The controller for the connection edit modal. + * The controller for editing users. */ angular.module('manage').controller('manageUserController', ['$scope', '$injector', function manageUserController($scope, $injector) { diff --git a/guacamole/src/main/webapp/app/manage/templates/manageConnection.html b/guacamole/src/main/webapp/app/manage/templates/manageConnection.html index 4d4ab7de7..aaf67618b 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manageConnection.html +++ b/guacamole/src/main/webapp/app/manage/templates/manageConnection.html @@ -25,10 +25,8 @@ THE SOFTWARE. {{'home.logout' | translate}}
- +

{{'manage.edit.connection.title' | translate}}

- -
@@ -61,31 +59,28 @@ THE SOFTWARE.

{{'manage.edit.connection.parameters' | translate}}

-
-
- - - - - - -
{{'protocol.' + connection.protocol + '.parameters.' + parameter.name + '.label' | translate}}: - -
+ + + + + + + +
{{'protocol.' + connection.protocol + '.parameters.' + parameter.name + '.label' | translate}}: + +
- - - + + +
- +

{{'manage.edit.connection.history.usageHistory' | translate}}

- -

{{'manage.edit.connection.history.connectionNotUsed' | translate}}

diff --git a/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html b/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html index 336ee2a9f..05fd26c5f 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html +++ b/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html @@ -25,8 +25,8 @@ THE SOFTWARE. {{'home.logout' | translate}} - -

{{connectionGroup.name}}

+ +

{{'manage.edit.connectionGroup.title' | translate}}

@@ -42,8 +42,8 @@ THE SOFTWARE. + + @@ -57,9 +57,9 @@ THE SOFTWARE.
{{'manage.edit.connectionGroup.location' | translate}} - -
- +
- - - + + +
\ No newline at end of file diff --git a/guacamole/src/main/webapp/translations/en_US.json b/guacamole/src/main/webapp/translations/en_US.json index 84637ccf2..231000dda 100644 --- a/guacamole/src/main/webapp/translations/en_US.json +++ b/guacamole/src/main/webapp/translations/en_US.json @@ -64,9 +64,14 @@ } }, "connectionGroup": { + "title" : "Edit Connection Group", "cancel" : "Cancel", "save" : "Save", "delete" : "Delete", + "confirmDelete" : { + "title" : "Delete Connection", + "text" : "Connection groups cannot be restored after they have been deleted. Are you sure you want to delete this connection group?" + }, "usageHistory" : "Usage History:", "type" : { "label" : "Type", From fae8f16780081028f61c2b55d71680d7e673df76 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 22 Dec 2014 12:26:48 -0800 Subject: [PATCH 07/18] GUAC-932: Partially-working user editor (no permissions). --- .../controllers/manageUserController.js | 333 +++++++----------- .../app/manage/templates/manageUser.html | 19 +- .../src/main/webapp/translations/en_US.json | 7 + 3 files changed, 143 insertions(+), 216 deletions(-) diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index bf1a6cfdf..3133922fc 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -27,231 +27,146 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto function manageUserController($scope, $injector) { // Required services + var $location = $injector.get('$location'); var $routeParams = $injector.get('$routeParams'); var userService = $injector.get('userService'); var permissionService = $injector.get('permissionService'); - - var identifier = $routeParams.id; + + /** + * An action to be provided along with the object sent to showStatus which + * closes the currently-shown status dialog. + */ + var ACKNOWLEDGE_ACTION = { + name : "manage.error.action.acknowledge", + // Handle action + callback : function acknowledgeCallback() { + $scope.showStatus(false); + } + }; + + /** + * The username of the user being edited. + * + * @type String + */ + var username = $routeParams.id; // Pull user data - userService.getUser(identifier).success(function userReceived(user) { + userService.getUser(username).success(function userReceived(user) { $scope.user = user; }); - - /** - * Close the modal. + + // Pull user permissions + permissionService.getPermissions(username).success(function gotPermissions(permissions) { + $scope.permissions = permissions; + }); + + /** + * Cancels all pending edits, returning to the management page. */ - $scope.close = function close() { - userEditModal.deactivate(); + $scope.cancel = function cancel() { + $location.path('/manage/'); }; - - /* - * All the permissions that have been modified since this modal was opened. - * Maps of type or id to value. - */ - $scope.modifiedSystemPermissions = {}; - $scope.modifiedConnectionPermissions = {}; - $scope.modifiedConnectionGroupPermissions = {}; - - $scope.markSystemPermissionModified = function markSystemPermissionModified(type) { - $scope.modifiedSystemPermissions[type] = $scope.systemPermissions[type]; - }; - - $scope.markConnectionPermissionModified = function markConnectionPermissionModified(id) { - $scope.modifiedConnectionPermissions[id] = $scope.connectionPermissions[id]; - }; - - $scope.markConnectionGroupPermissionModified = function markConnectionGroupPermissionModified(id) { - $scope.modifiedConnectionGroupPermissions[id] = $scope.connectionGroupPermissions[id]; - }; - + /** - * Save the user and close the modal. + * Saves the user, updating the existing user only. */ - $scope.save = function save() { - - if($scope.passwordMatch !== $scope.user.password) { - //TODO: Display an error + $scope.saveUser = function saveUser() { + + // Verify passwords match + if ($scope.passwordMatch !== $scope.user.password) { + $scope.showStatus({ + 'className' : 'error', + 'title' : 'manage.error.title', + 'text' : 'manage.edit.user.passwordMismatch', + 'actions' : [ ACKNOWLEDGE_ACTION ] + }); return; } - - userService.saveUser($scope.user).success(function successfullyUpdatedUser() { - - //Figure out what permissions have changed - var connectionPermissionsToCreate = [], - connectionPermissionsToDelete = [], - connectionGroupPermissionsToCreate = [], - connectionGroupPermissionsToDelete = [], - systemPermissionsToCreate = [], - systemPermissionsToDelete = []; - - for(var type in $scope.modifiedSystemPermissions) { - // It was added - if($scope.modifiedSystemPermissions[type] && !originalSystemPermissions[type]) { - systemPermissionsToCreate.push(type); - } - // It was removed - else if(!$scope.modifiedSystemPermissions[type] && originalSystemPermissions[type]) { - systemPermissionsToDelete.push(type); - } - } - - for(var id in $scope.modifiedConnectionPermissions) { - // It was added - if($scope.modifiedConnectionPermissions[id] && !originalConnectionPermissions[id]) { - connectionPermissionsToCreate.push(id); - } - // It was removed - else if(!$scope.modifiedConnectionPermissions[id] && originalConnectionPermissions[id]) { - connectionPermissionsToDelete.push(id); - } - } - - for(var id in $scope.modifiedConnectionGroupPermissions) { - // It was added - if($scope.modifiedConnectionGroupPermissions[id] && !originalConnectionGroupPermissions[id]) { - connectionGroupPermissionsToCreate.push(id); - } - // It was removed - else if(!$scope.modifiedConnectionGroupPermissions[id] && originalConnectionGroupPermissions[id]) { - connectionGroupPermissionsToDelete.push(id); - } - } - - var permissionsToAdd = []; - var permissionsToRemove = []; - - // Create new connection permissions - for(var i = 0; i < connectionPermissionsToCreate.length; i++) { - permissionsToAdd.push({ - objectType : "CONNECTION", - objectIdentifier : connectionPermissionsToCreate[i], - permissionType : "READ" - }); - } - - // Delete old connection permissions - for(var i = 0; i < connectionPermissionsToDelete.length; i++) { - permissionsToRemove.push({ - objectType : "CONNECTION", - objectIdentifier : connectionPermissionsToDelete[i], - permissionType : "READ" - }); - } - - // Create new connection group permissions - for(var i = 0; i < connectionGroupPermissionsToCreate.length; i++) { - permissionsToAdd.push({ - objectType : "CONNECTION_GROUP", - objectIdentifier : connectionGroupPermissionsToCreate[i], - permissionType : "READ" - }); - } - - // Delete old connection group permissions - for(var i = 0; i < connectionGroupPermissionsToDelete.length; i++) { - permissionsToRemove.push({ - objectType : "CONNECTION_GROUP", - objectIdentifier : connectionGroupPermissionsToDelete[i], - permissionType : "READ" - }); - } - - // Create new system permissions - for(var i = 0; i < systemPermissionsToCreate.length; i++) { - permissionsToAdd.push({ - objectType : "SYSTEM", - permissionType : systemPermissionsToCreate[i] - }); - } - - // Delete old system permissions - for(var i = 0; i < systemPermissionsToDelete.length; i++) { - permissionsToRemove.push({ - objectType : "SYSTEM", - permissionType : systemPermissionsToDelete[i] - }); - } - - function completeSaveProcess() { - // Close the modal - userEditModal.deactivate(); - } - - function handleFailure() { - //TODO: Handle the permission API call failure - } - - if(permissionsToAdd.length || permissionsToRemove.length) { - // Make the call to update the permissions - permissionService.patchPermissions( - $scope.user.username, permissionsToAdd, permissionsToRemove) - .success(completeSaveProcess).error(handleFailure); - } else { - completeSaveProcess(); - } - - }); - }; - - $scope.permissions = []; - // Maps of connection and connection group IDs to access permission booleans - $scope.connectionPermissions = {}; - $scope.connectionGroupPermissions = {}; - $scope.systemPermissions = {}; - - // The original permissions to compare against - var originalConnectionPermissions, - originalConnectionGroupPermissions, - originalSystemPermissions; - - // Get the permissions for the user we are editing - permissionService.getPermissions(identifier).success(function gotPermissions(permissions) { - $scope.permissions = permissions; - - // Figure out if the user has any system level permissions - for(var i = 0; i < $scope.permissions.length; i++) { - var permission = $scope.permissions[i]; - if(permission.objectType === "SYSTEM") { - - $scope.systemPermissions[permission.permissionType] = true; - - // Only READ permission is editable via this UI - } else if (permission.permissionType === "READ") { - switch(permission.objectType) { - case "CONNECTION": - $scope.connectionPermissions[permission.objectIdentifier] = true; - break; - case "CONNECTION_GROUP": - $scope.connectionGroupPermissions[permission.objectIdentifier] = true; - break; - } - } - } - - // Copy the original permissions so we can compare later - originalConnectionPermissions = angular.copy($scope.connectionPermissions); - originalConnectionGroupPermissions = angular.copy($scope.connectionGroupPermissions); - originalSystemPermissions = angular.copy($scope.systemPermissions); - - }); + // Save the user + userService.saveUser($scope.user) + .success(function savedUser() { + $location.path('/manage/'); + + // TODO: Save permissions + }) + + // Notify of any errors + .error(function userSaveFailed(error) { + $scope.showStatus({ + 'className' : 'error', + 'title' : 'manage.error.title', + 'text' : error.message, + 'actions' : [ ACKNOWLEDGE_ACTION ] + }); + }); + + }; /** - * Delete the user and close the modal. + * An action to be provided along with the object sent to showStatus which + * immediately deletes the current user. */ - $scope['delete'] = function deleteUser() { - userService.deleteUser($scope.user).success(function successfullyDeletedUser() { - - // Remove the user from the list - $scope.removeUser($scope.user); - - // Close the modal - userEditModal.deactivate(); - }); + var DELETE_ACTION = { + name : "manage.edit.user.delete", + className : "danger", + // Handle action + callback : function deleteCallback() { + deleteUserImmediately(); + $scope.showStatus(false); + } }; - + + /** + * An action to be provided along with the object sent to showStatus which + * closes the currently-shown status dialog. + */ + var CANCEL_ACTION = { + name : "manage.edit.user.cancel", + // Handle action + callback : function cancelCallback() { + $scope.showStatus(false); + } + }; + + /** + * Immediately deletes the current user, without prompting the user for + * confirmation. + */ + var deleteUserImmediately = function deleteUserImmediately() { + + // Delete the user + userService.deleteUser($scope.user) + .success(function deletedUser() { + $location.path('/manage/'); + }) + + // Notify of any errors + .error(function userDeletionFailed(error) { + $scope.showStatus({ + 'className' : 'error', + 'title' : 'manage.error.title', + 'text' : error.message, + 'actions' : [ ACKNOWLEDGE_ACTION ] + }); + }); + + }; + + /** + * Deletes the user, prompting the user first to confirm that deletion is + * desired. + */ + $scope.deleteUser = function deleteUser() { + + // Confirm deletion request + $scope.showStatus({ + 'title' : 'manage.edit.user.confirmDelete.title', + 'text' : 'manage.edit.user.confirmDelete.text', + 'actions' : [ DELETE_ACTION, CANCEL_ACTION] + }); + + }; + }]); - - - diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUser.html b/guacamole/src/main/webapp/app/manage/templates/manageUser.html index dc14babb2..ac97a7c54 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manageUser.html +++ b/guacamole/src/main/webapp/app/manage/templates/manageUser.html @@ -60,10 +60,15 @@ THE SOFTWARE. {{'home.logout' | translate}}
- -

{{user.username}}

+ +

{{'manage.edit.user.title' | translate}}

+ + + + + @@ -117,9 +122,9 @@ THE SOFTWARE. - +
- - - -
\ No newline at end of file + + + + diff --git a/guacamole/src/main/webapp/translations/en_US.json b/guacamole/src/main/webapp/translations/en_US.json index 231000dda..67a1c7cb9 100644 --- a/guacamole/src/main/webapp/translations/en_US.json +++ b/guacamole/src/main/webapp/translations/en_US.json @@ -83,13 +83,20 @@ "name" : "Name:" }, "user": { + "title" : "Edit User", "cancel" : "Cancel", "save" : "Save", "delete" : "Delete", + "confirmDelete" : { + "title" : "Delete User", + "text" : "Users cannot be restored after they have been deleted. Are you sure you want to delete this user?" + }, "properties" : "Properties:", "password" : "Password:", "passwordMatch" : "Re-enter Password:", + "passwordMismatch" : "The provided passwords do not match.", "permissions" : "Permissions:", + "username" : "Username:", "administerSystem" : "Administer system:", "createUser" : "Create new users:", "createConnection" : "Create new connections:", From b8e335e3c79c8c1917457b7b4ac8e274cb368a0a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 22 Dec 2014 16:40:06 -0800 Subject: [PATCH 08/18] GUAC-932: Add permissions list. --- .../groupList/templates/guacGroupList.html | 4 +- .../main/webapp/app/index/styles/lists.css | 4 ++ .../src/main/webapp/app/index/styles/ui.css | 4 +- .../manage/controllers/manageController.js | 2 +- .../controllers/manageUserController.js | 21 +++++-- .../services/connectionGroupEditModal.js | 35 ------------ .../app/manage/services/userEditModal.js | 35 ------------ .../main/webapp/app/manage/styles/forms.css | 6 +- .../app/manage/templates/connection.html | 17 +++--- .../app/manage/templates/connectionGroup.html | 2 +- .../templates/connectionGroupPermission.html | 26 +++++++++ .../templates/connectionPermission.html | 35 ++++++++++++ .../manage/templates/manageConnection.html | 10 ++-- .../templates/manageConnectionGroup.html | 4 +- .../app/manage/templates/manageUser.html | 55 ++++--------------- .../src/main/webapp/translations/en_US.json | 5 +- 16 files changed, 115 insertions(+), 150 deletions(-) delete mode 100644 guacamole/src/main/webapp/app/manage/services/connectionGroupEditModal.js delete mode 100644 guacamole/src/main/webapp/app/manage/services/userEditModal.js create mode 100644 guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html create mode 100644 guacamole/src/main/webapp/app/manage/templates/connectionPermission.html diff --git a/guacamole/src/main/webapp/app/groupList/templates/guacGroupList.html b/guacamole/src/main/webapp/app/groupList/templates/guacGroupList.html index 942cacb79..21c040318 100644 --- a/guacamole/src/main/webapp/app/groupList/templates/guacGroupList.html +++ b/guacamole/src/main/webapp/app/groupList/templates/guacGroupList.html @@ -25,7 +25,9 @@
- +
+ +
diff --git a/guacamole/src/main/webapp/app/index/styles/lists.css b/guacamole/src/main/webapp/app/index/styles/lists.css index ad0a1d542..4f462f41b 100644 --- a/guacamole/src/main/webapp/app/index/styles/lists.css +++ b/guacamole/src/main/webapp/app/index/styles/lists.css @@ -73,6 +73,10 @@ div.recent-connections .protocol { vertical-align: middle; } +.caption > * { + display: inline-block; +} + .caption .name { margin-left: 0.25em; } diff --git a/guacamole/src/main/webapp/app/index/styles/ui.css b/guacamole/src/main/webapp/app/index/styles/ui.css index 433b38178..d2d928f54 100644 --- a/guacamole/src/main/webapp/app/index/styles/ui.css +++ b/guacamole/src/main/webapp/app/index/styles/ui.css @@ -72,8 +72,8 @@ h2 ~ h2 { } div.section { - margin: 0; - padding: 1em; + margin: 1em; + padding: 0; } /* diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageController.js b/guacamole/src/main/webapp/app/manage/controllers/manageController.js index 80af55931..d83073cd3 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageController.js @@ -55,7 +55,7 @@ angular.module('manage').controller('manageController', ['$scope', '$injector', */ $scope.newUsername = ""; - // Retrieve all users for whom we have UPDATE permission + // Retrieve all connections for which we have UPDATE permission connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, PermissionSet.ObjectPermissionType.UPDATE) .success(function connectionGroupReceived(rootGroup) { $scope.rootGroup = rootGroup; diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index 3133922fc..672dec7bf 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -26,11 +26,16 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injector', function manageUserController($scope, $injector) { + // Required types + var ConnectionGroup = $injector.get('ConnectionGroup'); + var PermissionSet = $injector.get('PermissionSet'); + // Required services - var $location = $injector.get('$location'); - var $routeParams = $injector.get('$routeParams'); - var userService = $injector.get('userService'); - var permissionService = $injector.get('permissionService'); + var $location = $injector.get('$location'); + var $routeParams = $injector.get('$routeParams'); + var connectionGroupService = $injector.get('connectionGroupService'); + var userService = $injector.get('userService'); + var permissionService = $injector.get('permissionService'); /** * An action to be provided along with the object sent to showStatus which @@ -61,7 +66,13 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto $scope.permissions = permissions; }); - /** + // Retrieve all connections for which we have UPDATE permission + connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, PermissionSet.ObjectPermissionType.ADMINISTER) + .success(function connectionGroupReceived(rootGroup) { + $scope.rootGroup = rootGroup; + }); + + /** * Cancels all pending edits, returning to the management page. */ $scope.cancel = function cancel() { diff --git a/guacamole/src/main/webapp/app/manage/services/connectionGroupEditModal.js b/guacamole/src/main/webapp/app/manage/services/connectionGroupEditModal.js deleted file mode 100644 index 70f57903a..000000000 --- a/guacamole/src/main/webapp/app/manage/services/connectionGroupEditModal.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * A modal for editing a connection group. - */ -angular.module('manage').factory('connectionGroupEditModal', ['btfModal', - function connectionGroupEditModal(btfModal) { - - // Create the modal object to be used later to actually create the modal - return btfModal({ - controller: 'connectionGroupEditModalController', - controllerAs: 'modal', - templateUrl: 'app/manage/templates/editableConnectionGroup.html', - }); -}]); diff --git a/guacamole/src/main/webapp/app/manage/services/userEditModal.js b/guacamole/src/main/webapp/app/manage/services/userEditModal.js deleted file mode 100644 index 5dd04d8ad..000000000 --- a/guacamole/src/main/webapp/app/manage/services/userEditModal.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * A modal for editing a connection. - */ -angular.module('manage').factory('userEditModal', ['btfModal', - function userEditModal(btfModal) { - - // Create the modal object to be used later to actually create the modal - return btfModal({ - controller: 'userEditModalController', - controllerAs: 'modal', - templateUrl: 'app/manage/templates/editableUser.html', - }); -}]); diff --git a/guacamole/src/main/webapp/app/manage/styles/forms.css b/guacamole/src/main/webapp/app/manage/styles/forms.css index 656d86a20..0724562e2 100644 --- a/guacamole/src/main/webapp/app/manage/styles/forms.css +++ b/guacamole/src/main/webapp/app/manage/styles/forms.css @@ -20,11 +20,7 @@ * THE SOFTWARE. */ -.manage .properties table { - margin: 1em; -} - -.manage .properties table th { +.manage table.properties th { text-align: left; font-weight: normal; padding-right: 1em; diff --git a/guacamole/src/main/webapp/app/manage/templates/connection.html b/guacamole/src/main/webapp/app/manage/templates/connection.html index b92988048..b2d1c770b 100644 --- a/guacamole/src/main/webapp/app/manage/templates/connection.html +++ b/guacamole/src/main/webapp/app/manage/templates/connection.html @@ -21,15 +21,12 @@ THE SOFTWARE. --> -
- - -
-
-
- - - {{item.name}} - + +
+
+ + + {{item.name}} + diff --git a/guacamole/src/main/webapp/app/manage/templates/connectionGroup.html b/guacamole/src/main/webapp/app/manage/templates/connectionGroup.html index 499c53daa..50408c1eb 100644 --- a/guacamole/src/main/webapp/app/manage/templates/connectionGroup.html +++ b/guacamole/src/main/webapp/app/manage/templates/connectionGroup.html @@ -21,5 +21,5 @@ THE SOFTWARE. --> - {{item.name}} + {{item.name}} diff --git a/guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html b/guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html new file mode 100644 index 000000000..6c03b152a --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html @@ -0,0 +1,26 @@ +
+ + + + {{item.name}} +
diff --git a/guacamole/src/main/webapp/app/manage/templates/connectionPermission.html b/guacamole/src/main/webapp/app/manage/templates/connectionPermission.html new file mode 100644 index 000000000..1920ffe3f --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/templates/connectionPermission.html @@ -0,0 +1,35 @@ +
+ + + +
+
+
+ + + + + + {{item.name}} + +
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageConnection.html b/guacamole/src/main/webapp/app/manage/templates/manageConnection.html index aaf67618b..e204f501c 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manageConnection.html +++ b/guacamole/src/main/webapp/app/manage/templates/manageConnection.html @@ -27,8 +27,8 @@ THE SOFTWARE.

{{'manage.edit.connection.title' | translate}}

-
-
{{'manage.edit.user.username' | translate}}{{user.username}}
{{'manage.edit.user.password' | translate}}
+
+
@@ -59,8 +59,8 @@ THE SOFTWARE.

{{'manage.edit.connection.parameters' | translate}}

-
-
+
+
@@ -81,7 +81,7 @@ THE SOFTWARE.

{{'manage.edit.connection.history.usageHistory' | translate}}

-
+

{{'manage.edit.connection.history.connectionNotUsed' | translate}}

diff --git a/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html b/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html index 05fd26c5f..56a6ddc96 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html +++ b/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html @@ -27,8 +27,8 @@ THE SOFTWARE.

{{'manage.edit.connectionGroup.title' | translate}}

-
-
+
+
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUser.html b/guacamole/src/main/webapp/app/manage/templates/manageUser.html index ac97a7c54..01cffb88c 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manageUser.html +++ b/guacamole/src/main/webapp/app/manage/templates/manageUser.html @@ -20,41 +20,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --> - - -
{{'manage.back' | translate}} {{'home.logout' | translate}} @@ -62,8 +27,8 @@ THE SOFTWARE.

{{'manage.edit.user.title' | translate}}

-
-
+
+
@@ -84,8 +49,8 @@ THE SOFTWARE.

{{'manage.edit.user.permissions' | translate}}

-
-
{{'manage.edit.user.username' | translate}}
+
+
@@ -114,12 +79,12 @@ THE SOFTWARE.

{{'manage.edit.user.connections' | translate}}

-
-
-
-
-
-
+
+
diff --git a/guacamole/src/main/webapp/translations/en_US.json b/guacamole/src/main/webapp/translations/en_US.json index 67a1c7cb9..de2127c9a 100644 --- a/guacamole/src/main/webapp/translations/en_US.json +++ b/guacamole/src/main/webapp/translations/en_US.json @@ -91,17 +91,16 @@ "title" : "Delete User", "text" : "Users cannot be restored after they have been deleted. Are you sure you want to delete this user?" }, - "properties" : "Properties:", "password" : "Password:", "passwordMatch" : "Re-enter Password:", "passwordMismatch" : "The provided passwords do not match.", - "permissions" : "Permissions:", + "permissions" : "Permissions", "username" : "Username:", "administerSystem" : "Administer system:", "createUser" : "Create new users:", "createConnection" : "Create new connections:", "createConnectionGroup" : "Create new connection groups:", - "connections" : "Connections:" + "connections" : "Connections" } }, "error": { From d4153470e743d9b224dd131271590d45686c15e2 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 22 Dec 2014 22:37:44 -0800 Subject: [PATCH 09/18] GUAC-932: Display current connection/group permissions in user edit interface. --- .../controllers/manageUserController.js | 51 +++++++++++++++++++ .../templates/connectionGroupPermission.html | 2 +- .../templates/connectionPermission.html | 2 +- .../webapp/app/manage/templates/manage.html | 1 - 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index 672dec7bf..b6ce22b43 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -72,6 +72,57 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto $scope.rootGroup = rootGroup; }); + // Expose permission query and modification functions to group list template + $scope.groupListContext = { + + /** + * Determines whether read permission for the connection having the + * given identifier is granted for the user being edited. + * + * @param {String} identifier + * The identifier of the connection to check. + * + * @returns {Boolean} + * true if the user has read permission for the given connection, + * false if the user lacks read permission, or the permissions have + * not yet been loaded. + */ + canReadConnection : function canReadConnection(identifier) { + + // Assume no permission if permissions not available yet + if (!$scope.permissions) + return false; + + // Return whether READ permission is present + return PermissionSet.hasConnectionPermission($scope.permissions, PermissionSet.ObjectPermissionType.READ, identifier); + + }, + + /** + * Determines whether read permission for the connection group having + * the given identifier is granted for the user being edited. + * + * @param {String} identifier + * The identifier of the connection group to check. + * + * @returns {Boolean} + * true if the user has read permission for the given connection + * group, false if the user lacks read permission, or the + * permissions have not yet been loaded. + */ + canReadConnectionGroup : function canReadConnectionGroup(identifier) { + + // Assume no permission if permissions not available yet + if (!$scope.permissions) + return false; + + // Return whether READ permission is present + return PermissionSet.hasConnectionGroupPermission($scope.permissions, PermissionSet.ObjectPermissionType.READ, identifier); + + } + + }; + /** * Cancels all pending edits, returning to the management page. */ diff --git a/guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html b/guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html index 6c03b152a..0074b513d 100644 --- a/guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html +++ b/guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html @@ -21,6 +21,6 @@ THE SOFTWARE. --> - + {{item.name}}
diff --git a/guacamole/src/main/webapp/app/manage/templates/connectionPermission.html b/guacamole/src/main/webapp/app/manage/templates/connectionPermission.html index 1920ffe3f..c33f6018e 100644 --- a/guacamole/src/main/webapp/app/manage/templates/connectionPermission.html +++ b/guacamole/src/main/webapp/app/manage/templates/connectionPermission.html @@ -27,7 +27,7 @@ - + {{item.name}} diff --git a/guacamole/src/main/webapp/app/manage/templates/manage.html b/guacamole/src/main/webapp/app/manage/templates/manage.html index 55b6abed5..3a45f3f24 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manage.html +++ b/guacamole/src/main/webapp/app/manage/templates/manage.html @@ -64,7 +64,6 @@ THE SOFTWARE.
From c26d5a77ab2cc9843ae854d4c43c9dd2f23123a2 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 22 Dec 2014 22:51:43 -0800 Subject: [PATCH 10/18] GUAC-932: Display current system permissions in user edit interface. --- .../controllers/manageUserController.js | 48 +++++++++++++++++++ .../app/manage/templates/manageUser.html | 25 ++-------- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index b6ce22b43..25d38d3ef 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -72,6 +72,31 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto $scope.rootGroup = rootGroup; }); + /** + * Available system permission types, as translation string / internal + * value pairs. + * + * @type Object[] + */ + $scope.systemPermissionTypes = [ + { + label: "manage.edit.user.administerSystem", + value: PermissionSet.SystemPermissionType.ADMINISTER + }, + { + label: "manage.edit.user.createUser", + value: PermissionSet.SystemPermissionType.CREATE_USER + }, + { + label: "manage.edit.user.createConnection", + value: PermissionSet.SystemPermissionType.CREATE_CONNECTION + }, + { + label: "manage.edit.user.createConnectionGroup", + value: PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP + } + ]; + // Expose permission query and modification functions to group list template $scope.groupListContext = { @@ -123,6 +148,29 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto }; + /** + * Determines whether the given system permission is granted for the + * user being edited. + * + * @param {String} type + * The type string of the system permission to check. + * + * @returns {Boolean} + * true if the user has the given system permission, false if the + * user lacks the given system permission, or the permissions have + * not yet been loaded. + */ + $scope.hasSystemPermission = function hasSystemPermission(type) { + + // Assume no permission if permissions not available yet + if (!$scope.permissions) + return false; + + // Return whether given permission is present + return PermissionSet.hasSystemPermission($scope.permissions, type); + + }; + /** * Cancels all pending edits, returning to the management page. */ diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUser.html b/guacamole/src/main/webapp/app/manage/templates/manageUser.html index 01cffb88c..0b02695ca 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manageUser.html +++ b/guacamole/src/main/webapp/app/manage/templates/manageUser.html @@ -51,28 +51,9 @@ THE SOFTWARE.

{{'manage.edit.user.permissions' | translate}}

{{'manage.edit.user.administerSystem' | translate}}
- - - - - - - - - - - - - - - - - - - - - - + + +
{{'manage.edit.user.administerSystem' | translate}}
{{'manage.edit.user.createUser' | translate}}
{{'manage.edit.user.createConnection' | translate}}
{{'manage.edit.user.createConnectionGroup' | translate}}
{{systemPermissionType.label | translate}}
From f564e26fd14690005ef486f4ded0e8fdafc04c43 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 23 Dec 2014 01:40:23 -0800 Subject: [PATCH 11/18] GUAC-932: Provide flag-based view for PermissionSets. Use ngModel instead of ngChecked for permission checkboxes in user edit UI. --- .../controllers/manageUserController.js | 80 ++------- .../templates/connectionGroupPermission.html | 2 +- .../templates/connectionPermission.html | 2 +- .../app/manage/templates/manageUser.html | 2 +- .../app/rest/types/PermissionFlagSet.js | 155 ++++++++++++++++++ 5 files changed, 170 insertions(+), 71 deletions(-) create mode 100644 guacamole/src/main/webapp/app/rest/types/PermissionFlagSet.js diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index 25d38d3ef..faf35044c 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -27,8 +27,9 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto function manageUserController($scope, $injector) { // Required types - var ConnectionGroup = $injector.get('ConnectionGroup'); - var PermissionSet = $injector.get('PermissionSet'); + var ConnectionGroup = $injector.get('ConnectionGroup'); + var PermissionFlagSet = $injector.get('PermissionFlagSet'); + var PermissionSet = $injector.get('PermissionSet'); // Required services var $location = $injector.get('$location'); @@ -63,7 +64,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto // Pull user permissions permissionService.getPermissions(username).success(function gotPermissions(permissions) { - $scope.permissions = permissions; + $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(permissions); }); // Retrieve all connections for which we have UPDATE permission @@ -101,76 +102,19 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto $scope.groupListContext = { /** - * Determines whether read permission for the connection having the - * given identifier is granted for the user being edited. - * - * @param {String} identifier - * The identifier of the connection to check. - * - * @returns {Boolean} - * true if the user has read permission for the given connection, - * false if the user lacks read permission, or the permissions have - * not yet been loaded. + * Returns the PermissionFlagSet that contains the current state of + * granted permissions. + * + * @returns {PermissionFlagSet} + * The PermissionFlagSet describing the current state of granted + * permissions for the user being edited. */ - canReadConnection : function canReadConnection(identifier) { - - // Assume no permission if permissions not available yet - if (!$scope.permissions) - return false; - - // Return whether READ permission is present - return PermissionSet.hasConnectionPermission($scope.permissions, PermissionSet.ObjectPermissionType.READ, identifier); - - }, - - /** - * Determines whether read permission for the connection group having - * the given identifier is granted for the user being edited. - * - * @param {String} identifier - * The identifier of the connection group to check. - * - * @returns {Boolean} - * true if the user has read permission for the given connection - * group, false if the user lacks read permission, or the - * permissions have not yet been loaded. - */ - canReadConnectionGroup : function canReadConnectionGroup(identifier) { - - // Assume no permission if permissions not available yet - if (!$scope.permissions) - return false; - - // Return whether READ permission is present - return PermissionSet.hasConnectionGroupPermission($scope.permissions, PermissionSet.ObjectPermissionType.READ, identifier); - + getPermissionFlags : function getPermissionFlags() { + return $scope.permissionFlags; } }; - /** - * Determines whether the given system permission is granted for the - * user being edited. - * - * @param {String} type - * The type string of the system permission to check. - * - * @returns {Boolean} - * true if the user has the given system permission, false if the - * user lacks the given system permission, or the permissions have - * not yet been loaded. - */ - $scope.hasSystemPermission = function hasSystemPermission(type) { - - // Assume no permission if permissions not available yet - if (!$scope.permissions) - return false; - - // Return whether given permission is present - return PermissionSet.hasSystemPermission($scope.permissions, type); - - }; - /** * Cancels all pending edits, returning to the management page. */ diff --git a/guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html b/guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html index 0074b513d..1ca169386 100644 --- a/guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html +++ b/guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html @@ -21,6 +21,6 @@ THE SOFTWARE. --> - + {{item.name}}
diff --git a/guacamole/src/main/webapp/app/manage/templates/connectionPermission.html b/guacamole/src/main/webapp/app/manage/templates/connectionPermission.html index c33f6018e..295bde763 100644 --- a/guacamole/src/main/webapp/app/manage/templates/connectionPermission.html +++ b/guacamole/src/main/webapp/app/manage/templates/connectionPermission.html @@ -27,7 +27,7 @@
- + {{item.name}} diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUser.html b/guacamole/src/main/webapp/app/manage/templates/manageUser.html index 0b02695ca..7a48014ab 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manageUser.html +++ b/guacamole/src/main/webapp/app/manage/templates/manageUser.html @@ -53,7 +53,7 @@ THE SOFTWARE. - +
{{systemPermissionType.label | translate}}
diff --git a/guacamole/src/main/webapp/app/rest/types/PermissionFlagSet.js b/guacamole/src/main/webapp/app/rest/types/PermissionFlagSet.js new file mode 100644 index 000000000..197752379 --- /dev/null +++ b/guacamole/src/main/webapp/app/rest/types/PermissionFlagSet.js @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * A service for defining the PermissionFlagSet class. + */ +angular.module('rest').factory('PermissionFlagSet', ['PermissionSet', + function definePermissionFlagSet(PermissionSet) { + + /** + * Alternative view of a @link{PermissionSet} which allows manipulation of + * each permission through the setting (or retrieval) of boolean property + * values. + * + * @constructor + * @param {PermissionFlagSet|Object} template + * The object whose properties should be copied within the new + * PermissionFlagSet. + */ + var PermissionFlagSet = function PermissionFlagSet(template) { + + // Use empty object by default + template = template || {}; + + /** + * The granted state of each system permission, as a map of system + * permission type string to boolean value. A particular permission is + * granted if its corresponding boolean value is set to true. Valid + * permission type strings are defined within + * PermissionSet.SystemPermissionType. Permissions which are not + * granted may be set to false, but this is not required. + * + * @type Object. + */ + this.systemPermissions = template.systemPermissions || {}; + + /** + * The granted state of each permission for each connection, as a map + * of object permission type string to permission map. The permission + * map is, in turn, a map of connection identifier to boolean value. A + * particular permission is granted if its corresponding boolean value + * is set to true. Valid permission type strings are defined within + * PermissionSet.ObjectPermissionType. Permissions which are not + * granted may be set to false, but this is not required. + * + * @type Object.> + */ + this.connectionPermissions = template.connectionPermissions || {}; + + /** + * The granted state of each permission for each connection group, as a + * map of object permission type string to permission map. The + * permission map is, in turn, a map of connection group identifier to + * boolean value. A particular permission is granted if its + * corresponding boolean value is set to true. Valid permission type + * strings are defined within PermissionSet.ObjectPermissionType. + * Permissions which are not granted may be set to false, but this is + * not required. + * + * @type Object.> + */ + this.connectionGroupPermissions = template.connectionGroupPermissions || {}; + + /** + * The granted state of each permission for each user, as a map of + * object permission type string to permission map. The permission map + * is, in turn, a map of username to boolean value. A particular + * permission is granted if its corresponding boolean value is set to + * true. Valid permission type strings are defined within + * PermissionSet.ObjectPermissionType. Permissions which are not + * granted may be set to false, but this is not required. + * + * @type Object.> + */ + this.userPermissions = template.userPermissions || {}; + + }; + + var addObjectPermissions = function addObjectPermissions(permMap, flagMap) { + + // For each defined identifier in the permission map + for (var identifier in permMap) { + + // Pull the permission array and loop through each permission + var permissions = permMap[identifier]; + permissions.forEach(function addObjectPermission(type) { + + // Get identifier/flag mapping, creating first if necessary + var objectFlags = flagMap[type] = flagMap[type] || {}; + + // Set flag for current permission + objectFlags[identifier] = true; + + }); + + } + + }; + + /** + * Creates a new PermissionFlagSet, populating it with all the permissions + * indicated as granted within the given PermissionSet. + * + * @param {PermissionSet} permissionSet + * The PermissionSet containing the permissions to be copied into a new + * PermissionFlagSet. + * + * @returns {PermissionFlagSet} + * A new PermissionFlagSet containing flags representing all granted + * permissions from the given PermissionSet. + */ + PermissionFlagSet.fromPermissionSet = function fromPermissionSet(permissionSet) { + + var permissionFlagSet = new PermissionFlagSet(); + + // Add all granted system permissions + permissionSet.systemPermissions.forEach(function addSystemPermission(type) { + permissionFlagSet.systemPermissions[type] = true; + }); + + // Add all granted connection permissions + addObjectPermissions(permissionSet.connectionPermissions, permissionFlagSet.connectionPermissions); + + // Add all granted connection group permissions + addObjectPermissions(permissionSet.connectionGroupPermissions, permissionFlagSet.connectionGroupPermissions); + + // Add all granted user permissions + addObjectPermissions(permissionSet.userPermissions, permissionFlagSet.userPermissions); + + return permissionFlagSet; + + }; + + return PermissionFlagSet; + +}]); \ No newline at end of file From a25f169f2799bc94e3f5b3290a62e839dc4534bb Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 24 Dec 2014 00:51:53 -0800 Subject: [PATCH 12/18] GUAC-932: Allow easy add/remove of system permissions to PermissionSets. --- .../webapp/app/rest/types/PermissionSet.js | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/guacamole/src/main/webapp/app/rest/types/PermissionSet.js b/guacamole/src/main/webapp/app/rest/types/PermissionSet.js index 740b9437f..fdc4c141c 100644 --- a/guacamole/src/main/webapp/app/rest/types/PermissionSet.js +++ b/guacamole/src/main/webapp/app/rest/types/PermissionSet.js @@ -275,6 +275,64 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet() return permSet.systemPermissions.indexOf(type) !== -1; }; + /** + * Adds the given system permission to the given permission set, if not + * already present. If the permission is already present, this function has + * no effect. + * + * @param {PermissionSet} permSet + * The permission set to modify. + * + * @param {String} type + * The permission to add, as defined by + * PermissionSet.SystemPermissionType. + * + * @returns {Boolean} + * true if the permission was added, false if the permission was + * already present in the given permission set. + */ + PermissionSet.addSystemPermission = function addSystemPermission(permSet, type) { + + // Add permission, if it doesn't already exist + if (permSet.systemPermissions.indexOf(type) === -1) { + permSet.systemPermissions.push(type); + return true; + } + + // Permission already present + return false; + + }; + + /** + * Removes the given system permission from the given permission set, if + * present. If the permission is not present, this function has no effect. + * + * @param {PermissionSet} permSet + * The permission set to modify. + * + * @param {String} type + * The permission to remove, as defined by + * PermissionSet.SystemPermissionType. + * + * @returns {Boolean} + * true if the permission was removed, false if the permission was not + * present in the given permission set. + */ + PermissionSet.removeSystemPermission = function removeSystemPermission(permSet, type) { + + // Remove permission, if it exists + var permLocation = permSet.systemPermissions.indexOf(type); + if (permLocation !== -1) { + permSet.systemPermissions.splice(permLocation, 1); + return true; + } + + // Permission not present + return false; + + }; + return PermissionSet; }]); \ No newline at end of file From ab553adb3bc3d6f99c5f2e4a91b8994977655ca6 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 24 Dec 2014 00:52:19 -0800 Subject: [PATCH 13/18] GUAC-932: Actually save changes to system permissions when users are edited. --- .../controllers/manageUserController.js | 97 ++++++++++++++++++- .../app/manage/templates/manageUser.html | 3 +- 2 files changed, 97 insertions(+), 3 deletions(-) diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index faf35044c..1e40fbad4 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -98,6 +98,85 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto } ]; + /** + * The set of permissions that will be added to the user when the user is + * saved. Permissions will only be present in this set if they are + * manually added, and not later manually removed before saving. + * + * @type PermissionSet + */ + var permissionsAdded = new PermissionSet(); + + /** + * The set of permissions that will be removed from the user when the user + * is saved. Permissions will only be present in this set if they are + * manually removed, and not later manually added before saving. + * + * @type PermissionSet + */ + var permissionsRemoved = new PermissionSet(); + + /** + * Updates the permissionsAdded and permissionsRemoved permission sets to + * reflect the addition of the given permission. + * + * @param {String} type + * The system permission to remove, as defined by + * PermissionSet.SystemPermissionType. + */ + var addSystemPermission = function addSystemPermission(type) { + + // If permission was previously removed, simply un-remove it + if (PermissionSet.hasSystemPermission(permissionsRemoved, type)) + PermissionSet.removeSystemPermission(permissionsRemoved, type); + + // Otherwise, explicitly add the permission + else + PermissionSet.addSystemPermission(permissionsAdded, type); + + }; + + /** + * Updates the permissionsAdded and permissionsRemoved permission sets to + * reflect the removal of the given permission. + * + * @param {String} type + * The system permission to add, as defined by + * PermissionSet.SystemPermissionType. + */ + var removeSystemPermission = function removeSystemPermission(type) { + + // If permission was previously added, simply un-add it + if (PermissionSet.hasSystemPermission(permissionsAdded, type)) + PermissionSet.removeSystemPermission(permissionsAdded, type); + + // Otherwise, explicitly remove the permission + else + PermissionSet.addSystemPermission(permissionsRemoved, type); + + }; + + /** + * Notifies of a change to the selected system permissions for the user + * being edited. + * + * @param {String} type + * The system permission that was changed, as defined by + * PermissionSet.SystemPermissionType. + */ + $scope.systemPermissionChanged = function systemPermissionChanged(type) { + + // Determine current permission setting + var value = $scope.permissionFlags.systemPermissions[type]; + + // Add/remove permission depending on flag state + if (value) + addSystemPermission(type); + else + removeSystemPermission(type); + + }; + // Expose permission query and modification functions to group list template $scope.groupListContext = { @@ -141,9 +220,23 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto // Save the user userService.saveUser($scope.user) .success(function savedUser() { - $location.path('/manage/'); - // TODO: Save permissions + // Upon success, save any changed permissions + permissionService.patchPermissions($scope.user.username, permissionsAdded, permissionsRemoved) + .success(function patchedUserPermissions() { + $location.path('/manage/'); + }) + + // Notify of any errors + .error(function userPermissionsPatchFailed(error) { + $scope.showStatus({ + 'className' : 'error', + 'title' : 'manage.error.title', + 'text' : error.message, + 'actions' : [ ACKNOWLEDGE_ACTION ] + }); + }); + }) // Notify of any errors diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUser.html b/guacamole/src/main/webapp/app/manage/templates/manageUser.html index 7a48014ab..e11d5224c 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manageUser.html +++ b/guacamole/src/main/webapp/app/manage/templates/manageUser.html @@ -53,7 +53,8 @@ THE SOFTWARE. - +
{{systemPermissionType.label | translate}}
From 71584fa59f4d1f97daa43bf612a7717f7a7de570 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 24 Dec 2014 22:58:38 -0800 Subject: [PATCH 14/18] GUAC-932: null connection group identifier does NOT mean root. This is implementation-dependent. --- .../net/basic/rest/connectiongroup/APIConnectionGroup.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java index 3f166f41c..45ca049e7 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java @@ -88,11 +88,7 @@ public class APIConnectionGroup { this.identifier = connectionGroup.getIdentifier(); this.parentIdentifier = connectionGroup.getParentIdentifier(); - - // Use the explicit ROOT group ID - if (this.parentIdentifier == null) - this.parentIdentifier = ROOT_IDENTIFIER; - + this.name = connectionGroup.getName(); this.type = connectionGroup.getType(); From 5e5c36f5679bcdcaeddb048691a5e6a5791ceae1 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 24 Dec 2014 22:59:05 -0800 Subject: [PATCH 15/18] GUAC-932: Update connection location upon save. --- .../basic/rest/connection/ConnectionRESTService.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java index 95efdd54b..25ff2f5f6 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java @@ -250,7 +250,7 @@ public class ConnectionRESTService { ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); // Use root group if identifier is null (or the standard root identifier) - if (identifier == null || identifier.equals(APIConnectionGroup.ROOT_IDENTIFIER)) + if (identifier != null && identifier.equals(APIConnectionGroup.ROOT_IDENTIFIER)) return rootGroup; // Pull specified connection group otherwise @@ -340,12 +340,12 @@ public class ConnectionRESTService { Directory connectionDirectory = rootGroup.getConnectionDirectory(); - // Make sure the connection is there before trying to update + // Retrieve connection to update Connection existingConnection = connectionDirectory.get(connectionID); if (existingConnection == null) throw new GuacamoleResourceNotFoundException("No such connection: \"" + connectionID + "\""); - // Retrieve connection configuration + // Build updated configuration GuacamoleConfiguration config = new GuacamoleConfiguration(); config.setProtocol(connection.getProtocol()); config.setParameters(connection.getParameters()); @@ -355,6 +355,10 @@ public class ConnectionRESTService { existingConnection.setName(connection.getName()); connectionDirectory.update(existingConnection); + // Update connection parent + ConnectionGroup updatedParentGroup = retrieveConnectionGroup(userContext, connection.getParentIdentifier()); + connectionDirectory.move(connectionID, updatedParentGroup.getConnectionDirectory()); + } } From f1d20c3c54eac0ed380ef8699773f0417662eae8 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 24 Dec 2014 23:36:09 -0800 Subject: [PATCH 16/18] GUAC-932: Migrate to generic service for object retrieval. Add parent update (move) to group service. --- .../basic/rest/ObjectRetrievalService.java | 145 ++++++++++++++++++ .../guacamole/net/basic/rest/RESTModule.java | 1 + .../connection/ConnectionRESTService.java | 89 +++-------- .../ConnectionGroupRESTService.java | 79 ++++------ .../net/basic/rest/user/UserRESTService.java | 22 ++- 5 files changed, 201 insertions(+), 135 deletions(-) create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/ObjectRetrievalService.java diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/ObjectRetrievalService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/ObjectRetrievalService.java new file mode 100644 index 000000000..27cb6d9ef --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/ObjectRetrievalService.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest; + +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.GuacamoleResourceNotFoundException; +import org.glyptodon.guacamole.net.auth.Connection; +import org.glyptodon.guacamole.net.auth.ConnectionGroup; +import org.glyptodon.guacamole.net.auth.Directory; +import org.glyptodon.guacamole.net.auth.User; +import org.glyptodon.guacamole.net.auth.UserContext; +import org.glyptodon.guacamole.net.basic.rest.connectiongroup.APIConnectionGroup; + +/** + * Provides easy access and automatic error handling for retrieval of objects, + * such as users, connections, or connection groups. REST API semantics, such + * as the special root connection group identifier, are also handled + * automatically. + */ +public class ObjectRetrievalService { + + /** + * Retrieves a single user from the given user context. + * + * @param userContext + * The user context to retrieve the user from. + * + * @param identifier + * The identifier of the user to retrieve. + * + * @return + * The user having the given identifier. + * + * @throws GuacamoleException + * If an error occurs while retrieving the user, or if the + * user does not exist. + */ + public User retrieveUser(UserContext userContext, + String identifier) throws GuacamoleException { + + // Get user directory + Directory directory = userContext.getUserDirectory(); + + // Pull specified user + User user = directory.get(identifier); + if (user == null) + throw new GuacamoleResourceNotFoundException("No such user: \"" + identifier + "\""); + + return user; + + } + + /** + * Retrieves a single connection from the given user context. + * + * @param userContext + * The user context to retrieve the connection from. + * + * @param identifier + * The identifier of the connection to retrieve. + * + * @return + * The connection having the given identifier. + * + * @throws GuacamoleException + * If an error occurs while retrieving the connection, or if the + * connection does not exist. + */ + public Connection retrieveConnection(UserContext userContext, + String identifier) throws GuacamoleException { + + // Get root directory + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + Directory directory = rootGroup.getConnectionDirectory(); + + // Pull specified connection + Connection connection = directory.get(identifier); + if (connection == null) + throw new GuacamoleResourceNotFoundException("No such connection: \"" + identifier + "\""); + + return connection; + + } + + /** + * Retrieves a single connection group from the given user context. If + * the given identifier the REST API root identifier, the root connection + * group will be returned. The underlying authentication provider may + * additionally use a different identifier for root. + * + * @param userContext + * The user context to retrieve the connection group from. + * + * @param identifier + * The identifier of the connection group to retrieve. + * + * @return + * The connection group having the given identifier, or the root + * connection group if the identifier the root identifier. + * + * @throws GuacamoleException + * If an error occurs while retrieving the connection group, or if the + * connection group does not exist. + */ + public ConnectionGroup retrieveConnectionGroup(UserContext userContext, + String identifier) throws GuacamoleException { + + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + + // Use root group if identifier is null (or the standard root identifier) + if (identifier != null && identifier.equals(APIConnectionGroup.ROOT_IDENTIFIER)) + return rootGroup; + + // Pull specified connection group otherwise + Directory directory = rootGroup.getConnectionGroupDirectory(); + ConnectionGroup connectionGroup = directory.get(identifier); + + if (connectionGroup == null) + throw new GuacamoleResourceNotFoundException("No such connection group: \"" + identifier + "\""); + + return connectionGroup; + + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java index 70fde2592..9b17d356a 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java @@ -38,6 +38,7 @@ public class RESTModule extends AbstractModule { // Bind generic low-level services bind(ProtocolRetrievalService.class); + bind(ObjectRetrievalService.class); } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java index 25ff2f5f6..e9633054b 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java @@ -44,8 +44,8 @@ import org.glyptodon.guacamole.net.auth.ConnectionRecord; import org.glyptodon.guacamole.net.auth.Directory; import org.glyptodon.guacamole.net.auth.UserContext; import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; +import org.glyptodon.guacamole.net.basic.rest.ObjectRetrievalService; import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; -import org.glyptodon.guacamole.net.basic.rest.connectiongroup.APIConnectionGroup; import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,6 +71,12 @@ public class ConnectionRESTService { @Inject private AuthenticationService authenticationService; + /** + * Service for convenient retrieval of objects. + */ + @Inject + private ObjectRetrievalService retrievalService; + /** * Retrieves an individual connection. * @@ -95,17 +101,8 @@ public class ConnectionRESTService { UserContext userContext = authenticationService.getUserContext(authToken); - // Get the connection directory - ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); - Directory connectionDirectory = - rootGroup.getConnectionDirectory(); - - // Get the connection - Connection connection = connectionDirectory.get(connectionID); - if (connection == null) - throw new GuacamoleResourceNotFoundException("No such connection: \"" + connectionID + "\""); - - return new APIConnection(connection); + // Retrieve the requested connection + return new APIConnection(retrievalService.retrieveConnection(userContext, connectionID)); } @@ -138,10 +135,8 @@ public class ConnectionRESTService { Directory connectionDirectory = rootGroup.getConnectionDirectory(); - // Get the connection - Connection connection = connectionDirectory.get(connectionID); - if (connection == null) - throw new GuacamoleResourceNotFoundException("No such connection: \"" + connectionID + "\""); + // Retrieve the requested connection + Connection connection = retrievalService.retrieveConnection(userContext, connectionID); // Retrieve connection configuration GuacamoleConfiguration config = connection.getConfiguration(); @@ -181,11 +176,8 @@ public class ConnectionRESTService { Directory connectionDirectory = rootGroup.getConnectionDirectory(); - // Get the connection - Connection connection = connectionDirectory.get(connectionID); - if (connection == null) - throw new GuacamoleResourceNotFoundException("No such connection: \"" + connectionID + "\""); - + // Retrieve the requested connection's history + Connection connection = retrievalService.retrieveConnection(userContext, connectionID); return connection.getHistory(); } @@ -216,54 +208,11 @@ public class ConnectionRESTService { Directory connectionDirectory = rootGroup.getConnectionDirectory(); - // Make sure the connection is there before trying to delete - if (connectionDirectory.get(connectionID) == null) - throw new GuacamoleResourceNotFoundException("No such connection: \"" + connectionID + "\""); - - // Delete the connection + // Delete the specified connection connectionDirectory.remove(connectionID); } - /** - * Retrieves a single connection group from the given user context. If - * the given identifier is null or the root identifier, the root connection - * group will be returned. - * - * @param userContext - * The user context to retrieve the connection group from. - * - * @param identifier - * The identifier of the connection group to retrieve. - * - * @return - * The connection group having the given identifier, or the root - * connection group if the identifier is null or the root identifier. - * - * @throws GuacamoleException - * If an error occurs while retrieving the connection group, or if the - * connection group does not exist. - */ - private ConnectionGroup retrieveConnectionGroup(UserContext userContext, - String identifier) throws GuacamoleException { - - ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); - - // Use root group if identifier is null (or the standard root identifier) - if (identifier != null && identifier.equals(APIConnectionGroup.ROOT_IDENTIFIER)) - return rootGroup; - - // Pull specified connection group otherwise - Directory directory = rootGroup.getConnectionGroupDirectory(); - ConnectionGroup connectionGroup = directory.get(identifier); - - if (connectionGroup == null) - throw new GuacamoleResourceNotFoundException("No such connection group: \"" + identifier + "\""); - - return connectionGroup; - - } - /** * Creates a new connection and returns the identifier of the new * connection. @@ -294,7 +243,7 @@ public class ConnectionRESTService { // Retrieve parent group String parentID = connection.getParentIdentifier(); - ConnectionGroup parentConnectionGroup = retrieveConnectionGroup(userContext, parentID); + ConnectionGroup parentConnectionGroup = retrievalService.retrieveConnectionGroup(userContext, parentID); // Add the new connection Directory connectionDirectory = parentConnectionGroup.getConnectionDirectory(); @@ -341,10 +290,8 @@ public class ConnectionRESTService { rootGroup.getConnectionDirectory(); // Retrieve connection to update - Connection existingConnection = connectionDirectory.get(connectionID); - if (existingConnection == null) - throw new GuacamoleResourceNotFoundException("No such connection: \"" + connectionID + "\""); - + Connection existingConnection = retrievalService.retrieveConnection(userContext, connectionID); + // Build updated configuration GuacamoleConfiguration config = new GuacamoleConfiguration(); config.setProtocol(connection.getProtocol()); @@ -356,7 +303,7 @@ public class ConnectionRESTService { connectionDirectory.update(existingConnection); // Update connection parent - ConnectionGroup updatedParentGroup = retrieveConnectionGroup(userContext, connection.getParentIdentifier()); + ConnectionGroup updatedParentGroup = retrievalService.retrieveConnectionGroup(userContext, connection.getParentIdentifier()); connectionDirectory.move(connectionID, updatedParentGroup.getConnectionDirectory()); } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java index 46a37c5e0..226ba8f9d 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java @@ -46,6 +46,7 @@ import org.glyptodon.guacamole.net.auth.UserContext; import org.glyptodon.guacamole.net.auth.permission.ConnectionPermission; import org.glyptodon.guacamole.net.auth.permission.ObjectPermission; import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; +import org.glyptodon.guacamole.net.basic.rest.ObjectRetrievalService; import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; import org.glyptodon.guacamole.net.basic.rest.connection.APIConnection; import org.slf4j.Logger; @@ -72,6 +73,12 @@ public class ConnectionGroupRESTService { @Inject private AuthenticationService authenticationService; + /** + * Service for convenient retrieval of objects. + */ + @Inject + private ObjectRetrievalService retrievalService; + /** * Retrieves the given connection group from the user context, including * all descendant connections and groups if requested. @@ -105,24 +112,14 @@ public class ConnectionGroupRESTService { User self = userContext.self(); ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); - + + // Retrieve specified connection group ConnectionGroup connectionGroup; - - // Use root group if requested - if (identifier == null || identifier.equals(APIConnectionGroup.ROOT_IDENTIFIER)) - connectionGroup = rootGroup; - - // Otherwise, query requested group using root group directory - else { - - Directory connectionGroupDirectory = - rootGroup.getConnectionGroupDirectory(); - - // Get the connection group from root directory - connectionGroup = connectionGroupDirectory.get(identifier); - if (connectionGroup == null) - return null; - + try { + connectionGroup = retrievalService.retrieveConnectionGroup(userContext, identifier); + } + catch (GuacamoleResourceNotFoundException e) { + return null; } // Wrap queried connection group @@ -274,18 +271,9 @@ public class ConnectionGroupRESTService { // Get the connection group directory ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); - - // Use the root group if it was asked for - if (connectionGroupID != null && connectionGroupID.equals(APIConnectionGroup.ROOT_IDENTIFIER)) - connectionGroupID = rootGroup.getIdentifier(); - Directory connectionGroupDirectory = rootGroup.getConnectionGroupDirectory(); - // Make sure the connection is there before trying to delete - if (connectionGroupDirectory.get(connectionGroupID) == null) - throw new GuacamoleResourceNotFoundException("No such connection group: \"" + connectionGroupID + "\""); - // Delete the connection group connectionGroupDirectory.remove(connectionGroupID); @@ -321,22 +309,9 @@ public class ConnectionGroupRESTService { if (connectionGroup == null) throw new GuacamoleClientException("Connection group JSON must be submitted when creating connections groups."); + // Retrieve parent group String parentID = connectionGroup.getParentIdentifier(); - ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); - - // Use root group if no parent is specified - ConnectionGroup parentConnectionGroup; - if (parentID == null) - parentConnectionGroup = rootGroup; - - // Pull specified connection group otherwise - else { - Directory connectionGroupDirectory = rootGroup.getConnectionGroupDirectory(); - parentConnectionGroup = connectionGroupDirectory.get(parentID); - - if (parentConnectionGroup == null) - throw new GuacamoleResourceNotFoundException("No such connection group: \"" + parentID + "\""); - } + ConnectionGroup parentConnectionGroup = retrievalService.retrieveConnectionGroup(userContext, parentID); // Add the new connection group Directory connectionGroupDirectory = parentConnectionGroup.getConnectionGroupDirectory(); @@ -378,22 +353,22 @@ public class ConnectionGroupRESTService { if (connectionGroup == null) throw new GuacamoleClientException("Connection group JSON must be submitted when updating connection groups."); - // Get the connection directory + // Get the connection group directory ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); - - // Use the root group if it was asked for - if (connectionGroupID != null && connectionGroupID.equals(APIConnectionGroup.ROOT_IDENTIFIER)) - connectionGroupID = rootGroup.getIdentifier(); - Directory connectionGroupDirectory = rootGroup.getConnectionGroupDirectory(); - // Make sure the connection group is there before trying to update - if (connectionGroupDirectory.get(connectionGroupID) == null) - throw new GuacamoleResourceNotFoundException("No such connection group: \"" + connectionGroupID + "\""); - + // Retrieve connection group to update + ConnectionGroup existingConnectionGroup = connectionGroupDirectory.get(connectionGroupID); + // Update the connection group - connectionGroupDirectory.update(new APIConnectionGroupWrapper(connectionGroup)); + existingConnectionGroup.setName(connectionGroup.getName()); + existingConnectionGroup.setType(connectionGroup.getType()); + connectionGroupDirectory.update(existingConnectionGroup); + + // Update connection group parent + ConnectionGroup updatedParentGroup = retrievalService.retrieveConnectionGroup(userContext, connectionGroup.getParentIdentifier()); + connectionGroupDirectory.move(connectionGroupID, updatedParentGroup.getConnectionGroupDirectory()); } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java index c9e6a6fde..7a9f28b9b 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java @@ -54,6 +54,7 @@ import static org.glyptodon.guacamole.net.basic.rest.APIPatch.Operation.add; import static org.glyptodon.guacamole.net.basic.rest.APIPatch.Operation.remove; import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; import org.glyptodon.guacamole.net.basic.rest.HTTPException; +import org.glyptodon.guacamole.net.basic.rest.ObjectRetrievalService; import org.glyptodon.guacamole.net.basic.rest.PATCH; import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; import org.glyptodon.guacamole.net.basic.rest.permission.APIPermissionSet; @@ -105,6 +106,12 @@ public class UserRESTService { @Inject private AuthenticationService authenticationService; + /** + * Service for convenient retrieval of objects. + */ + @Inject + private ObjectRetrievalService retrievalService; + /** * Gets a list of users in the system, filtering the returned list by the * given permission, if specified. @@ -177,15 +184,8 @@ public class UserRESTService { UserContext userContext = authenticationService.getUserContext(authToken); - // Get the directory - Directory userDirectory = userContext.getUserDirectory(); - - // Get the user - User user = userDirectory.get(username); - if (user == null) - throw new GuacamoleResourceNotFoundException("No such user: \"" + username + "\""); - - // Return the user + // Retrieve the requested user + User user = retrievalService.retrieveUser(userContext, username); return new APIUser(user); } @@ -254,9 +254,7 @@ public class UserRESTService { "Username in path does not match username provided JSON data."); // Get the user - User existingUser = userDirectory.get(username); - if (existingUser == null) - throw new GuacamoleResourceNotFoundException("No such user: \"" + username + "\""); + User existingUser = retrievalService.retrieveUser(userContext, username); // Do not update the user password if no password was provided if (user.getPassword() != null) From edb0c701eb63852a188dfe9d2667bde66f34bc73 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 25 Dec 2014 00:36:25 -0800 Subject: [PATCH 17/18] GUAC-932: Add support for modifying connection/group/user permissions within PermissionSet. --- .../webapp/app/rest/types/PermissionSet.js | 220 ++++++++++++++++++ 1 file changed, 220 insertions(+) diff --git a/guacamole/src/main/webapp/app/rest/types/PermissionSet.js b/guacamole/src/main/webapp/app/rest/types/PermissionSet.js index fdc4c141c..869f14523 100644 --- a/guacamole/src/main/webapp/app/rest/types/PermissionSet.js +++ b/guacamole/src/main/webapp/app/rest/types/PermissionSet.js @@ -333,6 +333,226 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet() }; + /** + * Adds the given permission applying to the arbitrary object with the + * given ID to the given permission set, if not already present. If the + * permission is already present, this function has no effect. + * + * @param {Object.} permMap + * The permission map to modify, where each entry maps an object + * identifer to the array of granted permissions. + * + * @param {String} type + * The permission to add, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the arbitrary object to which the permission + * applies. + * + * @returns {Boolean} + * true if the permission was added, false if the permission was + * already present in the given permission set. + */ + var addObjectPermission = function addObjectPermission(permMap, type, identifier) { + + // Pull array of permissions, creating it if necessary + var permArray = permMap[identifier] = permMap[identifier] || []; + + // Add permission, if it doesn't already exist + if (permArray.indexOf(type) === -1) { + permArray.push(type); + return true; + } + + // Permission already present + return false; + + }; + + /** + * Removes the given permission applying to the arbitrary object with the + * given ID from the given permission set, if present. If the permission is + * not present, this function has no effect. + * + * @param {Object.} permMap + * The permission map to modify, where each entry maps an object + * identifer to the array of granted permissions. + * + * @param {String} type + * The permission to remove, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the arbitrary object to which the permission + * applies. + * + * @returns {Boolean} + * true if the permission was removed, false if the permission was not + * present in the given permission set. + */ + var removeObjectPermission = function removeObjectPermission(permMap, type, identifier) { + + // Pull array of permissions + var permArray = permMap[identifier]; + + // If no permissions present at all, nothing to remove + if (!(identifier in permMap)) + return false; + + // Remove permission, if it exists + var permLocation = permArray.indexOf(type); + if (permLocation !== -1) { + permArray.splice(permLocation, 1); + return true; + } + + // Permission not present + return false; + + }; + + /** + * Adds the given connection permission applying to the connection with + * the given ID to the given permission set, if not already present. If the + * permission is already present, this function has no effect. + * + * @param {PermissionSet} permSet + * The permission set to modify. + * + * @param {String} type + * The permission to add, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the connection to which the permission applies. + * + * @returns {Boolean} + * true if the permission was added, false if the permission was + * already present in the given permission set. + */ + PermissionSet.addConnectionPermission = function addConnectionPermission(permSet, type, identifier) { + return addObjectPermission(permSet.connectionPermissions, type, identifier); + }; + + /** + * Removes the given connection permission applying to the connection with + * the given ID from the given permission set, if present. If the + * permission is not present, this function has no effect. + * + * @param {PermissionSet} permSet + * The permission set to modify. + * + * @param {String} type + * The permission to remove, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the connection to which the permission applies. + * + * @returns {Boolean} + * true if the permission was removed, false if the permission was not + * present in the given permission set. + */ + PermissionSet.removeConnectionPermission = function removeConnectionPermission(permSet, type, identifier) { + return removeObjectPermission(permSet.connectionPermissions, type, identifier); + }; + + /** + * Adds the given connection group permission applying to the connection + * group with the given ID to the given permission set, if not already + * present. If the permission is already present, this function has no + * effect. + * + * @param {PermissionSet} permSet + * The permission set to modify. + * + * @param {String} type + * The permission to add, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the connection group to which the permission + * applies. + * + * @returns {Boolean} + * true if the permission was added, false if the permission was + * already present in the given permission set. + */ + PermissionSet.addConnectionGroupPermission = function addConnectionGroupPermission(permSet, type, identifier) { + return addObjectPermission(permSet.connectionGroupPermissions, type, identifier); + }; + + /** + * Removes the given connection group permission applying to the connection + * group with the given ID from the given permission set, if present. If + * the permission is not present, this function has no effect. + * + * @param {PermissionSet} permSet + * The permission set to modify. + * + * @param {String} type + * The permission to remove, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the connection group to which the permission + * applies. + * + * @returns {Boolean} + * true if the permission was removed, false if the permission was not + * present in the given permission set. + */ + PermissionSet.removeConnectionGroupPermission = function removeConnectionGroupPermission(permSet, type, identifier) { + return removeObjectPermission(permSet.connectionGroupPermissions, type, identifier); + }; + + /** + * Adds the given user permission applying to the user with the given ID to + * the given permission set, if not already present. If the permission is + * already present, this function has no effect. + * + * @param {PermissionSet} permSet + * The permission set to modify. + * + * @param {String} type + * The permission to add, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the user to which the permission applies. + * + * @returns {Boolean} + * true if the permission was added, false if the permission was + * already present in the given permission set. + */ + PermissionSet.addUserPermission = function addUserPermission(permSet, type, identifier) { + return addObjectPermission(permSet.userPermissions, type, identifier); + }; + + /** + * Removes the given user permission applying to the user with the given ID + * from the given permission set, if present. If the permission is not + * present, this function has no effect. + * + * @param {PermissionSet} permSet + * The permission set to modify. + * + * @param {String} type + * The permission to remove, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the user to whom the permission applies. + * + * @returns {Boolean} + * true if the permission was removed, false if the permission was not + * present in the given permission set. + */ + PermissionSet.removeUserPermission = function removeUserPermission(permSet, type, identifier) { + return removeObjectPermission(permSet.userPermissions, type, identifier); + }; + return PermissionSet; }]); \ No newline at end of file From c1a606432a7c15e8a54602e5250cae7eada7d99a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 25 Dec 2014 00:36:43 -0800 Subject: [PATCH 18/18] GUAC-932: Update user connection/group permissions when saved. --- .../controllers/manageUserController.js | 122 +++++++++++++++++- .../templates/connectionGroupPermission.html | 4 +- .../templates/connectionPermission.html | 3 +- 3 files changed, 125 insertions(+), 4 deletions(-) diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index 1e40fbad4..a45fef809 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -118,7 +118,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto /** * Updates the permissionsAdded and permissionsRemoved permission sets to - * reflect the addition of the given permission. + * reflect the addition of the given system permission. * * @param {String} type * The system permission to remove, as defined by @@ -138,7 +138,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto /** * Updates the permissionsAdded and permissionsRemoved permission sets to - * reflect the removal of the given permission. + * reflect the removal of the given system permission. * * @param {String} type * The system permission to add, as defined by @@ -177,6 +177,82 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto }; + /** + * Updates the permissionsAdded and permissionsRemoved permission sets to + * reflect the addition of the given connection permission. + * + * @param {String} identifier + * The identifier of the connection to add READ permission for. + */ + var addConnectionPermission = function addConnectionPermission(identifier) { + + // If permission was previously removed, simply un-remove it + if (PermissionSet.hasConnectionPermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier)) + PermissionSet.removeConnectionPermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier); + + // Otherwise, explicitly add the permission + else + PermissionSet.addConnectionPermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier); + + }; + + /** + * Updates the permissionsAdded and permissionsRemoved permission sets to + * reflect the removal of the given connection permission. + * + * @param {String} identifier + * The identifier of the connection to remove READ permission for. + */ + var removeConnectionPermission = function removeConnectionPermission(identifier) { + + // If permission was previously added, simply un-add it + if (PermissionSet.hasConnectionPermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier)) + PermissionSet.removeConnectionPermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier); + + // Otherwise, explicitly remove the permission + else + PermissionSet.addConnectionPermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier); + + }; + + /** + * Updates the permissionsAdded and permissionsRemoved permission sets to + * reflect the addition of the given connection group permission. + * + * @param {String} identifier + * The identifier of the connection group to add READ permission for. + */ + var addConnectionGroupPermission = function addConnectionGroupPermission(identifier) { + + // If permission was previously removed, simply un-remove it + if (PermissionSet.hasConnectionGroupPermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier)) + PermissionSet.removeConnectionGroupPermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier); + + // Otherwise, explicitly add the permission + else + PermissionSet.addConnectionGroupPermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier); + + }; + + /** + * Updates the permissionsAdded and permissionsRemoved permission sets to + * reflect the removal of the given connection permission. + * + * @param {String} identifier + * The identifier of the connection to remove READ permission for. + */ + var removeConnectionGroupPermission = function removeConnectionGroupPermission(identifier) { + + // If permission was previously added, simply un-add it + if (PermissionSet.hasConnectionGroupPermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier)) + PermissionSet.removeConnectionGroupPermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier); + + // Otherwise, explicitly remove the permission + else + PermissionSet.addConnectionGroupPermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier); + + }; + // Expose permission query and modification functions to group list template $scope.groupListContext = { @@ -190,6 +266,48 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto */ getPermissionFlags : function getPermissionFlags() { return $scope.permissionFlags; + }, + + /** + * Notifies of a change to the selected connection permission for the + * user being edited. This only applies to READ permissions. + * + * @param {String} identifier + * The identifier of the connection affected by the changed + * permission. + */ + connectionPermissionChanged : function connectionPermissionChanged(identifier) { + + // Determine current permission setting + var value = $scope.permissionFlags.connectionPermissions.READ[identifier]; + + // Add/remove permission depending on flag state + if (value) + addConnectionPermission(identifier); + else + removeConnectionPermission(identifier); + + }, + + /** + * Notifies of a change to the selected connection group permission for + * the user being edited. This only applies to READ permissions. + * + * @param {String} identifier + * The identifier of the connection group affected by the changed + * permission. + */ + connectionGroupPermissionChanged : function connectionGroupPermissionChanged(identifier) { + + // Determine current permission setting + var value = $scope.permissionFlags.connectionGroupPermissions.READ[identifier]; + + // Add/remove permission depending on flag state + if (value) + addConnectionGroupPermission(identifier); + else + removeConnectionGroupPermission(identifier); + } }; diff --git a/guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html b/guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html index 1ca169386..86ebcaf48 100644 --- a/guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html +++ b/guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html @@ -21,6 +21,8 @@ THE SOFTWARE. --> - + + {{item.name}} diff --git a/guacamole/src/main/webapp/app/manage/templates/connectionPermission.html b/guacamole/src/main/webapp/app/manage/templates/connectionPermission.html index 295bde763..37985d723 100644 --- a/guacamole/src/main/webapp/app/manage/templates/connectionPermission.html +++ b/guacamole/src/main/webapp/app/manage/templates/connectionPermission.html @@ -27,7 +27,8 @@ - + {{item.name}}