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 @@