You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@guacamole.apache.org by mike-jumper <gi...@git.apache.org> on 2018/08/08 16:18:35 UTC
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
GitHub user mike-jumper opened a pull request:
https://github.com/apache/guacamole-client/pull/309
GUACAMOLE-220: Add user interface for managing user groups and membership.
This change:
* Adds a new tab for managing user groups, similar to the existing user management tab.
* Adds a new directive which provides an interface for manipulating whether an object shares some relation with another object (in this case group membership relations).
* Leverages the above directive to add support for manipulating a user's group memberships, as well as the group memberships of other groups.
* Upgrades both jQuery and Lodash to the latest versions. (The version of Lodash previously included with Guacamole lacks the `sortedIndexOf()` function, which is needed by the new directive mentioned above. If upgrading Lodash, we may as well upgrade jQuery while we're at it, as the version previously included within Guacamole is no longer maintained.)
With these changes in place, administrators will see a new "Groups" tab next to the existing "Users" tab:

When edited, each group appears largely like a user, with sets of permissions that can be granted to the group. In addition to the usual system and connection permission sections, there are three group membership sections which allow parent groups and member users/groups to be manipulated:

Clicking the "X" next to the name of a user or group marks that object for removal from that membership relation, to take effect once the admin clicks "Save". A similar new section is now present in the user editor for manipulating group membership at the user level:

These membership editor sections are collapsed by default. The intent there is to reduce clutter within the user and group edit screens while also providing admins with a quick view into the current state of a user or group's membership. When expanded, a full list of available users or groups is displayed, with checkboxes allowing those users/groups to be added/removed:

Each of these sections is displayed only if there are applicable users or groups.
The filters in the section headers have the effect you would expect, reducing the users/groups visible in both the abbreviated and expanded views of the editor.
You can merge this pull request into a Git repository by running:
$ git pull https://github.com/mike-jumper/guacamole-client user-group-ui
Alternatively you can review and apply these changes as the patch at:
https://github.com/apache/guacamole-client/pull/309.patch
To close this pull request, make a commit to your master/trunk branch
with (at least) the following in the commit message:
This closes #309
----
commit 9f01fcb1558b11b52c78bcddee2ea601ab4b102c
Author: Michael Jumper <mj...@...>
Date: 2018-04-19T21:38:24Z
GUACAMOLE-220: Add JavaScript service for retrieving/manipulating user groups.
commit 55bcf25a1c53f85ba2383fc83b46e2bc2ccd05af
Author: Michael Jumper <mj...@...>
Date: 2018-04-19T21:39:04Z
GUACAMOLE-220: Allow manipulation and retrieval of user group permissions via JavaScript.
commit 615f5c6bab28f78884cf42fb29c76ad44822a33d
Author: Michael Jumper <mj...@...>
Date: 2018-04-20T06:21:17Z
GUACAMOLE-220: Add missing getUserGroupAttributes() to JavaScript schemaService.
commit 1cf16d1dc6f02e608eb860d627c423dc6be958da
Author: Michael Jumper <mj...@...>
Date: 2018-07-22T02:41:47Z
GUACAMOLE-220: Upgrade to latest versions of jQuery and Lodash.
The version of Lodash previously included with Guacamole lacks the
sortedIndexOf() function, which is needed by the identity set editor
directive for manipulating sorted arrays of identifiers.
If upgrading Lodash, may as well upgrade jQuery while we're at it. The
version previously included within Guacamole is no longer maintained.
commit 229b0dee4882352e7583c4f5872bee92158da712
Author: Michael Jumper <mj...@...>
Date: 2018-07-25T09:34:27Z
GUACAMOLE-220: Implement generic editor directive for manipulating sets of identifiers.
commit fed2b9cb1440f21ba8f72055a29af07f7e4586cc
Author: Michael Jumper <mj...@...>
Date: 2018-08-07T19:15:46Z
GUACAMOLE-220: Add management interface for user parent groups.
commit 0059121716c869e34a8089ada3069ad67ffb6ee7
Author: Michael Jumper <mj...@...>
Date: 2018-08-07T19:25:55Z
GUACAMOLE-220: Hide identifier set editor if there are no identifiers to edit.
commit 59ea968614bfa747829eab4fcae8199a9bd44522
Author: Michael Jumper <mj...@...>
Date: 2018-08-07T20:05:09Z
GUACAMOLE-220: Do not display "X" for removing an identifier if the identifier cannot actually be edited.
commit 439030ac4b8743ac91cceb1cd2f27f7d133f6857
Author: Michael Jumper <mj...@...>
Date: 2018-04-20T06:51:25Z
GUACAMOLE-220: Add management tab and editor for user groups.
----
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by mike-jumper <gi...@git.apache.org>.
Github user mike-jumper commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r208804311
--- Diff: guacamole/src/main/webapp/app/manage/directives/identifierSetEditor.js ---
@@ -0,0 +1,299 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * A directive for manipulating a set of objects sharing some common relation
+ * and represented by an array of their identifiers. The specific objects
+ * added or removed are tracked within a separate pair of arrays of
+ * identifiers.
+ */
+angular.module('manage').directive('identifierSetEditor', ['$injector',
+ function identifierSetEditor($injector) {
+
+ var directive = {
+
+ // Element only
+ restrict: 'E',
+ replace: true,
+
+ scope: {
+
+ /**
+ * The translation key of the text which should be displayed within
+ * the main header of the identifier set editor.
+ *
+ * @type String
+ */
+ header : '@',
+
+ /**
+ * The translation key of the text which should be displayed if no
+ * identifiers are currently present within the set.
+ *
+ * @type String
+ */
+ emptyPlaceholder : '@',
+
+ /**
+ * The translation key of the text which should be displayed if no
+ * identifiers are available to be added within the set.
+ *
+ * @type String
+ */
+ unavailablePlaceholder : '@',
+
+ /**
+ * All identifiers which are available to be added to or removed
+ * from the identifier set being edited.
+ *
+ * @type String[]
+ */
+ identifiersAvailable : '=',
+
+ /**
+ * The current state of the identifier set being manipulated. This
+ * array will be modified as changes are made through this
+ * identifier set editor.
+ *
+ * @type String[]
+ */
+ identifiers : '=',
+
+ /**
+ * The set of identifiers that have been added, relative to the
+ * initial state of the identifier set being manipulated.
+ *
+ * @type String[]
+ */
+ identifiersAdded : '=',
+
+ /**
+ * The set of identifiers that have been removed, relative to the
+ * initial state of the identifier set being manipulated.
+ *
+ * @type String[]
+ */
+ identifiersRemoved : '='
+
+ },
+
+ templateUrl: 'app/manage/templates/identifierSetEditor.html'
+
+ };
+
+ directive.controller = ['$scope', function identifierSetEditorController($scope) {
+
+ /**
+ * Whether the full list of available identifiers should be displayed.
+ * Initially, only an abbreviated list of identifiers currently present
+ * is shown.
+ *
+ * @type Boolean
+ */
+ $scope.expanded = false;
+
+ /**
+ * Map of identifiers to boolean flags indicating whether that
+ * identifier is currently present (true) or absent (false). If an
+ * identifier is absent, it may also be absent from this map.
+ *
+ * @type Object.<String, Boolean>
+ */
+ $scope.identifierFlags = {};
+
+ /**
+ * Map of identifiers to boolean flags indicating whether that
+ * identifier is editable. If an identifier is not editable, it will be
+ * absent from this map.
+ *
+ * @type Object.<String, Boolean>
+ */
+ $scope.isEditable = {};
+
+ /**
+ * Adds the given identifier to the given sorted array of identifiers,
+ * preserving the sorted order of the array. If the identifier is
+ * already present, no change is made to the array. The given array
+ * must already be sorted in ascending order.
+ *
+ * @param {String[]} arr
+ * The sorted array of identifiers to add the given identifier to.
+ *
+ * @param {String} identifier
+ * The identifier to add to the given array.
+ */
+ var addIdentifier = function addIdentifier(arr, identifier) {
+
+ // Determine location that the identifier should be added to
+ // maintain sorted order
+ var index = _.sortedIndex(arr, identifier);
+
+ // Do not add if already present
+ if (arr[index] === identifier)
+ return;
+
+ // Insert identifier at determined location
+ arr.splice(index, 0, identifier);
+
+ };
+
+ /**
+ * Removes the given identifier from the given sorted array of
+ * identifiers, preserving the sorted order of the array. If the
+ * identifier is already absent, no change is made to the array. The
+ * given array must already be sorted in ascending order.
+ *
+ * @param {String[]} arr
+ * The sorted array of identifiers to remove the given identifier
+ * from.
+ *
+ * @param {String} identifier
+ * The identifier to remove from the given array.
+ *
+ * @returns {Boolean}
+ * true if the identifier was present in the given array and has
+ * been removed, false otherwise.
+ */
+ var removeIdentifier = function removeIdentifier(arr, identifier) {
+
+ // Search for identifier in sorted array
+ var index = _.sortedIndexOf(arr, identifier);
+
+ // Nothing to do if already absent
+ if (index === -1)
+ return false;
+
+ // Remove identifier
+ arr.splice(index, 1);
+ return true;
+
+ };
+
+ // Keep identifierFlags up to date when identifiers array is replaced
+ // or initially assigned
+ $scope.$watch('identifiers', function identifiersChanged(identifiers) {
+
+ // Maintain identifiers in sorted order so additions and removals
+ // can be made more efficiently
+ if (identifiers)
+ identifiers.sort();
+
+ // Convert array of identifiers into set of boolean
+ // presence/absence flags
+ $scope.identifierFlags = {};
+ angular.forEach(identifiers, function storeIdentifierFlag(identifier) {
+ $scope.identifierFlags[identifier] = true;
+ });
+
+ });
+
+ // Keep identifierFlags up to date when identifiers array is replaced
--- End diff --
🍝 😢
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by necouchman <gi...@git.apache.org>.
Github user necouchman commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r208855491
--- Diff: guacamole/src/main/webapp/app/settings/directives/guacSettingsUserGroups.js ---
@@ -0,0 +1,272 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * A directive for managing all user groups in the system.
+ */
+angular.module('settings').directive('guacSettingsUserGroups', ['$injector',
+ function guacSettingsUserGroups($injector) {
+
+ // Required types
+ var ManageableUserGroup = $injector.get('ManageableUserGroup');
+ var PermissionSet = $injector.get('PermissionSet');
+ var SortOrder = $injector.get('SortOrder');
+
+ // Required services
+ var $location = $injector.get('$location');
+ var authenticationService = $injector.get('authenticationService');
+ var dataSourceService = $injector.get('dataSourceService');
+ var permissionService = $injector.get('permissionService');
+ var requestService = $injector.get('requestService');
+ var userGroupService = $injector.get('userGroupService');
+
+ var directive = {
+ restrict : 'E',
+ replace : true,
+ templateUrl : 'app/settings/templates/settingsUserGroups.html',
+ scope : {}
+ };
+
+ directive.controller = ['$scope', function settingsUserGroupsController($scope) {
+
+ // Identifier of the current user
+ var currentUsername = authenticationService.getCurrentUsername();
+
+ /**
+ * The identifiers of all data sources accessible by the current
+ * user.
+ *
+ * @type String[]
+ */
+ var dataSources = authenticationService.getAvailableDataSources();
+
+ /**
+ * Map of data source identifiers to all permissions associated
+ * with the current user within that data source, or null if the
+ * user's permissions have not yet been loaded.
+ *
+ * @type Object.<String, PermissionSet>
+ */
+ var permissions = null;
+
+ /**
+ * All visible user groups, along with their corresponding data
+ * sources.
+ *
+ * @type ManageableUserGroup[]
+ */
+ $scope.manageableUserGroups = null;
+
+ /**
+ * Array of all user group properties that are filterable.
+ *
+ * @type String[]
+ */
+ $scope.filteredUserGroupProperties = [
+ 'userGroup.identifier'
+ ];
+
+ /**
+ * SortOrder instance which stores the sort order of the listed
+ * user groups.
+ *
+ * @type SortOrder
+ */
+ $scope.order = new SortOrder([
+ 'userGroup.identifier'
+ ]);
+
+ /**
+ * Returns whether critical data has completed being loaded.
+ *
+ * @returns {Boolean}
+ * true if enough data has been loaded for the user group
+ * interface to be useful, false otherwise.
+ */
+ $scope.isLoaded = function isLoaded() {
+ return $scope.manageableUserGroups !== null;
+ };
+
+ /**
+ * Returns the identifier of the data source that should be used by
+ * default when creating a new user group.
+ *
+ * @return {String}
+ * The identifier of the data source that should be used by
+ * default when creating a new user group, or null if user group
+ * creation is not allowed.
+ */
+ $scope.getDefaultDataSource = function getDefaultDataSource() {
+
+ // Abort if permissions have not yet loaded
+ if (!permissions)
+ return null;
+
+ // For each data source
+ var dataSources = _.keys(permissions).sort();
+ for (var i = 0; i < dataSources.length; i++) {
+
+ // Retrieve corresponding permission set
+ var dataSource = dataSources[i];
+ var permissionSet = permissions[dataSource];
+
+ // Can create user groups if adminstrator or have explicit permission
+ if (PermissionSet.hasSystemPermission(permissionSet, PermissionSet.SystemPermissionType.ADMINISTER)
+ || PermissionSet.hasSystemPermission(permissionSet, PermissionSet.SystemPermissionType.CREATE_USER_GROUP))
+ return dataSource;
+
+ }
+
+ // No data sources allow user group creation
+ return null;
+
+ };
+
+ /**
+ * Returns whether the current user can create new user groups
+ * within at least one data source.
+ *
+ * @return {Boolean}
+ * true if the current user can create new user groups within at
+ * least one data source, false otherwise.
+ */
+ $scope.canCreateUserGroups = function canCreateUserGroups() {
+ return $scope.getDefaultDataSource() !== null;
+ };
+
+ /**
+ * Returns whether the current user can create new user groups or
+ * make changes to existing user groups within at least one data
+ * source. The user group management interface as a whole is useless
+ * if this function returns false.
+ *
+ * @return {Boolean}
+ * true if the current user can create new user groups or make
+ * changes to existing user groups within at least one data
+ * source, false otherwise.
+ */
+ var canManageUserGroups = function canManageUserGroups() {
+
+ // Abort if permissions have not yet loaded
+ if (!permissions)
+ return false;
+
+ // Creating user groups counts as management
+ if ($scope.canCreateUserGroups())
+ return true;
+
+ // For each data source
+ for (var dataSource in permissions) {
+
+ // Retrieve corresponding permission set
+ var permissionSet = permissions[dataSource];
+
+ // Can manage user groups if granted explicit update or delete
+ if (PermissionSet.hasUserGroupPermission(permissionSet, PermissionSet.ObjectPermissionType.UPDATE)
+ || PermissionSet.hasUserGroupPermission(permissionSet, PermissionSet.ObjectPermissionType.DELETE))
+ return true;
+
+ }
+
+ // No data sources allow management of user groups
+ return false;
+
+ };
+
+ /**
+ * Sets the displayed list of user groups. If any user groups are
+ * already shown within the interface, those user groups are replaced
+ * with the given user groups.
+ *
+ * @param {Object.<String, PermissionSet>} permissions
+ * A map of data source identifiers to all permissions associated
+ * with the current user within that data source.
+ *
+ * @param {Object.<String, Object.<String, UserGroup>>} userGroups
+ * A map of all user groups which should be displayed, where each
+ * key is the data source identifier from which the user groups
+ * were retrieved and each value is a map of user group identifiers
+ * to their corresponding @link{UserGroup} objects.
+ */
+ var setDisplayedUserGroups = function setDisplayedUserGroups(permissions, userGroups) {
+
+ var addedUserGroups = {};
+ $scope.manageableUserGroups = [];
+
+ // For each user in each data source
--- End diff --
For each user or each user group? Looks like this is looping through user groups in data sources....
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by mike-jumper <gi...@git.apache.org>.
Github user mike-jumper commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r208805738
--- Diff: guacamole/src/main/webapp/app/manage/directives/identifierSetEditor.js ---
@@ -0,0 +1,299 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * A directive for manipulating a set of objects sharing some common relation
+ * and represented by an array of their identifiers. The specific objects
+ * added or removed are tracked within a separate pair of arrays of
+ * identifiers.
+ */
+angular.module('manage').directive('identifierSetEditor', ['$injector',
+ function identifierSetEditor($injector) {
+
+ var directive = {
+
+ // Element only
+ restrict: 'E',
+ replace: true,
+
+ scope: {
+
+ /**
+ * The translation key of the text which should be displayed within
+ * the main header of the identifier set editor.
+ *
+ * @type String
+ */
+ header : '@',
+
+ /**
+ * The translation key of the text which should be displayed if no
+ * identifiers are currently present within the set.
+ *
+ * @type String
+ */
+ emptyPlaceholder : '@',
+
+ /**
+ * The translation key of the text which should be displayed if no
+ * identifiers are available to be added within the set.
+ *
+ * @type String
+ */
+ unavailablePlaceholder : '@',
+
+ /**
+ * All identifiers which are available to be added to or removed
+ * from the identifier set being edited.
+ *
+ * @type String[]
+ */
+ identifiersAvailable : '=',
+
+ /**
+ * The current state of the identifier set being manipulated. This
+ * array will be modified as changes are made through this
+ * identifier set editor.
+ *
+ * @type String[]
+ */
+ identifiers : '=',
+
+ /**
+ * The set of identifiers that have been added, relative to the
+ * initial state of the identifier set being manipulated.
+ *
+ * @type String[]
+ */
+ identifiersAdded : '=',
+
+ /**
+ * The set of identifiers that have been removed, relative to the
+ * initial state of the identifier set being manipulated.
+ *
+ * @type String[]
+ */
+ identifiersRemoved : '='
+
+ },
+
+ templateUrl: 'app/manage/templates/identifierSetEditor.html'
+
+ };
+
+ directive.controller = ['$scope', function identifierSetEditorController($scope) {
+
+ /**
+ * Whether the full list of available identifiers should be displayed.
+ * Initially, only an abbreviated list of identifiers currently present
+ * is shown.
+ *
+ * @type Boolean
+ */
+ $scope.expanded = false;
+
+ /**
+ * Map of identifiers to boolean flags indicating whether that
+ * identifier is currently present (true) or absent (false). If an
+ * identifier is absent, it may also be absent from this map.
+ *
+ * @type Object.<String, Boolean>
+ */
+ $scope.identifierFlags = {};
+
+ /**
+ * Map of identifiers to boolean flags indicating whether that
+ * identifier is editable. If an identifier is not editable, it will be
+ * absent from this map.
+ *
+ * @type Object.<String, Boolean>
+ */
+ $scope.isEditable = {};
+
+ /**
+ * Adds the given identifier to the given sorted array of identifiers,
+ * preserving the sorted order of the array. If the identifier is
+ * already present, no change is made to the array. The given array
+ * must already be sorted in ascending order.
+ *
+ * @param {String[]} arr
+ * The sorted array of identifiers to add the given identifier to.
+ *
+ * @param {String} identifier
+ * The identifier to add to the given array.
+ */
+ var addIdentifier = function addIdentifier(arr, identifier) {
+
+ // Determine location that the identifier should be added to
+ // maintain sorted order
+ var index = _.sortedIndex(arr, identifier);
+
+ // Do not add if already present
+ if (arr[index] === identifier)
+ return;
+
+ // Insert identifier at determined location
+ arr.splice(index, 0, identifier);
+
+ };
+
+ /**
+ * Removes the given identifier from the given sorted array of
+ * identifiers, preserving the sorted order of the array. If the
+ * identifier is already absent, no change is made to the array. The
+ * given array must already be sorted in ascending order.
+ *
+ * @param {String[]} arr
+ * The sorted array of identifiers to remove the given identifier
+ * from.
+ *
+ * @param {String} identifier
+ * The identifier to remove from the given array.
+ *
+ * @returns {Boolean}
+ * true if the identifier was present in the given array and has
+ * been removed, false otherwise.
+ */
+ var removeIdentifier = function removeIdentifier(arr, identifier) {
+
+ // Search for identifier in sorted array
+ var index = _.sortedIndexOf(arr, identifier);
+
+ // Nothing to do if already absent
+ if (index === -1)
+ return false;
+
+ // Remove identifier
+ arr.splice(index, 1);
+ return true;
+
+ };
+
+ // Keep identifierFlags up to date when identifiers array is replaced
+ // or initially assigned
+ $scope.$watch('identifiers', function identifiersChanged(identifiers) {
+
+ // Maintain identifiers in sorted order so additions and removals
+ // can be made more efficiently
+ if (identifiers)
+ identifiers.sort();
+
+ // Convert array of identifiers into set of boolean
+ // presence/absence flags
+ $scope.identifierFlags = {};
+ angular.forEach(identifiers, function storeIdentifierFlag(identifier) {
+ $scope.identifierFlags[identifier] = true;
+ });
+
+ });
+
+ // Keep identifierFlags up to date when identifiers array is replaced
--- End diff --
OK, I've rebased away the pasta.
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by necouchman <gi...@git.apache.org>.
Github user necouchman commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r208729500
--- Diff: guacamole/src/main/webapp/app/manage/controllers/manageUserGroupController.js ---
@@ -0,0 +1,535 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * The controller for editing user groups.
+ */
+angular.module('manage').controller('manageUserGroupController', ['$scope', '$injector',
+ function manageUserGroupController($scope, $injector) {
+
+ // Required types
+ var ManagementPermissions = $injector.get('ManagementPermissions');
+ var PermissionFlagSet = $injector.get('PermissionFlagSet');
+ var PermissionSet = $injector.get('PermissionSet');
+ var UserGroup = $injector.get('UserGroup');
+
+ // Required services
+ var $location = $injector.get('$location');
+ var $routeParams = $injector.get('$routeParams');
+ var $q = $injector.get('$q');
+ var authenticationService = $injector.get('authenticationService');
+ var dataSourceService = $injector.get('dataSourceService');
+ var membershipService = $injector.get('membershipService');
+ var permissionService = $injector.get('permissionService');
+ var requestService = $injector.get('requestService');
+ var schemaService = $injector.get('schemaService');
+ var userGroupService = $injector.get('userGroupService');
+ var userService = $injector.get('userService');
+
+ /**
+ * The identifiers of all data sources currently available to the
+ * authenticated user.
+ *
+ * @type String[]
+ */
+ var dataSources = authenticationService.getAvailableDataSources();
+
+ /**
+ * The username of the current, authenticated user.
+ *
+ * @type String
+ */
+ var currentUsername = authenticationService.getCurrentUsername();
+
+ /**
+ * The identifier of the original user group from which this user group is
+ * being cloned. Only valid if this is a new user group.
+ *
+ * @type String
+ */
+ var cloneSourceIdentifier = $location.search().clone;
+
+ /**
+ * The identifier of the user group being edited. If a new user group is
+ * being created, this will not be defined.
+ *
+ * @type String
+ */
+ var identifier = $routeParams.id;
+
+ /**
+ * The unique identifier of the data source containing the user group being
+ * edited.
+ *
+ * @type String
+ */
+ $scope.dataSource = $routeParams.dataSource;
+
+ /**
+ * All user groups associated with the same identifier as the group being
+ * created or edited, as a map of data source identifier to the UserGroup
+ * object within that data source.
+ *
+ * @type Object.<String, UserGroup>
+ */
+ $scope.userGroups = null;
+
+ /**
+ * The user group being modified.
+ *
+ * @type UserGroup
+ */
+ $scope.userGroup = null;
+
+ /**
+ * All permissions associated with the user group being modified.
+ *
+ * @type PermissionFlagSet
+ */
+ $scope.permissionFlags = null;
+
+ /**
+ * The set of permissions that will be added to the user group when the
+ * user group is saved. Permissions will only be present in this set if they
+ * are manually added, and not later manually removed before saving.
+ *
+ * @type PermissionSet
+ */
+ $scope.permissionsAdded = new PermissionSet();
+
+ /**
+ * The set of permissions that will be removed from the user group when the
+ * user group is saved. Permissions will only be present in this set if they
+ * are manually removed, and not later manually added before saving.
+ *
+ * @type PermissionSet
+ */
+ $scope.permissionsRemoved = new PermissionSet();
+
+ /**
+ * The identifiers of all user groups which can be manipulated (all groups
+ * for which the user accessing this interface has UPDATE permission),
+ * whether that means changing the members of those groups or changing the
+ * groups of which those groups are members. If this information has not
+ * yet been retrieved, this will be null.
+ *
+ * @type String[]
+ */
+ $scope.availableGroups = null;
+
+ /**
+ * The identifiers of all users which can be manipulated (all users for
+ * which the user accessing this interface has UPDATE permission), either
+ * through adding those users as a member of the current group or removing
+ * those users from the current group. If this information has not yet been
+ * retrieved, this will be null.
+ *
+ * @type String[]
+ */
+ $scope.availableUsers = null;
+
+ /**
+ * The identifiers of all user groups of which this group is a member,
+ * taking into account any user groups which will be added/removed when
+ * saved. If this information has not yet been retrieved, this will be
+ * null.
+ *
+ * @type String[]
+ */
+ $scope.parentGroups = null;
+
+ /**
+ * The set of identifiers of all parent user groups to which this group
+ * will be added when saved. Parent groups will only be present in this set
+ * if they are manually added, and not later manually removed before
+ * saving.
+ *
+ * @type String[]
+ */
+ $scope.parentGroupsAdded = [];
+
+ /**
+ * The set of identifiers of all parent user groups from which this group
+ * will be removed when saved. Parent groups will only be present in this
+ * set if they are manually removed, and not later manually added before
+ * saving.
+ *
+ * @type String[]
+ */
+ $scope.parentGroupsRemoved = [];
+
+ /**
+ * The identifiers of all user groups which are members of this group,
+ * taking into account any user groups which will be added/removed when
+ * saved. If this information has not yet been retrieved, this will be
+ * null.
+ *
+ * @type String[]
+ */
+ $scope.memberGroups = null;
+
+ /**
+ * The set of identifiers of all member user groups which will be added to
+ * this group when saved. Member groups will only be present in this set if
+ * they are manually added, and not later manually removed before saving.
+ *
+ * @type String[]
+ */
+ $scope.memberGroupsAdded = [];
+
+ /**
+ * The set of identifiers of all member user groups which will be removed
+ * from this group when saved. Member groups will only be present in this
+ * set if they are manually removed, and not later manually added before
+ * saving.
+ *
+ * @type String[]
+ */
+ $scope.memberGroupsRemoved = [];
+
+ /**
+ * The identifiers of all users which are members of this group, taking
+ * into account any users which will be added/removed when saved. If this
+ * information has not yet been retrieved, this will be null.
+ *
+ * @type String[]
+ */
+ $scope.memberUsers = null;
+
+ /**
+ * The set of identifiers of all member users which will be added to this
+ * group when saved. Member users will only be present in this set if they
+ * are manually added, and not later manually removed before saving.
+ *
+ * @type String[]
+ */
+ $scope.memberUsersAdded = [];
+
+ /**
+ * The set of identifiers of all member users which will be removed from
+ * this group when saved. Member users will only be present in this set if
+ * they are manually removed, and not later manually added before saving.
+ *
+ * @type String[]
+ */
+ $scope.memberUsersRemoved = [];
+
+ /**
+ * For each applicable data source, the management-related actions that the
+ * current user may perform on the user group currently being created
+ * or modified, as a map of data source identifier to the
+ * {@link ManagementPermissions} object describing the actions available
+ * within that data source, or null if the current user's permissions have
+ * not yet been loaded.
+ *
+ * @type Object.<String, ManagementPermissions>
+ */
+ $scope.managementPermissions = null;
+
+ /**
+ * All available user group attributes. This is only the set of attribute
+ * definitions, organized as logical groupings of attributes, not attribute
+ * values.
+ *
+ * @type Form[]
+ */
+ $scope.attributes = null;
+
+ /**
+ * Returns whether critical data has completed being loaded.
+ *
+ * @returns {Boolean}
+ * true if enough data has been loaded for the user group interface to
+ * be useful, false otherwise.
+ */
+ $scope.isLoaded = function isLoaded() {
+
+ return $scope.userGroups !== null
+ && $scope.permissionFlags !== null
+ && $scope.managementPermissions !== null
+ && $scope.availableGroups !== null
+ && $scope.availableUsers !== null
+ && $scope.parentGroups !== null
+ && $scope.memberGroups !== null
+ && $scope.memberUsers !== null
+ && $scope.attributes !== null;
+
+ };
+
+ /**
+ * Returns whether the current user can edit the identifier of the user
+ * group being edited.
+ *
+ * @returns {Boolean}
+ * true if the current user can edit the identifier of the user group
+ * being edited, false otherwise.
+ */
+ $scope.canEditIdentifier = function canEditIdentifier() {
+ return !identifier;
+ };
+
+ /**
+ * Loads the data associated with the user group having the given
+ * identifier, preparing the interface for making modifications to that
+ * existing user. group
--- End diff --
Placement of period is ever so slightly off.
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by mike-jumper <gi...@git.apache.org>.
Github user mike-jumper commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r208803736
--- Diff: guacamole/src/main/webapp/app/manage/directives/identifierSetEditor.js ---
@@ -0,0 +1,299 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * A directive for manipulating a set of objects sharing some common relation
+ * and represented by an array of their identifiers. The specific objects
+ * added or removed are tracked within a separate pair of arrays of
+ * identifiers.
+ */
+angular.module('manage').directive('identifierSetEditor', ['$injector',
+ function identifierSetEditor($injector) {
+
+ var directive = {
+
+ // Element only
+ restrict: 'E',
+ replace: true,
+
+ scope: {
+
+ /**
+ * The translation key of the text which should be displayed within
+ * the main header of the identifier set editor.
+ *
+ * @type String
+ */
+ header : '@',
+
+ /**
+ * The translation key of the text which should be displayed if no
+ * identifiers are currently present within the set.
+ *
+ * @type String
+ */
+ emptyPlaceholder : '@',
+
+ /**
+ * The translation key of the text which should be displayed if no
+ * identifiers are available to be added within the set.
+ *
+ * @type String
+ */
+ unavailablePlaceholder : '@',
+
+ /**
+ * All identifiers which are available to be added to or removed
+ * from the identifier set being edited.
+ *
+ * @type String[]
+ */
+ identifiersAvailable : '=',
+
+ /**
+ * The current state of the identifier set being manipulated. This
+ * array will be modified as changes are made through this
+ * identifier set editor.
+ *
+ * @type String[]
+ */
+ identifiers : '=',
+
+ /**
+ * The set of identifiers that have been added, relative to the
+ * initial state of the identifier set being manipulated.
+ *
+ * @type String[]
+ */
+ identifiersAdded : '=',
+
+ /**
+ * The set of identifiers that have been removed, relative to the
+ * initial state of the identifier set being manipulated.
+ *
+ * @type String[]
+ */
+ identifiersRemoved : '='
+
+ },
+
+ templateUrl: 'app/manage/templates/identifierSetEditor.html'
+
+ };
+
+ directive.controller = ['$scope', function identifierSetEditorController($scope) {
+
+ /**
+ * Whether the full list of available identifiers should be displayed.
+ * Initially, only an abbreviated list of identifiers currently present
+ * is shown.
+ *
+ * @type Boolean
+ */
+ $scope.expanded = false;
+
+ /**
+ * Map of identifiers to boolean flags indicating whether that
+ * identifier is currently present (true) or absent (false). If an
+ * identifier is absent, it may also be absent from this map.
+ *
+ * @type Object.<String, Boolean>
+ */
+ $scope.identifierFlags = {};
+
+ /**
+ * Map of identifiers to boolean flags indicating whether that
+ * identifier is editabl. If an identifier is not editable, it will be
--- End diff --
Fixed via rebase.
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by mike-jumper <gi...@git.apache.org>.
Github user mike-jumper commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r209020994
--- Diff: guacamole/src/main/webapp/app/manage/templates/manageUserGroup.html ---
@@ -0,0 +1,102 @@
+
--- End diff --
Extra blank removed. GitHub has unfortunately now reassigned this comment to the intentional blank line following the first `<div>`, but the original issue should be addressed.
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by necouchman <gi...@git.apache.org>.
Github user necouchman commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r208731546
--- Diff: guacamole/src/main/webapp/app/manage/controllers/manageUserGroupController.js ---
@@ -0,0 +1,535 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * The controller for editing user groups.
+ */
+angular.module('manage').controller('manageUserGroupController', ['$scope', '$injector',
+ function manageUserGroupController($scope, $injector) {
+
+ // Required types
+ var ManagementPermissions = $injector.get('ManagementPermissions');
+ var PermissionFlagSet = $injector.get('PermissionFlagSet');
+ var PermissionSet = $injector.get('PermissionSet');
+ var UserGroup = $injector.get('UserGroup');
+
+ // Required services
+ var $location = $injector.get('$location');
+ var $routeParams = $injector.get('$routeParams');
+ var $q = $injector.get('$q');
+ var authenticationService = $injector.get('authenticationService');
+ var dataSourceService = $injector.get('dataSourceService');
+ var membershipService = $injector.get('membershipService');
+ var permissionService = $injector.get('permissionService');
+ var requestService = $injector.get('requestService');
+ var schemaService = $injector.get('schemaService');
+ var userGroupService = $injector.get('userGroupService');
+ var userService = $injector.get('userService');
+
+ /**
+ * The identifiers of all data sources currently available to the
+ * authenticated user.
+ *
+ * @type String[]
+ */
+ var dataSources = authenticationService.getAvailableDataSources();
+
+ /**
+ * The username of the current, authenticated user.
+ *
+ * @type String
+ */
+ var currentUsername = authenticationService.getCurrentUsername();
+
+ /**
+ * The identifier of the original user group from which this user group is
+ * being cloned. Only valid if this is a new user group.
+ *
+ * @type String
+ */
+ var cloneSourceIdentifier = $location.search().clone;
+
+ /**
+ * The identifier of the user group being edited. If a new user group is
+ * being created, this will not be defined.
+ *
+ * @type String
+ */
+ var identifier = $routeParams.id;
+
+ /**
+ * The unique identifier of the data source containing the user group being
+ * edited.
+ *
+ * @type String
+ */
+ $scope.dataSource = $routeParams.dataSource;
+
+ /**
+ * All user groups associated with the same identifier as the group being
+ * created or edited, as a map of data source identifier to the UserGroup
+ * object within that data source.
+ *
+ * @type Object.<String, UserGroup>
+ */
+ $scope.userGroups = null;
+
+ /**
+ * The user group being modified.
+ *
+ * @type UserGroup
+ */
+ $scope.userGroup = null;
+
+ /**
+ * All permissions associated with the user group being modified.
+ *
+ * @type PermissionFlagSet
+ */
+ $scope.permissionFlags = null;
+
+ /**
+ * The set of permissions that will be added to the user group when the
+ * user group is saved. Permissions will only be present in this set if they
+ * are manually added, and not later manually removed before saving.
+ *
+ * @type PermissionSet
+ */
+ $scope.permissionsAdded = new PermissionSet();
+
+ /**
+ * The set of permissions that will be removed from the user group when the
+ * user group is saved. Permissions will only be present in this set if they
+ * are manually removed, and not later manually added before saving.
+ *
+ * @type PermissionSet
+ */
+ $scope.permissionsRemoved = new PermissionSet();
+
+ /**
+ * The identifiers of all user groups which can be manipulated (all groups
+ * for which the user accessing this interface has UPDATE permission),
+ * whether that means changing the members of those groups or changing the
+ * groups of which those groups are members. If this information has not
+ * yet been retrieved, this will be null.
+ *
+ * @type String[]
+ */
+ $scope.availableGroups = null;
+
+ /**
+ * The identifiers of all users which can be manipulated (all users for
+ * which the user accessing this interface has UPDATE permission), either
+ * through adding those users as a member of the current group or removing
+ * those users from the current group. If this information has not yet been
+ * retrieved, this will be null.
+ *
+ * @type String[]
+ */
+ $scope.availableUsers = null;
+
+ /**
+ * The identifiers of all user groups of which this group is a member,
+ * taking into account any user groups which will be added/removed when
+ * saved. If this information has not yet been retrieved, this will be
+ * null.
+ *
+ * @type String[]
+ */
+ $scope.parentGroups = null;
+
+ /**
+ * The set of identifiers of all parent user groups to which this group
+ * will be added when saved. Parent groups will only be present in this set
+ * if they are manually added, and not later manually removed before
+ * saving.
+ *
+ * @type String[]
+ */
+ $scope.parentGroupsAdded = [];
+
+ /**
+ * The set of identifiers of all parent user groups from which this group
+ * will be removed when saved. Parent groups will only be present in this
+ * set if they are manually removed, and not later manually added before
+ * saving.
+ *
+ * @type String[]
+ */
+ $scope.parentGroupsRemoved = [];
+
+ /**
+ * The identifiers of all user groups which are members of this group,
+ * taking into account any user groups which will be added/removed when
+ * saved. If this information has not yet been retrieved, this will be
+ * null.
+ *
+ * @type String[]
+ */
+ $scope.memberGroups = null;
+
+ /**
+ * The set of identifiers of all member user groups which will be added to
+ * this group when saved. Member groups will only be present in this set if
+ * they are manually added, and not later manually removed before saving.
+ *
+ * @type String[]
+ */
+ $scope.memberGroupsAdded = [];
+
+ /**
+ * The set of identifiers of all member user groups which will be removed
+ * from this group when saved. Member groups will only be present in this
+ * set if they are manually removed, and not later manually added before
+ * saving.
+ *
+ * @type String[]
+ */
+ $scope.memberGroupsRemoved = [];
+
+ /**
+ * The identifiers of all users which are members of this group, taking
+ * into account any users which will be added/removed when saved. If this
+ * information has not yet been retrieved, this will be null.
+ *
+ * @type String[]
+ */
+ $scope.memberUsers = null;
+
+ /**
+ * The set of identifiers of all member users which will be added to this
+ * group when saved. Member users will only be present in this set if they
+ * are manually added, and not later manually removed before saving.
+ *
+ * @type String[]
+ */
+ $scope.memberUsersAdded = [];
+
+ /**
+ * The set of identifiers of all member users which will be removed from
+ * this group when saved. Member users will only be present in this set if
+ * they are manually removed, and not later manually added before saving.
+ *
+ * @type String[]
+ */
+ $scope.memberUsersRemoved = [];
+
+ /**
+ * For each applicable data source, the management-related actions that the
+ * current user may perform on the user group currently being created
+ * or modified, as a map of data source identifier to the
+ * {@link ManagementPermissions} object describing the actions available
+ * within that data source, or null if the current user's permissions have
+ * not yet been loaded.
+ *
+ * @type Object.<String, ManagementPermissions>
+ */
+ $scope.managementPermissions = null;
+
+ /**
+ * All available user group attributes. This is only the set of attribute
+ * definitions, organized as logical groupings of attributes, not attribute
+ * values.
+ *
+ * @type Form[]
+ */
+ $scope.attributes = null;
+
+ /**
+ * Returns whether critical data has completed being loaded.
+ *
+ * @returns {Boolean}
+ * true if enough data has been loaded for the user group interface to
+ * be useful, false otherwise.
+ */
+ $scope.isLoaded = function isLoaded() {
+
+ return $scope.userGroups !== null
+ && $scope.permissionFlags !== null
+ && $scope.managementPermissions !== null
+ && $scope.availableGroups !== null
+ && $scope.availableUsers !== null
+ && $scope.parentGroups !== null
+ && $scope.memberGroups !== null
+ && $scope.memberUsers !== null
+ && $scope.attributes !== null;
+
+ };
+
+ /**
+ * Returns whether the current user can edit the identifier of the user
+ * group being edited.
+ *
+ * @returns {Boolean}
+ * true if the current user can edit the identifier of the user group
+ * being edited, false otherwise.
+ */
+ $scope.canEditIdentifier = function canEditIdentifier() {
+ return !identifier;
+ };
+
+ /**
+ * Loads the data associated with the user group having the given
+ * identifier, preparing the interface for making modifications to that
+ * existing user. group
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the user group
+ * to load.
+ *
+ * @param {String} identifier
+ * The unique identifier of the user group to load.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * editing the given user group.
+ */
+ var loadExistingUserGroup = function loadExistingGroup(dataSource, identifier) {
+ return $q.all({
+ userGroups : dataSourceService.apply(userGroupService.getUserGroup, dataSources, identifier),
+ permissions : permissionService.getPermissions(dataSource, identifier, true),
+ parentGroups : membershipService.getUserGroups(dataSource, identifier, true),
+ memberGroups : membershipService.getMemberUserGroups(dataSource, identifier),
+ memberUsers : membershipService.getMemberUsers(dataSource, identifier)
+ })
+ .then(function userGroupDataRetrieved(values) {
+
+ $scope.userGroups = values.userGroups;
+ $scope.userGroup = values.userGroups[dataSource];
+ $scope.parentGroups = values.parentGroups;
+ $scope.memberGroups = values.memberGroups;
+ $scope.memberUsers = values.memberUsers;
+
+ // Create skeleton user group if user group does not exist
+ if (!$scope.userGroup)
+ $scope.userGroup = new UserGroup({
+ 'identifier' : identifier
+ });
+
+ $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(values.permissions);
+
+ });
+ };
+
+ /**
+ * Loads the data associated with the user group having the given
+ * identifier, preparing the interface for cloning that existing user
+ * group.
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the user group to
+ * be cloned.
+ *
+ * @param {String} identifier
+ * The unique identifier of the user group being cloned.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * cloning the given user group.
+ */
+ var loadClonedUserGroup = function loadClonedUserGroup(dataSource, identifier) {
+ return $q.all({
+ userGroups : dataSourceService.apply(userGroupService.getUserGroup, [dataSource], identifier),
+ permissions : permissionService.getPermissions(dataSource, identifier, true),
+ parentGroups : membershipService.getUserGroups(dataSource, identifier, true),
+ memberGroups : membershipService.getMemberUserGroups(dataSource, identifier),
+ memberUsers : membershipService.getMemberUsers(dataSource, identifier)
+ })
+ .then(function userGroupDataRetrieved(values) {
+
+ $scope.userGroups = {};
+ $scope.userGroup = values.userGroups[dataSource];
+ $scope.parentGroups = values.parentGroups;
+ $scope.parentGroupsAdded = values.parentGroups;
+ $scope.memberGroups = values.memberGroups;
+ $scope.memberGroupsAdded = values.memberGroups;
+ $scope.memberUsers = values.memberUsers;
+ $scope.memberUsersAdded = values.memberUsers;
+
+ $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(values.permissions);
+ $scope.permissionsAdded = values.permissions;
+
+ });
+ };
+
+ /**
+ * Loads skeleton user group data, preparing the interface for creating a
+ * new user group.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * creating a new user group.
+ */
+ var loadSkeletonUserGroup = function loadSkeletonUserGroup() {
+
+ // No user groups exist regardless of data source if there is no identifier
+ $scope.userGroups = {};
+
+ // Use skeleton user group object with no associated permissions
+ $scope.userGroup = new UserGroup();
+ $scope.parentGroups = [];
+ $scope.memberGroups = [];
+ $scope.memberUsers = [];
+ $scope.permissionFlags = new PermissionFlagSet();
+
+ return $q.resolve();
+
+ };
+
+ /**
+ * Loads the data required for performing the management task requested
+ * through the route parameters given at load time, automatically preparing
+ * the interface for editing an existing user group, cloning an existing
+ * user group, or creating an entirely new user group.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared
+ * for performing the requested management task.
+ */
+ var loadRequestedUserGroup = function loadRequestedUserGroup() {
+
+ // Pull user group data and permissions if we are editing an existing
+ // user group
+ if (identifier)
+ return loadExistingUserGroup($scope.dataSource, identifier);
+
+ // If we are cloning an existing user group, pull its data instead
+ if (cloneSourceIdentifier)
+ return loadClonedUserGroup($scope.dataSource, cloneSourceIdentifier);
+
+ // If we are creating a new user group, populate skeleton user group data
+ return loadSkeletonUserGroup();
+
+ };
+
+ // Populate interface with requested data
+ $q.all({
+ userGroupData : loadRequestedUserGroup(),
+ permissions : dataSourceService.apply(permissionService.getEffectivePermissions, dataSources, currentUsername),
+ userGroups : userGroupService.getUserGroups($scope.dataSource, [ PermissionSet.ObjectPermissionType.UPDATE ]),
+ users : userService.getUsers($scope.dataSource, [ PermissionSet.ObjectPermissionType.UPDATE ]),
+ attributes : schemaService.getUserGroupAttributes($scope.dataSource)
+ })
+ .then(function dataReceived(values) {
+
+ $scope.attributes = values.attributes;
+
+ $scope.managementPermissions = {};
+ angular.forEach(dataSources, function deriveManagementPermissions(dataSource) {
+
+ // Determine whether data source contains this user group
+ var exists = (dataSource in $scope.userGroups);
+
+ // Add the identifiers of all modifiable user groups
+ $scope.availableGroups = [];
+ angular.forEach(values.userGroups, function addUserGroupIdentifier(userGroup) {
+ $scope.availableGroups.push(userGroup.identifier);
+ });
+
+ // Add the identifiers of all modifiable users
+ $scope.availableUsers = [];
+ angular.forEach(values.users, function addUserIdentifier(user) {
+ $scope.availableUsers.push(user.username);
+ });
+
+ // Calculate management actions available for this specific group
+ $scope.managementPermissions[dataSource] = ManagementPermissions.fromPermissionSet(
+ values.permissions[dataSource],
+ PermissionSet.SystemPermissionType.CREATE_USER_GROUP,
+ PermissionSet.hasUserGroupPermission,
+ exists ? identifier : null);
+
+ });
+
+ }, requestService.WARN);
+
+ /**
+ * Returns the URL for the page which manages the user account currently
+ * being edited under the given data source. The given data source need not
+ * be the same as the data source currently selected.
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source that the URL is being
+ * generated for.
+ *
+ * @returns {String}
+ * The URL for the page which manages the user account currently being
+ * edited under the given data source.
+ */
+ $scope.getUserGroupURL = function getUserGroupURL(dataSource) {
+ return '/manage/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier || '');
+ };
+
+ /**
+ * Cancels all pending edits, returning to the main list of users.
+ */
+ $scope.returnToUserGroupList = function returnToUserGroupList() {
+ $location.url('/settings/userGroups');
+ };
+
+ /**
+ * Cancels all pending edits, opening an edit page for a new user
--- End diff --
"new user *group*"
Also on the line below.
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by necouchman <gi...@git.apache.org>.
Github user necouchman commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r208732169
--- Diff: guacamole/src/main/webapp/app/manage/controllers/manageUserGroupController.js ---
@@ -0,0 +1,535 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * The controller for editing user groups.
+ */
+angular.module('manage').controller('manageUserGroupController', ['$scope', '$injector',
+ function manageUserGroupController($scope, $injector) {
+
+ // Required types
+ var ManagementPermissions = $injector.get('ManagementPermissions');
+ var PermissionFlagSet = $injector.get('PermissionFlagSet');
+ var PermissionSet = $injector.get('PermissionSet');
+ var UserGroup = $injector.get('UserGroup');
+
+ // Required services
+ var $location = $injector.get('$location');
+ var $routeParams = $injector.get('$routeParams');
+ var $q = $injector.get('$q');
+ var authenticationService = $injector.get('authenticationService');
+ var dataSourceService = $injector.get('dataSourceService');
+ var membershipService = $injector.get('membershipService');
+ var permissionService = $injector.get('permissionService');
+ var requestService = $injector.get('requestService');
+ var schemaService = $injector.get('schemaService');
+ var userGroupService = $injector.get('userGroupService');
+ var userService = $injector.get('userService');
+
+ /**
+ * The identifiers of all data sources currently available to the
+ * authenticated user.
+ *
+ * @type String[]
+ */
+ var dataSources = authenticationService.getAvailableDataSources();
+
+ /**
+ * The username of the current, authenticated user.
+ *
+ * @type String
+ */
+ var currentUsername = authenticationService.getCurrentUsername();
+
+ /**
+ * The identifier of the original user group from which this user group is
+ * being cloned. Only valid if this is a new user group.
+ *
+ * @type String
+ */
+ var cloneSourceIdentifier = $location.search().clone;
+
+ /**
+ * The identifier of the user group being edited. If a new user group is
+ * being created, this will not be defined.
+ *
+ * @type String
+ */
+ var identifier = $routeParams.id;
+
+ /**
+ * The unique identifier of the data source containing the user group being
+ * edited.
+ *
+ * @type String
+ */
+ $scope.dataSource = $routeParams.dataSource;
+
+ /**
+ * All user groups associated with the same identifier as the group being
+ * created or edited, as a map of data source identifier to the UserGroup
+ * object within that data source.
+ *
+ * @type Object.<String, UserGroup>
+ */
+ $scope.userGroups = null;
+
+ /**
+ * The user group being modified.
+ *
+ * @type UserGroup
+ */
+ $scope.userGroup = null;
+
+ /**
+ * All permissions associated with the user group being modified.
+ *
+ * @type PermissionFlagSet
+ */
+ $scope.permissionFlags = null;
+
+ /**
+ * The set of permissions that will be added to the user group when the
+ * user group is saved. Permissions will only be present in this set if they
+ * are manually added, and not later manually removed before saving.
+ *
+ * @type PermissionSet
+ */
+ $scope.permissionsAdded = new PermissionSet();
+
+ /**
+ * The set of permissions that will be removed from the user group when the
+ * user group is saved. Permissions will only be present in this set if they
+ * are manually removed, and not later manually added before saving.
+ *
+ * @type PermissionSet
+ */
+ $scope.permissionsRemoved = new PermissionSet();
+
+ /**
+ * The identifiers of all user groups which can be manipulated (all groups
+ * for which the user accessing this interface has UPDATE permission),
+ * whether that means changing the members of those groups or changing the
+ * groups of which those groups are members. If this information has not
+ * yet been retrieved, this will be null.
+ *
+ * @type String[]
+ */
+ $scope.availableGroups = null;
+
+ /**
+ * The identifiers of all users which can be manipulated (all users for
+ * which the user accessing this interface has UPDATE permission), either
+ * through adding those users as a member of the current group or removing
+ * those users from the current group. If this information has not yet been
+ * retrieved, this will be null.
+ *
+ * @type String[]
+ */
+ $scope.availableUsers = null;
+
+ /**
+ * The identifiers of all user groups of which this group is a member,
+ * taking into account any user groups which will be added/removed when
+ * saved. If this information has not yet been retrieved, this will be
+ * null.
+ *
+ * @type String[]
+ */
+ $scope.parentGroups = null;
+
+ /**
+ * The set of identifiers of all parent user groups to which this group
+ * will be added when saved. Parent groups will only be present in this set
+ * if they are manually added, and not later manually removed before
+ * saving.
+ *
+ * @type String[]
+ */
+ $scope.parentGroupsAdded = [];
+
+ /**
+ * The set of identifiers of all parent user groups from which this group
+ * will be removed when saved. Parent groups will only be present in this
+ * set if they are manually removed, and not later manually added before
+ * saving.
+ *
+ * @type String[]
+ */
+ $scope.parentGroupsRemoved = [];
+
+ /**
+ * The identifiers of all user groups which are members of this group,
+ * taking into account any user groups which will be added/removed when
+ * saved. If this information has not yet been retrieved, this will be
+ * null.
+ *
+ * @type String[]
+ */
+ $scope.memberGroups = null;
+
+ /**
+ * The set of identifiers of all member user groups which will be added to
+ * this group when saved. Member groups will only be present in this set if
+ * they are manually added, and not later manually removed before saving.
+ *
+ * @type String[]
+ */
+ $scope.memberGroupsAdded = [];
+
+ /**
+ * The set of identifiers of all member user groups which will be removed
+ * from this group when saved. Member groups will only be present in this
+ * set if they are manually removed, and not later manually added before
+ * saving.
+ *
+ * @type String[]
+ */
+ $scope.memberGroupsRemoved = [];
+
+ /**
+ * The identifiers of all users which are members of this group, taking
+ * into account any users which will be added/removed when saved. If this
+ * information has not yet been retrieved, this will be null.
+ *
+ * @type String[]
+ */
+ $scope.memberUsers = null;
+
+ /**
+ * The set of identifiers of all member users which will be added to this
+ * group when saved. Member users will only be present in this set if they
+ * are manually added, and not later manually removed before saving.
+ *
+ * @type String[]
+ */
+ $scope.memberUsersAdded = [];
+
+ /**
+ * The set of identifiers of all member users which will be removed from
+ * this group when saved. Member users will only be present in this set if
+ * they are manually removed, and not later manually added before saving.
+ *
+ * @type String[]
+ */
+ $scope.memberUsersRemoved = [];
+
+ /**
+ * For each applicable data source, the management-related actions that the
+ * current user may perform on the user group currently being created
+ * or modified, as a map of data source identifier to the
+ * {@link ManagementPermissions} object describing the actions available
+ * within that data source, or null if the current user's permissions have
+ * not yet been loaded.
+ *
+ * @type Object.<String, ManagementPermissions>
+ */
+ $scope.managementPermissions = null;
+
+ /**
+ * All available user group attributes. This is only the set of attribute
+ * definitions, organized as logical groupings of attributes, not attribute
+ * values.
+ *
+ * @type Form[]
+ */
+ $scope.attributes = null;
+
+ /**
+ * Returns whether critical data has completed being loaded.
+ *
+ * @returns {Boolean}
+ * true if enough data has been loaded for the user group interface to
+ * be useful, false otherwise.
+ */
+ $scope.isLoaded = function isLoaded() {
+
+ return $scope.userGroups !== null
+ && $scope.permissionFlags !== null
+ && $scope.managementPermissions !== null
+ && $scope.availableGroups !== null
+ && $scope.availableUsers !== null
+ && $scope.parentGroups !== null
+ && $scope.memberGroups !== null
+ && $scope.memberUsers !== null
+ && $scope.attributes !== null;
+
+ };
+
+ /**
+ * Returns whether the current user can edit the identifier of the user
+ * group being edited.
+ *
+ * @returns {Boolean}
+ * true if the current user can edit the identifier of the user group
+ * being edited, false otherwise.
+ */
+ $scope.canEditIdentifier = function canEditIdentifier() {
+ return !identifier;
+ };
+
+ /**
+ * Loads the data associated with the user group having the given
+ * identifier, preparing the interface for making modifications to that
+ * existing user. group
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the user group
+ * to load.
+ *
+ * @param {String} identifier
+ * The unique identifier of the user group to load.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * editing the given user group.
+ */
+ var loadExistingUserGroup = function loadExistingGroup(dataSource, identifier) {
+ return $q.all({
+ userGroups : dataSourceService.apply(userGroupService.getUserGroup, dataSources, identifier),
+ permissions : permissionService.getPermissions(dataSource, identifier, true),
+ parentGroups : membershipService.getUserGroups(dataSource, identifier, true),
+ memberGroups : membershipService.getMemberUserGroups(dataSource, identifier),
+ memberUsers : membershipService.getMemberUsers(dataSource, identifier)
+ })
+ .then(function userGroupDataRetrieved(values) {
+
+ $scope.userGroups = values.userGroups;
+ $scope.userGroup = values.userGroups[dataSource];
+ $scope.parentGroups = values.parentGroups;
+ $scope.memberGroups = values.memberGroups;
+ $scope.memberUsers = values.memberUsers;
+
+ // Create skeleton user group if user group does not exist
+ if (!$scope.userGroup)
+ $scope.userGroup = new UserGroup({
+ 'identifier' : identifier
+ });
+
+ $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(values.permissions);
+
+ });
+ };
+
+ /**
+ * Loads the data associated with the user group having the given
+ * identifier, preparing the interface for cloning that existing user
+ * group.
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the user group to
+ * be cloned.
+ *
+ * @param {String} identifier
+ * The unique identifier of the user group being cloned.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * cloning the given user group.
+ */
+ var loadClonedUserGroup = function loadClonedUserGroup(dataSource, identifier) {
+ return $q.all({
+ userGroups : dataSourceService.apply(userGroupService.getUserGroup, [dataSource], identifier),
+ permissions : permissionService.getPermissions(dataSource, identifier, true),
+ parentGroups : membershipService.getUserGroups(dataSource, identifier, true),
+ memberGroups : membershipService.getMemberUserGroups(dataSource, identifier),
+ memberUsers : membershipService.getMemberUsers(dataSource, identifier)
+ })
+ .then(function userGroupDataRetrieved(values) {
+
+ $scope.userGroups = {};
+ $scope.userGroup = values.userGroups[dataSource];
+ $scope.parentGroups = values.parentGroups;
+ $scope.parentGroupsAdded = values.parentGroups;
+ $scope.memberGroups = values.memberGroups;
+ $scope.memberGroupsAdded = values.memberGroups;
+ $scope.memberUsers = values.memberUsers;
+ $scope.memberUsersAdded = values.memberUsers;
+
+ $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(values.permissions);
+ $scope.permissionsAdded = values.permissions;
+
+ });
+ };
+
+ /**
+ * Loads skeleton user group data, preparing the interface for creating a
+ * new user group.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * creating a new user group.
+ */
+ var loadSkeletonUserGroup = function loadSkeletonUserGroup() {
+
+ // No user groups exist regardless of data source if there is no identifier
+ $scope.userGroups = {};
+
+ // Use skeleton user group object with no associated permissions
+ $scope.userGroup = new UserGroup();
+ $scope.parentGroups = [];
+ $scope.memberGroups = [];
+ $scope.memberUsers = [];
+ $scope.permissionFlags = new PermissionFlagSet();
+
+ return $q.resolve();
+
+ };
+
+ /**
+ * Loads the data required for performing the management task requested
+ * through the route parameters given at load time, automatically preparing
+ * the interface for editing an existing user group, cloning an existing
+ * user group, or creating an entirely new user group.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared
+ * for performing the requested management task.
+ */
+ var loadRequestedUserGroup = function loadRequestedUserGroup() {
+
+ // Pull user group data and permissions if we are editing an existing
+ // user group
+ if (identifier)
+ return loadExistingUserGroup($scope.dataSource, identifier);
+
+ // If we are cloning an existing user group, pull its data instead
+ if (cloneSourceIdentifier)
+ return loadClonedUserGroup($scope.dataSource, cloneSourceIdentifier);
+
+ // If we are creating a new user group, populate skeleton user group data
+ return loadSkeletonUserGroup();
+
+ };
+
+ // Populate interface with requested data
+ $q.all({
+ userGroupData : loadRequestedUserGroup(),
+ permissions : dataSourceService.apply(permissionService.getEffectivePermissions, dataSources, currentUsername),
+ userGroups : userGroupService.getUserGroups($scope.dataSource, [ PermissionSet.ObjectPermissionType.UPDATE ]),
+ users : userService.getUsers($scope.dataSource, [ PermissionSet.ObjectPermissionType.UPDATE ]),
+ attributes : schemaService.getUserGroupAttributes($scope.dataSource)
+ })
+ .then(function dataReceived(values) {
+
+ $scope.attributes = values.attributes;
+
+ $scope.managementPermissions = {};
+ angular.forEach(dataSources, function deriveManagementPermissions(dataSource) {
+
+ // Determine whether data source contains this user group
+ var exists = (dataSource in $scope.userGroups);
+
+ // Add the identifiers of all modifiable user groups
+ $scope.availableGroups = [];
+ angular.forEach(values.userGroups, function addUserGroupIdentifier(userGroup) {
+ $scope.availableGroups.push(userGroup.identifier);
+ });
+
+ // Add the identifiers of all modifiable users
+ $scope.availableUsers = [];
+ angular.forEach(values.users, function addUserIdentifier(user) {
+ $scope.availableUsers.push(user.username);
+ });
+
+ // Calculate management actions available for this specific group
+ $scope.managementPermissions[dataSource] = ManagementPermissions.fromPermissionSet(
+ values.permissions[dataSource],
+ PermissionSet.SystemPermissionType.CREATE_USER_GROUP,
+ PermissionSet.hasUserGroupPermission,
+ exists ? identifier : null);
+
+ });
+
+ }, requestService.WARN);
+
+ /**
+ * Returns the URL for the page which manages the user account currently
+ * being edited under the given data source. The given data source need not
+ * be the same as the data source currently selected.
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source that the URL is being
+ * generated for.
+ *
+ * @returns {String}
+ * The URL for the page which manages the user account currently being
+ * edited under the given data source.
+ */
+ $scope.getUserGroupURL = function getUserGroupURL(dataSource) {
+ return '/manage/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier || '');
+ };
+
+ /**
+ * Cancels all pending edits, returning to the main list of users.
+ */
+ $scope.returnToUserGroupList = function returnToUserGroupList() {
+ $location.url('/settings/userGroups');
+ };
+
+ /**
+ * Cancels all pending edits, opening an edit page for a new user
+ * which is prepopulated with the data from the user currently being edited.
+ */
+ $scope.cloneUserGroup = function cloneUserGroup() {
+ $location.path('/manage/' + encodeURIComponent($scope.dataSource) + '/userGroups').search('clone', identifier);
+ };
+
+ /**
+ * Saves the current user, creating a new user or updating the existing
+ * user depending on context, returning a promise which is resolved if the
+ * save operation succeeds and rejected if the save operation fails.
+ *
+ * @returns {Promise}
+ * A promise which is resolved if the save operation succeeds and is
+ * rejected with an {@link Error} if the save operation fails.
+ */
+ $scope.saveUserGroup = function saveUserGroup() {
+
+ // Save or create the user group, depending on whether the user group exists
+ var saveUserGroupPromise;
+ if ($scope.dataSource in $scope.userGroups)
+ saveUserGroupPromise = userGroupService.saveUserGroup($scope.dataSource, $scope.userGroup);
+ else
+ saveUserGroupPromise = userGroupService.createUserGroup($scope.dataSource, $scope.userGroup);
+
+ return saveUserGroupPromise.then(function savedUserGroup() {
+ return $q.all([
+ permissionService.patchPermissions($scope.dataSource, $scope.userGroup.identifier, $scope.permissionsAdded, $scope.permissionsRemoved, true),
+ membershipService.patchUserGroups($scope.dataSource, $scope.userGroup.identifier, $scope.parentGroupsAdded, $scope.parentGroupsRemoved, true),
+ membershipService.patchMemberUserGroups($scope.dataSource, $scope.userGroup.identifier, $scope.memberGroupsAdded, $scope.memberGroupsRemoved),
+ membershipService.patchMemberUsers($scope.dataSource, $scope.userGroup.identifier, $scope.memberUsersAdded, $scope.memberUsersRemoved)
+ ]);
+ });
+
+ };
+
+ /**
+ * Deletes the current user, returning a promise which is resolved if the
--- End diff --
:spaghetti: user -> user group
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by necouchman <gi...@git.apache.org>.
Github user necouchman commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r208732077
--- Diff: guacamole/src/main/webapp/app/manage/controllers/manageUserGroupController.js ---
@@ -0,0 +1,535 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * The controller for editing user groups.
+ */
+angular.module('manage').controller('manageUserGroupController', ['$scope', '$injector',
+ function manageUserGroupController($scope, $injector) {
+
+ // Required types
+ var ManagementPermissions = $injector.get('ManagementPermissions');
+ var PermissionFlagSet = $injector.get('PermissionFlagSet');
+ var PermissionSet = $injector.get('PermissionSet');
+ var UserGroup = $injector.get('UserGroup');
+
+ // Required services
+ var $location = $injector.get('$location');
+ var $routeParams = $injector.get('$routeParams');
+ var $q = $injector.get('$q');
+ var authenticationService = $injector.get('authenticationService');
+ var dataSourceService = $injector.get('dataSourceService');
+ var membershipService = $injector.get('membershipService');
+ var permissionService = $injector.get('permissionService');
+ var requestService = $injector.get('requestService');
+ var schemaService = $injector.get('schemaService');
+ var userGroupService = $injector.get('userGroupService');
+ var userService = $injector.get('userService');
+
+ /**
+ * The identifiers of all data sources currently available to the
+ * authenticated user.
+ *
+ * @type String[]
+ */
+ var dataSources = authenticationService.getAvailableDataSources();
+
+ /**
+ * The username of the current, authenticated user.
+ *
+ * @type String
+ */
+ var currentUsername = authenticationService.getCurrentUsername();
+
+ /**
+ * The identifier of the original user group from which this user group is
+ * being cloned. Only valid if this is a new user group.
+ *
+ * @type String
+ */
+ var cloneSourceIdentifier = $location.search().clone;
+
+ /**
+ * The identifier of the user group being edited. If a new user group is
+ * being created, this will not be defined.
+ *
+ * @type String
+ */
+ var identifier = $routeParams.id;
+
+ /**
+ * The unique identifier of the data source containing the user group being
+ * edited.
+ *
+ * @type String
+ */
+ $scope.dataSource = $routeParams.dataSource;
+
+ /**
+ * All user groups associated with the same identifier as the group being
+ * created or edited, as a map of data source identifier to the UserGroup
+ * object within that data source.
+ *
+ * @type Object.<String, UserGroup>
+ */
+ $scope.userGroups = null;
+
+ /**
+ * The user group being modified.
+ *
+ * @type UserGroup
+ */
+ $scope.userGroup = null;
+
+ /**
+ * All permissions associated with the user group being modified.
+ *
+ * @type PermissionFlagSet
+ */
+ $scope.permissionFlags = null;
+
+ /**
+ * The set of permissions that will be added to the user group when the
+ * user group is saved. Permissions will only be present in this set if they
+ * are manually added, and not later manually removed before saving.
+ *
+ * @type PermissionSet
+ */
+ $scope.permissionsAdded = new PermissionSet();
+
+ /**
+ * The set of permissions that will be removed from the user group when the
+ * user group is saved. Permissions will only be present in this set if they
+ * are manually removed, and not later manually added before saving.
+ *
+ * @type PermissionSet
+ */
+ $scope.permissionsRemoved = new PermissionSet();
+
+ /**
+ * The identifiers of all user groups which can be manipulated (all groups
+ * for which the user accessing this interface has UPDATE permission),
+ * whether that means changing the members of those groups or changing the
+ * groups of which those groups are members. If this information has not
+ * yet been retrieved, this will be null.
+ *
+ * @type String[]
+ */
+ $scope.availableGroups = null;
+
+ /**
+ * The identifiers of all users which can be manipulated (all users for
+ * which the user accessing this interface has UPDATE permission), either
+ * through adding those users as a member of the current group or removing
+ * those users from the current group. If this information has not yet been
+ * retrieved, this will be null.
+ *
+ * @type String[]
+ */
+ $scope.availableUsers = null;
+
+ /**
+ * The identifiers of all user groups of which this group is a member,
+ * taking into account any user groups which will be added/removed when
+ * saved. If this information has not yet been retrieved, this will be
+ * null.
+ *
+ * @type String[]
+ */
+ $scope.parentGroups = null;
+
+ /**
+ * The set of identifiers of all parent user groups to which this group
+ * will be added when saved. Parent groups will only be present in this set
+ * if they are manually added, and not later manually removed before
+ * saving.
+ *
+ * @type String[]
+ */
+ $scope.parentGroupsAdded = [];
+
+ /**
+ * The set of identifiers of all parent user groups from which this group
+ * will be removed when saved. Parent groups will only be present in this
+ * set if they are manually removed, and not later manually added before
+ * saving.
+ *
+ * @type String[]
+ */
+ $scope.parentGroupsRemoved = [];
+
+ /**
+ * The identifiers of all user groups which are members of this group,
+ * taking into account any user groups which will be added/removed when
+ * saved. If this information has not yet been retrieved, this will be
+ * null.
+ *
+ * @type String[]
+ */
+ $scope.memberGroups = null;
+
+ /**
+ * The set of identifiers of all member user groups which will be added to
+ * this group when saved. Member groups will only be present in this set if
+ * they are manually added, and not later manually removed before saving.
+ *
+ * @type String[]
+ */
+ $scope.memberGroupsAdded = [];
+
+ /**
+ * The set of identifiers of all member user groups which will be removed
+ * from this group when saved. Member groups will only be present in this
+ * set if they are manually removed, and not later manually added before
+ * saving.
+ *
+ * @type String[]
+ */
+ $scope.memberGroupsRemoved = [];
+
+ /**
+ * The identifiers of all users which are members of this group, taking
+ * into account any users which will be added/removed when saved. If this
+ * information has not yet been retrieved, this will be null.
+ *
+ * @type String[]
+ */
+ $scope.memberUsers = null;
+
+ /**
+ * The set of identifiers of all member users which will be added to this
+ * group when saved. Member users will only be present in this set if they
+ * are manually added, and not later manually removed before saving.
+ *
+ * @type String[]
+ */
+ $scope.memberUsersAdded = [];
+
+ /**
+ * The set of identifiers of all member users which will be removed from
+ * this group when saved. Member users will only be present in this set if
+ * they are manually removed, and not later manually added before saving.
+ *
+ * @type String[]
+ */
+ $scope.memberUsersRemoved = [];
+
+ /**
+ * For each applicable data source, the management-related actions that the
+ * current user may perform on the user group currently being created
+ * or modified, as a map of data source identifier to the
+ * {@link ManagementPermissions} object describing the actions available
+ * within that data source, or null if the current user's permissions have
+ * not yet been loaded.
+ *
+ * @type Object.<String, ManagementPermissions>
+ */
+ $scope.managementPermissions = null;
+
+ /**
+ * All available user group attributes. This is only the set of attribute
+ * definitions, organized as logical groupings of attributes, not attribute
+ * values.
+ *
+ * @type Form[]
+ */
+ $scope.attributes = null;
+
+ /**
+ * Returns whether critical data has completed being loaded.
+ *
+ * @returns {Boolean}
+ * true if enough data has been loaded for the user group interface to
+ * be useful, false otherwise.
+ */
+ $scope.isLoaded = function isLoaded() {
+
+ return $scope.userGroups !== null
+ && $scope.permissionFlags !== null
+ && $scope.managementPermissions !== null
+ && $scope.availableGroups !== null
+ && $scope.availableUsers !== null
+ && $scope.parentGroups !== null
+ && $scope.memberGroups !== null
+ && $scope.memberUsers !== null
+ && $scope.attributes !== null;
+
+ };
+
+ /**
+ * Returns whether the current user can edit the identifier of the user
+ * group being edited.
+ *
+ * @returns {Boolean}
+ * true if the current user can edit the identifier of the user group
+ * being edited, false otherwise.
+ */
+ $scope.canEditIdentifier = function canEditIdentifier() {
+ return !identifier;
+ };
+
+ /**
+ * Loads the data associated with the user group having the given
+ * identifier, preparing the interface for making modifications to that
+ * existing user. group
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the user group
+ * to load.
+ *
+ * @param {String} identifier
+ * The unique identifier of the user group to load.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * editing the given user group.
+ */
+ var loadExistingUserGroup = function loadExistingGroup(dataSource, identifier) {
+ return $q.all({
+ userGroups : dataSourceService.apply(userGroupService.getUserGroup, dataSources, identifier),
+ permissions : permissionService.getPermissions(dataSource, identifier, true),
+ parentGroups : membershipService.getUserGroups(dataSource, identifier, true),
+ memberGroups : membershipService.getMemberUserGroups(dataSource, identifier),
+ memberUsers : membershipService.getMemberUsers(dataSource, identifier)
+ })
+ .then(function userGroupDataRetrieved(values) {
+
+ $scope.userGroups = values.userGroups;
+ $scope.userGroup = values.userGroups[dataSource];
+ $scope.parentGroups = values.parentGroups;
+ $scope.memberGroups = values.memberGroups;
+ $scope.memberUsers = values.memberUsers;
+
+ // Create skeleton user group if user group does not exist
+ if (!$scope.userGroup)
+ $scope.userGroup = new UserGroup({
+ 'identifier' : identifier
+ });
+
+ $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(values.permissions);
+
+ });
+ };
+
+ /**
+ * Loads the data associated with the user group having the given
+ * identifier, preparing the interface for cloning that existing user
+ * group.
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the user group to
+ * be cloned.
+ *
+ * @param {String} identifier
+ * The unique identifier of the user group being cloned.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * cloning the given user group.
+ */
+ var loadClonedUserGroup = function loadClonedUserGroup(dataSource, identifier) {
+ return $q.all({
+ userGroups : dataSourceService.apply(userGroupService.getUserGroup, [dataSource], identifier),
+ permissions : permissionService.getPermissions(dataSource, identifier, true),
+ parentGroups : membershipService.getUserGroups(dataSource, identifier, true),
+ memberGroups : membershipService.getMemberUserGroups(dataSource, identifier),
+ memberUsers : membershipService.getMemberUsers(dataSource, identifier)
+ })
+ .then(function userGroupDataRetrieved(values) {
+
+ $scope.userGroups = {};
+ $scope.userGroup = values.userGroups[dataSource];
+ $scope.parentGroups = values.parentGroups;
+ $scope.parentGroupsAdded = values.parentGroups;
+ $scope.memberGroups = values.memberGroups;
+ $scope.memberGroupsAdded = values.memberGroups;
+ $scope.memberUsers = values.memberUsers;
+ $scope.memberUsersAdded = values.memberUsers;
+
+ $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(values.permissions);
+ $scope.permissionsAdded = values.permissions;
+
+ });
+ };
+
+ /**
+ * Loads skeleton user group data, preparing the interface for creating a
+ * new user group.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * creating a new user group.
+ */
+ var loadSkeletonUserGroup = function loadSkeletonUserGroup() {
+
+ // No user groups exist regardless of data source if there is no identifier
+ $scope.userGroups = {};
+
+ // Use skeleton user group object with no associated permissions
+ $scope.userGroup = new UserGroup();
+ $scope.parentGroups = [];
+ $scope.memberGroups = [];
+ $scope.memberUsers = [];
+ $scope.permissionFlags = new PermissionFlagSet();
+
+ return $q.resolve();
+
+ };
+
+ /**
+ * Loads the data required for performing the management task requested
+ * through the route parameters given at load time, automatically preparing
+ * the interface for editing an existing user group, cloning an existing
+ * user group, or creating an entirely new user group.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared
+ * for performing the requested management task.
+ */
+ var loadRequestedUserGroup = function loadRequestedUserGroup() {
+
+ // Pull user group data and permissions if we are editing an existing
+ // user group
+ if (identifier)
+ return loadExistingUserGroup($scope.dataSource, identifier);
+
+ // If we are cloning an existing user group, pull its data instead
+ if (cloneSourceIdentifier)
+ return loadClonedUserGroup($scope.dataSource, cloneSourceIdentifier);
+
+ // If we are creating a new user group, populate skeleton user group data
+ return loadSkeletonUserGroup();
+
+ };
+
+ // Populate interface with requested data
+ $q.all({
+ userGroupData : loadRequestedUserGroup(),
+ permissions : dataSourceService.apply(permissionService.getEffectivePermissions, dataSources, currentUsername),
+ userGroups : userGroupService.getUserGroups($scope.dataSource, [ PermissionSet.ObjectPermissionType.UPDATE ]),
+ users : userService.getUsers($scope.dataSource, [ PermissionSet.ObjectPermissionType.UPDATE ]),
+ attributes : schemaService.getUserGroupAttributes($scope.dataSource)
+ })
+ .then(function dataReceived(values) {
+
+ $scope.attributes = values.attributes;
+
+ $scope.managementPermissions = {};
+ angular.forEach(dataSources, function deriveManagementPermissions(dataSource) {
+
+ // Determine whether data source contains this user group
+ var exists = (dataSource in $scope.userGroups);
+
+ // Add the identifiers of all modifiable user groups
+ $scope.availableGroups = [];
+ angular.forEach(values.userGroups, function addUserGroupIdentifier(userGroup) {
+ $scope.availableGroups.push(userGroup.identifier);
+ });
+
+ // Add the identifiers of all modifiable users
+ $scope.availableUsers = [];
+ angular.forEach(values.users, function addUserIdentifier(user) {
+ $scope.availableUsers.push(user.username);
+ });
+
+ // Calculate management actions available for this specific group
+ $scope.managementPermissions[dataSource] = ManagementPermissions.fromPermissionSet(
+ values.permissions[dataSource],
+ PermissionSet.SystemPermissionType.CREATE_USER_GROUP,
+ PermissionSet.hasUserGroupPermission,
+ exists ? identifier : null);
+
+ });
+
+ }, requestService.WARN);
+
+ /**
+ * Returns the URL for the page which manages the user account currently
+ * being edited under the given data source. The given data source need not
+ * be the same as the data source currently selected.
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source that the URL is being
+ * generated for.
+ *
+ * @returns {String}
+ * The URL for the page which manages the user account currently being
+ * edited under the given data source.
+ */
+ $scope.getUserGroupURL = function getUserGroupURL(dataSource) {
+ return '/manage/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier || '');
+ };
+
+ /**
+ * Cancels all pending edits, returning to the main list of users.
+ */
+ $scope.returnToUserGroupList = function returnToUserGroupList() {
+ $location.url('/settings/userGroups');
+ };
+
+ /**
+ * Cancels all pending edits, opening an edit page for a new user
+ * which is prepopulated with the data from the user currently being edited.
+ */
+ $scope.cloneUserGroup = function cloneUserGroup() {
+ $location.path('/manage/' + encodeURIComponent($scope.dataSource) + '/userGroups').search('clone', identifier);
+ };
+
+ /**
+ * Saves the current user, creating a new user or updating the existing
+ * user depending on context, returning a promise which is resolved if the
--- End diff --
More copy :spaghetti: ?? "user" -> "user group"
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by necouchman <gi...@git.apache.org>.
Github user necouchman commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r208755319
--- Diff: guacamole/src/main/webapp/app/manage/templates/manageUserGroup.html ---
@@ -0,0 +1,102 @@
+
--- End diff --
Extra line? Is this intentional?
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by mike-jumper <gi...@git.apache.org>.
Github user mike-jumper commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r209019439
--- Diff: guacamole/src/main/webapp/app/rest/types/UserGroup.js ---
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Service which defines the UserGroup class.
+ */
+angular.module('rest').factory('UserGroup', [function defineUserGroup() {
+
+ /**
+ * The object returned by REST API calls when representing the data
+ * associated with a user group.
+ *
+ * @constructor
+ * @param {UserGroup|Object} [template={}]
+ * The object whose properties should be copied within the new
+ * UserGroup.
+ */
+ var UserGroup = function UserGroup(template) {
+
+ // Use empty object by default
+ template = template || {};
+
+ /**
+ * The name which uniquely identifies this user group.
+ *
+ * @type String
+ */
+ this.identifier = template.identifier;
+
+ /**
+ * Arbitrary name/value pairs which further describe this user group.
+ * The semantics and validity of these attributes are dictated by the
+ * extension which defines them.
+ *
+ * @type Object.<String, String>
+ */
+ this.attributes = {};
--- End diff --
Not in practice, but it would make sense to do this. I'm not sure what this wasn't done in the first place.
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by mike-jumper <gi...@git.apache.org>.
Github user mike-jumper commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r209018438
--- Diff: guacamole/src/main/webapp/app/rest/services/membershipService.js ---
@@ -0,0 +1,385 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Service for operating on user group memberships via the REST API.
+ */
+angular.module('rest').factory('membershipService', ['$injector',
+ function membershipService($injector) {
+
+ // Required services
+ var requestService = $injector.get('requestService');
+ var authenticationService = $injector.get('authenticationService');
+ var cacheService = $injector.get('cacheService');
+
+ // Required types
+ var RelatedObjectPatch = $injector.get('RelatedObjectPatch');
+
+ var service = {};
+
+ /**
+ * Creates a new array of patches which represents the given changes to an
+ * arbitrary set of objects sharing some common relation.
+ *
+ * @param {String[]} [identifiersToAdd]
+ * The identifiers of all objects which should be added to the
+ * relation, if any.
+ *
+ * @param {String[]} [identifiersToRemove]
+ * The identifiers of all objects which should be removed from the
+ * relation, if any.
+ *
+ * @returns {RelatedObjectPatch[]}
+ * A new array of patches which represents the given changes.
+ */
+ var getRelatedObjectPatch = function getRelatedObjectPatch(identifiersToAdd, identifiersToRemove) {
+
+ var patch = [];
+
+ angular.forEach(identifiersToAdd, function addIdentifier(identifier) {
+ patch.push(new RelatedObjectPatch({
+ op : RelatedObjectPatch.Operation.ADD,
+ value : identifier
+ }));
+ });
+
+ angular.forEach(identifiersToRemove, function removeIdentifier(identifier) {
+ patch.push(new RelatedObjectPatch({
+ op : RelatedObjectPatch.Operation.REMOVE,
+ value : identifier
+ }));
+ });
+
+ return patch;
+
+ };
+
+ /**
+ * Returns the URL for the REST resource most appropriate for accessing
+ * the parent user groups of the user or group having the given identifier.
+ *
+ * It is important to note that a particular data source can authenticate
+ * and provide user groups for a user, even if that user does not exist
+ * within that data source (and thus cannot be found beneath
+ * "api/session/data/{dataSource}/users")
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the user or
+ * group whose parent user groups should be retrieved. This identifier
+ * corresponds to an AuthenticationProvider within the Guacamole web
+ * application.
+ *
+ * @param {String} identifier
+ * The identifier of the user or group for which the URL of the proper
+ * REST resource should be derived.
+ *
+ * @param {Boolean} [group]
+ * Whether the provided identifier refers to a user group. If false or
+ * omitted, the identifier given is assumed to refer to a user.
+ *
+ * @returns {String}
+ * The URL for the REST resource representing the parent user groups of
+ * the user or group having the given identifier.
+ */
+ var getUserGroupsResourceURL = function getUserGroupsResourceURL(dataSource, identifier, group) {
+
+ // Create base URL for data source
+ var base = 'api/session/data/' + encodeURIComponent(dataSource);
+
+ // Access parent groups directly (there is no "self" for user groups
+ // as there is for users)
+ if (group)
+ return base + '/userGroups/' + encodeURIComponent(identifier) + '/userGroups';
+
+ // If the username is that of the current user, do not rely on the
+ // user actually existing (they may not). Access their parent groups via
+ // "self" rather than the collection of defined users.
+ if (identifier === authenticationService.getCurrentUsername())
+ return base + '/self/userGroups';
+
+ // Otherwise, the user must exist for their parent groups to be
+ // accessible. Use the collection of defined users.
+ return base + '/users/' + encodeURIComponent(identifier) + '/userGroups';
+
+ };
+
+ /**
+ * Makes a request to the REST API to retrieve the identifiers of all
+ * parent user groups of which a given user or group is a member, returning
+ * a promise that can be used for processing the results of the call.
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the user or
+ * group whose parent user groups should be retrieved. This identifier
+ * corresponds to an AuthenticationProvider within the Guacamole web
+ * application.
+ *
+ * @param {String} identifier
+ * The identifier of the user or group to retrieve the parent user
+ * groups of.
+ *
+ * @param {Boolean} [group]
+ * Whether the provided identifier refers to a user group. If false or
+ * omitted, the identifier given is assumed to refer to a user.
+ *
+ * @returns {Promise.<String[]>}
+ * A promise for the HTTP call which will resolve with an array
+ * containing the requested identifiers upon success.
+ */
+ service.getUserGroups = function getUserGroups(dataSource, identifier, group) {
+
+ // Build HTTP parameters set
+ var httpParameters = {
+ token : authenticationService.getCurrentToken()
+ };
+
+ // Retrieve parent groups
+ return requestService({
+ cache : cacheService.users,
+ method : 'GET',
+ url : getUserGroupsResourceURL(dataSource, identifier, group),
+ params : httpParameters
+ });
+
+ };
+
+ /**
+ * Makes a request to the REST API to modify the parent user groups of
+ * which a given user or group is a member, returning a promise that can be
+ * used for processing the results of the call.
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the user or
+ * group whose parent user groups should be modified. This identifier
+ * corresponds to an AuthenticationProvider within the Guacamole web
+ * application.
+ *
+ * @param {String} identifier
+ * The identifier of the user or group to modify the parent user
+ * groups of.
+ *
+ * @param {String[]} [addToUserGroups]
+ * The identifier of all parent user groups to which the given user or
+ * group should be added as a member, if any.
+ *
+ * @param {String[]} [removeFromUserGroups]
+ * The identifier of all parent user groups from which the given member
+ * user or group should be removed, if any.
+ *
+ * @param {Boolean} [group]
+ * Whether the provided identifier refers to a user group. If false or
+ * omitted, the identifier given is assumed to refer to a user.
+ *
+ * @returns {Promise}
+ * A promise for the HTTP call which will succeed if and only if the
+ * patch operation is successful.
+ */
+ service.patchUserGroups = function patchUserGroups(dataSource, identifier,
+ addToUserGroups, removeFromUserGroups, group) {
+
+ // Build HTTP parameters set
+ var httpParameters = {
+ token : authenticationService.getCurrentToken()
+ };
+
+ // Update parent user groups
+ return requestService({
+ method : 'PATCH',
+ url : getUserGroupsResourceURL(dataSource, identifier, group),
+ params : httpParameters,
+ data : getRelatedObjectPatch(addToUserGroups, removeFromUserGroups)
+ })
+
+ // Clear the cache
+ .then(function parentUserGroupsChanged(){
+ cacheService.users.removeAll();
+ });
+
+ };
+
+ /**
+ * Makes a request to the REST API to retrieve the identifiers of all
+ * users which are members of the given user group, returning a promise
--- End diff --
That aspect is intentionally left to the implementation. While there is a similar call for retrieving permissions which explicitly requires permission inheritance to be taken into account, no assumption is made in general on how that group inheritance is implemented, nor whether it's taken into account here.
In this case, if an extension wants to expose inherited groups at this level, they can. The only requirement is that they make the set of groups mutable if the current user has permission to make changes to those memberships.
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by necouchman <gi...@git.apache.org>.
Github user necouchman commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r208853719
--- Diff: guacamole/src/main/webapp/app/rest/types/UserGroup.js ---
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Service which defines the UserGroup class.
+ */
+angular.module('rest').factory('UserGroup', [function defineUserGroup() {
+
+ /**
+ * The object returned by REST API calls when representing the data
+ * associated with a user group.
+ *
+ * @constructor
+ * @param {UserGroup|Object} [template={}]
+ * The object whose properties should be copied within the new
+ * UserGroup.
+ */
+ var UserGroup = function UserGroup(template) {
+
+ // Use empty object by default
+ template = template || {};
+
+ /**
+ * The name which uniquely identifies this user group.
+ *
+ * @type String
+ */
+ this.identifier = template.identifier;
+
+ /**
+ * Arbitrary name/value pairs which further describe this user group.
+ * The semantics and validity of these attributes are dictated by the
+ * extension which defines them.
+ *
+ * @type Object.<String, String>
+ */
+ this.attributes = {};
--- End diff --
So these are never expected to be provided to the constructor? Just checking...
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by mike-jumper <gi...@git.apache.org>.
Github user mike-jumper commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r208810507
--- Diff: guacamole/src/main/webapp/app/settings/directives/guacSettingsUserGroups.js ---
@@ -0,0 +1,270 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * A directive for managing all user groups in the system.
+ */
+angular.module('settings').directive('guacSettingsUserGroups', ['$injector',
+ function guacSettingsUserGroups($injector) {
+
+ // Required types
+ var ManageableUserGroup = $injector.get('ManageableUserGroup');
+ var PermissionSet = $injector.get('PermissionSet');
+ var SortOrder = $injector.get('SortOrder');
+
+ // Required services
+ var $location = $injector.get('$location');
+ var authenticationService = $injector.get('authenticationService');
+ var dataSourceService = $injector.get('dataSourceService');
+ var permissionService = $injector.get('permissionService');
+ var requestService = $injector.get('requestService');
+ var userGroupService = $injector.get('userGroupService');
+
+ var directive = {
+ restrict : 'E',
+ replace : true,
+ templateUrl : 'app/settings/templates/settingsUserGroups.html',
+ scope : {}
+ };
+
+ directive.controller = ['$scope', function settingsUserGroupsController($scope) {
+
+ // Identifier of the current user
+ var currentUsername = authenticationService.getCurrentUsername();
+
+ /**
+ * The identifiers of all data sources accessible by the current
+ * user.
+ *
+ * @type String[]
+ */
+ var dataSources = authenticationService.getAvailableDataSources();
+
+ /**
+ * Map of data source identifiers to all permissions associated
+ * with the current user within that data source, or null if the
+ * user's permissions have not yet been loaded.
+ *
+ * @type Object.<String, PermissionSet>
+ */
+ var permissions = null;
+
+ /**
+ * All visible user groups, along with their corresponding data
+ * sources.
+ *
+ * @type ManageableUserGroup[]
+ */
+ $scope.manageableUserGroups = null;
+
+ /**
+ * Array of all user group properties that are filterable.
+ *
+ * @type String[]
+ */
+ $scope.filteredUserGroupProperties = [
+ 'userGroup.identifier'
+ ];
+
+ /**
+ * SortOrder instance which stores the sort order of the listed
+ * user groups.
+ *
+ * @type SortOrder
+ */
+ $scope.order = new SortOrder([
+ 'userGroup.identifier'
+ ]);
+
+ /**
+ * Returns whether critical data has completed being loaded.
+ *
+ * @returns {Boolean}
+ * true if enough data has been loaded for the user group
+ * interface to be useful, false otherwise.
+ */
+ $scope.isLoaded = function isLoaded() {
+ return $scope.manageableUserGroups !== null;
+ };
+
+ /**
+ * Returns the identifier of the data source that should be used by
+ * default when creating a new user group.
+ *
+ * @return {String}
+ * The identifier of the data source that should be used by
+ * default when creating a new user group, or null if user group
+ * creation is not allowed.
+ */
+ $scope.getDefaultDataSource = function getDefaultDataSource() {
+
+ // Abort if permissions have not yet loaded
+ if (!permissions)
+ return null;
+
+ // For each data source
+ for (var dataSource in permissions) {
--- End diff --
Ick. Yeah, this is definitely non-detereministic. The user settings page will have the same issue. I will try to make this reasonable.
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by jmuehlner <gi...@git.apache.org>.
Github user jmuehlner commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r208801372
--- Diff: guacamole/src/main/webapp/app/manage/controllers/manageUserGroupController.js ---
@@ -0,0 +1,535 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * The controller for editing user groups.
+ */
+angular.module('manage').controller('manageUserGroupController', ['$scope', '$injector',
+ function manageUserGroupController($scope, $injector) {
+
+ // Required types
+ var ManagementPermissions = $injector.get('ManagementPermissions');
+ var PermissionFlagSet = $injector.get('PermissionFlagSet');
+ var PermissionSet = $injector.get('PermissionSet');
+ var UserGroup = $injector.get('UserGroup');
+
+ // Required services
+ var $location = $injector.get('$location');
+ var $routeParams = $injector.get('$routeParams');
+ var $q = $injector.get('$q');
+ var authenticationService = $injector.get('authenticationService');
+ var dataSourceService = $injector.get('dataSourceService');
+ var membershipService = $injector.get('membershipService');
+ var permissionService = $injector.get('permissionService');
+ var requestService = $injector.get('requestService');
+ var schemaService = $injector.get('schemaService');
+ var userGroupService = $injector.get('userGroupService');
+ var userService = $injector.get('userService');
+
+ /**
+ * The identifiers of all data sources currently available to the
+ * authenticated user.
+ *
+ * @type String[]
+ */
+ var dataSources = authenticationService.getAvailableDataSources();
+
+ /**
+ * The username of the current, authenticated user.
+ *
+ * @type String
+ */
+ var currentUsername = authenticationService.getCurrentUsername();
+
+ /**
+ * The identifier of the original user group from which this user group is
+ * being cloned. Only valid if this is a new user group.
+ *
+ * @type String
+ */
+ var cloneSourceIdentifier = $location.search().clone;
+
+ /**
+ * The identifier of the user group being edited. If a new user group is
+ * being created, this will not be defined.
+ *
+ * @type String
+ */
+ var identifier = $routeParams.id;
+
+ /**
+ * The unique identifier of the data source containing the user group being
+ * edited.
+ *
+ * @type String
+ */
+ $scope.dataSource = $routeParams.dataSource;
+
+ /**
+ * All user groups associated with the same identifier as the group being
+ * created or edited, as a map of data source identifier to the UserGroup
+ * object within that data source.
+ *
+ * @type Object.<String, UserGroup>
+ */
+ $scope.userGroups = null;
+
+ /**
+ * The user group being modified.
+ *
+ * @type UserGroup
+ */
+ $scope.userGroup = null;
+
+ /**
+ * All permissions associated with the user group being modified.
+ *
+ * @type PermissionFlagSet
+ */
+ $scope.permissionFlags = null;
+
+ /**
+ * The set of permissions that will be added to the user group when the
+ * user group is saved. Permissions will only be present in this set if they
+ * are manually added, and not later manually removed before saving.
+ *
+ * @type PermissionSet
+ */
+ $scope.permissionsAdded = new PermissionSet();
+
+ /**
+ * The set of permissions that will be removed from the user group when the
+ * user group is saved. Permissions will only be present in this set if they
+ * are manually removed, and not later manually added before saving.
+ *
+ * @type PermissionSet
+ */
+ $scope.permissionsRemoved = new PermissionSet();
+
+ /**
+ * The identifiers of all user groups which can be manipulated (all groups
+ * for which the user accessing this interface has UPDATE permission),
+ * whether that means changing the members of those groups or changing the
+ * groups of which those groups are members. If this information has not
+ * yet been retrieved, this will be null.
+ *
+ * @type String[]
+ */
+ $scope.availableGroups = null;
+
+ /**
+ * The identifiers of all users which can be manipulated (all users for
+ * which the user accessing this interface has UPDATE permission), either
+ * through adding those users as a member of the current group or removing
+ * those users from the current group. If this information has not yet been
+ * retrieved, this will be null.
+ *
+ * @type String[]
+ */
+ $scope.availableUsers = null;
+
+ /**
+ * The identifiers of all user groups of which this group is a member,
+ * taking into account any user groups which will be added/removed when
+ * saved. If this information has not yet been retrieved, this will be
+ * null.
+ *
+ * @type String[]
+ */
+ $scope.parentGroups = null;
+
+ /**
+ * The set of identifiers of all parent user groups to which this group
+ * will be added when saved. Parent groups will only be present in this set
+ * if they are manually added, and not later manually removed before
+ * saving.
+ *
+ * @type String[]
+ */
+ $scope.parentGroupsAdded = [];
+
+ /**
+ * The set of identifiers of all parent user groups from which this group
+ * will be removed when saved. Parent groups will only be present in this
+ * set if they are manually removed, and not later manually added before
+ * saving.
+ *
+ * @type String[]
+ */
+ $scope.parentGroupsRemoved = [];
+
+ /**
+ * The identifiers of all user groups which are members of this group,
+ * taking into account any user groups which will be added/removed when
+ * saved. If this information has not yet been retrieved, this will be
+ * null.
+ *
+ * @type String[]
+ */
+ $scope.memberGroups = null;
+
+ /**
+ * The set of identifiers of all member user groups which will be added to
+ * this group when saved. Member groups will only be present in this set if
+ * they are manually added, and not later manually removed before saving.
+ *
+ * @type String[]
+ */
+ $scope.memberGroupsAdded = [];
+
+ /**
+ * The set of identifiers of all member user groups which will be removed
+ * from this group when saved. Member groups will only be present in this
+ * set if they are manually removed, and not later manually added before
+ * saving.
+ *
+ * @type String[]
+ */
+ $scope.memberGroupsRemoved = [];
+
+ /**
+ * The identifiers of all users which are members of this group, taking
+ * into account any users which will be added/removed when saved. If this
+ * information has not yet been retrieved, this will be null.
+ *
+ * @type String[]
+ */
+ $scope.memberUsers = null;
+
+ /**
+ * The set of identifiers of all member users which will be added to this
+ * group when saved. Member users will only be present in this set if they
+ * are manually added, and not later manually removed before saving.
+ *
+ * @type String[]
+ */
+ $scope.memberUsersAdded = [];
+
+ /**
+ * The set of identifiers of all member users which will be removed from
+ * this group when saved. Member users will only be present in this set if
+ * they are manually removed, and not later manually added before saving.
+ *
+ * @type String[]
+ */
+ $scope.memberUsersRemoved = [];
+
+ /**
+ * For each applicable data source, the management-related actions that the
+ * current user may perform on the user group currently being created
+ * or modified, as a map of data source identifier to the
+ * {@link ManagementPermissions} object describing the actions available
+ * within that data source, or null if the current user's permissions have
+ * not yet been loaded.
+ *
+ * @type Object.<String, ManagementPermissions>
+ */
+ $scope.managementPermissions = null;
+
+ /**
+ * All available user group attributes. This is only the set of attribute
+ * definitions, organized as logical groupings of attributes, not attribute
+ * values.
+ *
+ * @type Form[]
+ */
+ $scope.attributes = null;
+
+ /**
+ * Returns whether critical data has completed being loaded.
+ *
+ * @returns {Boolean}
+ * true if enough data has been loaded for the user group interface to
+ * be useful, false otherwise.
+ */
+ $scope.isLoaded = function isLoaded() {
+
+ return $scope.userGroups !== null
+ && $scope.permissionFlags !== null
+ && $scope.managementPermissions !== null
+ && $scope.availableGroups !== null
+ && $scope.availableUsers !== null
+ && $scope.parentGroups !== null
+ && $scope.memberGroups !== null
+ && $scope.memberUsers !== null
+ && $scope.attributes !== null;
+
+ };
+
+ /**
+ * Returns whether the current user can edit the identifier of the user
+ * group being edited.
+ *
+ * @returns {Boolean}
+ * true if the current user can edit the identifier of the user group
+ * being edited, false otherwise.
+ */
+ $scope.canEditIdentifier = function canEditIdentifier() {
+ return !identifier;
+ };
+
+ /**
+ * Loads the data associated with the user group having the given
+ * identifier, preparing the interface for making modifications to that
+ * existing user. group
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the user group
+ * to load.
+ *
+ * @param {String} identifier
+ * The unique identifier of the user group to load.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * editing the given user group.
+ */
+ var loadExistingUserGroup = function loadExistingGroup(dataSource, identifier) {
+ return $q.all({
+ userGroups : dataSourceService.apply(userGroupService.getUserGroup, dataSources, identifier),
+ permissions : permissionService.getPermissions(dataSource, identifier, true),
+ parentGroups : membershipService.getUserGroups(dataSource, identifier, true),
+ memberGroups : membershipService.getMemberUserGroups(dataSource, identifier),
+ memberUsers : membershipService.getMemberUsers(dataSource, identifier)
+ })
+ .then(function userGroupDataRetrieved(values) {
+
+ $scope.userGroups = values.userGroups;
+ $scope.userGroup = values.userGroups[dataSource];
+ $scope.parentGroups = values.parentGroups;
+ $scope.memberGroups = values.memberGroups;
+ $scope.memberUsers = values.memberUsers;
+
+ // Create skeleton user group if user group does not exist
+ if (!$scope.userGroup)
+ $scope.userGroup = new UserGroup({
+ 'identifier' : identifier
+ });
+
+ $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(values.permissions);
+
+ });
+ };
+
+ /**
+ * Loads the data associated with the user group having the given
+ * identifier, preparing the interface for cloning that existing user
+ * group.
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the user group to
+ * be cloned.
+ *
+ * @param {String} identifier
+ * The unique identifier of the user group being cloned.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * cloning the given user group.
+ */
+ var loadClonedUserGroup = function loadClonedUserGroup(dataSource, identifier) {
+ return $q.all({
+ userGroups : dataSourceService.apply(userGroupService.getUserGroup, [dataSource], identifier),
+ permissions : permissionService.getPermissions(dataSource, identifier, true),
+ parentGroups : membershipService.getUserGroups(dataSource, identifier, true),
+ memberGroups : membershipService.getMemberUserGroups(dataSource, identifier),
+ memberUsers : membershipService.getMemberUsers(dataSource, identifier)
+ })
+ .then(function userGroupDataRetrieved(values) {
+
+ $scope.userGroups = {};
+ $scope.userGroup = values.userGroups[dataSource];
+ $scope.parentGroups = values.parentGroups;
+ $scope.parentGroupsAdded = values.parentGroups;
+ $scope.memberGroups = values.memberGroups;
+ $scope.memberGroupsAdded = values.memberGroups;
+ $scope.memberUsers = values.memberUsers;
+ $scope.memberUsersAdded = values.memberUsers;
+
+ $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(values.permissions);
+ $scope.permissionsAdded = values.permissions;
+
+ });
+ };
+
+ /**
+ * Loads skeleton user group data, preparing the interface for creating a
+ * new user group.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * creating a new user group.
+ */
+ var loadSkeletonUserGroup = function loadSkeletonUserGroup() {
+
+ // No user groups exist regardless of data source if there is no identifier
--- End diff --
It's a little bit unclear what "if there is no identifier" means in this context.
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by mike-jumper <gi...@git.apache.org>.
Github user mike-jumper commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r209014326
--- Diff: guacamole/src/main/webapp/app/manage/templates/manageUserGroup.html ---
@@ -0,0 +1,102 @@
+
--- End diff --
Nope. I'll get rid of it.
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by asfgit <gi...@git.apache.org>.
Github user asfgit closed the pull request at:
https://github.com/apache/guacamole-client/pull/309
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by necouchman <gi...@git.apache.org>.
Github user necouchman commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r208846984
--- Diff: guacamole/src/main/webapp/app/rest/services/membershipService.js ---
@@ -0,0 +1,385 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Service for operating on user group memberships via the REST API.
+ */
+angular.module('rest').factory('membershipService', ['$injector',
+ function membershipService($injector) {
+
+ // Required services
+ var requestService = $injector.get('requestService');
+ var authenticationService = $injector.get('authenticationService');
+ var cacheService = $injector.get('cacheService');
+
+ // Required types
+ var RelatedObjectPatch = $injector.get('RelatedObjectPatch');
+
+ var service = {};
+
+ /**
+ * Creates a new array of patches which represents the given changes to an
+ * arbitrary set of objects sharing some common relation.
+ *
+ * @param {String[]} [identifiersToAdd]
+ * The identifiers of all objects which should be added to the
+ * relation, if any.
+ *
+ * @param {String[]} [identifiersToRemove]
+ * The identifiers of all objects which should be removed from the
+ * relation, if any.
+ *
+ * @returns {RelatedObjectPatch[]}
+ * A new array of patches which represents the given changes.
+ */
+ var getRelatedObjectPatch = function getRelatedObjectPatch(identifiersToAdd, identifiersToRemove) {
+
+ var patch = [];
+
+ angular.forEach(identifiersToAdd, function addIdentifier(identifier) {
+ patch.push(new RelatedObjectPatch({
+ op : RelatedObjectPatch.Operation.ADD,
+ value : identifier
+ }));
+ });
+
+ angular.forEach(identifiersToRemove, function removeIdentifier(identifier) {
+ patch.push(new RelatedObjectPatch({
+ op : RelatedObjectPatch.Operation.REMOVE,
+ value : identifier
+ }));
+ });
+
+ return patch;
+
+ };
+
+ /**
+ * Returns the URL for the REST resource most appropriate for accessing
+ * the parent user groups of the user or group having the given identifier.
+ *
+ * It is important to note that a particular data source can authenticate
+ * and provide user groups for a user, even if that user does not exist
+ * within that data source (and thus cannot be found beneath
+ * "api/session/data/{dataSource}/users")
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the user or
+ * group whose parent user groups should be retrieved. This identifier
+ * corresponds to an AuthenticationProvider within the Guacamole web
+ * application.
+ *
+ * @param {String} identifier
+ * The identifier of the user or group for which the URL of the proper
+ * REST resource should be derived.
+ *
+ * @param {Boolean} [group]
+ * Whether the provided identifier refers to a user group. If false or
+ * omitted, the identifier given is assumed to refer to a user.
+ *
+ * @returns {String}
+ * The URL for the REST resource representing the parent user groups of
+ * the user or group having the given identifier.
+ */
+ var getUserGroupsResourceURL = function getUserGroupsResourceURL(dataSource, identifier, group) {
+
+ // Create base URL for data source
+ var base = 'api/session/data/' + encodeURIComponent(dataSource);
+
+ // Access parent groups directly (there is no "self" for user groups
+ // as there is for users)
+ if (group)
+ return base + '/userGroups/' + encodeURIComponent(identifier) + '/userGroups';
+
+ // If the username is that of the current user, do not rely on the
+ // user actually existing (they may not). Access their parent groups via
+ // "self" rather than the collection of defined users.
+ if (identifier === authenticationService.getCurrentUsername())
+ return base + '/self/userGroups';
+
+ // Otherwise, the user must exist for their parent groups to be
+ // accessible. Use the collection of defined users.
+ return base + '/users/' + encodeURIComponent(identifier) + '/userGroups';
+
+ };
+
+ /**
+ * Makes a request to the REST API to retrieve the identifiers of all
+ * parent user groups of which a given user or group is a member, returning
+ * a promise that can be used for processing the results of the call.
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the user or
+ * group whose parent user groups should be retrieved. This identifier
+ * corresponds to an AuthenticationProvider within the Guacamole web
+ * application.
+ *
+ * @param {String} identifier
+ * The identifier of the user or group to retrieve the parent user
+ * groups of.
+ *
+ * @param {Boolean} [group]
+ * Whether the provided identifier refers to a user group. If false or
+ * omitted, the identifier given is assumed to refer to a user.
+ *
+ * @returns {Promise.<String[]>}
+ * A promise for the HTTP call which will resolve with an array
+ * containing the requested identifiers upon success.
+ */
+ service.getUserGroups = function getUserGroups(dataSource, identifier, group) {
+
+ // Build HTTP parameters set
+ var httpParameters = {
+ token : authenticationService.getCurrentToken()
+ };
+
+ // Retrieve parent groups
+ return requestService({
+ cache : cacheService.users,
+ method : 'GET',
+ url : getUserGroupsResourceURL(dataSource, identifier, group),
+ params : httpParameters
+ });
+
+ };
+
+ /**
+ * Makes a request to the REST API to modify the parent user groups of
+ * which a given user or group is a member, returning a promise that can be
+ * used for processing the results of the call.
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the user or
+ * group whose parent user groups should be modified. This identifier
+ * corresponds to an AuthenticationProvider within the Guacamole web
+ * application.
+ *
+ * @param {String} identifier
+ * The identifier of the user or group to modify the parent user
+ * groups of.
+ *
+ * @param {String[]} [addToUserGroups]
+ * The identifier of all parent user groups to which the given user or
+ * group should be added as a member, if any.
+ *
+ * @param {String[]} [removeFromUserGroups]
+ * The identifier of all parent user groups from which the given member
+ * user or group should be removed, if any.
+ *
+ * @param {Boolean} [group]
+ * Whether the provided identifier refers to a user group. If false or
+ * omitted, the identifier given is assumed to refer to a user.
+ *
+ * @returns {Promise}
+ * A promise for the HTTP call which will succeed if and only if the
+ * patch operation is successful.
+ */
+ service.patchUserGroups = function patchUserGroups(dataSource, identifier,
+ addToUserGroups, removeFromUserGroups, group) {
+
+ // Build HTTP parameters set
+ var httpParameters = {
+ token : authenticationService.getCurrentToken()
+ };
+
+ // Update parent user groups
+ return requestService({
+ method : 'PATCH',
+ url : getUserGroupsResourceURL(dataSource, identifier, group),
+ params : httpParameters,
+ data : getRelatedObjectPatch(addToUserGroups, removeFromUserGroups)
+ })
+
+ // Clear the cache
+ .then(function parentUserGroupsChanged(){
+ cacheService.users.removeAll();
+ });
+
+ };
+
+ /**
+ * Makes a request to the REST API to retrieve the identifiers of all
+ * users which are members of the given user group, returning a promise
--- End diff --
Based on the capability within these changes for a user to be a member of a group which is then a member of another group, it might be useful to be slightly more explicit, here, about how this method behaves regarding users who are members of groups that are members of the group being asked for - that is, will it retrieve only users who are *direct* members of the group being queried, or users who are members of this group and any child/sub-groups?
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by jmuehlner <gi...@git.apache.org>.
Github user jmuehlner commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r208804256
--- Diff: guacamole/src/main/webapp/app/manage/directives/identifierSetEditor.js ---
@@ -0,0 +1,299 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * A directive for manipulating a set of objects sharing some common relation
+ * and represented by an array of their identifiers. The specific objects
+ * added or removed are tracked within a separate pair of arrays of
+ * identifiers.
+ */
+angular.module('manage').directive('identifierSetEditor', ['$injector',
+ function identifierSetEditor($injector) {
+
+ var directive = {
+
+ // Element only
+ restrict: 'E',
+ replace: true,
+
+ scope: {
+
+ /**
+ * The translation key of the text which should be displayed within
+ * the main header of the identifier set editor.
+ *
+ * @type String
+ */
+ header : '@',
+
+ /**
+ * The translation key of the text which should be displayed if no
+ * identifiers are currently present within the set.
+ *
+ * @type String
+ */
+ emptyPlaceholder : '@',
+
+ /**
+ * The translation key of the text which should be displayed if no
+ * identifiers are available to be added within the set.
+ *
+ * @type String
+ */
+ unavailablePlaceholder : '@',
+
+ /**
+ * All identifiers which are available to be added to or removed
+ * from the identifier set being edited.
+ *
+ * @type String[]
+ */
+ identifiersAvailable : '=',
+
+ /**
+ * The current state of the identifier set being manipulated. This
+ * array will be modified as changes are made through this
+ * identifier set editor.
+ *
+ * @type String[]
+ */
+ identifiers : '=',
+
+ /**
+ * The set of identifiers that have been added, relative to the
+ * initial state of the identifier set being manipulated.
+ *
+ * @type String[]
+ */
+ identifiersAdded : '=',
+
+ /**
+ * The set of identifiers that have been removed, relative to the
+ * initial state of the identifier set being manipulated.
+ *
+ * @type String[]
+ */
+ identifiersRemoved : '='
+
+ },
+
+ templateUrl: 'app/manage/templates/identifierSetEditor.html'
+
+ };
+
+ directive.controller = ['$scope', function identifierSetEditorController($scope) {
+
+ /**
+ * Whether the full list of available identifiers should be displayed.
+ * Initially, only an abbreviated list of identifiers currently present
+ * is shown.
+ *
+ * @type Boolean
+ */
+ $scope.expanded = false;
+
+ /**
+ * Map of identifiers to boolean flags indicating whether that
+ * identifier is currently present (true) or absent (false). If an
+ * identifier is absent, it may also be absent from this map.
+ *
+ * @type Object.<String, Boolean>
+ */
+ $scope.identifierFlags = {};
+
+ /**
+ * Map of identifiers to boolean flags indicating whether that
+ * identifier is editable. If an identifier is not editable, it will be
+ * absent from this map.
+ *
+ * @type Object.<String, Boolean>
+ */
+ $scope.isEditable = {};
+
+ /**
+ * Adds the given identifier to the given sorted array of identifiers,
+ * preserving the sorted order of the array. If the identifier is
+ * already present, no change is made to the array. The given array
+ * must already be sorted in ascending order.
+ *
+ * @param {String[]} arr
+ * The sorted array of identifiers to add the given identifier to.
+ *
+ * @param {String} identifier
+ * The identifier to add to the given array.
+ */
+ var addIdentifier = function addIdentifier(arr, identifier) {
+
+ // Determine location that the identifier should be added to
+ // maintain sorted order
+ var index = _.sortedIndex(arr, identifier);
+
+ // Do not add if already present
+ if (arr[index] === identifier)
+ return;
+
+ // Insert identifier at determined location
+ arr.splice(index, 0, identifier);
+
+ };
+
+ /**
+ * Removes the given identifier from the given sorted array of
+ * identifiers, preserving the sorted order of the array. If the
+ * identifier is already absent, no change is made to the array. The
+ * given array must already be sorted in ascending order.
+ *
+ * @param {String[]} arr
+ * The sorted array of identifiers to remove the given identifier
+ * from.
+ *
+ * @param {String} identifier
+ * The identifier to remove from the given array.
+ *
+ * @returns {Boolean}
+ * true if the identifier was present in the given array and has
+ * been removed, false otherwise.
+ */
+ var removeIdentifier = function removeIdentifier(arr, identifier) {
+
+ // Search for identifier in sorted array
+ var index = _.sortedIndexOf(arr, identifier);
+
+ // Nothing to do if already absent
+ if (index === -1)
+ return false;
+
+ // Remove identifier
+ arr.splice(index, 1);
+ return true;
+
+ };
+
+ // Keep identifierFlags up to date when identifiers array is replaced
+ // or initially assigned
+ $scope.$watch('identifiers', function identifiersChanged(identifiers) {
+
+ // Maintain identifiers in sorted order so additions and removals
+ // can be made more efficiently
+ if (identifiers)
+ identifiers.sort();
+
+ // Convert array of identifiers into set of boolean
+ // presence/absence flags
+ $scope.identifierFlags = {};
+ angular.forEach(identifiers, function storeIdentifierFlag(identifier) {
+ $scope.identifierFlags[identifier] = true;
+ });
+
+ });
+
+ // Keep identifierFlags up to date when identifiers array is replaced
--- End diff --
Looks like this comment is just copypasta'd from the one above? I don't think it applies here.
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by mike-jumper <gi...@git.apache.org>.
Github user mike-jumper commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r208801944
--- Diff: guacamole/src/main/webapp/app/manage/controllers/manageUserGroupController.js ---
@@ -0,0 +1,535 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * The controller for editing user groups.
+ */
+angular.module('manage').controller('manageUserGroupController', ['$scope', '$injector',
+ function manageUserGroupController($scope, $injector) {
+
+ // Required types
+ var ManagementPermissions = $injector.get('ManagementPermissions');
+ var PermissionFlagSet = $injector.get('PermissionFlagSet');
+ var PermissionSet = $injector.get('PermissionSet');
+ var UserGroup = $injector.get('UserGroup');
+
+ // Required services
+ var $location = $injector.get('$location');
+ var $routeParams = $injector.get('$routeParams');
+ var $q = $injector.get('$q');
+ var authenticationService = $injector.get('authenticationService');
+ var dataSourceService = $injector.get('dataSourceService');
+ var membershipService = $injector.get('membershipService');
+ var permissionService = $injector.get('permissionService');
+ var requestService = $injector.get('requestService');
+ var schemaService = $injector.get('schemaService');
+ var userGroupService = $injector.get('userGroupService');
+ var userService = $injector.get('userService');
+
+ /**
+ * The identifiers of all data sources currently available to the
+ * authenticated user.
+ *
+ * @type String[]
+ */
+ var dataSources = authenticationService.getAvailableDataSources();
+
+ /**
+ * The username of the current, authenticated user.
+ *
+ * @type String
+ */
+ var currentUsername = authenticationService.getCurrentUsername();
+
+ /**
+ * The identifier of the original user group from which this user group is
+ * being cloned. Only valid if this is a new user group.
+ *
+ * @type String
+ */
+ var cloneSourceIdentifier = $location.search().clone;
+
+ /**
+ * The identifier of the user group being edited. If a new user group is
+ * being created, this will not be defined.
+ *
+ * @type String
+ */
+ var identifier = $routeParams.id;
+
+ /**
+ * The unique identifier of the data source containing the user group being
+ * edited.
+ *
+ * @type String
+ */
+ $scope.dataSource = $routeParams.dataSource;
+
+ /**
+ * All user groups associated with the same identifier as the group being
+ * created or edited, as a map of data source identifier to the UserGroup
+ * object within that data source.
+ *
+ * @type Object.<String, UserGroup>
+ */
+ $scope.userGroups = null;
+
+ /**
+ * The user group being modified.
+ *
+ * @type UserGroup
+ */
+ $scope.userGroup = null;
+
+ /**
+ * All permissions associated with the user group being modified.
+ *
+ * @type PermissionFlagSet
+ */
+ $scope.permissionFlags = null;
+
+ /**
+ * The set of permissions that will be added to the user group when the
+ * user group is saved. Permissions will only be present in this set if they
+ * are manually added, and not later manually removed before saving.
+ *
+ * @type PermissionSet
+ */
+ $scope.permissionsAdded = new PermissionSet();
+
+ /**
+ * The set of permissions that will be removed from the user group when the
+ * user group is saved. Permissions will only be present in this set if they
+ * are manually removed, and not later manually added before saving.
+ *
+ * @type PermissionSet
+ */
+ $scope.permissionsRemoved = new PermissionSet();
+
+ /**
+ * The identifiers of all user groups which can be manipulated (all groups
+ * for which the user accessing this interface has UPDATE permission),
+ * whether that means changing the members of those groups or changing the
+ * groups of which those groups are members. If this information has not
+ * yet been retrieved, this will be null.
+ *
+ * @type String[]
+ */
+ $scope.availableGroups = null;
+
+ /**
+ * The identifiers of all users which can be manipulated (all users for
+ * which the user accessing this interface has UPDATE permission), either
+ * through adding those users as a member of the current group or removing
+ * those users from the current group. If this information has not yet been
+ * retrieved, this will be null.
+ *
+ * @type String[]
+ */
+ $scope.availableUsers = null;
+
+ /**
+ * The identifiers of all user groups of which this group is a member,
+ * taking into account any user groups which will be added/removed when
+ * saved. If this information has not yet been retrieved, this will be
+ * null.
+ *
+ * @type String[]
+ */
+ $scope.parentGroups = null;
+
+ /**
+ * The set of identifiers of all parent user groups to which this group
+ * will be added when saved. Parent groups will only be present in this set
+ * if they are manually added, and not later manually removed before
+ * saving.
+ *
+ * @type String[]
+ */
+ $scope.parentGroupsAdded = [];
+
+ /**
+ * The set of identifiers of all parent user groups from which this group
+ * will be removed when saved. Parent groups will only be present in this
+ * set if they are manually removed, and not later manually added before
+ * saving.
+ *
+ * @type String[]
+ */
+ $scope.parentGroupsRemoved = [];
+
+ /**
+ * The identifiers of all user groups which are members of this group,
+ * taking into account any user groups which will be added/removed when
+ * saved. If this information has not yet been retrieved, this will be
+ * null.
+ *
+ * @type String[]
+ */
+ $scope.memberGroups = null;
+
+ /**
+ * The set of identifiers of all member user groups which will be added to
+ * this group when saved. Member groups will only be present in this set if
+ * they are manually added, and not later manually removed before saving.
+ *
+ * @type String[]
+ */
+ $scope.memberGroupsAdded = [];
+
+ /**
+ * The set of identifiers of all member user groups which will be removed
+ * from this group when saved. Member groups will only be present in this
+ * set if they are manually removed, and not later manually added before
+ * saving.
+ *
+ * @type String[]
+ */
+ $scope.memberGroupsRemoved = [];
+
+ /**
+ * The identifiers of all users which are members of this group, taking
+ * into account any users which will be added/removed when saved. If this
+ * information has not yet been retrieved, this will be null.
+ *
+ * @type String[]
+ */
+ $scope.memberUsers = null;
+
+ /**
+ * The set of identifiers of all member users which will be added to this
+ * group when saved. Member users will only be present in this set if they
+ * are manually added, and not later manually removed before saving.
+ *
+ * @type String[]
+ */
+ $scope.memberUsersAdded = [];
+
+ /**
+ * The set of identifiers of all member users which will be removed from
+ * this group when saved. Member users will only be present in this set if
+ * they are manually removed, and not later manually added before saving.
+ *
+ * @type String[]
+ */
+ $scope.memberUsersRemoved = [];
+
+ /**
+ * For each applicable data source, the management-related actions that the
+ * current user may perform on the user group currently being created
+ * or modified, as a map of data source identifier to the
+ * {@link ManagementPermissions} object describing the actions available
+ * within that data source, or null if the current user's permissions have
+ * not yet been loaded.
+ *
+ * @type Object.<String, ManagementPermissions>
+ */
+ $scope.managementPermissions = null;
+
+ /**
+ * All available user group attributes. This is only the set of attribute
+ * definitions, organized as logical groupings of attributes, not attribute
+ * values.
+ *
+ * @type Form[]
+ */
+ $scope.attributes = null;
+
+ /**
+ * Returns whether critical data has completed being loaded.
+ *
+ * @returns {Boolean}
+ * true if enough data has been loaded for the user group interface to
+ * be useful, false otherwise.
+ */
+ $scope.isLoaded = function isLoaded() {
+
+ return $scope.userGroups !== null
+ && $scope.permissionFlags !== null
+ && $scope.managementPermissions !== null
+ && $scope.availableGroups !== null
+ && $scope.availableUsers !== null
+ && $scope.parentGroups !== null
+ && $scope.memberGroups !== null
+ && $scope.memberUsers !== null
+ && $scope.attributes !== null;
+
+ };
+
+ /**
+ * Returns whether the current user can edit the identifier of the user
+ * group being edited.
+ *
+ * @returns {Boolean}
+ * true if the current user can edit the identifier of the user group
+ * being edited, false otherwise.
+ */
+ $scope.canEditIdentifier = function canEditIdentifier() {
+ return !identifier;
+ };
+
+ /**
+ * Loads the data associated with the user group having the given
+ * identifier, preparing the interface for making modifications to that
+ * existing user. group
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the user group
+ * to load.
+ *
+ * @param {String} identifier
+ * The unique identifier of the user group to load.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * editing the given user group.
+ */
+ var loadExistingUserGroup = function loadExistingGroup(dataSource, identifier) {
+ return $q.all({
+ userGroups : dataSourceService.apply(userGroupService.getUserGroup, dataSources, identifier),
+ permissions : permissionService.getPermissions(dataSource, identifier, true),
+ parentGroups : membershipService.getUserGroups(dataSource, identifier, true),
+ memberGroups : membershipService.getMemberUserGroups(dataSource, identifier),
+ memberUsers : membershipService.getMemberUsers(dataSource, identifier)
+ })
+ .then(function userGroupDataRetrieved(values) {
+
+ $scope.userGroups = values.userGroups;
+ $scope.userGroup = values.userGroups[dataSource];
+ $scope.parentGroups = values.parentGroups;
+ $scope.memberGroups = values.memberGroups;
+ $scope.memberUsers = values.memberUsers;
+
+ // Create skeleton user group if user group does not exist
+ if (!$scope.userGroup)
+ $scope.userGroup = new UserGroup({
+ 'identifier' : identifier
+ });
+
+ $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(values.permissions);
+
+ });
+ };
+
+ /**
+ * Loads the data associated with the user group having the given
+ * identifier, preparing the interface for cloning that existing user
+ * group.
+ *
+ * @param {String} dataSource
+ * The unique identifier of the data source containing the user group to
+ * be cloned.
+ *
+ * @param {String} identifier
+ * The unique identifier of the user group being cloned.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * cloning the given user group.
+ */
+ var loadClonedUserGroup = function loadClonedUserGroup(dataSource, identifier) {
+ return $q.all({
+ userGroups : dataSourceService.apply(userGroupService.getUserGroup, [dataSource], identifier),
+ permissions : permissionService.getPermissions(dataSource, identifier, true),
+ parentGroups : membershipService.getUserGroups(dataSource, identifier, true),
+ memberGroups : membershipService.getMemberUserGroups(dataSource, identifier),
+ memberUsers : membershipService.getMemberUsers(dataSource, identifier)
+ })
+ .then(function userGroupDataRetrieved(values) {
+
+ $scope.userGroups = {};
+ $scope.userGroup = values.userGroups[dataSource];
+ $scope.parentGroups = values.parentGroups;
+ $scope.parentGroupsAdded = values.parentGroups;
+ $scope.memberGroups = values.memberGroups;
+ $scope.memberGroupsAdded = values.memberGroups;
+ $scope.memberUsers = values.memberUsers;
+ $scope.memberUsersAdded = values.memberUsers;
+
+ $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(values.permissions);
+ $scope.permissionsAdded = values.permissions;
+
+ });
+ };
+
+ /**
+ * Loads skeleton user group data, preparing the interface for creating a
+ * new user group.
+ *
+ * @returns {Promise}
+ * A promise which is resolved when the interface has been prepared for
+ * creating a new user group.
+ */
+ var loadSkeletonUserGroup = function loadSkeletonUserGroup() {
+
+ // No user groups exist regardless of data source if there is no identifier
--- End diff --
Fair enough. I'll reword and amend.
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by jmuehlner <gi...@git.apache.org>.
Github user jmuehlner commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r208809231
--- Diff: guacamole/src/main/webapp/app/settings/directives/guacSettingsUserGroups.js ---
@@ -0,0 +1,270 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * A directive for managing all user groups in the system.
+ */
+angular.module('settings').directive('guacSettingsUserGroups', ['$injector',
+ function guacSettingsUserGroups($injector) {
+
+ // Required types
+ var ManageableUserGroup = $injector.get('ManageableUserGroup');
+ var PermissionSet = $injector.get('PermissionSet');
+ var SortOrder = $injector.get('SortOrder');
+
+ // Required services
+ var $location = $injector.get('$location');
+ var authenticationService = $injector.get('authenticationService');
+ var dataSourceService = $injector.get('dataSourceService');
+ var permissionService = $injector.get('permissionService');
+ var requestService = $injector.get('requestService');
+ var userGroupService = $injector.get('userGroupService');
+
+ var directive = {
+ restrict : 'E',
+ replace : true,
+ templateUrl : 'app/settings/templates/settingsUserGroups.html',
+ scope : {}
+ };
+
+ directive.controller = ['$scope', function settingsUserGroupsController($scope) {
+
+ // Identifier of the current user
+ var currentUsername = authenticationService.getCurrentUsername();
+
+ /**
+ * The identifiers of all data sources accessible by the current
+ * user.
+ *
+ * @type String[]
+ */
+ var dataSources = authenticationService.getAvailableDataSources();
+
+ /**
+ * Map of data source identifiers to all permissions associated
+ * with the current user within that data source, or null if the
+ * user's permissions have not yet been loaded.
+ *
+ * @type Object.<String, PermissionSet>
+ */
+ var permissions = null;
+
+ /**
+ * All visible user groups, along with their corresponding data
+ * sources.
+ *
+ * @type ManageableUserGroup[]
+ */
+ $scope.manageableUserGroups = null;
+
+ /**
+ * Array of all user group properties that are filterable.
+ *
+ * @type String[]
+ */
+ $scope.filteredUserGroupProperties = [
+ 'userGroup.identifier'
+ ];
+
+ /**
+ * SortOrder instance which stores the sort order of the listed
+ * user groups.
+ *
+ * @type SortOrder
+ */
+ $scope.order = new SortOrder([
+ 'userGroup.identifier'
+ ]);
+
+ /**
+ * Returns whether critical data has completed being loaded.
+ *
+ * @returns {Boolean}
+ * true if enough data has been loaded for the user group
+ * interface to be useful, false otherwise.
+ */
+ $scope.isLoaded = function isLoaded() {
+ return $scope.manageableUserGroups !== null;
+ };
+
+ /**
+ * Returns the identifier of the data source that should be used by
+ * default when creating a new user group.
+ *
+ * @return {String}
+ * The identifier of the data source that should be used by
+ * default when creating a new user group, or null if user group
+ * creation is not allowed.
+ */
+ $scope.getDefaultDataSource = function getDefaultDataSource() {
+
+ // Abort if permissions have not yet loaded
+ if (!permissions)
+ return null;
+
+ // For each data source
+ for (var dataSource in permissions) {
--- End diff --
Doesn't this just iterate over the data sources in non-deterministic order and choose the first one that the user has permission to?
I guess the description of the function doesn't say HOW it chooses the default data source, but choosing a non-deterministic one from all the available choices seems odd.
---
[GitHub] guacamole-client pull request #309: GUACAMOLE-220: Add user interface for ma...
Posted by jmuehlner <gi...@git.apache.org>.
Github user jmuehlner commented on a diff in the pull request:
https://github.com/apache/guacamole-client/pull/309#discussion_r208803042
--- Diff: guacamole/src/main/webapp/app/manage/directives/identifierSetEditor.js ---
@@ -0,0 +1,299 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * A directive for manipulating a set of objects sharing some common relation
+ * and represented by an array of their identifiers. The specific objects
+ * added or removed are tracked within a separate pair of arrays of
+ * identifiers.
+ */
+angular.module('manage').directive('identifierSetEditor', ['$injector',
+ function identifierSetEditor($injector) {
+
+ var directive = {
+
+ // Element only
+ restrict: 'E',
+ replace: true,
+
+ scope: {
+
+ /**
+ * The translation key of the text which should be displayed within
+ * the main header of the identifier set editor.
+ *
+ * @type String
+ */
+ header : '@',
+
+ /**
+ * The translation key of the text which should be displayed if no
+ * identifiers are currently present within the set.
+ *
+ * @type String
+ */
+ emptyPlaceholder : '@',
+
+ /**
+ * The translation key of the text which should be displayed if no
+ * identifiers are available to be added within the set.
+ *
+ * @type String
+ */
+ unavailablePlaceholder : '@',
+
+ /**
+ * All identifiers which are available to be added to or removed
+ * from the identifier set being edited.
+ *
+ * @type String[]
+ */
+ identifiersAvailable : '=',
+
+ /**
+ * The current state of the identifier set being manipulated. This
+ * array will be modified as changes are made through this
+ * identifier set editor.
+ *
+ * @type String[]
+ */
+ identifiers : '=',
+
+ /**
+ * The set of identifiers that have been added, relative to the
+ * initial state of the identifier set being manipulated.
+ *
+ * @type String[]
+ */
+ identifiersAdded : '=',
+
+ /**
+ * The set of identifiers that have been removed, relative to the
+ * initial state of the identifier set being manipulated.
+ *
+ * @type String[]
+ */
+ identifiersRemoved : '='
+
+ },
+
+ templateUrl: 'app/manage/templates/identifierSetEditor.html'
+
+ };
+
+ directive.controller = ['$scope', function identifierSetEditorController($scope) {
+
+ /**
+ * Whether the full list of available identifiers should be displayed.
+ * Initially, only an abbreviated list of identifiers currently present
+ * is shown.
+ *
+ * @type Boolean
+ */
+ $scope.expanded = false;
+
+ /**
+ * Map of identifiers to boolean flags indicating whether that
+ * identifier is currently present (true) or absent (false). If an
+ * identifier is absent, it may also be absent from this map.
+ *
+ * @type Object.<String, Boolean>
+ */
+ $scope.identifierFlags = {};
+
+ /**
+ * Map of identifiers to boolean flags indicating whether that
+ * identifier is editabl. If an identifier is not editable, it will be
--- End diff --
Typo: "editabl".
---