GUACAMOLE-220: Add management tab and editor for user groups.
Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/8ad3f253 Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/8ad3f253 Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/8ad3f253 Branch: refs/heads/master Commit: 8ad3f2537119d61becad38558dc1365742ba7444 Parents: de80957 Author: Michael Jumper <mjum...@apache.org> Authored: Thu Apr 19 23:51:25 2018 -0700 Committer: Michael Jumper <mjum...@apache.org> Committed: Thu Aug 9 10:46:06 2018 -0700 ---------------------------------------------------------------------- .../webapp/app/index/config/indexRouteConfig.js | 9 + .../src/main/webapp/app/index/styles/lists.css | 4 + .../src/main/webapp/app/index/styles/ui.css | 8 + .../controllers/manageUserGroupController.js | 538 +++++++++++++++++++ .../manage/directives/systemPermissionEditor.js | 4 + .../app/manage/styles/manage-user-group.css | 71 +++ .../app/manage/templates/manageUserGroup.html | 101 ++++ .../app/manage/types/ManageableUserGroup.js | 53 ++ .../app/navigation/services/userPageService.js | 27 + .../settings/controllers/settingsController.js | 4 +- .../directives/guacSettingsUserGroups.js | 270 ++++++++++ .../main/webapp/app/settings/styles/buttons.css | 6 + .../app/settings/styles/user-group-list.css | 36 ++ .../webapp/app/settings/templates/settings.html | 1 + .../settings/templates/settingsUserGroups.html | 48 ++ .../images/action-icons/guac-user-group-add.png | Bin 0 -> 1222 bytes .../images/user-icons/guac-user-group.png | Bin 0 -> 1428 bytes guacamole/src/main/webapp/translations/en.json | 67 ++- 18 files changed, 1244 insertions(+), 3 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ad3f253/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js b/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js index 47bc48e..5a8c3fb 100644 --- a/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js +++ b/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js @@ -171,6 +171,15 @@ angular.module('index').config(['$routeProvider', '$locationProvider', resolve : { updateCurrentToken: updateCurrentToken } }) + // User group editor + .when('/manage/:dataSource/userGroups/:id?', { + title : 'APP.NAME', + bodyClassName : 'manage', + templateUrl : 'app/manage/templates/manageUserGroup.html', + controller : 'manageUserGroupController', + resolve : { updateCurrentToken: updateCurrentToken } + }) + // Client view .when('/client/:id/:params?', { bodyClassName : 'client', http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ad3f253/guacamole/src/main/webapp/app/index/styles/lists.css ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/index/styles/lists.css b/guacamole/src/main/webapp/app/index/styles/lists.css index 0c761ae..80df491 100644 --- a/guacamole/src/main/webapp/app/index/styles/lists.css +++ b/guacamole/src/main/webapp/app/index/styles/lists.css @@ -18,12 +18,14 @@ */ .user, +.user-group, .connection-group, .connection { cursor: pointer; } .user a, +.user-group a, .connection a, .connection-group a { text-decoration:none; @@ -31,6 +33,7 @@ } .user a:hover, +.user-group a:hover, .connection a:hover, .connection-group a:hover { text-decoration:none; @@ -38,6 +41,7 @@ } .user a:visited, +.user-group a:visited, .connection a:visited, .connection-group a:visited { text-decoration:none; http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ad3f253/guacamole/src/main/webapp/app/index/styles/ui.css ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/index/styles/ui.css b/guacamole/src/main/webapp/app/index/styles/ui.css index 434f443..58406eb 100644 --- a/guacamole/src/main/webapp/app/index/styles/ui.css +++ b/guacamole/src/main/webapp/app/index/styles/ui.css @@ -156,6 +156,14 @@ div.section { background-image: url('images/action-icons/guac-user-add.png'); } +.icon.user-group { + background-image: url('images/user-icons/guac-user-group.png'); +} + +.icon.user-group.add { + background-image: url('images/action-icons/guac-user-group-add.png'); +} + .icon.connection { background-image: url('images/protocol-icons/guac-plug.png'); } http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ad3f253/guacamole/src/main/webapp/app/manage/controllers/manageUserGroupController.js ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserGroupController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserGroupController.js new file mode 100644 index 0000000..229b3b8 --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserGroupController.js @@ -0,0 +1,538 @@ +/* + * 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 the user group is + // being created + $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 group 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 group 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 user groups. + */ + $scope.returnToUserGroupList = function returnToUserGroupList() { + $location.url('/settings/userGroups'); + }; + + /** + * Cancels all pending edits, opening an edit page for a new user group + * 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 group, creating a new user group or updating the + * existing user group 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 group, returning a promise which is resolved if + * the delete operation succeeds and rejected if the delete operation + * fails. + * + * @returns {Promise} + * A promise which is resolved if the delete operation succeeds and is + * rejected with an {@link Error} if the delete operation fails. + */ + $scope.deleteUserGroup = function deleteUserGroup() { + return userGroupService.deleteUserGroup($scope.dataSource, $scope.userGroup); + }; + +}]); http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ad3f253/guacamole/src/main/webapp/app/manage/directives/systemPermissionEditor.js ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/manage/directives/systemPermissionEditor.js b/guacamole/src/main/webapp/app/manage/directives/systemPermissionEditor.js index ec41872..67fd3f4 100644 --- a/guacamole/src/main/webapp/app/manage/directives/systemPermissionEditor.js +++ b/guacamole/src/main/webapp/app/manage/directives/systemPermissionEditor.js @@ -126,6 +126,10 @@ angular.module('manage').directive('systemPermissionEditor', ['$injector', value: PermissionSet.SystemPermissionType.CREATE_USER }, { + label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_USER_GROUPS", + value: PermissionSet.SystemPermissionType.CREATE_USER_GROUP + }, + { label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_CONNECTIONS", value: PermissionSet.SystemPermissionType.CREATE_CONNECTION }, http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ad3f253/guacamole/src/main/webapp/app/manage/styles/manage-user-group.css ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/manage/styles/manage-user-group.css b/guacamole/src/main/webapp/app/manage/styles/manage-user-group.css new file mode 100644 index 0000000..df9e80d --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/styles/manage-user-group.css @@ -0,0 +1,71 @@ +/* + * 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. + */ + +.manage-user-group .page-tabs .page-list li.read-only a[href], +.manage-user-group .page-tabs .page-list li.unlinked a[href], +.manage-user-group .page-tabs .page-list li.linked a[href] { + padding-right: 2.5em; + position: relative; +} + +.manage-user-group .page-tabs .page-list li.read-only a[href]:before, +.manage-user-group .page-tabs .page-list li.unlinked a[href]:before, +.manage-user-group .page-tabs .page-list li.linked a[href]:before { + content: ' '; + position: absolute; + right: 0; + bottom: 0; + top: 0; + width: 2.5em; + background-size: 1.25em; + background-repeat: no-repeat; + background-position: center; +} + +.manage-user-group .page-tabs .page-list li.read-only a[href]:before { + background-image: url('images/lock.png'); +} + +.manage-user-group .page-tabs .page-list li.unlinked a[href]:before { + background-image: url('images/plus.png'); +} + +.manage-user-group .page-tabs .page-list li.unlinked a[href] { + opacity: 0.5; +} + +.manage-user-group .page-tabs .page-list li.unlinked a[href]:hover, +.manage-user-group .page-tabs .page-list li.unlinked a[href].current { + opacity: 1; +} + +.manage-user-group .page-tabs .page-list li.linked a[href]:before { + background-image: url('images/checkmark.png'); +} + +.manage-user-group .notice.read-only { + + background: #FDA; + border: 1px solid rgba(0, 0, 0, 0.125); + border-radius: 0.25em; + + text-align: center; + padding: 1em; + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ad3f253/guacamole/src/main/webapp/app/manage/templates/manageUserGroup.html ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUserGroup.html b/guacamole/src/main/webapp/app/manage/templates/manageUserGroup.html new file mode 100644 index 0000000..c659915 --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/templates/manageUserGroup.html @@ -0,0 +1,101 @@ +<div class="manage-user-group view" ng-class="{loading: !isLoaded()}"> + + <!-- User group header and data source tabs --> + <div class="header tabbed"> + <h2>{{'MANAGE_USER_GROUP.SECTION_HEADER_EDIT_USER_GROUP' | translate}}</h2> + <guac-user-menu></guac-user-menu> + </div> + <data-data-source-tabs ng-hide="cloneSourceIdentifier" + permissions="managementPermissions" + url="getUserGroupURL(dataSource)"> + </data-data-source-tabs> + + <!-- Warn if user group is read-only --> + <div class="section" ng-hide="managementPermissions[dataSource].canSaveObject"> + <p class="notice read-only">{{'MANAGE_USER_GROUP.INFO_READ_ONLY' | translate}}</p> + </div> + + <!-- Sections applicable to non-read-only user groups --> + <div ng-show="managementPermissions[dataSource].canSaveObject"> + + <!-- User group name --> + <div class="section"> + <table class="properties"> + <tr> + <th>{{'MANAGE_USER_GROUP.FIELD_HEADER_USER_GROUP_NAME' | translate}}</th> + <td> + <input ng-show="canEditIdentifier()" ng-model="userGroup.identifier" type="text"/> + <span ng-hide="canEditIdentifier()">{{userGroup.identifier}}</span> + </td> + </tr> + </table> + </div> + + <!-- User group attributes section --> + <div class="attributes" ng-show="managementPermissions[dataSource].canChangeAttributes"> + <guac-form namespace="'USER_GROUP_ATTRIBUTES'" content="attributes" + model="userGroup.attributes" + model-only="!managementPermissions[dataSource].canChangeAllAttributes"></guac-form> + </div> + + <!-- System permissions section --> + <system-permission-editor ng-show="managementPermissions[dataSource].canChangePermissions" + data-data-source="dataSource" + permission-flags="permissionFlags" + permissions-added="permissionsAdded" + permissions-removed="permissionsRemoved"> + </system-permission-editor> + + <!-- Parent group section --> + <identifier-set-editor + header="MANAGE_USER_GROUP.SECTION_HEADER_USER_GROUPS" + empty-placeholder="MANAGE_USER_GROUP.HELP_NO_USER_GROUPS" + unavailable-placeholder="MANAGE_USER_GROUP.INFO_NO_USER_GROUPS_AVAILABLE" + identifiers-available="availableGroups" + identifiers="parentGroups" + identifiers-added="parentGroupsAdded" + identifiers-removed="parentGroupsRemoved"> + </identifier-set-editor> + + <!-- Member group section --> + <identifier-set-editor + header="MANAGE_USER_GROUP.SECTION_HEADER_MEMBER_USER_GROUPS" + empty-placeholder="MANAGE_USER_GROUP.HELP_NO_MEMBER_USER_GROUPS" + unavailable-placeholder="MANAGE_USER_GROUP.INFO_NO_USER_GROUPS_AVAILABLE" + identifiers-available="availableGroups" + identifiers="memberGroups" + identifiers-added="memberGroupsAdded" + identifiers-removed="memberGroupsRemoved"> + </identifier-set-editor> + + <!-- Member user section --> + <identifier-set-editor + header="MANAGE_USER_GROUP.SECTION_HEADER_MEMBER_USERS" + empty-placeholder="MANAGE_USER_GROUP.HELP_NO_MEMBER_USERS" + unavailable-placeholder="MANAGE_USER_GROUP.INFO_NO_USERS_AVAILABLE" + identifiers-available="availableUsers" + identifiers="memberUsers" + identifiers-added="memberUsersAdded" + identifiers-removed="memberUsersRemoved"> + </identifier-set-editor> + + <!-- Connection permissions section --> + <connection-permission-editor ng-show="managementPermissions[dataSource].canChangePermissions" + data-data-source="dataSource" + permission-flags="permissionFlags" + permissions-added="permissionsAdded" + permissions-removed="permissionsRemoved"> + </connection-permission-editor> + + <!-- Form action buttons --> + <management-buttons namespace="MANAGE_USER_GROUP" + permissions="managementPermissions[dataSource]" + save="saveUserGroup()" + delete="deleteUserGroup()" + clone="cloneUserGroup()" + return="returnToUserGroupList()"> + </management-buttons> + + </div> + +</div> http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ad3f253/guacamole/src/main/webapp/app/manage/types/ManageableUserGroup.js ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/manage/types/ManageableUserGroup.js b/guacamole/src/main/webapp/app/manage/types/ManageableUserGroup.js new file mode 100644 index 0000000..6853fa0 --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/types/ManageableUserGroup.js @@ -0,0 +1,53 @@ +/* + * 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 service for defining the ManageableUserGroup class. + */ +angular.module('manage').factory('ManageableUserGroup', [function defineManageableUserGroup() { + + /** + * A pairing of an @link{UserGroup} with the identifier of its corresponding + * data source. + * + * @constructor + * @param {Object|ManageableUserGroup} template + */ + var ManageableUserGroup = function ManageableUserGroup(template) { + + /** + * The unique identifier of the data source containing this user. + * + * @type String + */ + this.dataSource = template.dataSource; + + /** + * The @link{UserGroup} object represented by this ManageableUserGroup + * and contained within the associated data source. + * + * @type UserGroup + */ + this.userGroup = template.userGroup; + + }; + + return ManageableUserGroup; + +}]); http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ad3f253/guacamole/src/main/webapp/app/navigation/services/userPageService.js ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/navigation/services/userPageService.js b/guacamole/src/main/webapp/app/navigation/services/userPageService.js index 4d1e612..f5bc308 100644 --- a/guacamole/src/main/webapp/app/navigation/services/userPageService.js +++ b/guacamole/src/main/webapp/app/navigation/services/userPageService.js @@ -192,6 +192,7 @@ angular.module('navigation').factory('userPageService', ['$injector', var pages = []; var canManageUsers = []; + var canManageUserGroups = []; var canManageConnections = []; var canViewConnectionRecords = []; var canManageSessions = []; @@ -235,6 +236,24 @@ angular.module('navigation').factory('userPageService', ['$injector', canManageUsers.push(dataSource); } + // Determine whether the current user needs access to the group management UI + if ( + // System permissions + PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) + || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_USER_GROUP) + + // Permission to update user groups + || PermissionSet.hasUserGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) + + // Permission to delete user groups + || PermissionSet.hasUserGroupPermission(permissions, PermissionSet.ObjectPermissionType.DELETE) + + // Permission to administer user groups + || PermissionSet.hasUserGroupPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER) + ) { + canManageUserGroups.push(dataSource); + } + // Determine whether the current user needs access to the connection management UI if ( // System permissions @@ -295,6 +314,14 @@ angular.module('navigation').factory('userPageService', ['$injector', })); } + // If user can manage user groups, add link to group management page + if (canManageUserGroups.length) { + pages.push(new PageDefinition({ + name : 'USER_MENU.ACTION_MANAGE_USER_GROUPS', + url : '/settings/userGroups' + })); + } + // If user can manage connections, add links for connection management pages angular.forEach(canManageConnections, function addConnectionManagementLink(dataSource) { pages.push(new PageDefinition({ http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ad3f253/guacamole/src/main/webapp/app/settings/controllers/settingsController.js ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/settings/controllers/settingsController.js b/guacamole/src/main/webapp/app/settings/controllers/settingsController.js index 91ef633..a462d87 100644 --- a/guacamole/src/main/webapp/app/settings/controllers/settingsController.js +++ b/guacamole/src/main/webapp/app/settings/controllers/settingsController.js @@ -36,8 +36,8 @@ angular.module('manage').controller('settingsController', ['$scope', '$injector' $scope.settingsPages = null; /** - * The currently-selected settings tab. This may be 'users', 'connections', - * or 'sessions'. + * The currently-selected settings tab. This may be 'users', 'userGroups', + * 'connections', 'history', 'preferences', or 'sessions'. * * @type String */ http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ad3f253/guacamole/src/main/webapp/app/settings/directives/guacSettingsUserGroups.js ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsUserGroups.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsUserGroups.js new file mode 100644 index 0000000..5d45bc1 --- /dev/null +++ b/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) { + + // Retrieve corresponding permission set + 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 group in each data source + angular.forEach(dataSources, function addUserGroupList(dataSource) { + angular.forEach(userGroups[dataSource], function addUserGroup(userGroup) { + + // Do not add the same user group twice + if (addedUserGroups[userGroup.identifier]) + return; + + // Link to default creation data source if we cannot manage this user + if (!PermissionSet.hasSystemPermission(permissions[dataSource], PermissionSet.ObjectPermissionType.ADMINISTER) + && !PermissionSet.hasUserGroupPermission(permissions[dataSource], PermissionSet.ObjectPermissionType.UPDATE, userGroup.identifier) + && !PermissionSet.hasUserGroupPermission(permissions[dataSource], PermissionSet.ObjectPermissionType.DELETE, userGroup.identifier)) + dataSource = $scope.getDefaultDataSource(); + + // Add user group to overall list + addedUserGroups[userGroup.identifier] = userGroup; + $scope.manageableUserGroups.push(new ManageableUserGroup ({ + 'dataSource' : dataSource, + 'userGroup' : userGroup + })); + + }); + }); + + }; + + // Retrieve current permissions + dataSourceService.apply( + permissionService.getEffectivePermissions, + dataSources, + currentUsername + ) + .then(function permissionsRetrieved(retrievedPermissions) { + + // Store retrieved permissions + permissions = retrievedPermissions; + + // Return to home if there's nothing to do here + if (!canManageUserGroups()) + $location.path('/'); + + // If user groups can be created, list all readable user groups + if ($scope.canCreateUserGroups()) + return dataSourceService.apply(userGroupService.getUserGroups, dataSources); + + // Otherwise, list only updateable/deletable users + return dataSourceService.apply(userGroupService.getUserGroups, dataSources, [ + PermissionSet.ObjectPermissionType.UPDATE, + PermissionSet.ObjectPermissionType.DELETE + ]); + + }) + .then(function userGroupsReceived(userGroups) { + setDisplayedUserGroups(permissions, userGroups); + }, requestService.WARN); + + }]; + + return directive; + +}]); http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ad3f253/guacamole/src/main/webapp/app/settings/styles/buttons.css ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/settings/styles/buttons.css b/guacamole/src/main/webapp/app/settings/styles/buttons.css index 17401c3..e530510 100644 --- a/guacamole/src/main/webapp/app/settings/styles/buttons.css +++ b/guacamole/src/main/webapp/app/settings/styles/buttons.css @@ -18,6 +18,7 @@ */ a.button.add-user, +a.button.add-user-group, a.button.add-connection, a.button.add-connection-group { font-size: 0.8em; @@ -26,6 +27,7 @@ a.button.add-connection-group { } a.button.add-user::before, +a.button.add-user-group::before, a.button.add-connection::before, a.button.add-connection-group::before { @@ -46,6 +48,10 @@ a.button.add-user::before { background-image: url('images/action-icons/guac-user-add.png'); } +a.button.add-user-group::before { + background-image: url('images/action-icons/guac-user-group-add.png'); +} + a.button.add-connection::before { background-image: url('images/action-icons/guac-monitor-add.png'); } http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ad3f253/guacamole/src/main/webapp/app/settings/styles/user-group-list.css ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/settings/styles/user-group-list.css b/guacamole/src/main/webapp/app/settings/styles/user-group-list.css new file mode 100644 index 0000000..2040eb4 --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/styles/user-group-list.css @@ -0,0 +1,36 @@ +/* + * 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. + */ + +.settings.user-groups table.user-group-list { + width: 100%; +} + +.settings.user-groups table.user-group-list th.user-group-name, +.settings.user-groups table.user-group-list td.user-group-name { + width: 100%; +} + +.settings.user-groups table.user-group-list tr.user td.user-group-name a[href] { + display: block; + padding: .5em 1em; +} + +.settings.user-groups table.user-group-list tr.user td.user-group-name { + padding: 0; +} http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ad3f253/guacamole/src/main/webapp/app/settings/templates/settings.html ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/settings/templates/settings.html b/guacamole/src/main/webapp/app/settings/templates/settings.html index b29d809..2bae3ae 100644 --- a/guacamole/src/main/webapp/app/settings/templates/settings.html +++ b/guacamole/src/main/webapp/app/settings/templates/settings.html @@ -13,6 +13,7 @@ <!-- Selected tab --> <guac-settings-users ng-if="activeTab === 'users'"></guac-settings-users> + <guac-settings-user-groups ng-if="activeTab === 'userGroups'"></guac-settings-user-groups> <guac-settings-connections ng-if="activeTab === 'connections'"></guac-settings-connections> <guac-settings-connection-history ng-if="activeTab === 'history'"></guac-settings-connection-history> <guac-settings-sessions ng-if="activeTab === 'sessions'"></guac-settings-sessions> http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ad3f253/guacamole/src/main/webapp/app/settings/templates/settingsUserGroups.html ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/settings/templates/settingsUserGroups.html b/guacamole/src/main/webapp/app/settings/templates/settingsUserGroups.html new file mode 100644 index 0000000..1943773 --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/templates/settingsUserGroups.html @@ -0,0 +1,48 @@ +<div class="settings section user-groups" ng-class="{loading: !isLoaded()}"> + + <!-- User group management --> + <p>{{'SETTINGS_USER_GROUPS.HELP_USER_GROUPS' | translate}}</p> + + + <!-- User management toolbar --> + <div class="toolbar"> + + <!-- Form action buttons --> + <div class="action-buttons"> + <a class="add-user-group button" ng-show="canCreateUserGroups()" + href="#/manage/{{getDefaultDataSource()}}/userGroups/">{{'SETTINGS_USER_GROUPS.ACTION_NEW_USER_GROUP' | translate}}</a> + </div> + + <!-- User group filter --> + <guac-filter filtered-items="filteredManageableUserGroups" items="manageableUserGroups" + placeholder="'SETTINGS_USER_GROUPS.FIELD_PLACEHOLDER_FILTER' | translate" + properties="filteredUserGroupProperties"></guac-filter> + + </div> + + <!-- List of user groups this user has access to --> + <table class="sorted user-group-list"> + <thead> + <tr> + <th guac-sort-order="order" guac-sort-property="'userGroup.identifier'" class="user-group-name"> + {{'SETTINGS_USER_GROUPS.TABLE_HEADER_USER_GROUP_NAME' | translate}} + </th> + </tr> + </thead> + <tbody ng-class="{loading: !isLoaded()}"> + <tr ng-repeat="manageableUserGroup in manageableUserGroupPage" class="user-group"> + <td class="user-group-name"> + <a ng-href="#/manage/{{manageableUserGroup.dataSource}}/userGroups/{{manageableUserGroup.userGroup.identifier}}"> + <div class="icon user-group"></div> + <span class="name">{{manageableUserGroup.userGroup.identifier}}</span> + </a> + </td> + </tr> + </tbody> + </table> + + <!-- Pager controls for user group list --> + <guac-pager page="manageableUserGroupPage" page-size="25" + items="filteredManageableUserGroups | orderBy : order.predicate"></guac-pager> + +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ad3f253/guacamole/src/main/webapp/images/action-icons/guac-user-group-add.png ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/images/action-icons/guac-user-group-add.png b/guacamole/src/main/webapp/images/action-icons/guac-user-group-add.png new file mode 100644 index 0000000..a833433 Binary files /dev/null and b/guacamole/src/main/webapp/images/action-icons/guac-user-group-add.png differ http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ad3f253/guacamole/src/main/webapp/images/user-icons/guac-user-group.png ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/images/user-icons/guac-user-group.png b/guacamole/src/main/webapp/images/user-icons/guac-user-group.png new file mode 100644 index 0000000..4eb0aa4 Binary files /dev/null and b/guacamole/src/main/webapp/images/user-icons/guac-user-group.png differ http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/8ad3f253/guacamole/src/main/webapp/translations/en.json ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/translations/en.json b/guacamole/src/main/webapp/translations/en.json index d0eaa9a..24ab0d7 100644 --- a/guacamole/src/main/webapp/translations/en.json +++ b/guacamole/src/main/webapp/translations/en.json @@ -21,6 +21,7 @@ "ACTION_MANAGE_SETTINGS" : "Settings", "ACTION_MANAGE_SESSIONS" : "Active Sessions", "ACTION_MANAGE_USERS" : "Users", + "ACTION_MANAGE_USER_GROUPS" : "Groups", "ACTION_NAVIGATE_BACK" : "Back", "ACTION_NAVIGATE_HOME" : "Home", "ACTION_SAVE" : "Save", @@ -292,6 +293,7 @@ "FIELD_HEADER_ADMINISTER_SYSTEM" : "Administer system:", "FIELD_HEADER_CHANGE_OWN_PASSWORD" : "Change own password:", "FIELD_HEADER_CREATE_NEW_USERS" : "Create new users:", + "FIELD_HEADER_CREATE_NEW_USER_GROUPS" : "Create new user groups:", "FIELD_HEADER_CREATE_NEW_CONNECTIONS" : "Create new connections:", "FIELD_HEADER_CREATE_NEW_CONNECTION_GROUPS" : "Create new connection groups:", "FIELD_HEADER_CREATE_NEW_SHARING_PROFILES" : "Create new sharing profiles:", @@ -316,6 +318,49 @@ "TEXT_CONFIRM_DELETE" : "Users cannot be restored after they have been deleted. Are you sure you want to delete this user?" }, + + "MANAGE_USER_GROUP" : { + + "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", + "ACTION_CLONE" : "@:APP.ACTION_CLONE", + "ACTION_DELETE" : "@:APP.ACTION_DELETE", + "ACTION_SAVE" : "@:APP.ACTION_SAVE", + + "DIALOG_HEADER_CONFIRM_DELETE" : "Delete Group", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", + + "FIELD_HEADER_ADMINISTER_SYSTEM" : "@:MANAGE_USER.FIELD_HEADER_ADMINISTER_SYSTEM", + "FIELD_HEADER_CHANGE_OWN_PASSWORD" : "@:MANAGE_USER.FIELD_HEADER_CHANGE_OWN_PASSWORD", + "FIELD_HEADER_CREATE_NEW_USERS" : "@:MANAGE_USER.FIELD_HEADER_CREATE_NEW_USERS", + "FIELD_HEADER_CREATE_NEW_USER_GROUPS" : "@:MANAGE_USER.FIELD_HEADER_CREATE_NEW_USER_GROUPS", + "FIELD_HEADER_CREATE_NEW_CONNECTIONS" : "@:MANAGE_USER.FIELD_HEADER_CREATE_NEW_CONNECTIONS", + "FIELD_HEADER_CREATE_NEW_CONNECTION_GROUPS" : "@:MANAGE_USER.FIELD_HEADER_CREATE_NEW_CONNECTION_GROUPS", + "FIELD_HEADER_CREATE_NEW_SHARING_PROFILES" : "@:MANAGE_USER.FIELD_HEADER_CREATE_NEW_SHARING_PROFILES", + "FIELD_HEADER_USER_GROUP_NAME" : "Group name:", + + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + + "HELP_NO_USER_GROUPS" : "This group does not currently belong to any groups. Expand this section to add groups.", + "HELP_NO_MEMBER_USER_GROUPS" : "This group does not currently contain any groups. Expand this section to add groups.", + "HELP_NO_MEMBER_USERS" : "This group does not currently contain any users. Expand this section to add users.", + + "INFO_READ_ONLY" : "Sorry, but this group cannot be edited.", + "INFO_NO_USER_GROUPS_AVAILABLE" : "@:MANAGE_USER.INFO_NO_USER_GROUPS_AVAILABLE", + "INFO_NO_USERS_AVAILABLE" : "No users available.", + + "SECTION_HEADER_ALL_CONNECTIONS" : "@:MANAGE_USER.SECTION_HEADER_ALL_CONNECTIONS", + "SECTION_HEADER_CONNECTIONS" : "@:MANAGE_USER.SECTION_HEADER_CONNECTIONS", + "SECTION_HEADER_CURRENT_CONNECTIONS" : "@:MANAGE_USER.SECTION_HEADER_CURRENT_CONNECTIONS", + "SECTION_HEADER_EDIT_USER_GROUP" : "Edit Group", + "SECTION_HEADER_MEMBER_USERS" : "Member Users", + "SECTION_HEADER_MEMBER_USER_GROUPS" : "Member Groups", + "SECTION_HEADER_PERMISSIONS" : "@:MANAGE_USER.SECTION_HEADER_PERMISSIONS", + "SECTION_HEADER_USER_GROUPS" : "Parent Groups", + + "TEXT_CONFIRM_DELETE" : "Groups cannot be restored after they have been deleted. Are you sure you want to delete this group?" + + }, "PROTOCOL_RDP" : { @@ -747,7 +792,26 @@ "TABLE_HEADER_USERNAME" : "Username" }, - + + "SETTINGS_USER_GROUPS" : { + + "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_NEW_USER_GROUP" : "New Group", + + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", + + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + + "FORMAT_DATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", + + "HELP_USER_GROUPS" : "Click or tap on a group below to manage that group. Depending on your access level, groups can be added and deleted, and their member users and groups can be changed.", + + "SECTION_HEADER_USER_GROUPS" : "Groups", + + "TABLE_HEADER_USER_GROUP_NAME" : "Group Name" + + }, + "SETTINGS_SESSIONS" : { "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", @@ -793,6 +857,7 @@ "ACTION_MANAGE_SESSIONS" : "@:APP.ACTION_MANAGE_SESSIONS", "ACTION_MANAGE_SETTINGS" : "@:APP.ACTION_MANAGE_SETTINGS", "ACTION_MANAGE_USERS" : "@:APP.ACTION_MANAGE_USERS", + "ACTION_MANAGE_USER_GROUPS" : "@:APP.ACTION_MANAGE_USER_GROUPS", "ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME", "ACTION_VIEW_HISTORY" : "@:APP.ACTION_VIEW_HISTORY"