diff --git a/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js b/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js
index d5404c778..87adb4947 100644
--- a/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js
+++ b/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js
@@ -46,37 +46,18 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList()
context : '=',
/**
- * The URL or ID of the Angular template to use when rendering a
- * connection. The @link{GroupListItem} associated with that
- * connection will be exposed within the scope of the template
- * as item
, and the arbitrary context object, if any,
- * will be exposed as context
.
+ * The map of @link{GroupListItem} type to the URL or ID of the
+ * Angular template to use when rendering a @link{GroupListItem} of
+ * that type. The @link{GroupListItem} itself will be within the
+ * scope of the template as item
, and the arbitrary
+ * context object, if any, will be exposed as context
.
+ * If the template for a type is omitted, items of that type will
+ * not be rendered. All standard types are defined by
+ * @link{GroupListItem.Type}, but use of custom types is legal.
*
- * @type String
+ * @type Object.
*/
- connectionTemplate : '=',
-
- /**
- * The URL or ID of the Angular template to use when rendering a
- * connection group. The @link{GroupListItem} associated with that
- * connection group will be exposed within the scope of the
- * template as item
, and the arbitrary context object,
- * if any, will be exposed as context
.
- *
- * @type String
- */
- connectionGroupTemplate : '=',
-
- /**
- * The URL or ID of the Angular template to use when rendering a
- * sharing profile. The @link{GroupListItem} associated with that
- * sharing profile will be exposed within the scope of the template
- * as item
, and the arbitrary context object, if any,
- * will be exposed as context
.
- *
- * @type String
- */
- sharingProfileTemplate : '=',
+ templates : '=',
/**
* Whether the root of the connection group hierarchy given should
@@ -92,7 +73,18 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList()
*
* @type Number
*/
- pageSize : '='
+ pageSize : '=',
+
+ /**
+ * A callback which accepts an array of GroupListItems as its sole
+ * parameter. If provided, the callback will be invoked whenever an
+ * array of root-level GroupListItems is about to be rendered.
+ * Changes may be made by this function to that array or to the
+ * GroupListItems themselves.
+ *
+ * @type Function
+ */
+ decorator : '='
},
@@ -145,51 +137,20 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList()
};
/**
- * Returns whether the given item represents a connection that can
- * be displayed. If there is no connection template, then no
- * connection is visible.
- *
- * @param {GroupListItem} item
- * The item to check.
+ * Returns whether a @link{GroupListItem} of the given type can be
+ * displayed. If there is no template associated with the given
+ * type, then a @link{GroupListItem} of that type cannot be
+ * displayed.
+ *
+ * @param {String} type
+ * The type to check.
*
* @returns {Boolean}
- * true if the given item is a connection that can be
- * displayed, false otherwise.
+ * true if the given @link{GroupListItem} type can be displayed,
+ * false otherwise.
*/
- $scope.isVisibleConnection = function isVisibleConnection(item) {
- return item.isConnection && !!$scope.connectionTemplate;
- };
-
- /**
- * Returns whether the given item represents a connection group
- * that can be displayed. If there is no connection group template,
- * then no connection group is visible.
- *
- * @param {GroupListItem} item
- * The item to check.
- *
- * @returns {Boolean}
- * true if the given item is a connection group that can be
- * displayed, false otherwise.
- */
- $scope.isVisibleConnectionGroup = function isVisibleConnectionGroup(item) {
- return item.isConnectionGroup && !!$scope.connectionGroupTemplate;
- };
-
- /**
- * Returns whether the given item represents a sharing profile that
- * can be displayed. If there is no sharing profile template, then
- * no sharing profile is visible.
- *
- * @param {GroupListItem} item
- * The item to check.
- *
- * @returns {Boolean}
- * true if the given item is a sharing profile that can be
- * displayed, false otherwise.
- */
- $scope.isVisibleSharingProfile = function isVisibleSharingProfile(item) {
- return item.isSharingProfile && !!$scope.sharingProfileTemplate;
+ $scope.isVisible = function isVisible(type) {
+ return !!$scope.templates[type];
};
// Set contents whenever the connection group is assigned or changed
@@ -212,7 +173,8 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList()
// Create root item for current connection group
var rootItem = GroupListItem.fromConnectionGroup(dataSource, connectionGroup,
- !!$scope.connectionTemplate, !!$scope.sharingProfileTemplate,
+ $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
@@ -255,6 +217,10 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList()
}
+ // Invoke item decorator, if provided
+ if ($scope.decorator)
+ $scope.decorator($scope.rootItems);
+
});
/**
@@ -265,7 +231,7 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList()
* connection group.
*/
$scope.toggleExpanded = function toggleExpanded(groupListItem) {
- groupListItem.isExpanded = !groupListItem.isExpanded;
+ groupListItem.expanded = !groupListItem.expanded;
};
}]
diff --git a/guacamole/src/main/webapp/app/groupList/templates/guacGroupList.html b/guacamole/src/main/webapp/app/groupList/templates/guacGroupList.html
index df9b5b6c1..4c9bb424c 100644
--- a/guacamole/src/main/webapp/app/groupList/templates/guacGroupList.html
+++ b/guacamole/src/main/webapp/app/groupList/templates/guacGroupList.html
@@ -1,55 +1,31 @@
@@ -58,7 +34,7 @@
-
diff --git a/guacamole/src/main/webapp/app/groupList/types/GroupListItem.js b/guacamole/src/main/webapp/app/groupList/types/GroupListItem.js
index 2fc9cf5b0..3591dd0ce 100644
--- a/guacamole/src/main/webapp/app/groupList/types/GroupListItem.js
+++ b/guacamole/src/main/webapp/app/groupList/types/GroupListItem.js
@@ -77,43 +77,36 @@ angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', functio
this.children = template.children || [];
/**
- * Whether this item represents a connection. If this item represents
- * a connection group or sharing profile, this MUST be false.
+ * The type of object represented by this GroupListItem. Standard types
+ * are defined by GroupListItem.Type, but custom types are also legal.
*
- * @type Boolean
+ * @type String
*/
- this.isConnection = template.isConnection;
+ this.type = template.type;
/**
- * Whether this item represents a connection group. If this item
- * represents a connection or sharing profile, this MUST be false.
+ * Whether this item, or items of the same type, can contain children.
+ * This may be true even if this particular item does not presently
+ * contain children.
*
* @type Boolean
*/
- this.isConnectionGroup = template.isConnectionGroup;
-
- /**
- * Whether this item represents a sharing profile. If this item
- * represents a connection or connection group, this MUST be false.
- *
- * @type Boolean
- */
- this.isSharingProfile = template.isSharingProfile;
+ this.expandable = template.expandable;
/**
* Whether this item represents a balancing connection group.
*
* @type Boolean
*/
- this.isBalancing = template.isBalancing;
+ this.balancing = template.balancing;
/**
* Whether the children items should be displayed.
*
* @type Boolean
*/
- this.isExpanded = template.isExpanded;
-
+ this.expanded = template.expanded;
+
/**
* Returns the number of currently active users for this connection,
* connection group, or sharing profile, if known.
@@ -126,12 +119,24 @@ angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', functio
/**
* The connection, connection group, or sharing profile whose data is
- * exposed within this GroupListItem.
+ * exposed within this GroupListItem. If the type of this GroupListItem
+ * is not one of the types defined by GroupListItem.Type, then this
+ * value may be anything.
*
- * @type Connection|ConnectionGroup|SharingProfile
+ * @type Connection|ConnectionGroup|SharingProfile|*
*/
this.wrappedItem = template.wrappedItem;
+ /**
+ * The sorting weight to apply when displaying this GroupListItem. This
+ * weight is relative only to other sorting weights. If two items have
+ * the same weight, they will be sorted based on their names.
+ *
+ * @type Number
+ * @default 0
+ */
+ this.weight = template.weight || 0;
+
};
/**
@@ -182,9 +187,8 @@ angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', functio
dataSource : dataSource,
// Type information
- isConnection : true,
- isConnectionGroup : false,
- isSharingProfile : false,
+ expandable : includeSharingProfiles,
+ type : GroupListItem.Type.CONNECTION,
// Already-converted children
children : children,
@@ -277,10 +281,9 @@ angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', functio
dataSource : dataSource,
// Type information
- isConnection : false,
- isConnectionGroup : true,
- isSharingProfile : false,
- isBalancing : connectionGroup.type === ConnectionGroup.Type.BALANCING,
+ type : GroupListItem.Type.CONNECTION_GROUP,
+ balancing : connectionGroup.type === ConnectionGroup.Type.BALANCING,
+ expandable : true,
// Already-converted children
children : children,
@@ -331,9 +334,7 @@ angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', functio
dataSource : dataSource,
// Type information
- isConnection : false,
- isConnectionGroup : false,
- isSharingProfile : true,
+ type : GroupListItem.Type.SHARING_PROFILE,
// Wrapped item
wrappedItem : sharingProfile
@@ -342,6 +343,42 @@ angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', functio
};
+ /**
+ * All pre-defined types of GroupListItems. Note that, while these are the
+ * standard types supported by GroupListItem and the related guacGroupList
+ * directive, the type string is otherwise arbitrary and custom types are
+ * legal.
+ *
+ * @type Object.
+ */
+ GroupListItem.Type = {
+
+ /**
+ * The standard type string of a GroupListItem which represents a
+ * connection.
+ *
+ * @type String
+ */
+ CONNECTION : 'connection',
+
+ /**
+ * The standard type string of a GroupListItem which represents a
+ * connection group.
+ *
+ * @type String
+ */
+ CONNECTION_GROUP : 'connection-group',
+
+ /**
+ * The standard type string of a GroupListItem which represents a
+ * sharing profile.
+ *
+ * @type String
+ */
+ SHARING_PROFILE : 'sharing-profile'
+
+ };
+
return GroupListItem;
}]);
diff --git a/guacamole/src/main/webapp/app/home/controllers/homeController.js b/guacamole/src/main/webapp/app/home/controllers/homeController.js
index aff81ae83..150ac4ead 100644
--- a/guacamole/src/main/webapp/app/home/controllers/homeController.js
+++ b/guacamole/src/main/webapp/app/home/controllers/homeController.js
@@ -26,6 +26,7 @@ angular.module('home').controller('homeController', ['$scope', '$injector',
// Get required types
var ConnectionGroup = $injector.get('ConnectionGroup');
var ClientIdentifier = $injector.get('ClientIdentifier');
+ var GroupListItem = $injector.get('GroupListItem');
// Get required services
var authenticationService = $injector.get('authenticationService');
@@ -95,15 +96,15 @@ angular.module('home').controller('homeController', ['$scope', '$injector',
getClientIdentifier : function getClientIdentifier(item) {
// If the item is a connection, generate a connection identifier
- if (item.isConnection)
+ if (item.type === GroupListItem.Type.CONNECTION)
return ClientIdentifier.toString({
dataSource : item.dataSource,
type : ClientIdentifier.Types.CONNECTION,
id : item.identifier
});
- // If the item is a connection, generate a connection group identifier
- if (item.isConnectionGroup)
+ // If the item is a connection group, generate a connection group identifier
+ if (item.type === GroupListItem.Type.CONNECTION_GROUP)
return ClientIdentifier.toString({
dataSource : item.dataSource,
type : ClientIdentifier.Types.CONNECTION_GROUP,
diff --git a/guacamole/src/main/webapp/app/home/templates/connectionGroup.html b/guacamole/src/main/webapp/app/home/templates/connectionGroup.html
index 1fe151570..7356883c2 100644
--- a/guacamole/src/main/webapp/app/home/templates/connectionGroup.html
+++ b/guacamole/src/main/webapp/app/home/templates/connectionGroup.html
@@ -1,5 +1,4 @@
-
- {{item.name}}
- {{item.name}}
+ {{item.name}}
+ {{item.name}}
diff --git a/guacamole/src/main/webapp/app/home/templates/home.html b/guacamole/src/main/webapp/app/home/templates/home.html
index 727194520..f68a0a9f9 100644
--- a/guacamole/src/main/webapp/app/home/templates/home.html
+++ b/guacamole/src/main/webapp/app/home/templates/home.html
@@ -25,8 +25,10 @@
diff --git a/guacamole/src/main/webapp/app/index/styles/lists.css b/guacamole/src/main/webapp/app/index/styles/lists.css
index 5ce162a13..0c761aef3 100644
--- a/guacamole/src/main/webapp/app/index/styles/lists.css
+++ b/guacamole/src/main/webapp/app/index/styles/lists.css
@@ -18,28 +18,28 @@
*/
.user,
-.group,
+.connection-group,
.connection {
cursor: pointer;
}
.user a,
.connection a,
-.group a {
+.connection-group a {
text-decoration:none;
color: black;
}
.user a:hover,
.connection a:hover,
-.group a:hover {
+.connection-group a:hover {
text-decoration:none;
color: black;
}
.user a:visited,
.connection a:visited,
-.group a:visited {
+.connection-group a:visited {
text-decoration:none;
color: black;
}
diff --git a/guacamole/src/main/webapp/app/index/styles/ui.css b/guacamole/src/main/webapp/app/index/styles/ui.css
index 62db41fb5..434f443f6 100644
--- a/guacamole/src/main/webapp/app/index/styles/ui.css
+++ b/guacamole/src/main/webapp/app/index/styles/ui.css
@@ -178,11 +178,11 @@ div.section {
background-position: center center;
}
-.group > .caption .icon {
+.connection-group > .caption .icon {
background-image: url('images/folder-closed.png');
}
-.group.expanded > .caption .icon {
+.connection-group.expanded > .caption .icon {
background-image: url('images/folder-open.png');
}
@@ -213,7 +213,7 @@ div.section {
padding-left: 13px;
}
-.group.empty.balancer .icon {
+.connection-group.empty.balancer .icon {
background-image: url('images/protocol-icons/guac-monitor.png');
}
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/manage/templates/locationChooser.html b/guacamole/src/main/webapp/app/manage/templates/locationChooser.html
index 57c957e5a..baeebded9 100644
--- a/guacamole/src/main/webapp/app/manage/templates/locationChooser.html
+++ b/guacamole/src/main/webapp/app/manage/templates/locationChooser.html
@@ -9,7 +9,9 @@
context="groupListContext"
show-root-group="true"
connection-groups="rootGroups"
- connection-group-template="'app/manage/templates/locationChooserConnectionGroup.html'"/>
+ templates="{
+ 'connection-group' : 'app/manage/templates/locationChooserConnectionGroup.html'
+ }"/>
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUser.html b/guacamole/src/main/webapp/app/manage/templates/manageUser.html
index 82bc1e978..e32d725ae 100644
--- a/guacamole/src/main/webapp/app/manage/templates/manageUser.html
+++ b/guacamole/src/main/webapp/app/manage/templates/manageUser.html
@@ -78,9 +78,11 @@
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 ea1805bb7..dfdf686ef 100644
--- a/guacamole/src/main/webapp/app/settings/templates/settingsConnections.html
+++ b/guacamole/src/main/webapp/app/settings/templates/settingsConnections.html
@@ -33,7 +33,15 @@
+ decorator="rootItemDecorator"
+ templates="{
+
+ 'connection' : 'app/settings/templates/connection.html',
+ 'connection-group' : 'app/settings/templates/connectionGroup.html',
+
+ 'new-connection' : 'app/settings/templates/newConnection.html',
+ 'new-connection-group' : 'app/settings/templates/newConnectionGroup.html'
+
+ }"/>