diff --git a/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js b/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js index 87adb4947..97a4f22d1 100644 --- a/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js +++ b/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js @@ -32,7 +32,7 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList() * The connection groups to display as a map of data source * identifier to corresponding root group. * - * @type Object. + * @type Object. */ connectionGroups : '=', @@ -167,15 +167,23 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList() // Add each provided connection group angular.forEach(connectionGroups, function addConnectionGroup(connectionGroup, dataSource) { + var rootItem; + // Prepare data source for active connection counting dataSources.push(dataSource); connectionCount[dataSource] = {}; + // If the provided connection group is already a + // GroupListItem, no need to create a new item + if (connectionGroup instanceof GroupListItem) + rootItem = connectionGroup; + // Create root item for current connection group - var rootItem = GroupListItem.fromConnectionGroup(dataSource, connectionGroup, - $scope.isVisible(GroupListItem.Type.CONNECTION), - $scope.isVisible(GroupListItem.Type.SHARING_PROFILE), - countActiveConnections); + else + rootItem = GroupListItem.fromConnectionGroup(dataSource, connectionGroup, + $scope.isVisible(GroupListItem.Type.CONNECTION), + $scope.isVisible(GroupListItem.Type.SHARING_PROFILE), + countActiveConnections); // If root group is to be shown, add it as a root item if ($scope.showRootGroup) diff --git a/guacamole/src/main/webapp/app/groupList/directives/guacGroupListFilter.js b/guacamole/src/main/webapp/app/groupList/directives/guacGroupListFilter.js index 818b2ff93..03523932d 100644 --- a/guacamole/src/main/webapp/app/groupList/directives/guacGroupListFilter.js +++ b/guacamole/src/main/webapp/app/groupList/directives/guacGroupListFilter.js @@ -49,7 +49,7 @@ angular.module('groupList').directive('guacGroupListFilter', [function guacGroup * identifier to corresponding root group. A subset of this map * will be exposed as filteredConnectionGroups. * - * @type Object. + * @type Object. */ connectionGroups : '&', @@ -81,6 +81,7 @@ angular.module('groupList').directive('guacGroupListFilter', [function guacGroup // Required types var ConnectionGroup = $injector.get('ConnectionGroup'); var FilterPattern = $injector.get('FilterPattern'); + var GroupListItem = $injector.get('GroupListItem'); /** * The pattern object to use when filtering connections. @@ -176,6 +177,10 @@ angular.module('groupList').directive('guacGroupListFilter', [function guacGroup if (connectionGroups) { angular.forEach(connectionGroups, function updateFilteredConnectionGroup(connectionGroup, dataSource) { + // Unwrap GroupListItem + if (connectionGroup instanceof GroupListItem) + connectionGroup = connectionGroup.wrappedItem; + // Flatten hierarchy of connection group var filteredGroup = flattenConnectionGroup(connectionGroup); diff --git a/guacamole/src/main/webapp/app/groupList/types/GroupListItem.js b/guacamole/src/main/webapp/app/groupList/types/GroupListItem.js index 3591dd0ce..e6629728d 100644 --- a/guacamole/src/main/webapp/app/groupList/types/GroupListItem.js +++ b/guacamole/src/main/webapp/app/groupList/types/GroupListItem.js @@ -187,7 +187,7 @@ angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', functio dataSource : dataSource, // Type information - expandable : includeSharingProfiles, + expandable : includeSharingProfiles !== false, type : GroupListItem.Type.CONNECTION, // Already-converted children diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index 7f50e434d..dd6939138 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -25,6 +25,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto // Required types var ConnectionGroup = $injector.get('ConnectionGroup'); + var GroupListItem = $injector.get('GroupListItem'); var PageDefinition = $injector.get('PageDefinition'); var PermissionFlagSet = $injector.get('PermissionFlagSet'); var PermissionSet = $injector.get('PermissionSet'); @@ -133,7 +134,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto * thost data sources. As only one data source is applicable to any one * user being edited/created, this will only contain a single key. * - * @type Object. + * @type Object. */ $scope.rootGroups = null; @@ -607,6 +608,59 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto } + /** + * Expands all items within the tree descending from the given + * GroupListItem which have at least one descendant for which explicit READ + * permission is granted. The expanded state of all other items is left + * untouched. + * + * @param {GroupListItem} item + * The GroupListItem which should be conditionally expanded depending + * on whether READ permission is granted for any of its descendants. + * + * @param {PemissionFlagSet} flags + * The set of permissions which should be used to determine whether the + * given item and its descendants are expanded. + */ + var expandReadable = function expandReadable(item, flags) { + + // If the current item is expandable and has defined children, + // determine whether it should be expanded + if (item.expandable && item.children) { + angular.forEach(item.children, function expandReadableChild(child) { + + // Determine whether the user has READ permission for the + // current child object + var readable = false; + switch (child.type) { + + case GroupListItem.Type.CONNECTION: + readable = flags.connectionPermissions.READ[child.identifier]; + break; + + case GroupListItem.Type.CONNECTION_GROUP: + readable = flags.connectionGroupPermissions.READ[child.identifier]; + break; + + case GroupListItem.Type.SHARING_PROFILE: + readable = flags.sharingProfilePermissions.READ[child.identifier]; + break; + + } + + // The parent should be expanded by default if the child is + // expanded by default OR the user has READ permission on the + // child + item.expanded |= expandReadable(child, flags) || readable; + + }); + } + + return item.expanded; + + }; + + // Retrieve all connections for which we have ADMINISTER permission dataSourceService.apply( connectionGroupService.getConnectionGroupTree, @@ -615,7 +669,13 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto [PermissionSet.ObjectPermissionType.ADMINISTER] ) .then(function connectionGroupReceived(rootGroups) { - $scope.rootGroups = rootGroups; + + // Convert all received ConnectionGroup objects into GroupListItems + $scope.rootGroups = {}; + angular.forEach(rootGroups, function addGroupListItem(rootGroup, dataSource) { + $scope.rootGroups[dataSource] = GroupListItem.fromConnectionGroup(dataSource, rootGroup); + }); + }); // Query the user's permissions for the current user @@ -628,6 +688,19 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto $scope.permissions = permissions; }); + // Update default expanded state whenever connection groups and associated + // permissions change + $scope.$watchGroup(['rootGroups', 'permissionFlags'], function updateDefaultExpandedStates() { + angular.forEach($scope.rootGroups, function updateExpandedStates(rootGroup) { + + // Automatically expand all objects with any descendants for which + // the user has READ permission + if ($scope.permissionFlags) + expandReadable(rootGroup, $scope.permissionFlags); + + }); + }); + /** * Available system permission types, as translation string / internal * value pairs.