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/05/04 08:26:55 UTC

[01/21] guacamole-client git commit: GUACAMOLE-220: Separate system/connection permission editing into directives.

Repository: guacamole-client
Updated Branches:
  refs/heads/master 91f19b2e0 -> 9b99b1804


GUACAMOLE-220: Separate system/connection permission editing into directives.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/94ad1f9f
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/94ad1f9f
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/94ad1f9f

Branch: refs/heads/master
Commit: 94ad1f9f349826ede932ea843ed87f58cc76d598
Parents: 8181a96
Author: Michael Jumper <mj...@apache.org>
Authored: Sat Apr 28 23:05:27 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Mon Apr 30 14:43:54 2018 -0700

----------------------------------------------------------------------
 .../manage/controllers/manageUserController.js  | 580 ++-----------------
 .../directives/connectionPermissionEditor.js    | 406 +++++++++++++
 .../manage/directives/systemPermissionEditor.js | 308 ++++++++++
 .../templates/connectionPermissionEditor.html   |  21 +
 .../webapp/app/manage/templates/manageUser.html |  52 +-
 .../templates/systemPermissionEditor.html       |  18 +
 6 files changed, 809 insertions(+), 576 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/94ad1f9f/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
index eae141b..c385065 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
@@ -24,8 +24,6 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
         function manageUserController($scope, $injector) {
             
     // 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');
@@ -35,7 +33,6 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     var $location                = $injector.get('$location');
     var $routeParams             = $injector.get('$routeParams');
     var authenticationService    = $injector.get('authenticationService');
-    var connectionGroupService   = $injector.get('connectionGroupService');
     var dataSourceService        = $injector.get('dataSourceService');
     var guacNotification         = $injector.get('guacNotification');
     var permissionService        = $injector.get('permissionService');
@@ -72,14 +69,6 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     var currentUsername = authenticationService.getCurrentUsername();
 
     /**
-     * The unique identifier of the data source containing the user being
-     * edited.
-     *
-     * @type String
-     */
-    var selectedDataSource = $routeParams.dataSource;
-
-    /**
      * The username of the original user from which this user is
      * being cloned. Only valid if this is a new user.
      *
@@ -96,6 +85,14 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     var username = $routeParams.id;
 
     /**
+     * The unique identifier of the data source containing the user being
+     * edited.
+     *
+     * @type String
+     */
+    $scope.dataSource = $routeParams.dataSource;
+
+    /**
      * The string value representing the user currently being edited within the
      * permission flag set. Note that his may not match the user's actual
      * username - it is a marker that is (1) guaranteed to be associated with
@@ -131,34 +128,6 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     $scope.permissionFlags = null;
 
     /**
-     * A map of data source identifiers to the root connection groups within
-     * 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.<String, GroupListItem>
-     */
-    $scope.rootGroups = null;
-
-    /**
-     * Array of all connection properties that are filterable.
-     *
-     * @type String[]
-     */
-    $scope.filteredConnectionProperties = [
-        'name',
-        'protocol'
-    ];
-
-    /**
-     * Array of all connection group properties that are filterable.
-     *
-     * @type String[]
-     */
-    $scope.filteredConnectionGroupProperties = [
-        'name'
-    ];
-
-    /**
      * A map of data source identifiers to the set of all permissions
      * associated with the current user under that data source, or null if the
      * user's permissions have not yet been loaded.
@@ -219,7 +188,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
             return false;
 
         // Use currently-selected data source if unspecified
-        dataSource = dataSource || selectedDataSource;
+        dataSource = dataSource || $scope.dataSource;
 
         // Account exists only if it was successfully retrieved
         return (dataSource in $scope.users);
@@ -245,7 +214,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
             return false;
 
         // Use currently-selected data source if unspecified
-        dataSource = dataSource || selectedDataSource;
+        dataSource = dataSource || $scope.dataSource;
 
         // Attributes can always be set if we are creating the user
         if (!$scope.userExists(dataSource))
@@ -275,7 +244,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     $scope.canChangeAllAttributes = function canChangeAllAttributes() {
 
         // All attributes can be set if we are creating the user
-        return !$scope.userExists(selectedDataSource);
+        return !$scope.userExists($scope.dataSource);
 
     };
 
@@ -299,7 +268,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
             return false;
 
         // Use currently-selected data source if unspecified
-        dataSource = dataSource || selectedDataSource;
+        dataSource = dataSource || $scope.dataSource;
 
         // Permissions can always be set if we are creating the user
         if (!$scope.userExists(dataSource))
@@ -318,33 +287,6 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     };
 
     /**
-     * Returns whether the current user can change the system permissions
-     * granted to the user being edited within the given data source.
-     *
-     * @param {String} [dataSource]
-     *     The identifier of the data source to check. If omitted, this will
-     *     default to the currently-selected data source.
-     *
-     * @returns {Boolean}
-     *     true if the current user can grant or revoke system permissions to
-     *     the user being edited, false otherwise.
-     */
-    $scope.canChangeSystemPermissions = function canChangeSystemPermissions(dataSource) {
-
-        // Do not check if permissions are not yet loaded
-        if (!$scope.permissions)
-            return false;
-
-        // Use currently-selected data source if unspecified
-        dataSource = dataSource || selectedDataSource;
-
-        // Only the administrator can modify system permissions
-        return PermissionSet.hasSystemPermission($scope.permissions[dataSource],
-            PermissionSet.SystemPermissionType.ADMINISTER);
-
-    };
-
-    /**
      * Returns whether the current user can edit the username of the user being
      * edited within the given data source.
      *
@@ -380,7 +322,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
             return false;
 
         // Use currently-selected data source if unspecified
-        dataSource = dataSource || selectedDataSource;
+        dataSource = dataSource || $scope.dataSource;
 
         // The administrator can always save users
         if (PermissionSet.hasSystemPermission($scope.permissions[dataSource],
@@ -417,10 +359,10 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
             return false;
 
         // Use currently-selected data source if unspecified
-        dataSource = dataSource || selectedDataSource;
+        dataSource = dataSource || $scope.dataSource;
 
         // If we are not editing an existing user, we cannot clone
-        if (!$scope.userExists(selectedDataSource))
+        if (!$scope.userExists($scope.dataSource))
             return false;
 
         // The administrator can always clone users
@@ -453,7 +395,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
             return false;
 
         // Use currently-selected data source if unspecified
-        dataSource = dataSource || selectedDataSource;
+        dataSource = dataSource || $scope.dataSource;
 
         // Can't delete what doesn't exist
         if (!$scope.userExists(dataSource))
@@ -485,7 +427,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     $scope.isReadOnly = function isReadOnly(dataSource) {
 
         // Use currently-selected data source if unspecified
-        dataSource = dataSource || selectedDataSource;
+        dataSource = dataSource || $scope.dataSource;
 
         // User is read-only if they cannot be saved
         return !$scope.canSaveUser(dataSource);
@@ -509,7 +451,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
                 return;
 
             // Only the selected data source is relevant when cloning
-            if (cloneSourceUsername && dataSource !== selectedDataSource)
+            if (cloneSourceUsername && dataSource !== $scope.dataSource)
                 return;
 
             // Determine class name based on read-only / linked status
@@ -530,7 +472,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     });
 
     // Pull user attribute schema
-    schemaService.getUserAttributes(selectedDataSource).then(function attributesReceived(attributes) {
+    schemaService.getUserAttributes($scope.dataSource).then(function attributesReceived(attributes) {
         $scope.attributes = attributes;
     }, requestService.WARN);
 
@@ -543,7 +485,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
 
             // Get user for currently-selected data source
             $scope.users = users;
-            $scope.user  = users[selectedDataSource];
+            $scope.user  = users[$scope.dataSource];
 
             // Create skeleton user if user does not exist
             if (!$scope.user)
@@ -558,7 +500,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
         $scope.selfUsername = username;
 
         // Pull user permissions
-        permissionService.getPermissions(selectedDataSource, username).then(function gotPermissions(permissions) {
+        permissionService.getPermissions($scope.dataSource, username).then(function gotPermissions(permissions) {
             $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(permissions);
         })
 
@@ -576,7 +518,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
 
             // Get user for currently-selected data source
             $scope.users = {};
-            $scope.user  = users[selectedDataSource];
+            $scope.user  = users[$scope.dataSource];
 
         }, requestService.WARN);
 
@@ -585,10 +527,10 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
         $scope.selfUsername = cloneSourceUsername;
 
         // Pull user permissions
-        permissionService.getPermissions(selectedDataSource, cloneSourceUsername)
+        permissionService.getPermissions($scope.dataSource, cloneSourceUsername)
         .then(function gotPermissions(permissions) {
             $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(permissions);
-            permissionsAdded = permissions;
+            $scope.permissionsAdded = permissions;
         })
 
         // If permissions cannot be retrieved, use empty permissions
@@ -607,78 +549,13 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
         $scope.user = new User();
         $scope.permissionFlags = new PermissionFlagSet();
 
-    }
+        // As no permissions are yet associated with the user, it is safe to
+        // use any non-empty username as a placeholder for self-referential
+        // permissions
+        $scope.selfUsername = 'SELF';
 
-    /**
-     * 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,
-        [selectedDataSource],
-        ConnectionGroup.ROOT_IDENTIFIER,
-        [PermissionSet.ObjectPermissionType.ADMINISTER]
-    )
-    .then(function connectionGroupReceived(rootGroups) {
-
-        // Convert all received ConnectionGroup objects into GroupListItems
-        $scope.rootGroups = {};
-        angular.forEach(rootGroups, function addGroupListItem(rootGroup, dataSource) {
-            $scope.rootGroups[dataSource] = GroupListItem.fromConnectionGroup(dataSource, rootGroup);
-        });
+    }
 
-    }, requestService.WARN);
-    
     // Query the user's permissions for the current user
     dataSourceService.apply(
         permissionService.getEffectivePermissions,
@@ -689,48 +566,6 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
         $scope.permissions = permissions;
     }, requestService.WARN);
 
-    // 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.
-     * 
-     * @type Object[]
-     */
-    $scope.systemPermissionTypes = [
-        {
-            label: "MANAGE_USER.FIELD_HEADER_ADMINISTER_SYSTEM",
-            value: PermissionSet.SystemPermissionType.ADMINISTER
-        },
-        {
-            label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_USERS",
-            value: PermissionSet.SystemPermissionType.CREATE_USER
-        },
-        {
-            label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_CONNECTIONS",
-            value: PermissionSet.SystemPermissionType.CREATE_CONNECTION
-        },
-        {
-            label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_CONNECTION_GROUPS",
-            value: PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP
-        },
-        {
-            label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_SHARING_PROFILES",
-            value: PermissionSet.SystemPermissionType.CREATE_SHARING_PROFILE
-        }
-    ];
-
     /**
      * The set of permissions that will be added to the user when the user is
      * saved. Permissions will only be present in this set if they are
@@ -738,7 +573,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
      *
      * @type PermissionSet
      */
-    var permissionsAdded = new PermissionSet();
+    $scope.permissionsAdded = new PermissionSet();
 
     /**
      * The set of permissions that will be removed from the user when the user 
@@ -747,336 +582,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
      *
      * @type PermissionSet
      */
-    var permissionsRemoved = new PermissionSet();
-
-    /**
-     * Updates the permissionsAdded and permissionsRemoved permission sets to
-     * reflect the addition of the given system permission.
-     * 
-     * @param {String} type
-     *     The system permission to add, as defined by
-     *     PermissionSet.SystemPermissionType.
-     */
-    var addSystemPermission = function addSystemPermission(type) {
-
-        // If permission was previously removed, simply un-remove it
-        if (PermissionSet.hasSystemPermission(permissionsRemoved, type))
-            PermissionSet.removeSystemPermission(permissionsRemoved, type);
-
-        // Otherwise, explicitly add the permission
-        else
-            PermissionSet.addSystemPermission(permissionsAdded, type);
-
-    };
-
-    /**
-     * Updates the permissionsAdded and permissionsRemoved permission sets to
-     * reflect the removal of the given system permission.
-     *
-     * @param {String} type
-     *     The system permission to remove, as defined by
-     *     PermissionSet.SystemPermissionType.
-     */
-    var removeSystemPermission = function removeSystemPermission(type) {
-
-        // If permission was previously added, simply un-add it
-        if (PermissionSet.hasSystemPermission(permissionsAdded, type))
-            PermissionSet.removeSystemPermission(permissionsAdded, type);
-
-        // Otherwise, explicitly remove the permission
-        else
-            PermissionSet.addSystemPermission(permissionsRemoved, type);
-
-    };
-
-    /**
-     * Notifies the controller that a change has been made to the given
-     * system permission for the user being edited.
-     *
-     * @param {String} type
-     *     The system permission that was changed, as defined by
-     *     PermissionSet.SystemPermissionType.
-     */
-    $scope.systemPermissionChanged = function systemPermissionChanged(type) {
-
-        // Determine current permission setting
-        var granted = $scope.permissionFlags.systemPermissions[type];
-
-        // Add/remove permission depending on flag state
-        if (granted)
-            addSystemPermission(type);
-        else
-            removeSystemPermission(type);
-
-    };
-
-    /**
-     * Updates the permissionsAdded and permissionsRemoved permission sets to
-     * reflect the addition of the given user permission.
-     * 
-     * @param {String} type
-     *     The user permission to add, as defined by
-     *     PermissionSet.ObjectPermissionType.
-     *
-     * @param {String} identifier
-     *     The identifier of the user affected by the permission being added.
-     */
-    var addUserPermission = function addUserPermission(type, identifier) {
-
-        // If permission was previously removed, simply un-remove it
-        if (PermissionSet.hasUserPermission(permissionsRemoved, type, identifier))
-            PermissionSet.removeUserPermission(permissionsRemoved, type, identifier);
-
-        // Otherwise, explicitly add the permission
-        else
-            PermissionSet.addUserPermission(permissionsAdded, type, identifier);
-
-    };
-
-    /**
-     * Updates the permissionsAdded and permissionsRemoved permission sets to
-     * reflect the removal of the given user permission.
-     *
-     * @param {String} type
-     *     The user permission to remove, as defined by
-     *     PermissionSet.ObjectPermissionType.
-     *
-     * @param {String} identifier
-     *     The identifier of the user affected by the permission being removed.
-     */
-    var removeUserPermission = function removeUserPermission(type, identifier) {
-
-        // If permission was previously added, simply un-add it
-        if (PermissionSet.hasUserPermission(permissionsAdded, type, identifier))
-            PermissionSet.removeUserPermission(permissionsAdded, type, identifier);
-
-        // Otherwise, explicitly remove the permission
-        else
-            PermissionSet.addUserPermission(permissionsRemoved, type, identifier);
-
-    };
-
-    /**
-     * Notifies the controller that a change has been made to the given user
-     * permission for the user being edited.
-     *
-     * @param {String} type
-     *     The user permission that was changed, as defined by
-     *     PermissionSet.ObjectPermissionType.
-     *
-     * @param {String} identifier
-     *     The identifier of the user affected by the changed permission.
-     */
-    $scope.userPermissionChanged = function userPermissionChanged(type, identifier) {
-
-        // Determine current permission setting
-        var granted = $scope.permissionFlags.userPermissions[type][identifier];
-
-        // Add/remove permission depending on flag state
-        if (granted)
-            addUserPermission(type, identifier);
-        else
-            removeUserPermission(type, identifier);
-
-    };
-
-    /**
-     * Updates the permissionsAdded and permissionsRemoved permission sets to
-     * reflect the addition of the given connection permission.
-     * 
-     * @param {String} identifier
-     *     The identifier of the connection to add READ permission for.
-     */
-    var addConnectionPermission = function addConnectionPermission(identifier) {
-
-        // If permission was previously removed, simply un-remove it
-        if (PermissionSet.hasConnectionPermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier))
-            PermissionSet.removeConnectionPermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
-
-        // Otherwise, explicitly add the permission
-        else
-            PermissionSet.addConnectionPermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
-
-    };
-
-    /**
-     * Updates the permissionsAdded and permissionsRemoved permission sets to
-     * reflect the removal of the given connection permission.
-     *
-     * @param {String} identifier
-     *     The identifier of the connection to remove READ permission for.
-     */
-    var removeConnectionPermission = function removeConnectionPermission(identifier) {
-
-        // If permission was previously added, simply un-add it
-        if (PermissionSet.hasConnectionPermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier))
-            PermissionSet.removeConnectionPermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
-
-        // Otherwise, explicitly remove the permission
-        else
-            PermissionSet.addConnectionPermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
-
-    };
-
-    /**
-     * Updates the permissionsAdded and permissionsRemoved permission sets to
-     * reflect the addition of the given connection group permission.
-     * 
-     * @param {String} identifier
-     *     The identifier of the connection group to add READ permission for.
-     */
-    var addConnectionGroupPermission = function addConnectionGroupPermission(identifier) {
-
-        // If permission was previously removed, simply un-remove it
-        if (PermissionSet.hasConnectionGroupPermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier))
-            PermissionSet.removeConnectionGroupPermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
-
-        // Otherwise, explicitly add the permission
-        else
-            PermissionSet.addConnectionGroupPermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
-
-    };
-
-    /**
-     * Updates the permissionsAdded and permissionsRemoved permission sets to
-     * reflect the removal of the given connection permission.
-     *
-     * @param {String} identifier
-     *     The identifier of the connection to remove READ permission for.
-     */
-    var removeConnectionGroupPermission = function removeConnectionGroupPermission(identifier) {
-
-        // If permission was previously added, simply un-add it
-        if (PermissionSet.hasConnectionGroupPermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier))
-            PermissionSet.removeConnectionGroupPermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
-
-        // Otherwise, explicitly remove the permission
-        else
-            PermissionSet.addConnectionGroupPermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
-
-    };
-
-    /**
-     * Updates the permissionsAdded and permissionsRemoved permission sets to
-     * reflect the addition of the given sharing profile permission.
-     *
-     * @param {String} identifier
-     *     The identifier of the sharing profile to add READ permission for.
-     */
-    var addSharingProfilePermission = function addSharingProfilePermission(identifier) {
-
-        // If permission was previously removed, simply un-remove it
-        if (PermissionSet.hasSharingProfilePermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier))
-            PermissionSet.removeSharingProfilePermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
-
-        // Otherwise, explicitly add the permission
-        else
-            PermissionSet.addSharingProfilePermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
-
-    };
-
-    /**
-     * Updates the permissionsAdded and permissionsRemoved permission sets to
-     * reflect the removal of the given sharing profile permission.
-     *
-     * @param {String} identifier
-     *     The identifier of the sharing profile to remove READ permission for.
-     */
-    var removeSharingProfilePermission = function removeSharingProfilePermission(identifier) {
-
-        // If permission was previously added, simply un-add it
-        if (PermissionSet.hasSharingProfilePermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier))
-            PermissionSet.removeSharingProfilePermission(permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
-
-        // Otherwise, explicitly remove the permission
-        else
-            PermissionSet.addSharingProfilePermission(permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
-
-    };
-
-
-    // Expose permission query and modification functions to group list template
-    $scope.groupListContext = {
-
-        /**
-         * Returns the PermissionFlagSet that contains the current state of
-         * granted permissions.
-         *
-         * @returns {PermissionFlagSet}
-         *     The PermissionFlagSet describing the current state of granted
-         *     permissions for the user being edited.
-         */
-        getPermissionFlags : function getPermissionFlags() {
-            return $scope.permissionFlags;
-        },
-
-        /**
-         * Notifies the controller that a change has been made to the given
-         * connection permission for the user being edited. This only applies
-         * to READ permissions.
-         *
-         * @param {String} identifier
-         *     The identifier of the connection affected by the changed
-         *     permission.
-         */
-        connectionPermissionChanged : function connectionPermissionChanged(identifier) {
-
-            // Determine current permission setting
-            var granted = $scope.permissionFlags.connectionPermissions.READ[identifier];
-
-            // Add/remove permission depending on flag state
-            if (granted)
-                addConnectionPermission(identifier);
-            else
-                removeConnectionPermission(identifier);
-
-        },
-
-        /**
-         * Notifies the controller that a change has been made to the given
-         * connection group permission for the user being edited. This only
-         * applies to READ permissions.
-         *
-         * @param {String} identifier
-         *     The identifier of the connection group affected by the changed
-         *     permission.
-         */
-        connectionGroupPermissionChanged : function connectionGroupPermissionChanged(identifier) {
-
-            // Determine current permission setting
-            var granted = $scope.permissionFlags.connectionGroupPermissions.READ[identifier];
-
-            // Add/remove permission depending on flag state
-            if (granted)
-                addConnectionGroupPermission(identifier);
-            else
-                removeConnectionGroupPermission(identifier);
-
-        },
-
-        /**
-         * Notifies the controller that a change has been made to the given
-         * sharing profile permission for the user being edited. This only
-         * applies to READ permissions.
-         *
-         * @param {String} identifier
-         *     The identifier of the sharing profile affected by the changed
-         *     permission.
-         */
-        sharingProfilePermissionChanged : function sharingProfilePermissionChanged(identifier) {
-
-            // Determine current permission setting
-            var granted = $scope.permissionFlags.sharingProfilePermissions.READ[identifier];
-
-            // Add/remove permission depending on flag state
-            if (granted)
-                addSharingProfilePermission(identifier);
-            else
-                removeSharingProfilePermission(identifier);
-
-        }
-
-    };
+    $scope.permissionsRemoved = new PermissionSet();
 
     /**
      * Cancels all pending edits, returning to the management page.
@@ -1090,7 +596,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
      * which is prepopulated with the data from the user currently being edited.
      */
     $scope.cloneUser = function cloneUser() {
-        $location.path('/manage/' + encodeURIComponent(selectedDataSource) + '/users').search('clone', username);
+        $location.path('/manage/' + encodeURIComponent($scope.dataSource) + '/users').search('clone', username);
     };
             
     /**
@@ -1113,10 +619,10 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
 
         // Save or create the user, depending on whether the user exists
         var saveUserPromise;
-        if ($scope.userExists(selectedDataSource))
-            saveUserPromise = userService.saveUser(selectedDataSource, $scope.user);
+        if ($scope.userExists($scope.dataSource))
+            saveUserPromise = userService.saveUser($scope.dataSource, $scope.user);
         else
-            saveUserPromise = userService.createUser(selectedDataSource, $scope.user);
+            saveUserPromise = userService.createUser($scope.dataSource, $scope.user);
 
         saveUserPromise.then(function savedUser() {
 
@@ -1124,21 +630,21 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
             if ($scope.selfUsername !== $scope.user.username) {
 
                 // Rename added permission
-                if (permissionsAdded.userPermissions[$scope.selfUsername]) {
-                    permissionsAdded.userPermissions[$scope.user.username] = permissionsAdded.userPermissions[$scope.selfUsername];
-                    delete permissionsAdded.userPermissions[$scope.selfUsername];
+                if ($scope.permissionsAdded.userPermissions[$scope.selfUsername]) {
+                    $scope.permissionsAdded.userPermissions[$scope.user.username] = $scope.permissionsAdded.userPermissions[$scope.selfUsername];
+                    delete $scope.permissionsAdded.userPermissions[$scope.selfUsername];
                 }
 
                 // Rename removed permission
-                if (permissionsRemoved.userPermissions[$scope.selfUsername]) {
-                    permissionsRemoved.userPermissions[$scope.user.username] = permissionsRemoved.userPermissions[$scope.selfUsername];
-                    delete permissionsRemoved.userPermissions[$scope.selfUsername];
+                if ($scope.permissionsRemoved.userPermissions[$scope.selfUsername]) {
+                    $scope.permissionsRemoved.userPermissions[$scope.user.username] = $scope.permissionsRemoved.userPermissions[$scope.selfUsername];
+                    delete $scope.permissionsRemoved.userPermissions[$scope.selfUsername];
                 }
                 
             }
 
             // Upon success, save any changed permissions
-            permissionService.patchPermissions(selectedDataSource, $scope.user.username, permissionsAdded, permissionsRemoved)
+            permissionService.patchPermissions($scope.dataSource, $scope.user.username, $scope.permissionsAdded, $scope.permissionsRemoved)
             .then(function patchedUserPermissions() {
                 $location.url('/settings/users');
             }, guacNotification.SHOW_REQUEST_ERROR);
@@ -1180,7 +686,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     var deleteUserImmediately = function deleteUserImmediately() {
 
         // Delete the user 
-        userService.deleteUser(selectedDataSource, $scope.user)
+        userService.deleteUser($scope.dataSource, $scope.user)
         .then(function deletedUser() {
             $location.path('/settings/users');
         }, guacNotification.SHOW_REQUEST_ERROR);

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/94ad1f9f/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js b/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js
new file mode 100644
index 0000000..c4be071
--- /dev/null
+++ b/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js
@@ -0,0 +1,406 @@
+/*
+ * 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 manipulating the connection permissions granted within a
+ * given {@link PermissionFlagSet}, tracking the specific permissions added or
+ * removed within a separate pair of {@link PermissionSet} objects.
+ */
+angular.module('manage').directive('connectionPermissionEditor', ['$injector',
+    function connectionPermissionEditor($injector) {
+
+    // Required types
+    var ConnectionGroup   = $injector.get('ConnectionGroup');
+    var GroupListItem     = $injector.get('GroupListItem');
+    var PermissionSet     = $injector.get('PermissionSet');
+
+    // Required services
+    var connectionGroupService = $injector.get('connectionGroupService');
+    var dataSourceService      = $injector.get('dataSourceService');
+    var requestService         = $injector.get('requestService');
+
+    var directive = {
+
+        // Element only
+        restrict: 'E',
+        replace: true,
+
+        scope: {
+
+            /**
+             * The unique identifier of the data source associated with the
+             * permissions being manipulated.
+             *
+             * @type String
+             */
+            dataSource : '=',
+
+            /**
+             * The current state of the permissions being manipulated. This
+             * {@link PemissionFlagSet} will be modified as changes are made
+             * through this permission editor.
+             *
+             * @type PermissionFlagSet
+             */
+            permissionFlags : '=',
+
+            /**
+             * The set of permissions that have been added, relative to the
+             * initial state of the permissions being manipulated.
+             *
+             * @type PermissionSet
+             */
+            permissionsAdded : '=',
+
+            /**
+             * The set of permissions that have been added, relative to the
+             * initial state of the permissions being manipulated.
+             *
+             * @type PermissionSet
+             */
+            permissionsRemoved : '='
+
+        },
+
+        templateUrl: 'app/manage/templates/connectionPermissionEditor.html'
+
+    };
+
+    directive.controller = ['$scope', function connectionPermissionEditorController($scope) {
+
+        /**
+         * Array of all connection properties that are filterable.
+         *
+         * @type String[]
+         */
+        $scope.filteredConnectionProperties = [
+            'name',
+            'protocol'
+        ];
+
+        /**
+         * Array of all connection group properties that are filterable.
+         *
+         * @type String[]
+         */
+        $scope.filteredConnectionGroupProperties = [
+            'name'
+        ];
+
+        /**
+         * A map of data source identifiers to the root connection groups within
+         * thost data sources. As only one data source is applicable to any
+         * particular permission set being edited/created, this will only
+         * contain a single key.
+         *
+         * @type Object.<String, GroupListItem>
+         */
+        $scope.rootGroups = null;
+
+        // Retrieve all connections for which we have ADMINISTER permission
+        dataSourceService.apply(
+            connectionGroupService.getConnectionGroupTree,
+            [$scope.dataSource],
+            ConnectionGroup.ROOT_IDENTIFIER,
+            [PermissionSet.ObjectPermissionType.ADMINISTER]
+        )
+        .then(function connectionGroupReceived(rootGroups) {
+
+            // Convert all received ConnectionGroup objects into GroupListItems
+            $scope.rootGroups = {};
+            angular.forEach(rootGroups, function addGroupListItem(rootGroup, dataSource) {
+                $scope.rootGroups[dataSource] = GroupListItem.fromConnectionGroup(dataSource, rootGroup);
+            });
+
+        }, requestService.WARN);
+
+        /**
+         * 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 permission set contains 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 permission set contains READ
+                    // permission on the child
+                    item.expanded |= expandReadable(child, flags) || readable;
+
+                });
+            }
+
+            return item.expanded;
+
+        };
+
+        // Update default expanded state whenever connection groups and
+        // associated permissions change
+        $scope.$watchGroup(['rootGroups', 'permissionFlags'], function updateDefaultExpandedStates() {
+
+            if (!$scope.rootGroups || !$scope.permissionFlags)
+                return;
+
+            angular.forEach($scope.rootGroups, function updateExpandedStates(rootGroup) {
+
+                // Automatically expand all objects with any descendants for
+                // which the permission set contains READ permission
+                expandReadable(rootGroup, $scope.permissionFlags);
+
+            });
+
+        });
+
+        /**
+         * Updates the permissionsAdded and permissionsRemoved permission sets
+         * to reflect the addition of the given connection permission.
+         *
+         * @param {String} identifier
+         *     The identifier of the connection to add READ permission for.
+         */
+        var addConnectionPermission = function addConnectionPermission(identifier) {
+
+            // If permission was previously removed, simply un-remove it
+            if (PermissionSet.hasConnectionPermission($scope.permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier))
+                PermissionSet.removeConnectionPermission($scope.permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
+
+            // Otherwise, explicitly add the permission
+            else
+                PermissionSet.addConnectionPermission($scope.permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
+
+        };
+
+        /**
+         * Updates the permissionsAdded and permissionsRemoved permission sets
+         * to reflect the removal of the given connection permission.
+         *
+         * @param {String} identifier
+         *     The identifier of the connection to remove READ permission for.
+         */
+        var removeConnectionPermission = function removeConnectionPermission(identifier) {
+
+            // If permission was previously added, simply un-add it
+            if (PermissionSet.hasConnectionPermission($scope.permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier))
+                PermissionSet.removeConnectionPermission($scope.permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
+
+            // Otherwise, explicitly remove the permission
+            else
+                PermissionSet.addConnectionPermission($scope.permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
+
+        };
+
+        /**
+         * Updates the permissionsAdded and permissionsRemoved permission sets
+         * to reflect the addition of the given connection group permission.
+         *
+         * @param {String} identifier
+         *     The identifier of the connection group to add READ permission
+         *     for.
+         */
+        var addConnectionGroupPermission = function addConnectionGroupPermission(identifier) {
+
+            // If permission was previously removed, simply un-remove it
+            if (PermissionSet.hasConnectionGroupPermission($scope.permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier))
+                PermissionSet.removeConnectionGroupPermission($scope.permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
+
+            // Otherwise, explicitly add the permission
+            else
+                PermissionSet.addConnectionGroupPermission($scope.permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
+
+        };
+
+        /**
+         * Updates the permissionsAdded and permissionsRemoved permission sets
+         * to reflect the removal of the given connection permission.
+         *
+         * @param {String} identifier
+         *     The identifier of the connection to remove READ permission for.
+         */
+        var removeConnectionGroupPermission = function removeConnectionGroupPermission(identifier) {
+
+            // If permission was previously added, simply un-add it
+            if (PermissionSet.hasConnectionGroupPermission($scope.permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier))
+                PermissionSet.removeConnectionGroupPermission($scope.permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
+
+            // Otherwise, explicitly remove the permission
+            else
+                PermissionSet.addConnectionGroupPermission($scope.permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
+
+        };
+
+        /**
+         * Updates the permissionsAdded and permissionsRemoved permission sets
+         * to reflect the addition of the given sharing profile permission.
+         *
+         * @param {String} identifier
+         *     The identifier of the sharing profile to add READ permission for.
+         */
+        var addSharingProfilePermission = function addSharingProfilePermission(identifier) {
+
+            // If permission was previously removed, simply un-remove it
+            if (PermissionSet.hasSharingProfilePermission($scope.permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier))
+                PermissionSet.removeSharingProfilePermission($scope.permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
+
+            // Otherwise, explicitly add the permission
+            else
+                PermissionSet.addSharingProfilePermission($scope.permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
+
+        };
+
+        /**
+         * Updates the permissionsAdded and permissionsRemoved permission sets
+         * to reflect the removal of the given sharing profile permission.
+         *
+         * @param {String} identifier
+         *     The identifier of the sharing profile to remove READ permission
+         *     for.
+         */
+        var removeSharingProfilePermission = function removeSharingProfilePermission(identifier) {
+
+            // If permission was previously added, simply un-add it
+            if (PermissionSet.hasSharingProfilePermission($scope.permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier))
+                PermissionSet.removeSharingProfilePermission($scope.permissionsAdded, PermissionSet.ObjectPermissionType.READ, identifier);
+
+            // Otherwise, explicitly remove the permission
+            else
+                PermissionSet.addSharingProfilePermission($scope.permissionsRemoved, PermissionSet.ObjectPermissionType.READ, identifier);
+
+        };
+
+        // Expose permission query and modification functions to group list template
+        $scope.groupListContext = {
+
+            /**
+             * Returns the PermissionFlagSet that contains the current state of
+             * granted permissions.
+             *
+             * @returns {PermissionFlagSet}
+             *     The PermissionFlagSet describing the current state of granted
+             *     permissions for the permission set being edited.
+             */
+            getPermissionFlags : function getPermissionFlags() {
+                return $scope.permissionFlags;
+            },
+
+            /**
+             * Notifies the controller that a change has been made to the given
+             * connection permission for the permission set being edited. This
+             * only applies to READ permissions.
+             *
+             * @param {String} identifier
+             *     The identifier of the connection affected by the changed
+             *     permission.
+             */
+            connectionPermissionChanged : function connectionPermissionChanged(identifier) {
+
+                // Determine current permission setting
+                var granted = $scope.permissionFlags.connectionPermissions.READ[identifier];
+
+                // Add/remove permission depending on flag state
+                if (granted)
+                    addConnectionPermission(identifier);
+                else
+                    removeConnectionPermission(identifier);
+
+            },
+
+            /**
+             * Notifies the controller that a change has been made to the given
+             * connection group permission for the permission set being edited.
+             * This only applies to READ permissions.
+             *
+             * @param {String} identifier
+             *     The identifier of the connection group affected by the
+             *     changed permission.
+             */
+            connectionGroupPermissionChanged : function connectionGroupPermissionChanged(identifier) {
+
+                // Determine current permission setting
+                var granted = $scope.permissionFlags.connectionGroupPermissions.READ[identifier];
+
+                // Add/remove permission depending on flag state
+                if (granted)
+                    addConnectionGroupPermission(identifier);
+                else
+                    removeConnectionGroupPermission(identifier);
+
+            },
+
+            /**
+             * Notifies the controller that a change has been made to the given
+             * sharing profile permission for the permission set being edited.
+             * This only applies to READ permissions.
+             *
+             * @param {String} identifier
+             *     The identifier of the sharing profile affected by the changed
+             *     permission.
+             */
+            sharingProfilePermissionChanged : function sharingProfilePermissionChanged(identifier) {
+
+                // Determine current permission setting
+                var granted = $scope.permissionFlags.sharingProfilePermissions.READ[identifier];
+
+                // Add/remove permission depending on flag state
+                if (granted)
+                    addSharingProfilePermission(identifier);
+                else
+                    removeSharingProfilePermission(identifier);
+
+            }
+
+        };
+
+    }];
+
+    return directive;
+
+}]);

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/94ad1f9f/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
new file mode 100644
index 0000000..ec41872
--- /dev/null
+++ b/guacamole/src/main/webapp/app/manage/directives/systemPermissionEditor.js
@@ -0,0 +1,308 @@
+/*
+ * 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 manipulating the system permissions granted within a given
+ * {@link PermissionFlagSet}, tracking the specific permissions added or
+ * removed within a separate pair of {@link PermissionSet} objects. Optionally,
+ * the permission for a particular user to update themselves (change their own
+ * password/attributes) may also be manipulated.
+ */
+angular.module('manage').directive('systemPermissionEditor', ['$injector',
+    function systemPermissionEditor($injector) {
+
+    // Required services
+    var authenticationService = $injector.get('authenticationService');
+    var dataSourceService     = $injector.get('dataSourceService');
+    var permissionService     = $injector.get('permissionService');
+    var requestService        = $injector.get('requestService');
+
+    // Required types
+    var PermissionSet = $injector.get('PermissionSet');
+
+    var directive = {
+
+        // Element only
+        restrict: 'E',
+        replace: true,
+
+        scope: {
+
+            /**
+             * The unique identifier of the data source associated with the
+             * permissions being manipulated.
+             *
+             * @type String
+             */
+            dataSource : '=',
+
+            /**
+             * The username of the user whose self-update permission (whether
+             * the user has permission to update their own user account) should
+             * be additionally controlled by this editor. If no such user
+             * permissions should be controlled, this should be left undefined.
+             *
+             * @type String
+             */
+            username : '=',
+
+            /**
+             * The current state of the permissions being manipulated. This
+             * {@link PemissionFlagSet} will be modified as changes are made
+             * through this permission editor.
+             *
+             * @type PermissionFlagSet
+             */
+            permissionFlags : '=',
+
+            /**
+             * The set of permissions that have been added, relative to the
+             * initial state of the permissions being manipulated.
+             *
+             * @type PermissionSet
+             */
+            permissionsAdded : '=',
+
+            /**
+             * The set of permissions that have been removed, relative to the
+             * initial state of the permissions being manipulated.
+             *
+             * @type PermissionSet
+             */
+            permissionsRemoved : '='
+
+        },
+
+        templateUrl: 'app/manage/templates/systemPermissionEditor.html'
+
+    };
+
+    directive.controller = ['$scope', function systemPermissionEditorController($scope) {
+
+        /**
+         * 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();
+
+        /**
+         * Available system permission types, as translation string / internal
+         * value pairs.
+         *
+         * @type Object[]
+         */
+        $scope.systemPermissionTypes = [
+            {
+                label: "MANAGE_USER.FIELD_HEADER_ADMINISTER_SYSTEM",
+                value: PermissionSet.SystemPermissionType.ADMINISTER
+            },
+            {
+                label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_USERS",
+                value: PermissionSet.SystemPermissionType.CREATE_USER
+            },
+            {
+                label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_CONNECTIONS",
+                value: PermissionSet.SystemPermissionType.CREATE_CONNECTION
+            },
+            {
+                label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_CONNECTION_GROUPS",
+                value: PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP
+            },
+            {
+                label: "MANAGE_USER.FIELD_HEADER_CREATE_NEW_SHARING_PROFILES",
+                value: PermissionSet.SystemPermissionType.CREATE_SHARING_PROFILE
+            }
+        ];
+
+        // Query the permissions granted to the currently-authenticated user
+        dataSourceService.apply(
+            permissionService.getEffectivePermissions,
+            dataSources,
+            currentUsername
+        )
+        .then(function permissionsReceived(permissions) {
+            $scope.permissions = permissions;
+        }, requestService.WARN);
+
+        /**
+         * Returns whether the current user has permission to change the system
+         * permissions granted to users.
+         *
+         * @returns {Boolean}
+         *     true if the current user can grant or revoke system permissions
+         *     to the permission set being edited, false otherwise.
+         */
+        $scope.canChangeSystemPermissions = function canChangeSystemPermissions() {
+
+            // Do not check if permissions are not yet loaded
+            if (!$scope.permissions)
+                return false;
+
+            // Only the administrator can modify system permissions
+            return PermissionSet.hasSystemPermission($scope.permissions[$scope.dataSource],
+                PermissionSet.SystemPermissionType.ADMINISTER);
+
+        };
+
+        /**
+         * Updates the permissionsAdded and permissionsRemoved permission sets
+         * to reflect the addition of the given system permission.
+         *
+         * @param {String} type
+         *     The system permission to add, as defined by
+         *     PermissionSet.SystemPermissionType.
+         */
+        var addSystemPermission = function addSystemPermission(type) {
+
+            // If permission was previously removed, simply un-remove it
+            if (PermissionSet.hasSystemPermission($scope.permissionsRemoved, type))
+                PermissionSet.removeSystemPermission($scope.permissionsRemoved, type);
+
+            // Otherwise, explicitly add the permission
+            else
+                PermissionSet.addSystemPermission($scope.permissionsAdded, type);
+
+        };
+
+        /**
+         * Updates the permissionsAdded and permissionsRemoved permission sets
+         * to reflect the removal of the given system permission.
+         *
+         * @param {String} type
+         *     The system permission to remove, as defined by
+         *     PermissionSet.SystemPermissionType.
+         */
+        var removeSystemPermission = function removeSystemPermission(type) {
+
+            // If permission was previously added, simply un-add it
+            if (PermissionSet.hasSystemPermission($scope.permissionsAdded, type))
+                PermissionSet.removeSystemPermission($scope.permissionsAdded, type);
+
+            // Otherwise, explicitly remove the permission
+            else
+                PermissionSet.addSystemPermission($scope.permissionsRemoved, type);
+
+        };
+
+        /**
+         * Notifies the controller that a change has been made to the given
+         * system permission for the permission set being edited.
+         *
+         * @param {String} type
+         *     The system permission that was changed, as defined by
+         *     PermissionSet.SystemPermissionType.
+         */
+        $scope.systemPermissionChanged = function systemPermissionChanged(type) {
+
+            // Determine current permission setting
+            var granted = $scope.permissionFlags.systemPermissions[type];
+
+            // Add/remove permission depending on flag state
+            if (granted)
+                addSystemPermission(type);
+            else
+                removeSystemPermission(type);
+
+        };
+
+        /**
+         * Updates the permissionsAdded and permissionsRemoved permission sets
+         * to reflect the addition of the given user permission.
+         *
+         * @param {String} type
+         *     The user permission to add, as defined by
+         *     PermissionSet.ObjectPermissionType.
+         *
+         * @param {String} identifier
+         *     The identifier of the user affected by the permission being added.
+         */
+        var addUserPermission = function addUserPermission(type, identifier) {
+
+            // If permission was previously removed, simply un-remove it
+            if (PermissionSet.hasUserPermission($scope.permissionsRemoved, type, identifier))
+                PermissionSet.removeUserPermission($scope.permissionsRemoved, type, identifier);
+
+            // Otherwise, explicitly add the permission
+            else
+                PermissionSet.addUserPermission($scope.permissionsAdded, type, identifier);
+
+        };
+
+        /**
+         * Updates the permissionsAdded and permissionsRemoved permission sets
+         * to reflect the removal of the given user permission.
+         *
+         * @param {String} type
+         *     The user permission to remove, as defined by
+         *     PermissionSet.ObjectPermissionType.
+         *
+         * @param {String} identifier
+         *     The identifier of the user affected by the permission being
+         *     removed.
+         */
+        var removeUserPermission = function removeUserPermission(type, identifier) {
+
+            // If permission was previously added, simply un-add it
+            if (PermissionSet.hasUserPermission($scope.permissionsAdded, type, identifier))
+                PermissionSet.removeUserPermission($scope.permissionsAdded, type, identifier);
+
+            // Otherwise, explicitly remove the permission
+            else
+                PermissionSet.addUserPermission($scope.permissionsRemoved, type, identifier);
+
+        };
+
+        /**
+         * Notifies the controller that a change has been made to the given user
+         * permission for the permission set being edited.
+         *
+         * @param {String} type
+         *     The user permission that was changed, as defined by
+         *     PermissionSet.ObjectPermissionType.
+         *
+         * @param {String} identifier
+         *     The identifier of the user affected by the changed permission.
+         */
+        $scope.userPermissionChanged = function userPermissionChanged(type, identifier) {
+
+            // Determine current permission setting
+            var granted = $scope.permissionFlags.userPermissions[type][identifier];
+
+            // Add/remove permission depending on flag state
+            if (granted)
+                addUserPermission(type, identifier);
+            else
+                removeUserPermission(type, identifier);
+
+        };
+
+    }];
+
+    return directive;
+
+}]);

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/94ad1f9f/guacamole/src/main/webapp/app/manage/templates/connectionPermissionEditor.html
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/templates/connectionPermissionEditor.html b/guacamole/src/main/webapp/app/manage/templates/connectionPermissionEditor.html
new file mode 100644
index 0000000..61d3804
--- /dev/null
+++ b/guacamole/src/main/webapp/app/manage/templates/connectionPermissionEditor.html
@@ -0,0 +1,21 @@
+<div class="connection-permissions">
+    <div class="header">
+        <h2>{{'MANAGE_USER.SECTION_HEADER_CONNECTIONS' | translate}}</h2>
+        <guac-group-list-filter connection-groups="rootGroups"
+            filtered-connection-groups="filteredRootGroups"
+            placeholder="'MANAGE_USER.FIELD_PLACEHOLDER_FILTER' | translate"
+            connection-properties="filteredConnectionProperties"
+            connection-group-properties="filteredConnectionGroupProperties"></guac-group-list-filter>
+    </div>
+    <div class="section">
+        <guac-group-list
+            context="groupListContext"
+            connection-groups="filteredRootGroups"
+            templates="{
+                'connection'       : 'app/manage/templates/connectionPermission.html',
+                'sharing-profile'  : 'app/manage/templates/sharingProfilePermission.html',
+                'connection-group' : 'app/manage/templates/connectionGroupPermission.html'
+            }"
+            page-size="20"></guac-group-list>
+    </div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/94ad1f9f/guacamole/src/main/webapp/app/manage/templates/manageUser.html
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUser.html b/guacamole/src/main/webapp/app/manage/templates/manageUser.html
index 55b6d30..5fed148 100644
--- a/guacamole/src/main/webapp/app/manage/templates/manageUser.html
+++ b/guacamole/src/main/webapp/app/manage/templates/manageUser.html
@@ -46,47 +46,21 @@
         </div>
 
         <!-- System permissions section -->
-        <div class="system-permissions" ng-show="canChangePermissions()">
-            <h2 class="header">{{'MANAGE_USER.SECTION_HEADER_PERMISSIONS' | translate}}</h2>
-            <div class="section">
-                <table class="properties">
-                    <tr ng-repeat="systemPermissionType in systemPermissionTypes"
-                        ng-show="canChangeSystemPermissions()">
-                        <th>{{systemPermissionType.label | translate}}</th>
-                        <td><input type="checkbox" ng-model="permissionFlags.systemPermissions[systemPermissionType.value]"
-                                                   ng-change="systemPermissionChanged(systemPermissionType.value)"/></td>
-                    </tr>
-                    <tr>
-                        <th>{{'MANAGE_USER.FIELD_HEADER_CHANGE_OWN_PASSWORD' | translate}}</th>
-                        <td><input type="checkbox" ng-model="permissionFlags.userPermissions.UPDATE[selfUsername]"
-                                                   ng-change="userPermissionChanged('UPDATE', selfUsername)"/></td>
-                    </tr>
-                </table>
-            </div>
-        </div>
+        <system-permission-editor ng-show="canChangePermissions()"
+              username="selfUsername"
+              data-data-source="dataSource"
+              permission-flags="permissionFlags"
+              permissions-added="permissionsAdded"
+              permissions-removed="permissionsRemoved">
+        </system-permission-editor>
 
         <!-- Connection permissions section -->
-        <div class="connection-permissions" ng-show="canChangePermissions()">
-            <div class="header">
-                <h2>{{'MANAGE_USER.SECTION_HEADER_CONNECTIONS' | translate}}</h2>
-                <guac-group-list-filter connection-groups="rootGroups"
-                    filtered-connection-groups="filteredRootGroups"
-                    placeholder="'MANAGE_USER.FIELD_PLACEHOLDER_FILTER' | translate"
-                    connection-properties="filteredConnectionProperties"
-                    connection-group-properties="filteredConnectionGroupProperties"></guac-group-list-filter>
-            </div>
-            <div class="section">
-                <guac-group-list
-                    context="groupListContext"
-                    connection-groups="filteredRootGroups"
-                    templates="{
-                        'connection'       : 'app/manage/templates/connectionPermission.html',
-                        'sharing-profile'  : 'app/manage/templates/sharingProfilePermission.html',
-                        'connection-group' : 'app/manage/templates/connectionGroupPermission.html'
-                    }"
-                    page-size="20"/>
-            </div>
-        </div>
+        <connection-permission-editor ng-show="canChangePermissions()"
+              data-data-source="dataSource"
+              permission-flags="permissionFlags"
+              permissions-added="permissionsAdded"
+              permissions-removed="permissionsRemoved">
+        </connection-permission-editor>
 
         <!-- Form action buttons -->
         <div class="action-buttons">

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/94ad1f9f/guacamole/src/main/webapp/app/manage/templates/systemPermissionEditor.html
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/templates/systemPermissionEditor.html b/guacamole/src/main/webapp/app/manage/templates/systemPermissionEditor.html
new file mode 100644
index 0000000..47fec66
--- /dev/null
+++ b/guacamole/src/main/webapp/app/manage/templates/systemPermissionEditor.html
@@ -0,0 +1,18 @@
+<div class="system-permissions">
+    <h2 class="header">{{'MANAGE_USER.SECTION_HEADER_PERMISSIONS' | translate}}</h2>
+    <div class="section">
+        <table class="properties">
+            <tr ng-repeat="systemPermissionType in systemPermissionTypes"
+                ng-show="canChangeSystemPermissions()">
+                <th>{{systemPermissionType.label | translate}}</th>
+                <td><input type="checkbox" ng-model="permissionFlags.systemPermissions[systemPermissionType.value]"
+                                           ng-change="systemPermissionChanged(systemPermissionType.value)"/></td>
+            </tr>
+            <tr ng-show="username">
+                <th>{{'MANAGE_USER.FIELD_HEADER_CHANGE_OWN_PASSWORD' | translate}}</th>
+                <td><input type="checkbox" ng-model="permissionFlags.userPermissions.UPDATE[username]"
+                                           ng-change="userPermissionChanged('UPDATE', username)"/></td>
+            </tr>
+        </table>
+    </div>
+</div>
\ No newline at end of file


[11/21] guacamole-client git commit: GUACAMOLE-220: Include the identifier of the associated object within ManagementPermissions.

Posted by vn...@apache.org.
GUACAMOLE-220: Include the identifier of the associated object within ManagementPermissions.

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/1989c11d
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/1989c11d
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/1989c11d

Branch: refs/heads/master
Commit: 1989c11dd98ac76258d44c001bdf6e362be4f871
Parents: 708c255
Author: Michael Jumper <mj...@apache.org>
Authored: Tue May 1 16:39:02 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Tue May 1 20:58:28 2018 -0700

----------------------------------------------------------------------
 .../main/webapp/app/manage/types/ManagementPermissions.js | 10 ++++++++++
 1 file changed, 10 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/1989c11d/guacamole/src/main/webapp/app/manage/types/ManagementPermissions.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/types/ManagementPermissions.js b/guacamole/src/main/webapp/app/manage/types/ManagementPermissions.js
index f9a78b7..5d02a76 100644
--- a/guacamole/src/main/webapp/app/manage/types/ManagementPermissions.js
+++ b/guacamole/src/main/webapp/app/manage/types/ManagementPermissions.js
@@ -38,6 +38,14 @@ angular.module('manage').factory('ManagementPermissions', ['$injector',
     var ManagementPermissions = function ManagementPermissions(template) {
 
         /**
+         * The identifier of the associated object, or null if the object does
+         * not yet exist.
+         *
+         * @type String
+         */
+        this.identifier = template.identifier || null;
+
+        /**
          * Whether the user can save the associated object. This could be
          * updating an existing object, or creating a new object.
          *
@@ -129,6 +137,8 @@ angular.module('manage').factory('ManagementPermissions', ['$injector',
 
         return new ManagementPermissions({
 
+            identifier : identifier,
+
             // A user can save (create or update) an object if they are a
             // system-level administrator, OR the object does not yet exist and
             // the user has explicit permission to create such objects, OR the


[20/21] guacamole-client git commit: GUACAMOLE-220: Correct grammar of saveObject() documentation ("to save", not "to saves").

Posted by vn...@apache.org.
GUACAMOLE-220: Correct grammar of saveObject() documentation ("to save", not "to saves").

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/b50e7099
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/b50e7099
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/b50e7099

Branch: refs/heads/master
Commit: b50e70998195b796194b7da72f60baf491b5e713
Parents: ae0512c
Author: Michael Jumper <mj...@apache.org>
Authored: Thu May 3 23:38:01 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Thu May 3 23:38:01 2018 -0700

----------------------------------------------------------------------
 .../src/main/webapp/app/manage/directives/managementButtons.js     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/b50e7099/guacamole/src/main/webapp/app/manage/directives/managementButtons.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/directives/managementButtons.js b/guacamole/src/main/webapp/app/manage/directives/managementButtons.js
index a83b82c..f44515a 100644
--- a/guacamole/src/main/webapp/app/manage/directives/managementButtons.js
+++ b/guacamole/src/main/webapp/app/manage/directives/managementButtons.js
@@ -167,7 +167,7 @@ angular.module('manage').directive('managementButtons', ['$injector',
         };
 
         /**
-         * Invokes the provided save function to saves the current object. If
+         * Invokes the provided save function to save the current object. If
          * saving is successful, the user is navigated back to the page they
          * started from. If saving fails, an error notification is displayed.
          */


[04/21] guacamole-client git commit: GUACAMOLE-220: Migrate user management screen to new, common management button directive.

Posted by vn...@apache.org.
GUACAMOLE-220: Migrate user management screen to new, common management button directive.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/0414cdd3
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/0414cdd3
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/0414cdd3

Branch: refs/heads/master
Commit: 0414cdd3edc4f5ba11900750ed26782e335de7b2
Parents: e045da1
Author: Michael Jumper <mj...@apache.org>
Authored: Tue May 1 09:53:26 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Tue May 1 17:20:32 2018 -0700

----------------------------------------------------------------------
 .../manage/controllers/manageUserController.js  | 105 +++++--------------
 .../webapp/app/manage/templates/manageUser.html |  13 +--
 2 files changed, 32 insertions(+), 86 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/0414cdd3/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
index 388e717..b1f4f2e 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
@@ -24,6 +24,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
         function manageUserController($scope, $injector) {
             
     // Required types
+    var Error                 = $injector.get('Error');
     var ManagementPermissions = $injector.get('ManagementPermissions');
     var PageDefinition        = $injector.get('PageDefinition');
     var PermissionFlagSet     = $injector.get('PermissionFlagSet');
@@ -36,7 +37,6 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     var $q                       = $injector.get('$q');
     var authenticationService    = $injector.get('authenticationService');
     var dataSourceService        = $injector.get('dataSourceService');
-    var guacNotification         = $injector.get('guacNotification');
     var permissionService        = $injector.get('permissionService');
     var requestService           = $injector.get('requestService');
     var schemaService            = $injector.get('schemaService');
@@ -44,18 +44,6 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     var userService              = $injector.get('userService');
 
     /**
-     * An action to be provided along with the object sent to showStatus which
-     * closes the currently-shown status dialog.
-     */
-    var ACKNOWLEDGE_ACTION = {
-        name        : "MANAGE_USER.ACTION_ACKNOWLEDGE",
-        // Handle action
-        callback    : function acknowledgeCallback() {
-            guacNotification.showStatus(false);
-        }
-    };
-
-    /**
      * The identifiers of all data sources currently available to the
      * authenticated user.
      *
@@ -409,9 +397,9 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     }, requestService.WARN);
 
     /**
-     * Cancels all pending edits, returning to the management page.
+     * Cancels all pending edits, returning to the main list of users.
      */
-    $scope.cancel = function cancel() {
+    $scope.returnToUserList = function returnToUserList() {
         $location.url('/settings/users');
     };
 
@@ -424,21 +412,23 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     };
             
     /**
-     * Saves the user, updating the existing user only.
+     * Saves the current user, creating a new user or updating the existing
+     * user 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.saveUser = function saveUser() {
 
         // Verify passwords match
         if ($scope.passwordMatch !== $scope.user.password) {
-            guacNotification.showStatus({
-                'className'  : 'error',
-                'title'      : 'MANAGE_USER.DIALOG_HEADER_ERROR',
-                'text'       : {
+            return $q.reject(new Error({
+                translatableMessage : {
                     key : 'MANAGE_USER.ERROR_PASSWORD_MISMATCH'
-                },
-                'actions'    : [ ACKNOWLEDGE_ACTION ]
-            });
-            return;
+                }
+            }));
         }
 
         // Save or create the user, depending on whether the user exists
@@ -448,7 +438,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
         else
             saveUserPromise = userService.createUser($scope.dataSource, $scope.user);
 
-        saveUserPromise.then(function savedUser() {
+        return saveUserPromise.then(function savedUser() {
 
             // Move permission flags if username differs from marker
             if ($scope.selfUsername !== $scope.user.username) {
@@ -468,70 +458,25 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
             }
 
             // Upon success, save any changed permissions
-            permissionService.patchPermissions($scope.dataSource, $scope.user.username, $scope.permissionsAdded, $scope.permissionsRemoved)
+            return permissionService.patchPermissions($scope.dataSource, $scope.user.username, $scope.permissionsAdded, $scope.permissionsRemoved)
             .then(function patchedUserPermissions() {
                 $location.url('/settings/users');
-            }, guacNotification.SHOW_REQUEST_ERROR);
+            });
 
-        }, guacNotification.SHOW_REQUEST_ERROR);
+        });
 
     };
     
     /**
-     * An action to be provided along with the object sent to showStatus which
-     * immediately deletes the current user.
-     */
-    var DELETE_ACTION = {
-        name        : "MANAGE_USER.ACTION_DELETE",
-        className   : "danger",
-        // Handle action
-        callback    : function deleteCallback() {
-            deleteUserImmediately();
-            guacNotification.showStatus(false);
-        }
-    };
-
-    /**
-     * An action to be provided along with the object sent to showStatus which
-     * closes the currently-shown status dialog.
-     */
-    var CANCEL_ACTION = {
-        name        : "MANAGE_USER.ACTION_CANCEL",
-        // Handle action
-        callback    : function cancelCallback() {
-            guacNotification.showStatus(false);
-        }
-    };
-
-    /**
-     * Immediately deletes the current user, without prompting the user for
-     * confirmation.
-     */
-    var deleteUserImmediately = function deleteUserImmediately() {
-
-        // Delete the user 
-        userService.deleteUser($scope.dataSource, $scope.user)
-        .then(function deletedUser() {
-            $location.path('/settings/users');
-        }, guacNotification.SHOW_REQUEST_ERROR);
-
-    };
-
-    /**
-     * Deletes the user, prompting the user first to confirm that deletion is
-     * desired.
+     * Deletes the current user, 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.deleteUser = function deleteUser() {
-
-        // Confirm deletion request
-        guacNotification.showStatus({
-            'title'      : 'MANAGE_USER.DIALOG_HEADER_CONFIRM_DELETE',
-            'text'       : {
-                key : 'MANAGE_USER.TEXT_CONFIRM_DELETE'
-            },
-            'actions'    : [ DELETE_ACTION, CANCEL_ACTION]
-        });
-
+        return userService.deleteUser($scope.dataSource, $scope.user);
     };
 
 }]);

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/0414cdd3/guacamole/src/main/webapp/app/manage/templates/manageUser.html
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUser.html b/guacamole/src/main/webapp/app/manage/templates/manageUser.html
index 24db74e..02b3ddc 100644
--- a/guacamole/src/main/webapp/app/manage/templates/manageUser.html
+++ b/guacamole/src/main/webapp/app/manage/templates/manageUser.html
@@ -63,12 +63,13 @@
         </connection-permission-editor>
 
         <!-- Form action buttons -->
-        <div class="action-buttons">
-            <button ng-show="managementPermissions.canSaveObject" ng-click="saveUser()">{{'MANAGE_USER.ACTION_SAVE' | translate}}</button>
-            <button ng-show="managementPermissions.canCloneObject" ng-click="cloneUser()">{{'MANAGE_USER.ACTION_CLONE' | translate}}</button>
-            <button ng-click="cancel()">{{'MANAGE_USER.ACTION_CANCEL' | translate}}</button>
-            <button ng-show="managementPermissions.canDeleteObject" ng-click="deleteUser()" class="danger">{{'MANAGE_USER.ACTION_DELETE' | translate}}</button>
-        </div>
+        <management-buttons namespace="'MANAGE_USER'"
+              permissions="managementPermissions"
+              save="saveUser()"
+              delete="deleteUser()"
+              clone="cloneUser()"
+              return="returnToUserList()">
+        </management-buttons>
 
     </div>
 


[07/21] guacamole-client git commit: GUACAMOLE-220: "Management", not "managment".

Posted by vn...@apache.org.
GUACAMOLE-220: "Management", not "managment".

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/708c255b
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/708c255b
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/708c255b

Branch: refs/heads/master
Commit: 708c255b8373167806a439332d029ae0f07ef4e5
Parents: 82803c9
Author: Michael Jumper <mj...@apache.org>
Authored: Tue May 1 16:29:25 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Tue May 1 20:58:28 2018 -0700

----------------------------------------------------------------------
 .../webapp/app/manage/controllers/manageConnectionController.js    | 2 +-
 .../app/manage/controllers/manageConnectionGroupController.js      | 2 +-
 .../app/manage/controllers/manageSharingProfileController.js       | 2 +-
 .../src/main/webapp/app/manage/controllers/manageUserController.js | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/708c255b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js
index ac55f11..6139781 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js
@@ -111,7 +111,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
     $scope.historyEntryWrappers = null;
 
     /**
-     * The managment-related actions that the current user may perform on the
+     * The management-related actions that the current user may perform on the
      * connection currently being created/modified, or null if the current
      * user's permissions have not yet been loaded.
      *

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/708c255b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js
index 842c395..1a4b64e 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js
@@ -95,7 +95,7 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
     $scope.connectionGroup = null;
     
     /**
-     * The managment-related actions that the current user may perform on the
+     * The management-related actions that the current user may perform on the
      * connection group currently being created/modified, or null if the current
      * user's permissions have not yet been loaded.
      *

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/708c255b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
index 940c305..7385aa1 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
@@ -87,7 +87,7 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
     $scope.parameters = null;
 
     /**
-     * The managment-related actions that the current user may perform on the
+     * The management-related actions that the current user may perform on the
      * sharing profile currently being created/modified, or null if the current
      * user's permissions have not yet been loaded.
      *

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/708c255b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
index b1f4f2e..dcb87e1 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
@@ -136,7 +136,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     $scope.permissionsRemoved = new PermissionSet();
 
     /**
-     * The managment-related actions that the current user may perform on the
+     * The management-related actions that the current user may perform on the
      * user currently being created/modified, or null if the current user's
      * permissions have not yet been loaded.
      *


[08/21] guacamole-client git commit: GUACAMOLE-220: Migrate sharing profile management screen to common buttons and permission logic.

Posted by vn...@apache.org.
GUACAMOLE-220: Migrate sharing profile management screen to common buttons and permission logic.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/c1f5ad40
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/c1f5ad40
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/c1f5ad40

Branch: refs/heads/master
Commit: c1f5ad407504c42f42158ea38e9da04e3842f3c1
Parents: 7e1dbf7
Author: Michael Jumper <mj...@apache.org>
Authored: Tue May 1 12:24:33 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Tue May 1 20:58:28 2018 -0700

----------------------------------------------------------------------
 .../manageSharingProfileController.js           | 355 ++++++++-----------
 .../manage/templates/manageSharingProfile.html  |  15 +-
 2 files changed, 161 insertions(+), 209 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/c1f5ad40/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
index 7414e65..a0a683c 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
@@ -24,15 +24,16 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
         function manageSharingProfileController($scope, $injector) {
 
     // Required types
-    var SharingProfile = $injector.get('SharingProfile');
-    var PermissionSet  = $injector.get('PermissionSet');
+    var ManagementPermissions = $injector.get('ManagementPermissions');
+    var SharingProfile        = $injector.get('SharingProfile');
+    var PermissionSet         = $injector.get('PermissionSet');
 
     // Required services
     var $location                = $injector.get('$location');
+    var $q                       = $injector.get('$q');
     var $routeParams             = $injector.get('$routeParams');
     var authenticationService    = $injector.get('authenticationService');
     var connectionService        = $injector.get('connectionService');
-    var guacNotification         = $injector.get('guacNotification');
     var permissionService        = $injector.get('permissionService');
     var requestService           = $injector.get('requestService');
     var schemaService            = $injector.get('schemaService');
@@ -40,18 +41,6 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
     var translationStringService = $injector.get('translationStringService');
 
     /**
-     * An action to be provided along with the object sent to showStatus which
-     * closes the currently-shown status dialog, effectively canceling the
-     * operation which was pending user confirmation.
-     */
-    var CANCEL_ACTION = {
-        name        : "MANAGE_SHARING_PROFILE.ACTION_CANCEL",
-        callback    : function cancelCallback() {
-            guacNotification.showStatus(false);
-        }
-    };
-
-    /**
      * The unique identifier of the data source containing the sharing profile
      * being edited.
      *
@@ -98,34 +87,13 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
     $scope.parameters = null;
 
     /**
-     * Whether the user can save the sharing profile being edited. This could be
-     * updating an existing sharing profile, or creating a new sharing profile.
-     *
-     * @type Boolean
-     */
-    $scope.canSaveSharingProfile = null;
-
-    /**
-     * Whether the user can delete the sharing profile being edited.
-     *
-     * @type Boolean
-     */
-    $scope.canDeleteSharingProfile = null;
-
-    /**
-     * Whether the user can clone the sharing profile being edited.
-     *
-     * @type Boolean
-     */
-    $scope.canCloneSharingProfile = null;
-
-    /**
-     * All permissions associated with the current user, or null if the user's
-     * permissions have not yet been loaded.
+     * The managment-related actions that the current user may perform on the
+     * sharing profile currently being created/modified, or null if the current
+     * user's permissions have not yet been loaded.
      *
-     * @type PermissionSet
+     * @type ManagementPermissions
      */
-    $scope.permissions = null;
+    $scope.managementPermissions = null;
 
     /**
      * All available sharing profile attributes. This is only the set of
@@ -149,146 +117,157 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
             && $scope.sharingProfile          !== null
             && $scope.primaryConnection       !== null
             && $scope.parameters              !== null
-            && $scope.permissions             !== null
-            && $scope.attributes              !== null
-            && $scope.canSaveSharingProfile   !== null
-            && $scope.canDeleteSharingProfile !== null
-            && $scope.canCloneSharingProfile  !== null;
+            && $scope.managementPermissions   !== null
+            && $scope.attributes              !== null;
 
     };
 
-    // Pull sharing profile attribute schema
-    schemaService.getSharingProfileAttributes($scope.selectedDataSource)
-    .then(function attributesReceived(attributes) {
-        $scope.attributes = attributes;
-    }, requestService.WARN);
-
-    // Query the user's permissions for the current sharing profile
-    permissionService.getEffectivePermissions($scope.selectedDataSource, authenticationService.getCurrentUsername())
-    .then(function permissionsReceived(permissions) {
-
-        $scope.permissions = permissions;
-
-        // The sharing profile can be saved if it is new or if the user has
-        // UPDATE permission for that sharing profile (either explicitly or by
-        // virtue of being an administrator)
-        $scope.canSaveSharingProfile =
-               !identifier
-            || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
-            || PermissionSet.hasSharingProfilePermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, identifier);
-
-        // The sharing profile can be saved only if it exists and the user has
-        // DELETE permission (either explicitly or by virtue of being an
-        // administrator)
-        $scope.canDeleteSharingProfile =
-            !!identifier && (
-                   PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
-               ||  PermissionSet.hasSharingProfilePermission(permissions, PermissionSet.ObjectPermissionType.DELETE, identifier)
-            );
-
-        // The sharing profile can be cloned only if it exists, the user has
-        // UPDATE permission on the sharing profile being cloned (is able to
-        // read parameters), and the user can create new sharing profiles
-        $scope.canCloneSharingProfile =
-            !!identifier && (
-               PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) || (
-                       PermissionSet.hasSharingProfilePermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, identifier)
-                   &&  PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_SHARING_PROFILE)
-               )
-            );
-
-    }, requestService.WARN);
-
-    // Get protocol metadata
-    schemaService.getProtocols($scope.selectedDataSource)
-    .then(function protocolsReceived(protocols) {
-        $scope.protocols = protocols;
-    }, requestService.WARN);
-
-    // If we are editing an existing sharing profile, pull its data
-    if (identifier) {
-
-        // Pull data from existing sharing profile
-        sharingProfileService.getSharingProfile($scope.selectedDataSource, identifier)
-        .then(function sharingProfileRetrieved(sharingProfile) {
-            $scope.sharingProfile = sharingProfile;
-        }, requestService.WARN);
-
-        // Pull sharing profile parameters
-        sharingProfileService.getSharingProfileParameters($scope.selectedDataSource, identifier)
-        .then(function parametersReceived(parameters) {
-            $scope.parameters = parameters;
-        }, requestService.WARN);
-
-    }
+    /**
+     * Loads the data associated with the sharing profile having the given
+     * identifier, preparing the interface for making modifications to that
+     * existing sharing profile.
+     *
+     * @param {String} dataSource
+     *     The unique identifier of the data source containing the sharing
+     *     profile to load.
+     *
+     * @param {String} identifier
+     *     The identifier of the sharing profile to load.
+     *
+     * @returns {Promise}
+     *     A promise which is resolved when the interface has been prepared for
+     *     editing the given sharing profile.
+     */
+    var loadExistingSharingProfile = function loadExistingSharingProfile(dataSource, identifier) {
+        return $q.all({
+            sharingProfile : sharingProfileService.getSharingProfile(dataSource, identifier),
+            parameters     : sharingProfileService.getSharingProfileParameters(dataSource, identifier)
+        })
+        .then(function sharingProfileDataRetrieved(values) {
+
+            $scope.sharingProfile = values.sharingProfile;
+            $scope.parameters = values.parameters;
+
+            // Load connection object for associated primary connection
+            return connectionService.getConnection(
+                dataSource,
+                values.sharingProfile.primaryConnectionIdentifier
+            )
+            .then(function connectionRetrieved(connection) {
+                $scope.primaryConnection = connection;
+            });
 
-    // If we are cloning an existing sharing profile, pull its data instead
-    else if (cloneSourceIdentifier) {
+        });
+    };
 
-        // Pull data from cloned sharing profile
-        sharingProfileService.getSharingProfile($scope.selectedDataSource, cloneSourceIdentifier)
-        .then(function sharingProfileRetrieved(sharingProfile) {
+    /**
+     * Loads the data associated with the sharing profile having the given
+     * identifier, preparing the interface for cloning that existing
+     * sharing profile.
+     *
+     * @param {String} dataSource
+     *     The unique identifier of the data source containing the sharing
+     *     profile to be cloned.
+     *
+     * @param {String} identifier
+     *     The identifier of the sharing profile being cloned.
+     *
+     * @returns {Promise}
+     *     A promise which is resolved when the interface has been prepared for
+     *     cloning the given sharing profile.
+     */
+    var loadClonedSharingProfile = function loadClonedSharingProfile(dataSource, identifier) {
+        return $q.all({
+            sharingProfile : sharingProfileService.getSharingProfile(dataSource, identifier),
+            parameters     : sharingProfileService.getSharingProfileParameters(dataSource, identifier)
+        })
+        .then(function sharingProfileDataRetrieved(values) {
 
-            // Store data of sharing profile being cloned
-            $scope.sharingProfile = sharingProfile;
+            $scope.sharingProfile = values.sharingProfile;
+            $scope.parameters = values.parameters;
 
             // Clear the identifier field because this sharing profile is new
             delete $scope.sharingProfile.identifier;
 
-        }, requestService.WARN);
-
-        // Pull sharing profile parameters from cloned sharing profile
-        sharingProfileService.getSharingProfileParameters($scope.selectedDataSource, cloneSourceIdentifier)
-        .then(function parametersReceived(parameters) {
-            $scope.parameters = parameters;
-        }, requestService.WARN);
+            // Load connection object for associated primary connection
+            return connectionService.getConnection(
+                dataSource,
+                values.sharingProfile.primaryConnectionIdentifier
+            )
+            .then(function connectionRetrieved(connection) {
+                $scope.primaryConnection = connection;
+            });
 
-    }
+        });
+    };
 
-    // If we are creating a new sharing profile, populate skeleton sharing
-    // profile data
-    else {
+    /**
+     * Loads skeleton sharing profile data, preparing the interface for
+     * creating a new sharing profile.
+     *
+     * @returns {Promise}
+     *     A promise which is resolved when the interface has been prepared for
+     *     creating a new sharing profile.
+     */
+    var loadSkeletonSharingProfile = function loadSkeletonSharingProfile() {
 
+        // Use skeleton sharing profile object with no associated parameters
         $scope.sharingProfile = new SharingProfile({
             primaryConnectionIdentifier : $location.search().parent
         });
-
         $scope.parameters = {};
 
-    }
-
-    // Populate primary connection once its identifier is known
-    $scope.$watch('sharingProfile.primaryConnectionIdentifier',
-        function retrievePrimaryConnection(identifier) {
-
-        if (identifier) {
-            connectionService.getConnection($scope.selectedDataSource, identifier)
-            .then(function connectionRetrieved(connection) {
-                $scope.primaryConnection = connection;
-            }, requestService.WARN);
-        }
+        return $q.resolve();
 
-    });
+    };
 
     /**
-     * Returns whether the current user can change/set all sharing profile
-     * attributes for the sharing profile being edited, regardless of whether
-     * those attributes are already explicitly associated with that sharing
-     * profile.
+     * Loads the data requred for performing the management task requested
+     * through the route parameters given at load time, automatically preparing
+     * the interface for editing an existing sharing profile, cloning an
+     * existing sharing profile, or creating an entirely new sharing profile.
      *
-     * @returns {Boolean}
-     *     true if the current user can change all attributes for the sharing
-     *     profile being edited, regardless of whether those attributes are
-     *     already explicitly associated with that sharing profile, false
-     *     otherwise.
+     * @returns {Promise}
+     *     A promise which is resolved when the interface has been prepared
+     *     for performing the requested management task.
      */
-    $scope.canChangeAllAttributes = function canChangeAllAttributes() {
+    var loadRequestedSharingProfile = function loadRequestedSharingProfile() {
+
+        // If we are editing an existing sharing profile, pull its data
+        if (identifier)
+            return loadExistingSharingProfile($scope.selectedDataSource, identifier);
 
-        // All attributes can be set if we are creating the sharing profile
-        return !identifier;
+        // If we are cloning an existing sharing profile, pull its data instead
+        if (cloneSourceIdentifier)
+            return loadClonedSharingProfile($scope.selectedDataSource, cloneSourceIdentifier);
+
+        // If we are creating a new sharing profile, populate skeleton sharing
+        // profile data
+        return loadSkeletonSharingProfile();
 
     };
 
+
+    // Query the user's permissions for the current sharing profile
+    $q.all({
+        sharingProfileData : loadRequestedSharingProfile(),
+        attributes  : schemaService.getSharingProfileAttributes($scope.selectedDataSource),
+        protocols   : schemaService.getProtocols($scope.selectedDataSource),
+        permissions : permissionService.getEffectivePermissions($scope.selectedDataSource, authenticationService.getCurrentUsername())
+    })
+    .then(function sharingProfileDataRetrieved(values) {
+
+        $scope.attributes = values.attributes;
+        $scope.protocols = values.protocols;
+
+        $scope.managementPermissions = ManagementPermissions.fromPermissionSet(
+                    values.permissions,
+                    PermissionSet.SystemPermissionType.CREATE_CONNECTION,
+                    PermissionSet.hasConnectionPermission,
+                    identifier);
+
+    }, requestService.WARN);
+
     /**
      * Returns the translation string namespace for the protocol having the
      * given name. The namespace will be of the form:
@@ -316,9 +295,10 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
     };
 
     /**
-     * Cancels all pending edits, returning to the management page.
+     * Cancels all pending edits, returning to the main list of connections
+     * within the selected data source.
      */
-    $scope.cancel = function cancel() {
+    $scope.returnToConnectionList = function returnToConnectionList() {
         $location.url('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
     };
 
@@ -332,64 +312,35 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
     };
 
     /**
-     * Saves the sharing profile, creating a new sharing profile or updating
-     * the existing sharing profile.
+     * Saves the current sharing profile, creating a new sharing profile or
+     * updating the existing sharing profile, 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.saveSharingProfile = function saveSharingProfile() {
 
         $scope.sharingProfile.parameters = $scope.parameters;
 
         // Save the sharing profile
-        sharingProfileService.saveSharingProfile($scope.selectedDataSource, $scope.sharingProfile)
-        .then(function savedSharingProfile() {
-            $location.url('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
-        }, guacNotification.SHOW_REQUEST_ERROR);
+        return sharingProfileService.saveSharingProfile($scope.selectedDataSource, $scope.sharingProfile);
 
     };
 
     /**
-     * An action to be provided along with the object sent to showStatus which
-     * immediately deletes the current sharing profile.
-     */
-    var DELETE_ACTION = {
-        name        : "MANAGE_SHARING_PROFILE.ACTION_DELETE",
-        className   : "danger",
-        // Handle action
-        callback    : function deleteCallback() {
-            deleteSharingProfileImmediately();
-            guacNotification.showStatus(false);
-        }
-    };
-
-    /**
-     * Immediately deletes the current sharing profile, without prompting the
-     * user for confirmation.
-     */
-    var deleteSharingProfileImmediately = function deleteSharingProfileImmediately() {
-
-        // Delete the sharing profile
-        sharingProfileService.deleteSharingProfile($scope.selectedDataSource, $scope.sharingProfile)
-        .then(function deletedSharingProfile() {
-            $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
-        }, guacNotification.SHOW_REQUEST_ERROR);
-
-    };
-
-    /**
-     * Deletes the sharing profile, prompting the user first to confirm that
-     * deletion is desired.
+     * Deletes the current sharing profile, 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.deleteSharingProfile = function deleteSharingProfile() {
-
-        // Confirm deletion request
-        guacNotification.showStatus({
-            'title'      : 'MANAGE_SHARING_PROFILE.DIALOG_HEADER_CONFIRM_DELETE',
-            'text'       : {
-                'key' : 'MANAGE_SHARING_PROFILE.TEXT_CONFIRM_DELETE'
-            },
-            'actions'    : [ DELETE_ACTION, CANCEL_ACTION]
-        });
-
+        return sharingProfileService.deleteSharingProfile($scope.selectedDataSource, $scope.sharingProfile);
     };
 
 }]);

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/c1f5ad40/guacamole/src/main/webapp/app/manage/templates/manageSharingProfile.html
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageSharingProfile.html b/guacamole/src/main/webapp/app/manage/templates/manageSharingProfile.html
index d6c7043..ac52fa3 100644
--- a/guacamole/src/main/webapp/app/manage/templates/manageSharingProfile.html
+++ b/guacamole/src/main/webapp/app/manage/templates/manageSharingProfile.html
@@ -22,7 +22,7 @@
     <!-- Sharing profile attributes section -->
     <div class="attributes">
         <guac-form namespace="'SHARING_PROFILE_ATTRIBUTES'" content="attributes"
-                   model="sharingProfile.attributes" model-only="!canChangeAllAttributes()"></guac-form>
+                   model="sharingProfile.attributes" model-only="!managementPermissions.canChangeAllAttributes"></guac-form>
     </div>
 
     <!-- Sharing profile parameters -->
@@ -34,11 +34,12 @@
     </div>
 
     <!-- Form action buttons -->
-    <div class="action-buttons">
-        <button ng-show="canSaveSharingProfile" ng-click="saveSharingProfile()">{{'MANAGE_SHARING_PROFILE.ACTION_SAVE' | translate}}</button>
-        <button ng-show="canCloneSharingProfile" ng-click="cloneSharingProfile()">{{'MANAGE_SHARING_PROFILE.ACTION_CLONE' | translate}}</button>
-        <button ng-click="cancel()">{{'MANAGE_SHARING_PROFILE.ACTION_CANCEL' | translate}}</button>
-        <button ng-show="canDeleteSharingProfile" ng-click="deleteSharingProfile()" class="danger">{{'MANAGE_SHARING_PROFILE.ACTION_DELETE' | translate}}</button>
-    </div>
+    <management-buttons namespace="'MANAGE_SHARING_PROFILE'"
+          permissions="managementPermissions"
+          save="saveSharingProfile()"
+          delete="deleteSharingProfile()"
+          clone="cloneSharingProfile()"
+          return="returnToConnectionList()">
+    </management-buttons>
 
 </div>


[02/21] guacamole-client git commit: GUACAMOLE-220: Define abstract object for querying the management-related actions a user may take on a particular object or type of object.

Posted by vn...@apache.org.
GUACAMOLE-220: Define abstract object for querying the management-related actions a user may take on a particular object or type of object.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/507202d1
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/507202d1
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/507202d1

Branch: refs/heads/master
Commit: 507202d1f3c6b5e96e9f2a6d6f341331af9d34c0
Parents: 94ad1f9
Author: Michael Jumper <mj...@apache.org>
Authored: Mon Apr 30 23:24:15 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Mon Apr 30 23:24:15 2018 -0700

----------------------------------------------------------------------
 .../app/manage/types/ManagementPermissions.js   | 172 +++++++++++++++++++
 1 file changed, 172 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/507202d1/guacamole/src/main/webapp/app/manage/types/ManagementPermissions.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/types/ManagementPermissions.js b/guacamole/src/main/webapp/app/manage/types/ManagementPermissions.js
new file mode 100644
index 0000000..f9a78b7
--- /dev/null
+++ b/guacamole/src/main/webapp/app/manage/types/ManagementPermissions.js
@@ -0,0 +1,172 @@
+/*
+ * 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 ManagementPermissions class.
+ */
+angular.module('manage').factory('ManagementPermissions', ['$injector',
+    function defineManagementPermissions($injector) {
+
+    // Required types
+    var PermissionSet = $injector.get('PermissionSet');
+
+    /**
+     * Higher-level representation of the management-related permissions
+     * available to the current user on a particular, arbitrary object.
+     *
+     * @constructor
+     * @param {ManagementPermissions|Object} template
+     *     An object whose properties should be copied into the new
+     *     ManagementPermissions object.
+     */
+    var ManagementPermissions = function ManagementPermissions(template) {
+
+        /**
+         * Whether the user can save the associated object. This could be
+         * updating an existing object, or creating a new object.
+         *
+         * @type Boolean
+         */
+        this.canSaveObject = template.canSaveObject;
+
+        /**
+         * Whether the user can clone the associated object.
+         *
+         * @type Boolean
+         */
+        this.canCloneObject = template.canCloneObject;
+
+        /**
+         * Whether the user can delete the associated object.
+         *
+         * @type Boolean
+         */
+        this.canDeleteObject = template.canDeleteObject;
+
+        /**
+         * Whether the user can change attributes which are currently
+         * associated with the object.
+         *
+         * @type Boolean
+         */
+        this.canChangeAttributes = template.canChangeAttributes;
+
+        /**
+         * Whether the user can change absolutely all attributes associated
+         * with the object, including those which are not already present.
+         *
+         * @type Boolean
+         */
+        this.canChangeAllAttributes = template.canChangeAllAttributes;
+
+        /**
+         * Whether the user can change permissions which are assigned to the
+         * associated object, if the object is capable of being assigned
+         * permissions.
+         *
+         * @type Boolean
+         */
+        this.canChangePermissions = template.canChangePermissions;
+
+    };
+
+    /**
+     * Creates a new {@link ManagementPermissions} which defines the high-level
+     * actions the current user may take for the given object.
+     *
+     * @param {PermissionSet} permissions
+     *     The effective permissions granted to the current user within the
+     *     data source associated with the object being managed.
+     *
+     * @param {String} createPermission
+     *     The system permission required to create objects of the same type as
+     *     the object being managed, as defined by
+     *     {@link PermissionSet.SystemPermissionTypes}.
+     *
+     * @param {Function} hasObjectPermission
+     *     The function to invoke to test whether a {@link PermissionSet}
+     *     contains a particular object permission. The parameters accepted
+     *     by this function must be identical to those accepted by
+     *     {@link PermissionSet.hasUserPermission()},
+     *     {@link PermissionSet.hasConnectionPermission()}, etc.
+     *
+     * @param {String} [identifier]
+     *     The identifier of the object being managed. If the object does not
+     *     yet exist, this parameter should be omitted or set to null.
+     *
+     * @returns {ManagementPermissions}
+     *     A new {@link ManagementPermissions} which defines the high-level
+     *     actions the current user may take for the given object.
+     */
+    ManagementPermissions.fromPermissionSet = function fromPermissionSet(
+            permissions, createPermission, hasObjectPermission, identifier) {
+
+        var isAdmin = PermissionSet.hasSystemPermission(permissions,
+                PermissionSet.SystemPermissionType.ADMINISTER);
+
+        var canCreate = PermissionSet.hasSystemPermission(permissions, createPermission);
+        var canAdminister = hasObjectPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER, identifier);
+        var canUpdate = hasObjectPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, identifier);
+        var canDelete = hasObjectPermission(permissions, PermissionSet.ObjectPermissionType.DELETE, identifier);
+
+        var exists = !!identifier;
+
+        return new ManagementPermissions({
+
+            // A user can save (create or update) an object if they are a
+            // system-level administrator, OR the object does not yet exist and
+            // the user has explicit permission to create such objects, OR the
+            // object does already exist and the user has explicit UPDATE
+            // permission on the object
+            canSaveObject : isAdmin || (!exists && canCreate) || canUpdate,
+
+            // A user can clone an object only if the object exists, and
+            // only if they are a system-level administrator OR they have
+            // explicit permission to create such objects
+            canCloneObject : exists && (isAdmin || canCreate),
+
+            // A user can delete an object only if the object exists, and
+            // only if they are a system-level administrator OR they have
+            // explicit DELETE permission on the object
+            canDeleteObject : exists && (isAdmin || canDelete),
+
+            // Attributes in general (with or without existing values) can only
+            // be changed if the object is being created, OR the user is a
+            // system-level administrator, OR the user has explicit UPDATE
+            // permission on the object
+            canChangeAttributes : !exists || isAdmin || canUpdate,
+
+            // A user can change the attributes of an object which are not
+            // explicitly defined on that object when the object is being
+            // created
+            canChangeAllAttributes : !exists,
+
+            // A user can change the system permissions related to an object
+            // if they are a system-level admin, OR they are creating the
+            // object, OR they have explicit ADMINISTER permission on the
+            // existing object
+            canChangePermissions : isAdmin || !exists || canAdminister
+
+        });
+
+    };
+
+    return ManagementPermissions;
+
+}]);


[10/21] guacamole-client git commit: GUACAMOLE-220: Move common protocol namespace/name retrieval to Protocol class.

Posted by vn...@apache.org.
GUACAMOLE-220: Move common protocol namespace/name retrieval to Protocol class.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/82803c91
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/82803c91
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/82803c91

Branch: refs/heads/master
Commit: 82803c914831bd860f1c9c045e0c5177042c6e24
Parents: c1f5ad4
Author: Michael Jumper <mj...@apache.org>
Authored: Tue May 1 12:42:36 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Tue May 1 20:58:28 2018 -0700

----------------------------------------------------------------------
 .../controllers/manageConnectionController.js   | 47 ++---------------
 .../manageSharingProfileController.js           | 27 ++--------
 .../src/main/webapp/app/rest/restModule.js      |  3 +-
 .../src/main/webapp/app/rest/types/Protocol.js  | 54 +++++++++++++++++++-
 4 files changed, 62 insertions(+), 69 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/82803c91/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js
index 7cd08ac..ac55f11 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js
@@ -29,6 +29,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
     var HistoryEntryWrapper   = $injector.get('HistoryEntryWrapper');
     var ManagementPermissions = $injector.get('ManagementPermissions');
     var PermissionSet         = $injector.get('PermissionSet');
+    var Protocol              = $injector.get('Protocol');
 
     // Required services
     var $location                = $injector.get('$location');
@@ -41,7 +42,6 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
     var permissionService        = $injector.get('permissionService');
     var requestService           = $injector.get('requestService');
     var schemaService            = $injector.get('schemaService');
-    var translationStringService = $injector.get('translationStringService');
 
     /**
      * The unique identifier of the data source containing the connection being
@@ -295,51 +295,14 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
     }, angular.noop);
 
     /**
-     * Returns the translation string namespace for the protocol having the
-     * given name. The namespace will be of the form:
-     *
-     * <code>PROTOCOL_NAME</code>
-     *
-     * where <code>NAME</code> is the protocol name transformed via
-     * translationStringService.canonicalize().
-     *
-     * @param {String} protocolName
-     *     The name of the protocol.
-     *
-     * @returns {String}
-     *     The translation namespace for the protocol specified, or null if no
-     *     namespace could be generated.
+     * @borrows Protocol.getNamespace
      */
-    $scope.getNamespace = function getNamespace(protocolName) {
-
-        // Do not generate a namespace if no protocol is selected
-        if (!protocolName)
-            return null;
-
-        return 'PROTOCOL_' + translationStringService.canonicalize(protocolName);
-
-    };
+    $scope.getNamespace = Protocol.getNamespace;
 
     /**
-     * Given the internal name of a protocol, produces the translation string
-     * for the localized version of that protocol's name. The translation
-     * string will be of the form:
-     *
-     * <code>NAMESPACE.NAME<code>
-     *
-     * where <code>NAMESPACE</code> is the namespace generated from
-     * $scope.getNamespace().
-     *
-     * @param {String} protocolName
-     *     The name of the protocol.
-     * 
-     * @returns {String}
-     *     The translation string which produces the localized name of the
-     *     protocol specified.
+     * @borrows Protocol.getName
      */
-    $scope.getProtocolName = function getProtocolName(protocolName) {
-        return $scope.getNamespace(protocolName) + '.NAME';
-    };
+    $scope.getProtocolName = Protocol.getName;
 
     /**
      * Cancels all pending edits, returning to the main list of connections

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/82803c91/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
index a0a683c..940c305 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
@@ -27,6 +27,7 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
     var ManagementPermissions = $injector.get('ManagementPermissions');
     var SharingProfile        = $injector.get('SharingProfile');
     var PermissionSet         = $injector.get('PermissionSet');
+    var Protocol              = $injector.get('Protocol');
 
     // Required services
     var $location                = $injector.get('$location');
@@ -38,7 +39,6 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
     var requestService           = $injector.get('requestService');
     var schemaService            = $injector.get('schemaService');
     var sharingProfileService    = $injector.get('sharingProfileService');
-    var translationStringService = $injector.get('translationStringService');
 
     /**
      * The unique identifier of the data source containing the sharing profile
@@ -269,30 +269,9 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
     }, requestService.WARN);
 
     /**
-     * Returns the translation string namespace for the protocol having the
-     * given name. The namespace will be of the form:
-     *
-     * <code>PROTOCOL_NAME</code>
-     *
-     * where <code>NAME</code> is the protocol name transformed via
-     * translationStringService.canonicalize().
-     *
-     * @param {String} protocolName
-     *     The name of the protocol.
-     *
-     * @returns {String}
-     *     The translation namespace for the protocol specified, or null if no
-     *     namespace could be generated.
+     * @borrows Protocol.getNamespace
      */
-    $scope.getNamespace = function getNamespace(protocolName) {
-
-        // Do not generate a namespace if no protocol is selected
-        if (!protocolName)
-            return null;
-
-        return 'PROTOCOL_' + translationStringService.canonicalize(protocolName);
-
-    };
+    $scope.getNamespace = Protocol.getNamespace;
 
     /**
      * Cancels all pending edits, returning to the main list of connections

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/82803c91/guacamole/src/main/webapp/app/rest/restModule.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/rest/restModule.js b/guacamole/src/main/webapp/app/rest/restModule.js
index f409e95..23901c0 100644
--- a/guacamole/src/main/webapp/app/rest/restModule.js
+++ b/guacamole/src/main/webapp/app/rest/restModule.js
@@ -22,5 +22,6 @@
  * Guacamole web application.
  */
 angular.module('rest', [
-    'auth'
+    'auth',
+    'locale'
 ]);

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/82803c91/guacamole/src/main/webapp/app/rest/types/Protocol.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/rest/types/Protocol.js b/guacamole/src/main/webapp/app/rest/types/Protocol.js
index cfb26f0..0b86d5b 100644
--- a/guacamole/src/main/webapp/app/rest/types/Protocol.js
+++ b/guacamole/src/main/webapp/app/rest/types/Protocol.js
@@ -20,8 +20,11 @@
 /**
  * Service which defines the Protocol class.
  */
-angular.module('rest').factory('Protocol', [function defineProtocol() {
-            
+angular.module('rest').factory('Protocol', ['$injector', function defineProtocol($injector) {
+
+    // Required services
+    var translationStringService = $injector.get('translationStringService');
+
     /**
      * The object returned by REST API calls when representing the data
      * associated with a supported remote desktop protocol.
@@ -64,6 +67,53 @@ angular.module('rest').factory('Protocol', [function defineProtocol() {
 
     };
 
+    /**
+     * Returns the translation string namespace for the protocol having the
+     * given name. The namespace will be of the form:
+     *
+     * <code>PROTOCOL_NAME</code>
+     *
+     * where <code>NAME</code> is the protocol name transformed via
+     * translationStringService.canonicalize().
+     *
+     * @param {String} protocolName
+     *     The name of the protocol.
+     *
+     * @returns {String}
+     *     The translation namespace for the protocol specified, or null if no
+     *     namespace could be generated.
+     */
+    Protocol.getNamespace = function getNamespace(protocolName) {
+
+        // Do not generate a namespace if no protocol is selected
+        if (!protocolName)
+            return null;
+
+        return 'PROTOCOL_' + translationStringService.canonicalize(protocolName);
+
+    };
+
+    /**
+     * Given the internal name of a protocol, produces the translation string
+     * for the localized version of that protocol's name. The translation
+     * string will be of the form:
+     *
+     * <code>NAMESPACE.NAME<code>
+     *
+     * where <code>NAMESPACE</code> is the namespace generated from
+     * $scope.getNamespace().
+     *
+     * @param {String} protocolName
+     *     The name of the protocol.
+     *
+     * @returns {String}
+     *     The translation string which produces the localized name of the
+     *     protocol specified.
+     */
+    Protocol.getName = function getProtocolName(protocolName) {
+        return Protocol.getNamespace(protocolName) + '.NAME';
+    };
+
     return Protocol;
 
 }]);
\ No newline at end of file


[16/21] guacamole-client git commit: GUACAMOLE-220: Document context of skeleton user creation.

Posted by vn...@apache.org.
GUACAMOLE-220: Document context of skeleton user creation.

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/7ba9c32a
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/7ba9c32a
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/7ba9c32a

Branch: refs/heads/master
Commit: 7ba9c32a0767893860645b9ee22cfe3c1885c355
Parents: f5f516d
Author: Michael Jumper <mj...@apache.org>
Authored: Thu May 3 23:32:07 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Thu May 3 23:32:07 2018 -0700

----------------------------------------------------------------------
 .../src/main/webapp/app/manage/controllers/manageUserController.js  | 1 +
 1 file changed, 1 insertion(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/7ba9c32a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
index 9a90441..1679886 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
@@ -305,6 +305,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
         if (cloneSourceUsername)
             return loadClonedUser($scope.dataSource, cloneSourceUsername);
 
+        // If we are creating a new user, populate skeleton user data
         return loadSkeletonUser();
 
     };


[21/21] guacamole-client git commit: GUACAMOLE-220: Merge extract common base for management UI.

Posted by vn...@apache.org.
GUACAMOLE-220: Merge extract common base for management UI.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/9b99b180
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/9b99b180
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/9b99b180

Branch: refs/heads/master
Commit: 9b99b18041fe0a7517dc6a82da1bfd401adea525
Parents: 91f19b2 b50e709
Author: Nick Couchman <vn...@apache.org>
Authored: Fri May 4 04:25:59 2018 -0400
Committer: Nick Couchman <vn...@apache.org>
Committed: Fri May 4 04:25:59 2018 -0400

----------------------------------------------------------------------
 .../controllers/manageConnectionController.js   |  414 +++----
 .../manageConnectionGroupController.js          |  321 ++---
 .../manageSharingProfileController.js           |  391 +++---
 .../manage/controllers/manageUserController.js  | 1127 +++---------------
 .../directives/connectionPermissionEditor.js    |  407 +++++++
 .../app/manage/directives/dataSourceTabs.js     |  111 ++
 .../app/manage/directives/managementButtons.js  |  201 ++++
 .../manage/directives/systemPermissionEditor.js |  308 +++++
 .../templates/connectionPermissionEditor.html   |   21 +
 .../app/manage/templates/dataSourceTabs.html    |    3 +
 .../app/manage/templates/manageConnection.html  |   15 +-
 .../manage/templates/manageConnectionGroup.html |   14 +-
 .../manage/templates/manageSharingProfile.html  |   15 +-
 .../webapp/app/manage/templates/manageUser.html |   81 +-
 .../app/manage/templates/managementButtons.html |    6 +
 .../templates/systemPermissionEditor.html       |   18 +
 .../app/manage/types/ManagementPermissions.js   |  182 +++
 .../src/main/webapp/app/rest/restModule.js      |    3 +-
 .../src/main/webapp/app/rest/types/Protocol.js  |   54 +-
 guacamole/src/main/webapp/translations/en.json  |    1 +
 20 files changed, 2038 insertions(+), 1655 deletions(-)
----------------------------------------------------------------------



[14/21] guacamole-client git commit: GUACAMOLE-220: "required", not "requred".

Posted by vn...@apache.org.
GUACAMOLE-220: "required", not "requred".


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/5028d85b
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/5028d85b
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/5028d85b

Branch: refs/heads/master
Commit: 5028d85bb1d5de40d3da70e3a263e8a2d9f35a62
Parents: 244dd19
Author: Michael Jumper <mj...@apache.org>
Authored: Tue May 1 21:09:51 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Tue May 1 21:09:51 2018 -0700

----------------------------------------------------------------------
 .../webapp/app/manage/controllers/manageConnectionController.js    | 2 +-
 .../app/manage/controllers/manageConnectionGroupController.js      | 2 +-
 .../app/manage/controllers/manageSharingProfileController.js       | 2 +-
 .../src/main/webapp/app/manage/controllers/manageUserController.js | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/5028d85b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js
index 6139781..b84886f 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js
@@ -243,7 +243,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
     };
 
     /**
-     * Loads the data requred for performing the management task requested
+     * 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 connection, cloning an existing
      * connection, or creating an entirely new connection.

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/5028d85b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js
index 1a4b64e..250ebc1 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js
@@ -201,7 +201,7 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
     };
 
     /**
-     * Loads the data requred for performing the management task requested
+     * 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 connection group, cloning an
      * existing connection group, or creating an entirely new connection group.

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/5028d85b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
index 7385aa1..8b57182 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
@@ -222,7 +222,7 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
     };
 
     /**
-     * Loads the data requred for performing the management task requested
+     * 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 sharing profile, cloning an
      * existing sharing profile, or creating an entirely new sharing profile.

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/5028d85b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
index 800ad00..9a90441 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
@@ -286,7 +286,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     };
 
     /**
-     * Loads the data requred for performing the management task requested
+     * 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, cloning an existing user, or
      * creating an entirely new user.


[18/21] guacamole-client git commit: GUACAMOLE-220: removeConnectionGroupPermission() affects connection group permissions, not connection permissions.

Posted by vn...@apache.org.
GUACAMOLE-220: removeConnectionGroupPermission() affects connection group permissions, not connection permissions.

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/2c41e38e
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/2c41e38e
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/2c41e38e

Branch: refs/heads/master
Commit: 2c41e38e557e4f56183ebafca8ec7d5c8632f329
Parents: e8f0c96
Author: Michael Jumper <mj...@apache.org>
Authored: Thu May 3 23:34:34 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Thu May 3 23:34:34 2018 -0700

----------------------------------------------------------------------
 .../webapp/app/manage/directives/connectionPermissionEditor.js  | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/2c41e38e/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js b/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js
index da85c11..43e80c2 100644
--- a/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js
+++ b/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js
@@ -260,10 +260,11 @@ angular.module('manage').directive('connectionPermissionEditor', ['$injector',
 
         /**
          * Updates the permissionsAdded and permissionsRemoved permission sets
-         * to reflect the removal of the given connection permission.
+         * to reflect the removal of the given connection group permission.
          *
          * @param {String} identifier
-         *     The identifier of the connection to remove READ permission for.
+         *     The identifier of the connection group to remove READ permission
+         *     for.
          */
         var removeConnectionGroupPermission = function removeConnectionGroupPermission(identifier) {
 


[05/21] guacamole-client git commit: GUACAMOLE-220: Add common directive for displaying the save/clone/cancel/delete buttons shared by all object management pages.

Posted by vn...@apache.org.
GUACAMOLE-220: Add common directive for displaying the save/clone/cancel/delete buttons shared by all object management pages.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/e045da13
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/e045da13
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/e045da13

Branch: refs/heads/master
Commit: e045da132c729d0567f256708d204d607c053f9f
Parents: 4f43ddc
Author: Michael Jumper <mj...@apache.org>
Authored: Tue May 1 09:46:46 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Tue May 1 17:20:32 2018 -0700

----------------------------------------------------------------------
 .../app/manage/directives/managementButtons.js  | 201 +++++++++++++++++++
 .../app/manage/templates/managementButtons.html |   6 +
 2 files changed, 207 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/e045da13/guacamole/src/main/webapp/app/manage/directives/managementButtons.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/directives/managementButtons.js b/guacamole/src/main/webapp/app/manage/directives/managementButtons.js
new file mode 100644
index 0000000..4074548
--- /dev/null
+++ b/guacamole/src/main/webapp/app/manage/directives/managementButtons.js
@@ -0,0 +1,201 @@
+/*
+ * 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.
+ */
+
+/**
+ * Directive which displays a set of object management buttons (save, delete,
+ * clone, etc.) representing the actions available to the current user in
+ * context of the object being edited/created.
+ */
+angular.module('manage').directive('managementButtons', ['$injector',
+    function managementButtons($injector) {
+
+    // Required services
+    var guacNotification = $injector.get('guacNotification');
+
+    var directive = {
+
+        restrict    : 'E',
+        replace     : true,
+        templateUrl : 'app/manage/templates/managementButtons.html',
+
+        scope : {
+
+            /**
+             * The translation namespace associated with all applicable
+             * translation strings. This directive requires at least the
+             * following translation strings within the given namespace:
+             *
+             *     - ACTION_CANCEL
+             *     - ACTION_CLONE
+             *     - ACTION_DELETE
+             *     - ACTION_SAVE
+             *     - DIALOG_HEADER_CONFIRM_DELETE
+             *     - TEXT_CONFIRM_DELETE
+             *
+             * @type String
+             */
+            namespace : '=',
+
+            /**
+             * The permissions which dictate the management actions available
+             * to the current user.
+             *
+             * @type ManagementPermissions
+             */
+            permissions : '=',
+
+            /**
+             * The function to invoke to save the arbitrary object being edited
+             * if the current user has permission to do so. The provided
+             * function MUST return a promise which is resolved if the save
+             * operation succeeds and is rejected with an {@link Error} if the
+             * save operation fails.
+             *
+             * @type Function
+             */
+            save : '&',
+
+            /**
+             * The function to invoke when the current user chooses to clone
+             * the object being edited. The provided function MUST perform the
+             * actions necessary to produce an interface which will clone the
+             * object.
+             *
+             * @type Function
+             */
+            clone : '&',
+
+            /**
+             * The function to invoke to delete the arbitrary object being edited
+             * if the current user has permission to do so. The provided
+             * function MUST return a promise which is resolved if the delete
+             * operation succeeds and is rejected with an {@link Error} if the
+             * delete operation fails.
+             *
+             * @type Function
+             */
+            delete : '&',
+
+            /**
+             * The function to invoke when the current user chooses to cancel
+             * the edit in progress, or when a save/delete operation has
+             * succeeded. The provided function MUST perform the actions
+             * necessary to return the user to a reasonable starting point.
+             *
+             * @type Function
+             */
+            return : '&'
+
+        }
+
+    };
+
+    directive.controller = ['$scope', function managementButtonsController($scope) {
+
+        /**
+         * An action to be provided along with the object sent to showStatus which
+         * immediately deletes the current connection.
+         */
+        var DELETE_ACTION = {
+            name      : $scope.namespace + '.ACTION_DELETE',
+            className : 'danger',
+            callback  : function deleteCallback() {
+                deleteObjectImmediately();
+                guacNotification.showStatus(false);
+            }
+        };
+
+        /**
+         * An action to be provided along with the object sent to showStatus which
+         * closes the currently-shown status dialog.
+         */
+        var CANCEL_ACTION = {
+            name     : $scope.namespace + '.ACTION_CANCEL',
+            callback : function cancelCallback() {
+                guacNotification.showStatus(false);
+            }
+        };
+
+        /**
+         * Invokes the provided return function to navigate the user back to
+         * the page they started from.
+         */
+        var navigateBack = function navigateBack() {
+            $scope['return']($scope.$parent);
+        };
+
+        /**
+         * Invokes the provided delete function, immediately deleting the
+         * current object without prompting the user for confirmation. If
+         * deletion is successful, the user is navigated back to the page they
+         * started from. If the deletion fails, an error notification is
+         * displayed.
+         */
+        var deleteObjectImmediately = function deleteObjectImmediately() {
+            $scope['delete']($scope.$parent).then(navigateBack, guacNotification.SHOW_REQUEST_ERROR);
+        };
+
+        /**
+         * Cancels all pending edits, returning to the page the user started
+         * from.
+         */
+        $scope.cancel = navigateBack;
+
+        /**
+         * Cancels all pending edits, invoking the provided clone function to
+         * open an edit page for a new object which is prepopulated with the
+         * data from the current object.
+         */
+        $scope.cloneObject = function cloneObject () {
+            $scope.clone($scope.$parent);
+        };
+
+        /**
+         * Invokes the provided save function to saves the current object. If
+         * saving is successful, the user is navigated back to the page they
+         * started from. If saving fails, an error notification is displayed.
+         */
+        $scope.saveObject = function saveObject() {
+            $scope.save($scope.$parent).then(navigateBack, guacNotification.SHOW_REQUEST_ERROR);
+        };
+
+        /**
+         * Deletes the current object, prompting the user first to confirm that
+         * deletion is desired. If the user confirms that deletion is desired,
+         * the object is deleted through invoking the provided delete function.
+         * The user is automatically navigated back to the page they started
+         * from or given an error notification depending on whether deletion
+         * succeeds.
+         */
+        $scope.deleteObject = function deleteObject() {
+
+            // Confirm deletion request
+            guacNotification.showStatus({
+                title   : $scope.namespace + '.DIALOG_HEADER_CONFIRM_DELETE',
+                text    : { key : $scope.namespace + '.TEXT_CONFIRM_DELETE' },
+                actions : [ DELETE_ACTION, CANCEL_ACTION]
+            });
+
+        };
+
+    }];
+
+    return directive;
+
+}]);

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/e045da13/guacamole/src/main/webapp/app/manage/templates/managementButtons.html
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/templates/managementButtons.html b/guacamole/src/main/webapp/app/manage/templates/managementButtons.html
new file mode 100644
index 0000000..d43209a
--- /dev/null
+++ b/guacamole/src/main/webapp/app/manage/templates/managementButtons.html
@@ -0,0 +1,6 @@
+<div class="action-buttons">
+    <button ng-show="permissions.canSaveObject" ng-click="saveObject()">{{namespace + '.ACTION_SAVE' | translate}}</button>
+    <button ng-show="permissions.canCloneObject" ng-click="cloneObject()">{{namespace + '.ACTION_CLONE' | translate}}</button>
+    <button ng-click="cancel()">{{namespace + '.ACTION_CANCEL' | translate}}</button>
+    <button ng-show="permissions.canDeleteObject" ng-click="deleteObject()" class="danger">{{namespace + '.ACTION_DELETE' | translate}}</button>
+</div>


[09/21] guacamole-client git commit: GUACAMOLE-220: Migrate connection group management screen to common buttons and permission logic. Add required clone option.

Posted by vn...@apache.org.
GUACAMOLE-220: Migrate connection group management screen to common buttons and permission logic. Add required clone option.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/7e1dbf7d
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/7e1dbf7d
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/7e1dbf7d

Branch: refs/heads/master
Commit: 7e1dbf7d11f51f7acd8b3ad39e30695bbda15abd
Parents: 00fee4a
Author: Michael Jumper <mj...@apache.org>
Authored: Tue May 1 11:54:38 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Tue May 1 20:58:28 2018 -0700

----------------------------------------------------------------------
 .../manageConnectionGroupController.js          | 321 ++++++++++---------
 .../manage/templates/manageConnectionGroup.html |  14 +-
 guacamole/src/main/webapp/translations/en.json  |   1 +
 3 files changed, 174 insertions(+), 162 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/7e1dbf7d/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js
index de29aff..842c395 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js
@@ -24,15 +24,16 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
         function manageConnectionGroupController($scope, $injector) {
             
     // Required types
-    var ConnectionGroup = $injector.get('ConnectionGroup');
-    var PermissionSet   = $injector.get('PermissionSet');
+    var ConnectionGroup       = $injector.get('ConnectionGroup');
+    var ManagementPermissions = $injector.get('ManagementPermissions');
+    var PermissionSet         = $injector.get('PermissionSet');
 
     // Required services
     var $location              = $injector.get('$location');
+    var $q                     = $injector.get('$q');
     var $routeParams           = $injector.get('$routeParams');
     var authenticationService  = $injector.get('authenticationService');
     var connectionGroupService = $injector.get('connectionGroupService');
-    var guacNotification       = $injector.get('guacNotification');
     var permissionService      = $injector.get('permissionService');
     var requestService         = $injector.get('requestService');
     var schemaService          = $injector.get('schemaService');
@@ -46,6 +47,15 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
     $scope.selectedDataSource = $routeParams.dataSource;
 
     /**
+     * The identifier of the original connection group from which this
+     * connection group is being cloned. Only valid if this is a new
+     * connection group.
+     *
+     * @type String
+     */
+    var cloneSourceIdentifier = $location.search().clone;
+
+    /**
      * The identifier of the connection group being edited. If a new connection
      * group is being created, this will not be defined.
      *
@@ -54,6 +64,23 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
     var identifier = $routeParams.id;
 
     /**
+     * Available connection group types, as translation string / internal value
+     * pairs.
+     *
+     * @type Object[]
+     */
+    $scope.types = [
+        {
+            label: "MANAGE_CONNECTION_GROUP.NAME_TYPE_ORGANIZATIONAL",
+            value: ConnectionGroup.Type.ORGANIZATIONAL
+        },
+        {
+            label: "MANAGE_CONNECTION_GROUP.NAME_TYPE_BALANCING",
+            value : ConnectionGroup.Type.BALANCING
+        }
+    ];
+
+    /**
      * The root connection group of the connection group hierarchy.
      *
      * @type ConnectionGroup
@@ -68,26 +95,13 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
     $scope.connectionGroup = null;
     
     /**
-     * Whether the user has UPDATE permission for the current connection group.
-     * 
-     * @type Boolean
-     */
-    $scope.hasUpdatePermission = null;
-    
-    /**
-     * Whether the user has DELETE permission for the current connection group.
-     * 
-     * @type Boolean
-     */
-    $scope.hasDeletePermission = null;
-
-    /**
-     * All permissions associated with the current user, or null if the user's
-     * permissions have not yet been loaded.
+     * The managment-related actions that the current user may perform on the
+     * connection group currently being created/modified, or null if the current
+     * user's permissions have not yet been loaded.
      *
-     * @type PermissionSet
+     * @type ManagementPermissions
      */
-    $scope.permissions = null;
+    $scope.managementPermissions = null;
 
     /**
      * All available connection group attributes. This is only the set of
@@ -109,177 +123,172 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
 
         return $scope.rootGroup                !== null
             && $scope.connectionGroup          !== null
-            && $scope.permissions              !== null
-            && $scope.attributes               !== null
-            && $scope.canSaveConnectionGroup   !== null
-            && $scope.canDeleteConnectionGroup !== null;
+            && $scope.managementPermissions    !== null
+            && $scope.attributes               !== null;
 
     };
-    
-    // Pull connection group attribute schema
-    schemaService.getConnectionGroupAttributes($scope.selectedDataSource)
-    .then(function attributesReceived(attributes) {
-        $scope.attributes = attributes;
-    }, requestService.WARN);
-
-    // Query the user's permissions for the current connection group
-    permissionService.getEffectivePermissions($scope.selectedDataSource, authenticationService.getCurrentUsername())
-    .then(function permissionsReceived(permissions) {
-                
-        $scope.permissions = permissions;
-                        
-        // Check if the connection group is new or if the user has UPDATE permission
-        $scope.canSaveConnectionGroup =
-              !identifier
-           || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
-           || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, identifier);
-
-        // Check if connection group is not new and the user has DELETE permission
-        $scope.canDeleteConnectionGroup =
-           !!identifier && (
-                  PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
-              ||  PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.DELETE, identifier)
-           );
-    
-    }, requestService.WARN);
-
-
-    // Pull connection group hierarchy
-    connectionGroupService.getConnectionGroupTree(
-        $scope.selectedDataSource,
-        ConnectionGroup.ROOT_IDENTIFIER,
-        [PermissionSet.ObjectPermissionType.ADMINISTER]
-    )
-    .then(function connectionGroupReceived(rootGroup) {
-        $scope.rootGroup = rootGroup;
-    }, requestService.WARN);
 
-    // If we are editing an existing connection group, pull its data
-    if (identifier) {
-        connectionGroupService.getConnectionGroup($scope.selectedDataSource, identifier)
+    /**
+     * Loads the data associated with the connection group having the given
+     * identifier, preparing the interface for making modifications to that
+     * existing connection group.
+     *
+     * @param {String} dataSource
+     *     The unique identifier of the data source containing the connection
+     *     group to load.
+     *
+     * @param {String} identifier
+     *     The identifier of the connection group to load.
+     *
+     * @returns {Promise}
+     *     A promise which is resolved when the interface has been prepared for
+     *     editing the given connection group.
+     */
+    var loadExistingConnectionGroup = function loadExistingConnectionGroup(dataSource, identifier) {
+        return connectionGroupService.getConnectionGroup(
+            dataSource,
+            identifier
+        )
         .then(function connectionGroupReceived(connectionGroup) {
             $scope.connectionGroup = connectionGroup;
-        }, requestService.WARN);
-    }
-
-    // If we are creating a new connection group, populate skeleton connection group data
-    else
-        $scope.connectionGroup = new ConnectionGroup({
-            parentIdentifier : $location.search().parent
         });
+    };
 
     /**
-     * Available connection group types, as translation string / internal value
-     * pairs.
-     * 
-     * @type Object[]
+     * Loads the data associated with the connection group having the given
+     * identifier, preparing the interface for cloning that existing
+     * connection group.
+     *
+     * @param {String} dataSource
+     *     The unique identifier of the data source containing the connection
+     *     group to be cloned.
+     *
+     * @param {String} identifier
+     *     The identifier of the connection group being cloned.
+     *
+     * @returns {Promise}
+     *     A promise which is resolved when the interface has been prepared for
+     *     cloning the given connection group.
      */
-    $scope.types = [
-        {
-            label: "MANAGE_CONNECTION_GROUP.NAME_TYPE_ORGANIZATIONAL",
-            value: ConnectionGroup.Type.ORGANIZATIONAL
-        },
-        {
-            label: "MANAGE_CONNECTION_GROUP.NAME_TYPE_BALANCING",
-            value : ConnectionGroup.Type.BALANCING
-        }
-    ];
+    var loadClonedConnectionGroup = function loadClonedConnectionGroup(dataSource, identifier) {
+        return connectionGroupService.getConnectionGroup(
+            dataSource,
+            identifier
+        )
+        .then(function connectionGroupReceived(connectionGroup) {
+            $scope.connectionGroup = connectionGroup;
+            delete $scope.connectionGroup.identifier;
+        });
+    };
 
     /**
-     * Returns whether the current user can change/set all connection group
-     * attributes for the connection group being edited, regardless of whether
-     * those attributes are already explicitly associated with that connection
-     * group.
+     * Loads skeleton connection group data, preparing the interface for
+     * creating a new connection group.
      *
-     * @returns {Boolean}
-     *     true if the current user can change all attributes for the
-     *     connection group being edited, regardless of whether those
-     *     attributes are already explicitly associated with that connection
-     *     group, false otherwise.
+     * @returns {Promise}
+     *     A promise which is resolved when the interface has been prepared for
+     *     creating a new connection group.
      */
-    $scope.canChangeAllAttributes = function canChangeAllAttributes() {
+    var loadSkeletonConnectionGroup = function loadSkeletonConnectionGroup() {
 
-        // All attributes can be set if we are creating the connection group
-        return !identifier;
+        // Use skeleton connection group object with specified parent
+        $scope.connectionGroup = new ConnectionGroup({
+            parentIdentifier : $location.search().parent
+        });
 
-    };
+        return $q.resolve();
 
-    /**
-     * Cancels all pending edits, returning to the management page.
-     */
-    $scope.cancel = function cancel() {
-        $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
     };
-   
+
     /**
-     * Saves the connection group, creating a new connection group or updating
-     * the existing connection group.
+     * Loads the data requred for performing the management task requested
+     * through the route parameters given at load time, automatically preparing
+     * the interface for editing an existing connection group, cloning an
+     * existing connection group, or creating an entirely new connection group.
+     *
+     * @returns {Promise}
+     *     A promise which is resolved when the interface has been prepared
+     *     for performing the requested management task.
      */
-    $scope.saveConnectionGroup = function saveConnectionGroup() {
+    var loadRequestedConnectionGroup = function loadRequestedConnectionGroup() {
+
+        // If we are editing an existing connection group, pull its data
+        if (identifier)
+            return loadExistingConnectionGroup($scope.selectedDataSource, identifier);
 
-        // Save the connection
-        connectionGroupService.saveConnectionGroup($scope.selectedDataSource, $scope.connectionGroup)
-        .then(function savedConnectionGroup() {
-            $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
-        }, guacNotification.SHOW_REQUEST_ERROR);
+        // If we are cloning an existing connection group, pull its data
+        // instead
+        if (cloneSourceIdentifier)
+            return loadClonedConnectionGroup($scope.selectedDataSource, cloneSourceIdentifier);
+
+        // If we are creating a new connection group, populate skeleton
+        // connection group data
+        return loadSkeletonConnectionGroup();
 
     };
-    
+
+    // Query the user's permissions for the current connection group
+    $q.all({
+        connectionGroupData : loadRequestedConnectionGroup(),
+        attributes  : schemaService.getConnectionGroupAttributes($scope.selectedDataSource),
+        permissions : permissionService.getEffectivePermissions($scope.selectedDataSource, authenticationService.getCurrentUsername()),
+        rootGroup   : connectionGroupService.getConnectionGroupTree($scope.selectedDataSource, ConnectionGroup.ROOT_IDENTIFIER, [PermissionSet.ObjectPermissionType.ADMINISTER])
+    })
+    .then(function connectionGroupDataRetrieved(values) {
+                
+        $scope.attributes = values.attributes;
+        $scope.rootGroup = values.rootGroup;
+
+        $scope.managementPermissions = ManagementPermissions.fromPermissionSet(
+                    values.permissions,
+                    PermissionSet.SystemPermissionType.CREATE_CONNECTION,
+                    PermissionSet.hasConnectionPermission,
+                    identifier);
+
+    }, requestService.WARN);
+
     /**
-     * An action to be provided along with the object sent to showStatus which
-     * immediately deletes the current connection group.
+     * Cancels all pending edits, returning to the main list of connections
+     * within the selected data source.
      */
-    var DELETE_ACTION = {
-        name        : "MANAGE_CONNECTION_GROUP.ACTION_DELETE",
-        className   : "danger",
-        // Handle action
-        callback    : function deleteCallback() {
-            deleteConnectionGroupImmediately();
-            guacNotification.showStatus(false);
-        }
+    $scope.returnToConnectionList = function returnToConnectionList() {
+        $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
     };
 
     /**
-     * An action to be provided along with the object sent to showStatus which
-     * closes the currently-shown status dialog.
+     * Cancels all pending edits, opening an edit page for a new connection
+     * group which is prepopulated with the data from the connection group
+     * currently being edited.
      */
-    var CANCEL_ACTION = {
-        name        : "MANAGE_CONNECTION_GROUP.ACTION_CANCEL",
-        // Handle action
-        callback    : function cancelCallback() {
-            guacNotification.showStatus(false);
-        }
+    $scope.cloneConnectionGroup = function cloneConnectionGRoup() {
+        $location.path('/manage/' + encodeURIComponent($scope.selectedDataSource) + '/connectionGroups').search('clone', identifier);
     };
 
     /**
-     * Immediately deletes the current connection group, without prompting the
-     * user for confirmation.
+     * Saves the current connection group, creating a new connection group or
+     * updating the existing connection group, 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.
      */
-    var deleteConnectionGroupImmediately = function deleteConnectionGroupImmediately() {
-
-        // Delete the connection group
-        connectionGroupService.deleteConnectionGroup($scope.selectedDataSource, $scope.connectionGroup)
-        .then(function deletedConnectionGroup() {
-            $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
-        }, guacNotification.SHOW_REQUEST_ERROR);
-
+    $scope.saveConnectionGroup = function saveConnectionGroup() {
+        return connectionGroupService.saveConnectionGroup($scope.selectedDataSource, $scope.connectionGroup);
     };
-
+    
     /**
-     * Deletes the connection group, prompting the user first to confirm that
-     * deletion is desired.
+     * Deletes the current connection 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.deleteConnectionGroup = function deleteConnectionGroup() {
-
-        // Confirm deletion request
-        guacNotification.showStatus({
-            'title'      : 'MANAGE_CONNECTION_GROUP.DIALOG_HEADER_CONFIRM_DELETE',
-            'text'       : {
-                key : 'MANAGE_CONNECTION_GROUP.TEXT_CONFIRM_DELETE'
-            },
-            'actions'    : [ DELETE_ACTION, CANCEL_ACTION]
-        });
-
+        return connectionGroupService.deleteConnectionGroup($scope.selectedDataSource, $scope.connectionGroup);
     };
 
 }]);

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/7e1dbf7d/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html b/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html
index d4c6613..926dc11 100644
--- a/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html
+++ b/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html
@@ -41,14 +41,16 @@
     <!-- Connection group attributes section -->
     <div class="attributes">
         <guac-form namespace="'CONNECTION_GROUP_ATTRIBUTES'" content="attributes"
-                   model="connectionGroup.attributes" model-only="!canChangeAllAttributes()"></guac-form>
+                   model="connectionGroup.attributes" model-only="!managementPermissions.canChangeAllAttributes"></guac-form>
     </div>
 
     <!-- Form action buttons -->
-    <div class="action-buttons">
-        <button ng-show="canSaveConnectionGroup" ng-click="saveConnectionGroup()">{{'MANAGE_CONNECTION_GROUP.ACTION_SAVE' | translate}}</button>
-        <button ng-click="cancel()">{{'MANAGE_CONNECTION_GROUP.ACTION_CANCEL' | translate}}</button>
-        <button ng-show="canDeleteConnectionGroup" ng-click="deleteConnectionGroup()" class="danger">{{'MANAGE_CONNECTION_GROUP.ACTION_DELETE' | translate}}</button>
-    </div>
+    <management-buttons namespace="'MANAGE_CONNECTION_GROUP'"
+          permissions="managementPermissions"
+          save="saveConnectionGroup()"
+          delete="deleteConnectionGroup()"
+          clone="cloneConnectionGroup()"
+          return="returnToConnectionList()">
+    </management-buttons>
 
 </div>

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/7e1dbf7d/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 a97406e..ca8acb8 100644
--- a/guacamole/src/main/webapp/translations/en.json
+++ b/guacamole/src/main/webapp/translations/en.json
@@ -234,6 +234,7 @@
 
         "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",
 


[13/21] guacamole-client git commit: GUACAMOLE-220: Move data source tabs to separate directive.

Posted by vn...@apache.org.
GUACAMOLE-220: Move data source tabs to separate directive.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/6ca076f7
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/6ca076f7
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/6ca076f7

Branch: refs/heads/master
Commit: 6ca076f7fcc59e4d499fa6b15277907f9a88bf8a
Parents: 1989c11
Author: Michael Jumper <mj...@apache.org>
Authored: Tue May 1 17:18:48 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Tue May 1 20:58:28 2018 -0700

----------------------------------------------------------------------
 .../manage/controllers/manageUserController.js  |  70 +++++-------
 .../app/manage/directives/dataSourceTabs.js     | 111 +++++++++++++++++++
 .../app/manage/templates/dataSourceTabs.html    |   3 +
 .../webapp/app/manage/templates/manageUser.html |  22 ++--
 4 files changed, 152 insertions(+), 54 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/6ca076f7/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
index dcb87e1..27cb933 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
@@ -26,7 +26,6 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     // Required types
     var Error                 = $injector.get('Error');
     var ManagementPermissions = $injector.get('ManagementPermissions');
-    var PageDefinition        = $injector.get('PageDefinition');
     var PermissionFlagSet     = $injector.get('PermissionFlagSet');
     var PermissionSet         = $injector.get('PermissionSet');
     var User                  = $injector.get('User');
@@ -40,7 +39,6 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     var permissionService        = $injector.get('permissionService');
     var requestService           = $injector.get('requestService');
     var schemaService            = $injector.get('schemaService');
-    var translationStringService = $injector.get('translationStringService');
     var userService              = $injector.get('userService');
 
     /**
@@ -136,11 +134,14 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     $scope.permissionsRemoved = new PermissionSet();
 
     /**
-     * The management-related actions that the current user may perform on the
-     * user currently being created/modified, or null if the current user's
-     * permissions have not yet been loaded.
+     * For each applicable data source, the management-related actions that the
+     * current user may perform on the user account 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 ManagementPermissions
+     * @type Object.<String, ManagementPermissions>
      */
     $scope.managementPermissions = null;
 
@@ -154,14 +155,6 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     $scope.attributes = null;
 
     /**
-     * The pages associated with each user account having the given username.
-     * Each user account will be associated with a particular data source.
-     *
-     * @type PageDefinition[]
-     */
-    $scope.accountPages = [];
-
-    /**
      * Returns whether critical data has completed being loaded.
      *
      * @returns {Boolean}
@@ -349,54 +342,43 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     })
     .then(function dataReceived(values) {
 
-        var managementPermissions = {};
-
         $scope.attributes = values.attributes;
 
-        // Generate pages for each applicable data source
-        $scope.accountPages = [];
+        $scope.managementPermissions = {};
         angular.forEach(dataSources, function addAccountPage(dataSource) {
 
             // Determine whether data source contains this user
             var exists = (dataSource in $scope.users);
 
             // Calculate management actions available for this specific account
-            managementPermissions[dataSource] = ManagementPermissions.fromPermissionSet(
+            $scope.managementPermissions[dataSource] = ManagementPermissions.fromPermissionSet(
                     values.permissions[dataSource],
                     PermissionSet.SystemPermissionType.CREATE_USER,
                     PermissionSet.hasUserPermission,
                     exists ? username : null);
 
-            // Account is not relevant if it does not exist and cannot be
-            // created
-            var readOnly = !managementPermissions[dataSource].canSaveObject;
-            if (!exists && readOnly)
-                return;
-
-            // Only the selected data source is relevant when cloning
-            if (cloneSourceUsername && dataSource !== $scope.dataSource)
-                return;
-
-            // Determine class name based on read-only / linked status
-            var className;
-            if (readOnly)    className = 'read-only';
-            else if (exists) className = 'linked';
-            else             className = 'unlinked';
-
-            // Add page entry
-            $scope.accountPages.push(new PageDefinition({
-                name      : translationStringService.canonicalize('DATA_SOURCE_' + dataSource) + '.NAME',
-                url       : '/manage/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(username || ''),
-                className : className
-            }));
-
         });
 
-        $scope.managementPermissions = managementPermissions[$scope.dataSource];
-
     }, requestService.WARN);
 
     /**
+     * Returns the URL for the page which manages the user account 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 account currently being
+     *     edited under the given data source.
+     */
+    $scope.getUserURL = function getUserURL(dataSource) {
+        return '/manage/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(username || '');
+    };
+
+    /**
      * Cancels all pending edits, returning to the main list of users.
      */
     $scope.returnToUserList = function returnToUserList() {

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/6ca076f7/guacamole/src/main/webapp/app/manage/directives/dataSourceTabs.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/directives/dataSourceTabs.js b/guacamole/src/main/webapp/app/manage/directives/dataSourceTabs.js
new file mode 100644
index 0000000..cf7068f
--- /dev/null
+++ b/guacamole/src/main/webapp/app/manage/directives/dataSourceTabs.js
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+
+/**
+ * Directive which displays a set of tabs pointing to the same object within
+ * different data sources, such as user accounts which span multiple data
+ * sources.
+ */
+angular.module('manage').directive('dataSourceTabs', ['$injector',
+    function dataSourceTabs($injector) {
+
+    // Required types
+    var PageDefinition = $injector.get('PageDefinition');
+
+    // Required services
+    var translationStringService = $injector.get('translationStringService');
+
+    var directive = {
+
+        restrict    : 'E',
+        replace     : true,
+        templateUrl : 'app/manage/templates/dataSourceTabs.html',
+
+        scope : {
+
+            /**
+             * The permissions which dictate the management actions available
+             * to the current user.
+             *
+             * @type Object.<String, ManagementPermissions>
+             */
+            permissions : '=',
+
+            /**
+             * A function which returns the URL of the object within a given
+             * data source. The relevant data source will be made available to
+             * the Angular expression defining this function as the
+             * "dataSource" variable. No other values will be made available,
+             * including values from the scope.
+             *
+             * @type Function
+             */
+            url : '&'
+
+        }
+
+    };
+
+    directive.controller = ['$scope', function dataSourceTabsController($scope) {
+
+        /**
+         * The set of pages which each manage the same object within different
+         * data sources.
+         *
+         * @type PageDefinition[]
+         */
+        $scope.pages = null;
+
+        // Keep pages synchronized with permissions
+        $scope.$watch('permissions', function permissionsChanged(permissions) {
+
+            $scope.pages = [];
+            angular.forEach(permissions, function addDataSourcePage(managementPermissions, dataSource) {
+
+                // Determine whether data source contains this object
+                var exists = !!managementPermissions.identifier;
+
+                // Data source is not relevant if the associated object does not
+                // exist and cannot be created
+                var readOnly = !managementPermissions.canSaveObject;
+                if (!exists && readOnly)
+                    return;
+
+                // Determine class name based on read-only / linked status
+                var className;
+                if (readOnly)    className = 'read-only';
+                else if (exists) className = 'linked';
+                else             className = 'unlinked';
+
+                // Add page entry
+                $scope.pages.push(new PageDefinition({
+                    name      : translationStringService.canonicalize('DATA_SOURCE_' + dataSource) + '.NAME',
+                    url       : $scope.url({ dataSource : dataSource }),
+                    className : className
+                }));
+
+            });
+
+        });
+
+    }];
+
+    return directive;
+
+}]);

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/6ca076f7/guacamole/src/main/webapp/app/manage/templates/dataSourceTabs.html
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/templates/dataSourceTabs.html b/guacamole/src/main/webapp/app/manage/templates/dataSourceTabs.html
new file mode 100644
index 0000000..a8a0843
--- /dev/null
+++ b/guacamole/src/main/webapp/app/manage/templates/dataSourceTabs.html
@@ -0,0 +1,3 @@
+<div class="page-tabs">
+    <guac-page-list pages="pages"></guac-page-list>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/6ca076f7/guacamole/src/main/webapp/app/manage/templates/manageUser.html
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUser.html b/guacamole/src/main/webapp/app/manage/templates/manageUser.html
index 02b3ddc..6770d31 100644
--- a/guacamole/src/main/webapp/app/manage/templates/manageUser.html
+++ b/guacamole/src/main/webapp/app/manage/templates/manageUser.html
@@ -6,17 +6,18 @@
         <h2>{{'MANAGE_USER.SECTION_HEADER_EDIT_USER' | translate}}</h2>
         <guac-user-menu></guac-user-menu>
     </div>
-    <div class="page-tabs">
-        <guac-page-list pages="accountPages"></guac-page-list>
-    </div>
+    <data-data-source-tabs ng-hide="cloneSourceUsername"
+        permissions="managementPermissions"
+        url="getUserURL(dataSource)">
+    </data-data-source-tabs>
 
     <!-- Warn if user is read-only -->
-    <div class="section" ng-hide="managementPermissions.canSaveObject">
+    <div class="section" ng-hide="managementPermissions[dataSource].canSaveObject">
         <p class="notice read-only">{{'MANAGE_USER.INFO_READ_ONLY' | translate}}</p>
     </div>
 
     <!-- Sections applicable to non-read-only users -->
-    <div ng-show="managementPermissions.canSaveObject">
+    <div ng-show="managementPermissions[dataSource].canSaveObject">
 
         <!-- User password section -->
         <div class="section">
@@ -40,13 +41,14 @@
         </div>
 
         <!-- User attributes section -->
-        <div class="attributes" ng-show="managementPermissions.canChangeAttributes">
+        <div class="attributes" ng-show="managementPermissions[dataSource].canChangeAttributes">
             <guac-form namespace="'USER_ATTRIBUTES'" content="attributes"
-                       model="user.attributes" model-only="!managementPermissions.canChangeAllAttributes"></guac-form>
+                       model="user.attributes"
+                       model-only="!managementPermissions[dataSource].canChangeAllAttributes"></guac-form>
         </div>
 
         <!-- System permissions section -->
-        <system-permission-editor ng-show="managementPermissions.canChangePermissions"
+        <system-permission-editor ng-show="managementPermissions[dataSource].canChangePermissions"
               username="selfUsername"
               data-data-source="dataSource"
               permission-flags="permissionFlags"
@@ -55,7 +57,7 @@
         </system-permission-editor>
 
         <!-- Connection permissions section -->
-        <connection-permission-editor ng-show="managementPermissions.canChangePermissions"
+        <connection-permission-editor ng-show="managementPermissions[dataSource].canChangePermissions"
               data-data-source="dataSource"
               permission-flags="permissionFlags"
               permissions-added="permissionsAdded"
@@ -64,7 +66,7 @@
 
         <!-- Form action buttons -->
         <management-buttons namespace="'MANAGE_USER'"
-              permissions="managementPermissions"
+              permissions="managementPermissions[dataSource]"
               save="saveUser()"
               delete="deleteUser()"
               clone="cloneUser()"


[12/21] guacamole-client git commit: GUACAMOLE-220: Remove unnecessary userExists() function.

Posted by vn...@apache.org.
GUACAMOLE-220: Remove unnecessary userExists() function.

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/244dd19a
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/244dd19a
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/244dd19a

Branch: refs/heads/master
Commit: 244dd19aa0b5b17f28443465d927b6f0d34b21db
Parents: 6ca076f
Author: Michael Jumper <mj...@apache.org>
Authored: Tue May 1 17:19:40 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Tue May 1 20:58:28 2018 -0700

----------------------------------------------------------------------
 .../manage/controllers/manageUserController.js  | 27 +-------------------
 1 file changed, 1 insertion(+), 26 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/244dd19a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
index 27cb933..800ad00 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
@@ -171,31 +171,6 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     };
 
     /**
-     * Returns whether the user being edited already exists within the data
-     * source specified.
-     *
-     * @param {String} [dataSource]
-     *     The identifier of the data source to check. If omitted, this will
-     *     default to the currently-selected data source.
-     *
-     * @returns {Boolean}
-     *     true if the user being edited already exists, false otherwise.
-     */
-    $scope.userExists = function userExists(dataSource) {
-
-        // Do not check if users are not yet loaded
-        if (!$scope.users)
-            return false;
-
-        // Use currently-selected data source if unspecified
-        dataSource = dataSource || $scope.dataSource;
-
-        // Account exists only if it was successfully retrieved
-        return (dataSource in $scope.users);
-
-    };
-
-    /**
      * Returns whether the current user can edit the username of the user being
      * edited within the given data source.
      *
@@ -415,7 +390,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
 
         // Save or create the user, depending on whether the user exists
         var saveUserPromise;
-        if ($scope.userExists($scope.dataSource))
+        if ($scope.dataSource in $scope.users)
             saveUserPromise = userService.saveUser($scope.dataSource, $scope.user);
         else
             saveUserPromise = userService.createUser($scope.dataSource, $scope.user);


[15/21] guacamole-client git commit: GUACAMOLE-220: Pull primary connection for new sharing profiles.

Posted by vn...@apache.org.
GUACAMOLE-220: Pull primary connection for new sharing profiles.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/f5f516d8
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/f5f516d8
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/f5f516d8

Branch: refs/heads/master
Commit: f5f516d82a2ebcee654185acb38f798f71a28c81
Parents: 5028d85
Author: Michael Jumper <mj...@apache.org>
Authored: Thu May 3 23:16:14 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Thu May 3 23:16:14 2018 -0700

----------------------------------------------------------------------
 .../controllers/manageSharingProfileController.js  | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/f5f516d8/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
index 8b57182..61bfbe9 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageSharingProfileController.js
@@ -205,11 +205,15 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
      * Loads skeleton sharing profile data, preparing the interface for
      * creating a new sharing profile.
      *
+     * @param {String} dataSource
+     *     The unique identifier of the data source containing the sharing
+     *     profile to be created.
+     *
      * @returns {Promise}
      *     A promise which is resolved when the interface has been prepared for
      *     creating a new sharing profile.
      */
-    var loadSkeletonSharingProfile = function loadSkeletonSharingProfile() {
+    var loadSkeletonSharingProfile = function loadSkeletonSharingProfile(dataSource) {
 
         // Use skeleton sharing profile object with no associated parameters
         $scope.sharingProfile = new SharingProfile({
@@ -217,7 +221,14 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
         });
         $scope.parameters = {};
 
-        return $q.resolve();
+        // Load connection object for associated primary connection
+        return connectionService.getConnection(
+            dataSource,
+            $scope.sharingProfile.primaryConnectionIdentifier
+        )
+        .then(function connectionRetrieved(connection) {
+            $scope.primaryConnection = connection;
+        });
 
     };
 
@@ -243,7 +254,7 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
 
         // If we are creating a new sharing profile, populate skeleton sharing
         // profile data
-        return loadSkeletonSharingProfile();
+        return loadSkeletonSharingProfile($scope.selectedDataSource);
 
     };
 


[06/21] guacamole-client git commit: GUACAMOLE-220: Migrate connection management screen to common buttons and permission logic.

Posted by vn...@apache.org.
GUACAMOLE-220: Migrate connection management screen to common buttons and permission logic.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/00fee4ac
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/00fee4ac
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/00fee4ac

Branch: refs/heads/master
Commit: 00fee4ac3af746faea9596b7e69af20d8b02d662
Parents: 0414cdd
Author: Michael Jumper <mj...@apache.org>
Authored: Tue May 1 11:22:31 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Tue May 1 20:58:27 2018 -0700

----------------------------------------------------------------------
 .../controllers/manageConnectionController.js   | 369 ++++++++-----------
 .../app/manage/templates/manageConnection.html  |  15 +-
 2 files changed, 166 insertions(+), 218 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/00fee4ac/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js
index 2bbd999..7cd08ac 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js
@@ -24,17 +24,18 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
         function manageConnectionController($scope, $injector) {
 
     // Required types
-    var Connection          = $injector.get('Connection');
-    var ConnectionGroup     = $injector.get('ConnectionGroup');
-    var HistoryEntryWrapper = $injector.get('HistoryEntryWrapper');
-    var PermissionSet       = $injector.get('PermissionSet');
+    var Connection            = $injector.get('Connection');
+    var ConnectionGroup       = $injector.get('ConnectionGroup');
+    var HistoryEntryWrapper   = $injector.get('HistoryEntryWrapper');
+    var ManagementPermissions = $injector.get('ManagementPermissions');
+    var PermissionSet         = $injector.get('PermissionSet');
 
     // Required services
     var $location                = $injector.get('$location');
+    var $q                       = $injector.get('$q');
     var $routeParams             = $injector.get('$routeParams');
     var $translate               = $injector.get('$translate');
     var authenticationService    = $injector.get('authenticationService');
-    var guacNotification         = $injector.get('guacNotification');
     var connectionService        = $injector.get('connectionService');
     var connectionGroupService   = $injector.get('connectionGroupService');
     var permissionService        = $injector.get('permissionService');
@@ -108,36 +109,15 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
      * @type HistoryEntryWrapper[]
      */
     $scope.historyEntryWrappers = null;
-    
-    /**
-     * Whether the user can save the connection being edited. This could be
-     * updating an existing connection, or creating a new connection.
-     * 
-     * @type Boolean
-     */
-    $scope.canSaveConnection = null;
-    
-    /**
-     * Whether the user can delete the connection being edited.
-     * 
-     * @type Boolean
-     */
-    $scope.canDeleteConnection = null;
-    
-    /**
-     * Whether the user can clone the connection being edited.
-     * 
-     * @type Boolean
-     */
-    $scope.canCloneConnection = null;
 
     /**
-     * All permissions associated with the current user, or null if the user's
-     * permissions have not yet been loaded.
+     * The managment-related actions that the current user may perform on the
+     * connection currently being created/modified, or null if the current
+     * user's permissions have not yet been loaded.
      *
-     * @type PermissionSet
+     * @type ManagementPermissions
      */
-    $scope.permissions = null;
+    $scope.managementPermissions = null;
 
     /**
      * All available connection attributes. This is only the set of attribute
@@ -157,154 +137,163 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
      */
     $scope.isLoaded = function isLoaded() {
 
-        return $scope.protocols            !== null
-            && $scope.rootGroup            !== null
-            && $scope.connection           !== null
-            && $scope.parameters           !== null
-            && $scope.historyDateFormat    !== null
-            && $scope.historyEntryWrappers !== null
-            && $scope.permissions          !== null
-            && $scope.attributes           !== null
-            && $scope.canSaveConnection    !== null
-            && $scope.canDeleteConnection  !== null
-            && $scope.canCloneConnection   !== null;
+        return $scope.protocols             !== null
+            && $scope.rootGroup             !== null
+            && $scope.connection            !== null
+            && $scope.parameters            !== null
+            && $scope.historyDateFormat     !== null
+            && $scope.historyEntryWrappers  !== null
+            && $scope.managementPermissions !== null
+            && $scope.attributes            !== null;
 
     };
 
-    // Pull connection attribute schema
-    schemaService.getConnectionAttributes($scope.selectedDataSource)
-    .then(function attributesReceived(attributes) {
-        $scope.attributes = attributes;
-    }, requestService.WARN);
-
-    // Pull connection group hierarchy
-    connectionGroupService.getConnectionGroupTree(
-        $scope.selectedDataSource,
-        ConnectionGroup.ROOT_IDENTIFIER,
-        [PermissionSet.ObjectPermissionType.ADMINISTER]
-    )
-    .then(function connectionGroupReceived(rootGroup) {
-        $scope.rootGroup = rootGroup;
-    }, requestService.WARN);
-    
-    // Query the user's permissions for the current connection
-    permissionService.getEffectivePermissions($scope.selectedDataSource, authenticationService.getCurrentUsername())
-    .then(function permissionsReceived(permissions) {
-                
-        $scope.permissions = permissions;
-                        
-        // Check if the connection is new or if the user has UPDATE permission
-        $scope.canSaveConnection =
-               !identifier
-            || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
-            || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, identifier);
-            
-        // Check if connection is not new and the user has DELETE permission
-        $scope.canDeleteConnection =
-            !!identifier && (
-                   PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
-               ||  PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.DELETE, identifier)
-            );
-                
-        // Check if the connection is not new and the user has UPDATE and CREATE_CONNECTION permissions
-        $scope.canCloneConnection =
-            !!identifier && (
-               PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) || (
-                       PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, identifier)
-                   &&  PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION)
-               )
-            );
-    
-    }, requestService.WARN);
-   
-    // Get protocol metadata
-    schemaService.getProtocols($scope.selectedDataSource)
-    .then(function protocolsReceived(protocols) {
-        $scope.protocols = protocols;
-    }, requestService.WARN);
-
-    // Get history date format
-    $translate('MANAGE_CONNECTION.FORMAT_HISTORY_START').then(function historyDateFormatReceived(historyDateFormat) {
-        $scope.historyDateFormat = historyDateFormat;
-    }, angular.noop);
-
-    // If we are editing an existing connection, pull its data
-    if (identifier) {
-
-        // Pull data from existing connection
-        connectionService.getConnection($scope.selectedDataSource, identifier)
-        .then(function connectionRetrieved(connection) {
-            $scope.connection = connection;
-        }, requestService.WARN);
+    /**
+     * Loads the data associated with the connection having the given
+     * identifier, preparing the interface for making modifications to that
+     * existing connection.
+     *
+     * @param {String} dataSource
+     *     The unique identifier of the data source containing the connection to
+     *     load.
+     *
+     * @param {String} identifier
+     *     The identifier of the connection to load.
+     *
+     * @returns {Promise}
+     *     A promise which is resolved when the interface has been prepared for
+     *     editing the given connection.
+     */
+    var loadExistingConnection = function loadExistingConnection(dataSource, identifier) {
+        return $q.all({
+            connection     : connectionService.getConnection(dataSource, identifier),
+            historyEntries : connectionService.getConnectionHistory(dataSource, identifier),
+            parameters     : connectionService.getConnectionParameters(dataSource, identifier)
+        })
+        .then(function connectionDataRetrieved(values) {
 
-        // Pull connection history
-        connectionService.getConnectionHistory($scope.selectedDataSource, identifier)
-        .then(function historyReceived(historyEntries) {
+            $scope.connection = values.connection;
+            $scope.parameters = values.parameters;
 
             // Wrap all history entries for sake of display
             $scope.historyEntryWrappers = [];
-            historyEntries.forEach(function wrapHistoryEntry(historyEntry) {
-               $scope.historyEntryWrappers.push(new HistoryEntryWrapper(historyEntry)); 
+            angular.forEach(values.historyEntries, function wrapHistoryEntry(historyEntry) {
+               $scope.historyEntryWrappers.push(new HistoryEntryWrapper(historyEntry));
             });
 
-        }, requestService.WARN);
+        });
+    };
+
+    /**
+     * Loads the data associated with the connection having the given
+     * identifier, preparing the interface for cloning that existing
+     * connection.
+     *
+     * @param {String} dataSource
+     *     The unique identifier of the data source containing the connection
+     *     to be cloned.
+     *
+     * @param {String} identifier
+     *     The identifier of the connection being cloned.
+     *
+     * @returns {Promise}
+     *     A promise which is resolved when the interface has been prepared for
+     *     cloning the given connection.
+     */
+    var loadClonedConnection = function loadClonedConnection(dataSource, identifier) {
+        return $q.all({
+            connection : connectionService.getConnection(dataSource, identifier),
+            parameters : connectionService.getConnectionParameters(dataSource, identifier)
+        })
+        .then(function connectionDataRetrieved(values) {
 
-        // Pull connection parameters
-        connectionService.getConnectionParameters($scope.selectedDataSource, identifier)
-        .then(function parametersReceived(parameters) {
-            $scope.parameters = parameters;
-        }, requestService.WARN);
-    }
-    
-    // If we are cloning an existing connection, pull its data instead
-    else if (cloneSourceIdentifier) {
+            $scope.connection = values.connection;
+            $scope.parameters = values.parameters;
 
-        // Pull data from cloned connection
-        connectionService.getConnection($scope.selectedDataSource, cloneSourceIdentifier)
-        .then(function connectionRetrieved(connection) {
-            $scope.connection = connection;
-            
             // Clear the identifier field because this connection is new
             delete $scope.connection.identifier;
-        }, requestService.WARN);
 
-        // Do not pull connection history
-        $scope.historyEntryWrappers = [];
-        
-        // Pull connection parameters from cloned connection
-        connectionService.getConnectionParameters($scope.selectedDataSource, cloneSourceIdentifier)
-        .then(function parametersReceived(parameters) {
-            $scope.parameters = parameters;
-        }, requestService.WARN);
-    }
-
-    // If we are creating a new connection, populate skeleton connection data
-    else {
+            // Cloned connections have no history
+            $scope.historyEntryWrappers = [];
+
+        });
+    };
+
+    /**
+     * Loads skeleton connection data, preparing the interface for creating a
+     * new connection.
+     *
+     * @returns {Promise}
+     *     A promise which is resolved when the interface has been prepared for
+     *     creating a new connection.
+     */
+    var loadSkeletonConnection = function loadSkeletonConnection() {
+
+        // Use skeleton connection object with no associated permissions,
+        // history, or parameters
         $scope.connection = new Connection({
             protocol         : 'vnc',
             parentIdentifier : $location.search().parent
         });
         $scope.historyEntryWrappers = [];
         $scope.parameters = {};
-    }
+
+        return $q.resolve();
+
+    };
 
     /**
-     * Returns whether the current user can change/set all connection
-     * attributes for the connection being edited, regardless of whether those
-     * attributes are already explicitly associated with that connection.
+     * Loads the data requred for performing the management task requested
+     * through the route parameters given at load time, automatically preparing
+     * the interface for editing an existing connection, cloning an existing
+     * connection, or creating an entirely new connection.
      *
-     * @returns {Boolean}
-     *     true if the current user can change all attributes for the
-     *     connection being edited, regardless of whether those attributes are
-     *     already explicitly associated with that connection, false otherwise.
+     * @returns {Promise}
+     *     A promise which is resolved when the interface has been prepared
+     *     for performing the requested management task.
      */
-    $scope.canChangeAllAttributes = function canChangeAllAttributes() {
+    var loadRequestedConnection = function loadRequestedConnection() {
+
+        // If we are editing an existing connection, pull its data
+        if (identifier)
+            return loadExistingConnection($scope.selectedDataSource, identifier);
 
-        // All attributes can be set if we are creating the connection
-        return !identifier;
+        // If we are cloning an existing connection, pull its data instead
+        if (cloneSourceIdentifier)
+            return loadClonedConnection($scope.selectedDataSource, cloneSourceIdentifier);
+
+        // If we are creating a new connection, populate skeleton connection data
+        return loadSkeletonConnection();
 
     };
 
+    // Populate interface with requested data
+    $q.all({
+        connectionData : loadRequestedConnection(),
+        attributes  : schemaService.getConnectionAttributes($scope.selectedDataSource),
+        permissions : permissionService.getEffectivePermissions($scope.selectedDataSource, authenticationService.getCurrentUsername()),
+        protocols   : schemaService.getProtocols($scope.selectedDataSource),
+        rootGroup   : connectionGroupService.getConnectionGroupTree($scope.selectedDataSource, ConnectionGroup.ROOT_IDENTIFIER, [PermissionSet.ObjectPermissionType.ADMINISTER])
+    })
+    .then(function dataRetrieved(values) {
+
+        $scope.attributes = values.attributes;
+        $scope.protocols = values.protocols;
+        $scope.rootGroup = values.rootGroup;
+
+        $scope.managementPermissions = ManagementPermissions.fromPermissionSet(
+                    values.permissions,
+                    PermissionSet.SystemPermissionType.CREATE_CONNECTION,
+                    PermissionSet.hasConnectionPermission,
+                    identifier);
+
+    }, requestService.WARN);
+    
+    // Get history date format
+    $translate('MANAGE_CONNECTION.FORMAT_HISTORY_START').then(function historyDateFormatReceived(historyDateFormat) {
+        $scope.historyDateFormat = historyDateFormat;
+    }, angular.noop);
+
     /**
      * Returns the translation string namespace for the protocol having the
      * given name. The namespace will be of the form:
@@ -353,9 +342,10 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
     };
 
     /**
-     * Cancels all pending edits, returning to the management page.
+     * Cancels all pending edits, returning to the main list of connections
+     * within the selected data source.
      */
-    $scope.cancel = function cancel() {
+    $scope.returnToConnectionList = function returnToConnectionList() {
         $location.url('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
     };
     
@@ -368,76 +358,33 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
     };
             
     /**
-     * Saves the connection, creating a new connection or updating the existing
-     * connection.
+     * Saves the current connection, creating a new connection or updating the
+     * existing connection, 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.saveConnection = function saveConnection() {
 
         $scope.connection.parameters = $scope.parameters;
 
         // Save the connection
-        connectionService.saveConnection($scope.selectedDataSource, $scope.connection)
-        .then(function savedConnection() {
-            $location.url('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
-        }, guacNotification.SHOW_REQUEST_ERROR);
+        return connectionService.saveConnection($scope.selectedDataSource, $scope.connection);
 
     };
-    
-    /**
-     * An action to be provided along with the object sent to showStatus which
-     * immediately deletes the current connection.
-     */
-    var DELETE_ACTION = {
-        name        : "MANAGE_CONNECTION.ACTION_DELETE",
-        className   : "danger",
-        // Handle action
-        callback    : function deleteCallback() {
-            deleteConnectionImmediately();
-            guacNotification.showStatus(false);
-        }
-    };
-
-    /**
-     * An action to be provided along with the object sent to showStatus which
-     * closes the currently-shown status dialog.
-     */
-    var CANCEL_ACTION = {
-        name        : "MANAGE_CONNECTION.ACTION_CANCEL",
-        // Handle action
-        callback    : function cancelCallback() {
-            guacNotification.showStatus(false);
-        }
-    };
 
     /**
-     * Immediately deletes the current connection, without prompting the user
-     * for confirmation.
-     */
-    var deleteConnectionImmediately = function deleteConnectionImmediately() {
-
-        // Delete the connection
-        connectionService.deleteConnection($scope.selectedDataSource, $scope.connection)
-        .then(function deletedConnection() {
-            $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections');
-        }, guacNotification.SHOW_REQUEST_ERROR);
-
-    };
-
-    /**
-     * Deletes the connection, prompting the user first to confirm that
-     * deletion is desired.
+     * Deletes the current connection, 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.deleteConnection = function deleteConnection() {
-
-        // Confirm deletion request
-        guacNotification.showStatus({
-            'title'      : 'MANAGE_CONNECTION.DIALOG_HEADER_CONFIRM_DELETE',
-            'text'       : {
-                key : 'MANAGE_CONNECTION.TEXT_CONFIRM_DELETE'
-            },
-            'actions'    : [ DELETE_ACTION, CANCEL_ACTION]
-        });
-
+        return connectionService.deleteConnection($scope.selectedDataSource, $scope.connection);
     };
 
 }]);

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/00fee4ac/guacamole/src/main/webapp/app/manage/templates/manageConnection.html
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageConnection.html b/guacamole/src/main/webapp/app/manage/templates/manageConnection.html
index ed10335..e780513 100644
--- a/guacamole/src/main/webapp/app/manage/templates/manageConnection.html
+++ b/guacamole/src/main/webapp/app/manage/templates/manageConnection.html
@@ -41,7 +41,7 @@
     <!-- Connection attributes section -->
     <div class="attributes">
         <guac-form namespace="'CONNECTION_ATTRIBUTES'" content="attributes"
-                   model="connection.attributes" model-only="!canChangeAllAttributes()"></guac-form>
+                   model="connection.attributes" model-only="!managementPermissions.canChangeAllAttributes"></guac-form>
     </div>
 
     <!-- Connection parameters -->
@@ -53,12 +53,13 @@
     </div>
 
     <!-- Form action buttons -->
-    <div class="action-buttons">
-        <button ng-show="canSaveConnection" ng-click="saveConnection()">{{'MANAGE_CONNECTION.ACTION_SAVE' | translate}}</button>
-        <button ng-show="canCloneConnection" ng-click="cloneConnection()">{{'MANAGE_CONNECTION.ACTION_CLONE' | translate}}</button>
-        <button ng-click="cancel()">{{'MANAGE_CONNECTION.ACTION_CANCEL' | translate}}</button>
-        <button ng-show="canDeleteConnection" ng-click="deleteConnection()" class="danger">{{'MANAGE_CONNECTION.ACTION_DELETE' | translate}}</button>
-    </div>
+    <management-buttons namespace="'MANAGE_CONNECTION'"
+          permissions="managementPermissions"
+          save="saveConnection()"
+          delete="deleteConnection()"
+          clone="cloneConnection()"
+          return="returnToConnectionList()">
+    </management-buttons>
 
     <!-- Connection history -->
     <h2 class="header">{{'MANAGE_CONNECTION.SECTION_HEADER_HISTORY' | translate}}</h2>


[19/21] guacamole-client git commit: GUACAMOLE-220: Convert "namespace" attribute of managementButtons directive to string binding.

Posted by vn...@apache.org.
GUACAMOLE-220: Convert "namespace" attribute of managementButtons directive to string binding.

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/ae0512c2
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/ae0512c2
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/ae0512c2

Branch: refs/heads/master
Commit: ae0512c2667b261405016a7d164239de8e96610c
Parents: 2c41e38
Author: Michael Jumper <mj...@apache.org>
Authored: Thu May 3 23:37:19 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Thu May 3 23:37:19 2018 -0700

----------------------------------------------------------------------
 .../src/main/webapp/app/manage/directives/managementButtons.js     | 2 +-
 .../src/main/webapp/app/manage/templates/manageConnection.html     | 2 +-
 .../main/webapp/app/manage/templates/manageConnectionGroup.html    | 2 +-
 .../src/main/webapp/app/manage/templates/manageSharingProfile.html | 2 +-
 guacamole/src/main/webapp/app/manage/templates/manageUser.html     | 2 +-
 5 files changed, 5 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/ae0512c2/guacamole/src/main/webapp/app/manage/directives/managementButtons.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/directives/managementButtons.js b/guacamole/src/main/webapp/app/manage/directives/managementButtons.js
index 4074548..a83b82c 100644
--- a/guacamole/src/main/webapp/app/manage/directives/managementButtons.js
+++ b/guacamole/src/main/webapp/app/manage/directives/managementButtons.js
@@ -50,7 +50,7 @@ angular.module('manage').directive('managementButtons', ['$injector',
              *
              * @type String
              */
-            namespace : '=',
+            namespace : '@',
 
             /**
              * The permissions which dictate the management actions available

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/ae0512c2/guacamole/src/main/webapp/app/manage/templates/manageConnection.html
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageConnection.html b/guacamole/src/main/webapp/app/manage/templates/manageConnection.html
index e780513..17a1108 100644
--- a/guacamole/src/main/webapp/app/manage/templates/manageConnection.html
+++ b/guacamole/src/main/webapp/app/manage/templates/manageConnection.html
@@ -53,7 +53,7 @@
     </div>
 
     <!-- Form action buttons -->
-    <management-buttons namespace="'MANAGE_CONNECTION'"
+    <management-buttons namespace="MANAGE_CONNECTION"
           permissions="managementPermissions"
           save="saveConnection()"
           delete="deleteConnection()"

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/ae0512c2/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html b/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html
index 926dc11..0f12180 100644
--- a/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html
+++ b/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html
@@ -45,7 +45,7 @@
     </div>
 
     <!-- Form action buttons -->
-    <management-buttons namespace="'MANAGE_CONNECTION_GROUP'"
+    <management-buttons namespace="MANAGE_CONNECTION_GROUP"
           permissions="managementPermissions"
           save="saveConnectionGroup()"
           delete="deleteConnectionGroup()"

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/ae0512c2/guacamole/src/main/webapp/app/manage/templates/manageSharingProfile.html
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageSharingProfile.html b/guacamole/src/main/webapp/app/manage/templates/manageSharingProfile.html
index ac52fa3..d242e59 100644
--- a/guacamole/src/main/webapp/app/manage/templates/manageSharingProfile.html
+++ b/guacamole/src/main/webapp/app/manage/templates/manageSharingProfile.html
@@ -34,7 +34,7 @@
     </div>
 
     <!-- Form action buttons -->
-    <management-buttons namespace="'MANAGE_SHARING_PROFILE'"
+    <management-buttons namespace="MANAGE_SHARING_PROFILE"
           permissions="managementPermissions"
           save="saveSharingProfile()"
           delete="deleteSharingProfile()"

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/ae0512c2/guacamole/src/main/webapp/app/manage/templates/manageUser.html
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUser.html b/guacamole/src/main/webapp/app/manage/templates/manageUser.html
index 6770d31..660554b 100644
--- a/guacamole/src/main/webapp/app/manage/templates/manageUser.html
+++ b/guacamole/src/main/webapp/app/manage/templates/manageUser.html
@@ -65,7 +65,7 @@
         </connection-permission-editor>
 
         <!-- Form action buttons -->
-        <management-buttons namespace="'MANAGE_USER'"
+        <management-buttons namespace="MANAGE_USER"
               permissions="managementPermissions[dataSource]"
               save="saveUser()"
               delete="deleteUser()"


[17/21] guacamole-client git commit: GUACAMOLE-220: Correct documentation of permissionsRemoved attribute (the permissions have been removed, not added).

Posted by vn...@apache.org.
GUACAMOLE-220: Correct documentation of permissionsRemoved attribute (the permissions have been removed, not added).

Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/e8f0c967
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/e8f0c967
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/e8f0c967

Branch: refs/heads/master
Commit: e8f0c967585d9d576e1c07dbd6528ce6a8ce4beb
Parents: 7ba9c32
Author: Michael Jumper <mj...@apache.org>
Authored: Thu May 3 23:33:41 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Thu May 3 23:33:41 2018 -0700

----------------------------------------------------------------------
 .../webapp/app/manage/directives/connectionPermissionEditor.js     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/e8f0c967/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js b/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js
index c4be071..da85c11 100644
--- a/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js
+++ b/guacamole/src/main/webapp/app/manage/directives/connectionPermissionEditor.js
@@ -69,7 +69,7 @@ angular.module('manage').directive('connectionPermissionEditor', ['$injector',
             permissionsAdded : '=',
 
             /**
-             * The set of permissions that have been added, relative to the
+             * The set of permissions that have been removed, relative to the
              * initial state of the permissions being manipulated.
              *
              * @type PermissionSet


[03/21] guacamole-client git commit: GUACAMOLE-220: Migrate user management controller to ManagementPermissions.

Posted by vn...@apache.org.
GUACAMOLE-220: Migrate user management controller to ManagementPermissions.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/4f43ddc4
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/4f43ddc4
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/4f43ddc4

Branch: refs/heads/master
Commit: 4f43ddc4203a59c7aea6834cfd8b5961c74ba57e
Parents: 507202d
Author: Michael Jumper <mj...@apache.org>
Authored: Tue May 1 00:03:52 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Tue May 1 00:12:58 2018 -0700

----------------------------------------------------------------------
 .../manage/controllers/manageUserController.js  | 464 ++++++-------------
 .../webapp/app/manage/templates/manageUser.html |  18 +-
 2 files changed, 153 insertions(+), 329 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/4f43ddc4/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
index c385065..388e717 100644
--- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
+++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js
@@ -24,14 +24,16 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
         function manageUserController($scope, $injector) {
             
     // Required types
-    var PageDefinition    = $injector.get('PageDefinition');
-    var PermissionFlagSet = $injector.get('PermissionFlagSet');
-    var PermissionSet     = $injector.get('PermissionSet');
-    var User              = $injector.get('User');
+    var ManagementPermissions = $injector.get('ManagementPermissions');
+    var PageDefinition        = $injector.get('PageDefinition');
+    var PermissionFlagSet     = $injector.get('PermissionFlagSet');
+    var PermissionSet         = $injector.get('PermissionSet');
+    var User                  = $injector.get('User');
 
     // 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 guacNotification         = $injector.get('guacNotification');
@@ -128,13 +130,31 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     $scope.permissionFlags = null;
 
     /**
-     * A map of data source identifiers to the set of all permissions
-     * associated with the current user under that data source, or null if the
-     * user's permissions have not yet been loaded.
+     * The set of permissions that will be added to the user when the user is
+     * saved. Permissions will only be present in this set if they are
+     * manually added, and not later manually removed before saving.
      *
-     * @type Object.<String, PermissionSet>
+     * @type PermissionSet
      */
-    $scope.permissions = null;
+    $scope.permissionsAdded = new PermissionSet();
+
+    /**
+     * The set of permissions that will be removed from the user when the user
+     * 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 managment-related actions that the current user may perform on the
+     * user currently being created/modified, or null if the current user's
+     * permissions have not yet been loaded.
+     *
+     * @type ManagementPermissions
+     */
+    $scope.managementPermissions = null;
 
     /**
      * All available user attributes. This is only the set of attribute
@@ -162,11 +182,10 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
      */
     $scope.isLoaded = function isLoaded() {
 
-        return $scope.users               !== null
-            && $scope.permissionFlags     !== null
-            && $scope.rootGroups          !== null
-            && $scope.permissions         !== null
-            && $scope.attributes          !== null;
+        return $scope.users                 !== null
+            && $scope.permissionFlags       !== null
+            && $scope.managementPermissions !== null
+            && $scope.attributes            !== null;
 
     };
 
@@ -196,97 +215,6 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     };
 
     /**
-     * Returns whether the current user can change attributes explicitly
-     * associated with the user being edited within the given data source.
-     *
-     * @param {String} [dataSource]
-     *     The identifier of the data source to check. If omitted, this will
-     *     default to the currently-selected data source.
-     *
-     * @returns {Boolean}
-     *     true if the current user can change attributes associated with the
-     *     user being edited, false otherwise.
-     */
-    $scope.canChangeAttributes = function canChangeAttributes(dataSource) {
-
-        // Do not check if permissions are not yet loaded
-        if (!$scope.permissions)
-            return false;
-
-        // Use currently-selected data source if unspecified
-        dataSource = dataSource || $scope.dataSource;
-
-        // Attributes can always be set if we are creating the user
-        if (!$scope.userExists(dataSource))
-            return true;
-
-        // The administrator can always change attributes
-        if (PermissionSet.hasSystemPermission($scope.permissions[dataSource],
-                PermissionSet.SystemPermissionType.ADMINISTER))
-            return true;
-
-        // Otherwise, can change attributes if we have permission to update this user
-        return PermissionSet.hasUserPermission($scope.permissions[dataSource],
-            PermissionSet.ObjectPermissionType.UPDATE, username);
-
-    };
-
-    /**
-     * Returns whether the current user can change/set all user attributes for
-     * the user being edited, regardless of whether those attributes are
-     * already explicitly associated with that user.
-     *
-     * @returns {Boolean}
-     *     true if the current user can change all attributes for the user
-     *     being edited, regardless of whether those attributes are already
-     *     explicitly associated with that user, false otherwise.
-     */
-    $scope.canChangeAllAttributes = function canChangeAllAttributes() {
-
-        // All attributes can be set if we are creating the user
-        return !$scope.userExists($scope.dataSource);
-
-    };
-
-    /**
-     * Returns whether the current user can change permissions of any kind
-     * which are associated with the user being edited within the given data
-     * source.
-     *
-     * @param {String} [dataSource]
-     *     The identifier of the data source to check. If omitted, this will
-     *     default to the currently-selected data source.
-     *
-     * @returns {Boolean}
-     *     true if the current user can grant or revoke permissions of any kind
-     *     which are associated with the user being edited, false otherwise.
-     */
-    $scope.canChangePermissions = function canChangePermissions(dataSource) {
-
-        // Do not check if permissions are not yet loaded
-        if (!$scope.permissions)
-            return false;
-
-        // Use currently-selected data source if unspecified
-        dataSource = dataSource || $scope.dataSource;
-
-        // Permissions can always be set if we are creating the user
-        if (!$scope.userExists(dataSource))
-            return true;
-
-        // The administrator can always modify permissions
-        if (PermissionSet.hasSystemPermission($scope.permissions[dataSource],
-                PermissionSet.SystemPermissionType.ADMINISTER))
-            return true;
-
-        // Otherwise, can only modify permissions if we have explicit
-        // ADMINISTER permission
-        return PermissionSet.hasUserPermission($scope.permissions[dataSource],
-            PermissionSet.ObjectPermissionType.ADMINISTER, username);
-
-    };
-
-    /**
      * Returns whether the current user can edit the username of the user being
      * edited within the given data source.
      *
@@ -303,151 +231,158 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
     };
 
     /**
-     * Returns whether the current user can save the user being edited within
-     * the given data source. Saving will create or update that user depending
-     * on whether the user already exists.
+     * Loads the data associated with the user having the given username,
+     * preparing the interface for making modifications to that existing user.
      *
-     * @param {String} [dataSource]
-     *     The identifier of the data source to check. If omitted, this will
-     *     default to the currently-selected data source.
+     * @param {String} dataSource
+     *     The unique identifier of the data source containing the user to
+     *     load.
      *
-     * @returns {Boolean}
-     *     true if the current user can save changes to the user being edited,
-     *     false otherwise.
-     */
-    $scope.canSaveUser = function canSaveUser(dataSource) {
-
-        // Do not check if permissions are not yet loaded
-        if (!$scope.permissions)
-            return false;
-
-        // Use currently-selected data source if unspecified
-        dataSource = dataSource || $scope.dataSource;
+     * @param {String} username
+     *     The username of the user to load.
+     *
+     * @returns {Promise}
+     *     A promise which is resolved when the interface has been prepared for
+     *     editing the given user.
+     */
+    var loadExistingUser = function loadExistingUser(dataSource, username) {
+        return $q.all({
+            users : dataSourceService.apply(userService.getUser, dataSources, username),
+            permissions : permissionService.getPermissions(dataSource, username)
+        })
+        .then(function userDataRetrieved(values) {
 
-        // The administrator can always save users
-        if (PermissionSet.hasSystemPermission($scope.permissions[dataSource],
-                PermissionSet.SystemPermissionType.ADMINISTER))
-            return true;
+            $scope.users = values.users;
+            $scope.user  = values.users[dataSource];
 
-        // If user does not exist, can only save if we have permission to create users
-        if (!$scope.userExists(dataSource))
-           return PermissionSet.hasSystemPermission($scope.permissions[dataSource],
-               PermissionSet.SystemPermissionType.CREATE_USER);
+            // Create skeleton user if user does not exist
+            if (!$scope.user)
+                $scope.user = new User({
+                    'username' : username
+                });
 
-        // Otherwise, can only save if we have permission to update this user
-        return PermissionSet.hasUserPermission($scope.permissions[dataSource],
-            PermissionSet.ObjectPermissionType.UPDATE, username);
+            // The current user will be associated with username of the existing
+            // user in the retrieved permission set
+            $scope.selfUsername = username;
+            $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(values.permissions);
 
+        });
     };
 
     /**
-     * Returns whether the current user can clone the user being edited within
-     * the given data source.
+     * Loads the data associated with the user having the given username,
+     * preparing the interface for cloning that existing user.
      *
-     * @param {String} [dataSource]
-     *     The identifier of the data source to check. If omitted, this will
-     *     default to the currently-selected data source.
+     * @param {String} dataSource
+     *     The unique identifier of the data source containing the user to
+     *     be cloned.
      *
-     * @returns {Boolean}
-     *     true if the current user can clone the user being edited, false
-     *     otherwise.
-     */
-    $scope.canCloneUser = function canCloneUser(dataSource) {
-
-        // Do not check if permissions are not yet loaded
-        if (!$scope.permissions)
-            return false;
-
-        // Use currently-selected data source if unspecified
-        dataSource = dataSource || $scope.dataSource;
-
-        // If we are not editing an existing user, we cannot clone
-        if (!$scope.userExists($scope.dataSource))
-            return false;
+     * @param {String} username
+     *     The username of the user being cloned.
+     *
+     * @returns {Promise}
+     *     A promise which is resolved when the interface has been prepared for
+     *     cloning the given user.
+     */
+    var loadClonedUser = function loadClonedUser(dataSource, username) {
+        return $q.all({
+            users : dataSourceService.apply(userService.getUser, [dataSource], username),
+            permissions : permissionService.getPermissions(dataSource, username)
+        })
+        .then(function userDataRetrieved(values) {
 
-        // The administrator can always clone users
-        if (PermissionSet.hasSystemPermission($scope.permissions[dataSource],
-                PermissionSet.SystemPermissionType.ADMINISTER))
-            return true;
+            $scope.users = {};
+            $scope.user  = values.users[dataSource];
 
-        // Otherwise we need explicit CREATE_USER permission
-        return PermissionSet.hasSystemPermission($scope.permissions[dataSource],
-            PermissionSet.SystemPermissionType.CREATE_USER);
+            // The current user will be associated with cloneSourceUsername in the
+            // retrieved permission set
+            $scope.selfUsername = username;
+            $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(values.permissions);
+            $scope.permissionsAdded = values.permissions;
 
+        });
     };
 
     /**
-     * Returns whether the current user can delete the user being edited from
-     * the given data source.
-     *
-     * @param {String} [dataSource]
-     *     The identifier of the data source to check. If omitted, this will
-     *     default to the currently-selected data source.
+     * Loads skeleton user data, preparing the interface for creating a new
+     * user.
      *
-     * @returns {Boolean}
-     *     true if the current user can delete the user being edited, false
-     *     otherwise.
+     * @returns {Promise}
+     *     A promise which is resolved when the interface has been prepared for
+     *     creating a new user.
      */
-    $scope.canDeleteUser = function canDeleteUser(dataSource) {
-
-        // Do not check if permissions are not yet loaded
-        if (!$scope.permissions)
-            return false;
+    var loadSkeletonUser = function loadSkeletonUser() {
 
-        // Use currently-selected data source if unspecified
-        dataSource = dataSource || $scope.dataSource;
+        // No users exist regardless of data source if there is no username
+        $scope.users = {};
 
-        // Can't delete what doesn't exist
-        if (!$scope.userExists(dataSource))
-            return false;
+        // Use skeleton user object with no associated permissions
+        $scope.user = new User();
+        $scope.permissionFlags = new PermissionFlagSet();
 
-        // The administrator can always delete users
-        if (PermissionSet.hasSystemPermission($scope.permissions[dataSource],
-                PermissionSet.SystemPermissionType.ADMINISTER))
-            return true;
+        // As no permissions are yet associated with the user, it is safe to
+        // use any non-empty username as a placeholder for self-referential
+        // permissions
+        $scope.selfUsername = 'SELF';
 
-        // Otherwise, require explicit DELETE permission on the user
-        return PermissionSet.hasUserPermission($scope.permissions[dataSource],
-            PermissionSet.ObjectPermissionType.DELETE, username);
+        return $q.resolve();
 
     };
 
     /**
-     * Returns whether the user being edited within the given data source is
-     * read-only, and thus cannot be modified by the current user.
-     *
-     * @param {String} [dataSource]
-     *     The identifier of the data source to check. If omitted, this will
-     *     default to the currently-selected data source.
+     * Loads the data requred for performing the management task requested
+     * through the route parameters given at load time, automatically preparing
+     * the interface for editing an existing user, cloning an existing user, or
+     * creating an entirely new user.
      *
-     * @returns {Boolean}
-     *     true if the user being edited is actually read-only and cannot be
-     *     edited at all, false otherwise.
+     * @returns {Promise}
+     *     A promise which is resolved when the interface has been prepared
+     *     for performing the requested management task.
      */
-    $scope.isReadOnly = function isReadOnly(dataSource) {
+    var loadRequestedUser = function loadRequestedUser() {
 
-        // Use currently-selected data source if unspecified
-        dataSource = dataSource || $scope.dataSource;
+        // Pull user data and permissions if we are editing an existing user
+        if (username)
+            return loadExistingUser($scope.dataSource, username);
+
+        // If we are cloning an existing user, pull his/her data instead
+        if (cloneSourceUsername)
+            return loadClonedUser($scope.dataSource, cloneSourceUsername);
 
-        // User is read-only if they cannot be saved
-        return !$scope.canSaveUser(dataSource);
+        return loadSkeletonUser();
 
     };
 
-    // Update visible account pages whenever available users/permissions changes
-    $scope.$watchGroup(['users', 'permissions'], function updateAccountPages() {
+    // Populate interface with requested data
+    $q.all({
+        userData    : loadRequestedUser(),
+        permissions : dataSourceService.apply(permissionService.getEffectivePermissions, dataSources, currentUsername),
+        attributes  : schemaService.getUserAttributes($scope.dataSource)
+    })
+    .then(function dataReceived(values) {
+
+        var managementPermissions = {};
+
+        $scope.attributes = values.attributes;
 
         // Generate pages for each applicable data source
         $scope.accountPages = [];
         angular.forEach(dataSources, function addAccountPage(dataSource) {
 
             // Determine whether data source contains this user
-            var linked   = $scope.userExists(dataSource);
-            var readOnly = $scope.isReadOnly(dataSource);
+            var exists = (dataSource in $scope.users);
+
+            // Calculate management actions available for this specific account
+            managementPermissions[dataSource] = ManagementPermissions.fromPermissionSet(
+                    values.permissions[dataSource],
+                    PermissionSet.SystemPermissionType.CREATE_USER,
+                    PermissionSet.hasUserPermission,
+                    exists ? username : null);
 
             // Account is not relevant if it does not exist and cannot be
             // created
-            if (!linked && readOnly)
+            var readOnly = !managementPermissions[dataSource].canSaveObject;
+            if (!exists && readOnly)
                 return;
 
             // Only the selected data source is relevant when cloning
@@ -457,7 +392,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
             // Determine class name based on read-only / linked status
             var className;
             if (readOnly)    className = 'read-only';
-            else if (linked) className = 'linked';
+            else if (exists) className = 'linked';
             else             className = 'unlinked';
 
             // Add page entry
@@ -469,121 +404,10 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
 
         });
 
-    });
+        $scope.managementPermissions = managementPermissions[$scope.dataSource];
 
-    // Pull user attribute schema
-    schemaService.getUserAttributes($scope.dataSource).then(function attributesReceived(attributes) {
-        $scope.attributes = attributes;
     }, requestService.WARN);
 
-    // Pull user data and permissions if we are editing an existing user
-    if (username) {
-
-        // Pull user data
-        dataSourceService.apply(userService.getUser, dataSources, username)
-        .then(function usersReceived(users) {
-
-            // Get user for currently-selected data source
-            $scope.users = users;
-            $scope.user  = users[$scope.dataSource];
-
-            // Create skeleton user if user does not exist
-            if (!$scope.user)
-                $scope.user = new User({
-                    'username' : username
-                });
-
-        }, requestService.WARN);
-
-        // The current user will be associated with username of the existing
-        // user in the retrieved permission set
-        $scope.selfUsername = username;
-
-        // Pull user permissions
-        permissionService.getPermissions($scope.dataSource, username).then(function gotPermissions(permissions) {
-            $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(permissions);
-        })
-
-        // If permissions cannot be retrieved, use empty permissions
-        ['catch'](requestService.createErrorCallback(function permissionRetrievalFailed() {
-            $scope.permissionFlags = new PermissionFlagSet();
-        }));
-    }
-
-    // If we are cloning an existing user, pull his/her data instead
-    else if (cloneSourceUsername) {
-
-        dataSourceService.apply(userService.getUser, dataSources, cloneSourceUsername)
-        .then(function usersReceived(users) {
-
-            // Get user for currently-selected data source
-            $scope.users = {};
-            $scope.user  = users[$scope.dataSource];
-
-        }, requestService.WARN);
-
-        // The current user will be associated with cloneSourceUsername in the
-        // retrieved permission set
-        $scope.selfUsername = cloneSourceUsername;
-
-        // Pull user permissions
-        permissionService.getPermissions($scope.dataSource, cloneSourceUsername)
-        .then(function gotPermissions(permissions) {
-            $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(permissions);
-            $scope.permissionsAdded = permissions;
-        })
-
-        // If permissions cannot be retrieved, use empty permissions
-        ['catch'](requestService.createErrorCallback(function permissionRetrievalFailed() {
-            $scope.permissionFlags = new PermissionFlagSet();
-        }));
-    }
-
-    // Use skeleton data if we are creating a new user
-    else {
-
-        // No users exist regardless of data source if there is no username
-        $scope.users = {};
-
-        // Use skeleton user object with no associated permissions
-        $scope.user = new User();
-        $scope.permissionFlags = new PermissionFlagSet();
-
-        // As no permissions are yet associated with the user, it is safe to
-        // use any non-empty username as a placeholder for self-referential
-        // permissions
-        $scope.selfUsername = 'SELF';
-
-    }
-
-    // Query the user's permissions for the current user
-    dataSourceService.apply(
-        permissionService.getEffectivePermissions,
-        dataSources,
-        currentUsername
-    )
-    .then(function permissionsReceived(permissions) {
-        $scope.permissions = permissions;
-    }, requestService.WARN);
-
-    /**
-     * The set of permissions that will be added to the user when the user 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 when the user 
-     * 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();
-
     /**
      * Cancels all pending edits, returning to the management page.
      */

http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/4f43ddc4/guacamole/src/main/webapp/app/manage/templates/manageUser.html
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUser.html b/guacamole/src/main/webapp/app/manage/templates/manageUser.html
index 5fed148..24db74e 100644
--- a/guacamole/src/main/webapp/app/manage/templates/manageUser.html
+++ b/guacamole/src/main/webapp/app/manage/templates/manageUser.html
@@ -11,12 +11,12 @@
     </div>
 
     <!-- Warn if user is read-only -->
-    <div class="section" ng-show="isReadOnly()">
+    <div class="section" ng-hide="managementPermissions.canSaveObject">
         <p class="notice read-only">{{'MANAGE_USER.INFO_READ_ONLY' | translate}}</p>
     </div>
 
     <!-- Sections applicable to non-read-only users -->
-    <div ng-show="!isReadOnly()">
+    <div ng-show="managementPermissions.canSaveObject">
 
         <!-- User password section -->
         <div class="section">
@@ -40,13 +40,13 @@
         </div>
 
         <!-- User attributes section -->
-        <div class="attributes" ng-show="canChangeAttributes()">
+        <div class="attributes" ng-show="managementPermissions.canChangeAttributes">
             <guac-form namespace="'USER_ATTRIBUTES'" content="attributes"
-                       model="user.attributes" model-only="!canChangeAllAttributes()"></guac-form>
+                       model="user.attributes" model-only="!managementPermissions.canChangeAllAttributes"></guac-form>
         </div>
 
         <!-- System permissions section -->
-        <system-permission-editor ng-show="canChangePermissions()"
+        <system-permission-editor ng-show="managementPermissions.canChangePermissions"
               username="selfUsername"
               data-data-source="dataSource"
               permission-flags="permissionFlags"
@@ -55,7 +55,7 @@
         </system-permission-editor>
 
         <!-- Connection permissions section -->
-        <connection-permission-editor ng-show="canChangePermissions()"
+        <connection-permission-editor ng-show="managementPermissions.canChangePermissions"
               data-data-source="dataSource"
               permission-flags="permissionFlags"
               permissions-added="permissionsAdded"
@@ -64,10 +64,10 @@
 
         <!-- Form action buttons -->
         <div class="action-buttons">
-            <button ng-show="canSaveUser()" ng-click="saveUser()">{{'MANAGE_USER.ACTION_SAVE' | translate}}</button>
-            <button ng-show="canCloneUser()" ng-click="cloneUser()">{{'MANAGE_USER.ACTION_CLONE' | translate}}</button>
+            <button ng-show="managementPermissions.canSaveObject" ng-click="saveUser()">{{'MANAGE_USER.ACTION_SAVE' | translate}}</button>
+            <button ng-show="managementPermissions.canCloneObject" ng-click="cloneUser()">{{'MANAGE_USER.ACTION_CLONE' | translate}}</button>
             <button ng-click="cancel()">{{'MANAGE_USER.ACTION_CANCEL' | translate}}</button>
-            <button ng-show="canDeleteUser()" ng-click="deleteUser()" class="danger">{{'MANAGE_USER.ACTION_DELETE' | translate}}</button>
+            <button ng-show="managementPermissions.canDeleteObject" ng-click="deleteUser()" class="danger">{{'MANAGE_USER.ACTION_DELETE' | translate}}</button>
         </div>
 
     </div>