You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@guacamole.apache.org by vn...@apache.org on 2018/08/14 23:39:48 UTC
[10/13] guacamole-client git commit: GUACAMOLE-220: Add management
tab and editor for user groups.
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 <mj...@apache.org>
Authored: Thu Apr 19 23:51:25 2018 -0700
Committer: Michael Jumper <mj...@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"