diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js index 0e21873dd..a911ab2ec 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js @@ -291,7 +291,10 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i // If we are creating a new connection, populate skeleton connection data else { - $scope.connection = new Connection({ protocol: 'vnc' }); + $scope.connection = new Connection({ + protocol : 'vnc', + parentIdentifier : $location.search().parent + }); $scope.historyEntryWrappers = []; $scope.parameters = {}; } diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js index d76b27a90..0d0af5921 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js @@ -175,7 +175,9 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope' // If we are creating a new connection group, populate skeleton connection group data else - $scope.connectionGroup = new ConnectionGroup(); + $scope.connectionGroup = new ConnectionGroup({ + parentIdentifier : $location.search().parent + }); /** * Available connection group types, as translation string / internal value diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js index 1414f9107..b174a8da0 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js @@ -35,6 +35,7 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe // Required types var ConnectionGroup = $injector.get('ConnectionGroup'); + var GroupListItem = $injector.get('GroupListItem'); var PermissionSet = $injector.get('PermissionSet'); // Required services @@ -205,6 +206,112 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe }; + /** + * Returns whether the current user can update the connection group + * having the given identifier within the current data source. + * + * @param {String} identifier + * The identifier of the connection group to check. + * + * @return {Boolean} + * true if the current user can update the connection group + * having the given identifier within the current data source, + * false otherwise. + */ + $scope.canUpdateConnectionGroup = function canUpdateConnectionGroup(identifier) { + + // Abort if permissions have not yet loaded + if (!$scope.permissions) + return false; + + // Can update the connection if adminstrator or have explicit permission + if (PermissionSet.hasSystemPermission($scope.permissions, PermissionSet.SystemPermissionType.ADMINISTER) + || PermissionSet.hasConnectionGroupPermission($scope.permissions, PermissionSet.ObjectPermissionType.UPDATE, identifier)) + return true; + + // Current data sources does not allow the connection group to be updated + return false; + + }; + + /** + * Adds connection-group-specific contextual actions to the given + * array of GroupListItems. Each contextual action will be + * represented by a new GroupListItem. + * + * @param {GroupListItem[]} items + * The array of GroupListItems to which new GroupListItems + * representing connection-group-specific contextual actions + * should be added. + * + * @param {GroupListItem} [parent] + * The GroupListItem representing the connection group which + * contains the given array of GroupListItems, if known. + */ + var addConnectionGroupActions = function addConnectionGroupActions(items, parent) { + + // Do nothing if we lack permission to modify the parent at all + if (parent && !$scope.canUpdateConnectionGroup(parent.identifier)) + return; + + // Add action for creating a child connection, if the user has + // permission to do so + if ($scope.canCreateConnections()) + items.push(new GroupListItem({ + type : 'new-connection', + dataSource : $scope.dataSource, + weight : 1, + wrappedItem : parent + })); + + // Add action for creating a child connection group, if the user + // has permission to do so + if ($scope.canCreateConnectionGroups()) + items.push(new GroupListItem({ + type : 'new-connection-group', + dataSource : $scope.dataSource, + weight : 1, + wrappedItem : parent + })); + + }; + + /** + * Decorates the given GroupListItem, including all descendants, + * adding contextual actions. + * + * @param {GroupListItem} item + * The GroupListItem which should be decorated with additional + * GroupListItems representing contextual actions. + */ + var decorateItem = function decorateItem(item) { + + // If the item is a connection group, add actions specific to + // connection groups + if (item.type === GroupListItem.Type.CONNECTION_GROUP) + addConnectionGroupActions(item.children, item); + + // Decorate all children + angular.forEach(item.children, decorateItem); + + }; + + /** + * Callback which decorates all items within the given array of + * GroupListItems, including their descendants, adding contextual + * actions. + * + * @param {GroupListItem[]} items + * The array of GroupListItems which should be decorated with + * additional GroupListItems representing contextual actions. + */ + $scope.rootItemDecorator = function rootItemDecorator(items) { + + // Decorate each root-level item + angular.forEach(items, decorateItem); + + }; + // Retrieve current permissions permissionService.getPermissions($scope.dataSource, currentUsername) .success(function permissionsRetrieved(permissions) { @@ -219,19 +326,19 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe if (!$scope.canManageConnections()) $location.path('/'); - }); - - // Retrieve all connections for which we have UPDATE or DELETE permission - dataSourceService.apply( - connectionGroupService.getConnectionGroupTree, - [$scope.dataSource], - ConnectionGroup.ROOT_IDENTIFIER, - [PermissionSet.ObjectPermissionType.UPDATE, PermissionSet.ObjectPermissionType.DELETE] - ) - .then(function connectionGroupsReceived(rootGroups) { - $scope.rootGroups = rootGroups; - }); - + // Retrieve all connections for which we have UPDATE or DELETE permission + dataSourceService.apply( + connectionGroupService.getConnectionGroupTree, + [$scope.dataSource], + ConnectionGroup.ROOT_IDENTIFIER, + [PermissionSet.ObjectPermissionType.UPDATE, PermissionSet.ObjectPermissionType.DELETE] + ) + .then(function connectionGroupsReceived(rootGroups) { + $scope.rootGroups = rootGroups; + }); + + }); // end retrieve permissions + }] }; diff --git a/guacamole/src/main/webapp/app/settings/styles/connection-list.css b/guacamole/src/main/webapp/app/settings/styles/connection-list.css new file mode 100644 index 000000000..7bf35538a --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/styles/connection-list.css @@ -0,0 +1,34 @@ +/* + * 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.connections .connection-list .new-connection, +.settings.connections .connection-list .new-connection-group { + opacity: 0.5; + font-style: italic; +} + +.settings.connections .connection-list .new-connection a, +.settings.connections .connection-list .new-connection a:hover, +.settings.connections .connection-list .new-connection a:visited, +.settings.connections .connection-list .new-connection-group a, +.settings.connections .connection-list .new-connection-group a:hover, +.settings.connections .connection-list .new-connection-group a:visited { + text-decoration:none; + color: black; +} diff --git a/guacamole/src/main/webapp/app/settings/templates/newConnection.html b/guacamole/src/main/webapp/app/settings/templates/newConnection.html new file mode 100644 index 000000000..e4debbe0d --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/templates/newConnection.html @@ -0,0 +1,3 @@ + + {{'SETTINGS_CONNECTIONS.ACTION_NEW_CONNECTION' | translate}} + diff --git a/guacamole/src/main/webapp/app/settings/templates/newConnectionGroup.html b/guacamole/src/main/webapp/app/settings/templates/newConnectionGroup.html new file mode 100644 index 000000000..c1d6bba5f --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/templates/newConnectionGroup.html @@ -0,0 +1,3 @@ + + {{'SETTINGS_CONNECTIONS.ACTION_NEW_CONNECTION_GROUP' | translate}} + diff --git a/guacamole/src/main/webapp/app/settings/templates/settingsConnections.html b/guacamole/src/main/webapp/app/settings/templates/settingsConnections.html index d56993ecc..dfdf686ef 100644 --- a/guacamole/src/main/webapp/app/settings/templates/settingsConnections.html +++ b/guacamole/src/main/webapp/app/settings/templates/settingsConnections.html @@ -33,9 +33,15 @@