diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/ObjectRetrievalService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/ObjectRetrievalService.java new file mode 100644 index 000000000..27cb6d9ef --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/ObjectRetrievalService.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest; + +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.GuacamoleResourceNotFoundException; +import org.glyptodon.guacamole.net.auth.Connection; +import org.glyptodon.guacamole.net.auth.ConnectionGroup; +import org.glyptodon.guacamole.net.auth.Directory; +import org.glyptodon.guacamole.net.auth.User; +import org.glyptodon.guacamole.net.auth.UserContext; +import org.glyptodon.guacamole.net.basic.rest.connectiongroup.APIConnectionGroup; + +/** + * Provides easy access and automatic error handling for retrieval of objects, + * such as users, connections, or connection groups. REST API semantics, such + * as the special root connection group identifier, are also handled + * automatically. + */ +public class ObjectRetrievalService { + + /** + * Retrieves a single user from the given user context. + * + * @param userContext + * The user context to retrieve the user from. + * + * @param identifier + * The identifier of the user to retrieve. + * + * @return + * The user having the given identifier. + * + * @throws GuacamoleException + * If an error occurs while retrieving the user, or if the + * user does not exist. + */ + public User retrieveUser(UserContext userContext, + String identifier) throws GuacamoleException { + + // Get user directory + Directory directory = userContext.getUserDirectory(); + + // Pull specified user + User user = directory.get(identifier); + if (user == null) + throw new GuacamoleResourceNotFoundException("No such user: \"" + identifier + "\""); + + return user; + + } + + /** + * Retrieves a single connection from the given user context. + * + * @param userContext + * The user context to retrieve the connection from. + * + * @param identifier + * The identifier of the connection to retrieve. + * + * @return + * The connection having the given identifier. + * + * @throws GuacamoleException + * If an error occurs while retrieving the connection, or if the + * connection does not exist. + */ + public Connection retrieveConnection(UserContext userContext, + String identifier) throws GuacamoleException { + + // Get root directory + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + Directory directory = rootGroup.getConnectionDirectory(); + + // Pull specified connection + Connection connection = directory.get(identifier); + if (connection == null) + throw new GuacamoleResourceNotFoundException("No such connection: \"" + identifier + "\""); + + return connection; + + } + + /** + * Retrieves a single connection group from the given user context. If + * the given identifier the REST API root identifier, the root connection + * group will be returned. The underlying authentication provider may + * additionally use a different identifier for root. + * + * @param userContext + * The user context to retrieve the connection group from. + * + * @param identifier + * The identifier of the connection group to retrieve. + * + * @return + * The connection group having the given identifier, or the root + * connection group if the identifier the root identifier. + * + * @throws GuacamoleException + * If an error occurs while retrieving the connection group, or if the + * connection group does not exist. + */ + public ConnectionGroup retrieveConnectionGroup(UserContext userContext, + String identifier) throws GuacamoleException { + + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + + // Use root group if identifier is null (or the standard root identifier) + if (identifier != null && identifier.equals(APIConnectionGroup.ROOT_IDENTIFIER)) + return rootGroup; + + // Pull specified connection group otherwise + Directory directory = rootGroup.getConnectionGroupDirectory(); + ConnectionGroup connectionGroup = directory.get(identifier); + + if (connectionGroup == null) + throw new GuacamoleResourceNotFoundException("No such connection group: \"" + identifier + "\""); + + return connectionGroup; + + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java index 70fde2592..9b17d356a 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java @@ -38,6 +38,7 @@ public class RESTModule extends AbstractModule { // Bind generic low-level services bind(ProtocolRetrievalService.class); + bind(ObjectRetrievalService.class); } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java index 95efdd54b..e9633054b 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java @@ -44,8 +44,8 @@ import org.glyptodon.guacamole.net.auth.ConnectionRecord; import org.glyptodon.guacamole.net.auth.Directory; import org.glyptodon.guacamole.net.auth.UserContext; import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; +import org.glyptodon.guacamole.net.basic.rest.ObjectRetrievalService; import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; -import org.glyptodon.guacamole.net.basic.rest.connectiongroup.APIConnectionGroup; import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,6 +71,12 @@ public class ConnectionRESTService { @Inject private AuthenticationService authenticationService; + /** + * Service for convenient retrieval of objects. + */ + @Inject + private ObjectRetrievalService retrievalService; + /** * Retrieves an individual connection. * @@ -95,17 +101,8 @@ public class ConnectionRESTService { UserContext userContext = authenticationService.getUserContext(authToken); - // Get the connection directory - ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); - Directory connectionDirectory = - rootGroup.getConnectionDirectory(); - - // Get the connection - Connection connection = connectionDirectory.get(connectionID); - if (connection == null) - throw new GuacamoleResourceNotFoundException("No such connection: \"" + connectionID + "\""); - - return new APIConnection(connection); + // Retrieve the requested connection + return new APIConnection(retrievalService.retrieveConnection(userContext, connectionID)); } @@ -138,10 +135,8 @@ public class ConnectionRESTService { Directory connectionDirectory = rootGroup.getConnectionDirectory(); - // Get the connection - Connection connection = connectionDirectory.get(connectionID); - if (connection == null) - throw new GuacamoleResourceNotFoundException("No such connection: \"" + connectionID + "\""); + // Retrieve the requested connection + Connection connection = retrievalService.retrieveConnection(userContext, connectionID); // Retrieve connection configuration GuacamoleConfiguration config = connection.getConfiguration(); @@ -181,11 +176,8 @@ public class ConnectionRESTService { Directory connectionDirectory = rootGroup.getConnectionDirectory(); - // Get the connection - Connection connection = connectionDirectory.get(connectionID); - if (connection == null) - throw new GuacamoleResourceNotFoundException("No such connection: \"" + connectionID + "\""); - + // Retrieve the requested connection's history + Connection connection = retrievalService.retrieveConnection(userContext, connectionID); return connection.getHistory(); } @@ -216,54 +208,11 @@ public class ConnectionRESTService { Directory connectionDirectory = rootGroup.getConnectionDirectory(); - // Make sure the connection is there before trying to delete - if (connectionDirectory.get(connectionID) == null) - throw new GuacamoleResourceNotFoundException("No such connection: \"" + connectionID + "\""); - - // Delete the connection + // Delete the specified connection connectionDirectory.remove(connectionID); } - /** - * Retrieves a single connection group from the given user context. If - * the given identifier is null or the root identifier, the root connection - * group will be returned. - * - * @param userContext - * The user context to retrieve the connection group from. - * - * @param identifier - * The identifier of the connection group to retrieve. - * - * @return - * The connection group having the given identifier, or the root - * connection group if the identifier is null or the root identifier. - * - * @throws GuacamoleException - * If an error occurs while retrieving the connection group, or if the - * connection group does not exist. - */ - private ConnectionGroup retrieveConnectionGroup(UserContext userContext, - String identifier) throws GuacamoleException { - - ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); - - // Use root group if identifier is null (or the standard root identifier) - if (identifier == null || identifier.equals(APIConnectionGroup.ROOT_IDENTIFIER)) - return rootGroup; - - // Pull specified connection group otherwise - Directory directory = rootGroup.getConnectionGroupDirectory(); - ConnectionGroup connectionGroup = directory.get(identifier); - - if (connectionGroup == null) - throw new GuacamoleResourceNotFoundException("No such connection group: \"" + identifier + "\""); - - return connectionGroup; - - } - /** * Creates a new connection and returns the identifier of the new * connection. @@ -294,7 +243,7 @@ public class ConnectionRESTService { // Retrieve parent group String parentID = connection.getParentIdentifier(); - ConnectionGroup parentConnectionGroup = retrieveConnectionGroup(userContext, parentID); + ConnectionGroup parentConnectionGroup = retrievalService.retrieveConnectionGroup(userContext, parentID); // Add the new connection Directory connectionDirectory = parentConnectionGroup.getConnectionDirectory(); @@ -340,12 +289,10 @@ public class ConnectionRESTService { Directory connectionDirectory = rootGroup.getConnectionDirectory(); - // Make sure the connection is there before trying to update - Connection existingConnection = connectionDirectory.get(connectionID); - if (existingConnection == null) - throw new GuacamoleResourceNotFoundException("No such connection: \"" + connectionID + "\""); - - // Retrieve connection configuration + // Retrieve connection to update + Connection existingConnection = retrievalService.retrieveConnection(userContext, connectionID); + + // Build updated configuration GuacamoleConfiguration config = new GuacamoleConfiguration(); config.setProtocol(connection.getProtocol()); config.setParameters(connection.getParameters()); @@ -355,6 +302,10 @@ public class ConnectionRESTService { existingConnection.setName(connection.getName()); connectionDirectory.update(existingConnection); + // Update connection parent + ConnectionGroup updatedParentGroup = retrievalService.retrieveConnectionGroup(userContext, connection.getParentIdentifier()); + connectionDirectory.move(connectionID, updatedParentGroup.getConnectionDirectory()); + } } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java index 3f166f41c..45ca049e7 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java @@ -88,11 +88,7 @@ public class APIConnectionGroup { this.identifier = connectionGroup.getIdentifier(); this.parentIdentifier = connectionGroup.getParentIdentifier(); - - // Use the explicit ROOT group ID - if (this.parentIdentifier == null) - this.parentIdentifier = ROOT_IDENTIFIER; - + this.name = connectionGroup.getName(); this.type = connectionGroup.getType(); diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java index 46a37c5e0..226ba8f9d 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java @@ -46,6 +46,7 @@ import org.glyptodon.guacamole.net.auth.UserContext; import org.glyptodon.guacamole.net.auth.permission.ConnectionPermission; import org.glyptodon.guacamole.net.auth.permission.ObjectPermission; import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; +import org.glyptodon.guacamole.net.basic.rest.ObjectRetrievalService; import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; import org.glyptodon.guacamole.net.basic.rest.connection.APIConnection; import org.slf4j.Logger; @@ -72,6 +73,12 @@ public class ConnectionGroupRESTService { @Inject private AuthenticationService authenticationService; + /** + * Service for convenient retrieval of objects. + */ + @Inject + private ObjectRetrievalService retrievalService; + /** * Retrieves the given connection group from the user context, including * all descendant connections and groups if requested. @@ -105,24 +112,14 @@ public class ConnectionGroupRESTService { User self = userContext.self(); ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); - + + // Retrieve specified connection group ConnectionGroup connectionGroup; - - // Use root group if requested - if (identifier == null || identifier.equals(APIConnectionGroup.ROOT_IDENTIFIER)) - connectionGroup = rootGroup; - - // Otherwise, query requested group using root group directory - else { - - Directory connectionGroupDirectory = - rootGroup.getConnectionGroupDirectory(); - - // Get the connection group from root directory - connectionGroup = connectionGroupDirectory.get(identifier); - if (connectionGroup == null) - return null; - + try { + connectionGroup = retrievalService.retrieveConnectionGroup(userContext, identifier); + } + catch (GuacamoleResourceNotFoundException e) { + return null; } // Wrap queried connection group @@ -274,18 +271,9 @@ public class ConnectionGroupRESTService { // Get the connection group directory ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); - - // Use the root group if it was asked for - if (connectionGroupID != null && connectionGroupID.equals(APIConnectionGroup.ROOT_IDENTIFIER)) - connectionGroupID = rootGroup.getIdentifier(); - Directory connectionGroupDirectory = rootGroup.getConnectionGroupDirectory(); - // Make sure the connection is there before trying to delete - if (connectionGroupDirectory.get(connectionGroupID) == null) - throw new GuacamoleResourceNotFoundException("No such connection group: \"" + connectionGroupID + "\""); - // Delete the connection group connectionGroupDirectory.remove(connectionGroupID); @@ -321,22 +309,9 @@ public class ConnectionGroupRESTService { if (connectionGroup == null) throw new GuacamoleClientException("Connection group JSON must be submitted when creating connections groups."); + // Retrieve parent group String parentID = connectionGroup.getParentIdentifier(); - ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); - - // Use root group if no parent is specified - ConnectionGroup parentConnectionGroup; - if (parentID == null) - parentConnectionGroup = rootGroup; - - // Pull specified connection group otherwise - else { - Directory connectionGroupDirectory = rootGroup.getConnectionGroupDirectory(); - parentConnectionGroup = connectionGroupDirectory.get(parentID); - - if (parentConnectionGroup == null) - throw new GuacamoleResourceNotFoundException("No such connection group: \"" + parentID + "\""); - } + ConnectionGroup parentConnectionGroup = retrievalService.retrieveConnectionGroup(userContext, parentID); // Add the new connection group Directory connectionGroupDirectory = parentConnectionGroup.getConnectionGroupDirectory(); @@ -378,22 +353,22 @@ public class ConnectionGroupRESTService { if (connectionGroup == null) throw new GuacamoleClientException("Connection group JSON must be submitted when updating connection groups."); - // Get the connection directory + // Get the connection group directory ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); - - // Use the root group if it was asked for - if (connectionGroupID != null && connectionGroupID.equals(APIConnectionGroup.ROOT_IDENTIFIER)) - connectionGroupID = rootGroup.getIdentifier(); - Directory connectionGroupDirectory = rootGroup.getConnectionGroupDirectory(); - // Make sure the connection group is there before trying to update - if (connectionGroupDirectory.get(connectionGroupID) == null) - throw new GuacamoleResourceNotFoundException("No such connection group: \"" + connectionGroupID + "\""); - + // Retrieve connection group to update + ConnectionGroup existingConnectionGroup = connectionGroupDirectory.get(connectionGroupID); + // Update the connection group - connectionGroupDirectory.update(new APIConnectionGroupWrapper(connectionGroup)); + existingConnectionGroup.setName(connectionGroup.getName()); + existingConnectionGroup.setType(connectionGroup.getType()); + connectionGroupDirectory.update(existingConnectionGroup); + + // Update connection group parent + ConnectionGroup updatedParentGroup = retrievalService.retrieveConnectionGroup(userContext, connectionGroup.getParentIdentifier()); + connectionGroupDirectory.move(connectionGroupID, updatedParentGroup.getConnectionGroupDirectory()); } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java index c9e6a6fde..7a9f28b9b 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java @@ -54,6 +54,7 @@ import static org.glyptodon.guacamole.net.basic.rest.APIPatch.Operation.add; import static org.glyptodon.guacamole.net.basic.rest.APIPatch.Operation.remove; import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; import org.glyptodon.guacamole.net.basic.rest.HTTPException; +import org.glyptodon.guacamole.net.basic.rest.ObjectRetrievalService; import org.glyptodon.guacamole.net.basic.rest.PATCH; import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; import org.glyptodon.guacamole.net.basic.rest.permission.APIPermissionSet; @@ -105,6 +106,12 @@ public class UserRESTService { @Inject private AuthenticationService authenticationService; + /** + * Service for convenient retrieval of objects. + */ + @Inject + private ObjectRetrievalService retrievalService; + /** * Gets a list of users in the system, filtering the returned list by the * given permission, if specified. @@ -177,15 +184,8 @@ public class UserRESTService { UserContext userContext = authenticationService.getUserContext(authToken); - // Get the directory - Directory userDirectory = userContext.getUserDirectory(); - - // Get the user - User user = userDirectory.get(username); - if (user == null) - throw new GuacamoleResourceNotFoundException("No such user: \"" + username + "\""); - - // Return the user + // Retrieve the requested user + User user = retrievalService.retrieveUser(userContext, username); return new APIUser(user); } @@ -254,9 +254,7 @@ public class UserRESTService { "Username in path does not match username provided JSON data."); // Get the user - User existingUser = userDirectory.get(username); - if (existingUser == null) - throw new GuacamoleResourceNotFoundException("No such user: \"" + username + "\""); + User existingUser = retrievalService.retrieveUser(userContext, username); // Do not update the user password if no password was provided if (user.getPassword() != null) diff --git a/guacamole/src/main/webapp/app/groupList/templates/guacGroupList.html b/guacamole/src/main/webapp/app/groupList/templates/guacGroupList.html index 942cacb79..21c040318 100644 --- a/guacamole/src/main/webapp/app/groupList/templates/guacGroupList.html +++ b/guacamole/src/main/webapp/app/groupList/templates/guacGroupList.html @@ -25,7 +25,9 @@
- +
+ +
diff --git a/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js b/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js index db16fc1dc..cf2434586 100644 --- a/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js +++ b/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js @@ -29,31 +29,49 @@ angular.module('index').config(['$routeProvider', '$locationProvider', // Disable HTML5 mode (use # for routing) $locationProvider.html5Mode(false); - $routeProvider. - when('/', { + $routeProvider + .when('/', { title: 'application.title', bodyClassName: 'home', templateUrl: 'app/home/templates/home.html', controller: 'homeController' - }). - when('/manage/', { + }) + .when('/manage/', { title: 'application.title', bodyClassName: 'manage', templateUrl: 'app/manage/templates/manage.html', controller: 'manageController' - }). - when('/login/', { + }) + .when('/manage/connections/:id?', { + title: 'application.title', + bodyClassName: 'manage', + templateUrl: 'app/manage/templates/manageConnection.html', + controller: 'manageConnectionController' + }) + .when('/manage/connectionGroups/:id?', { + title: 'application.title', + bodyClassName: 'manage', + templateUrl: 'app/manage/templates/manageConnectionGroup.html', + controller: 'manageConnectionGroupController' + }) + .when('/manage/users/:id', { + title: 'application.title', + bodyClassName: 'manage', + templateUrl: 'app/manage/templates/manageUser.html', + controller: 'manageUserController' + }) + .when('/login/', { title: 'application.title', bodyClassName: 'login', templateUrl: 'app/login/templates/login.html', controller: 'loginController' - }). - when('/client/:type/:id/:params?', { + }) + .when('/client/:type/:id/:params?', { bodyClassName: 'client', templateUrl: 'app/client/templates/client.html', controller: 'clientController' - }). - otherwise({ + }) + .otherwise({ redirectTo: '/' }); }]); diff --git a/guacamole/src/main/webapp/app/index/styles/dialog.css b/guacamole/src/main/webapp/app/index/styles/dialog.css index e514eaf5b..bad31104a 100644 --- a/guacamole/src/main/webapp/app/index/styles/dialog.css +++ b/guacamole/src/main/webapp/app/index/styles/dialog.css @@ -92,23 +92,6 @@ z-index: 1; } -.dialog .dropdown { - - position: absolute; - z-index: 2; - margin-top: -1px; - - width: 3in; - max-height: 2in; - overflow: auto; - - border: 1px solid rgba(0, 0, 0, 0.5); - background: white; - - font-size: 10pt; - -} - .dialog .footer { text-align: center; } diff --git a/guacamole/src/main/webapp/app/home/styles/connection.css b/guacamole/src/main/webapp/app/index/styles/lists.css similarity index 95% rename from guacamole/src/main/webapp/app/home/styles/connection.css rename to guacamole/src/main/webapp/app/index/styles/lists.css index 9d659d20f..4f462f41b 100644 --- a/guacamole/src/main/webapp/app/home/styles/connection.css +++ b/guacamole/src/main/webapp/app/index/styles/lists.css @@ -20,33 +20,33 @@ * THE SOFTWARE. */ +.user, .group, .connection { cursor: pointer; } +.user a, .connection a, .group a { text-decoration:none; color: black; } +.user a:hover, .connection a:hover, .group a:hover { text-decoration:none; color: black; } +.user a:visited, .connection a:visited, .group a:visited { text-decoration:none; color: black; } -.group .connection .bears { - display: none; -} - .connection:hover { background: #CDA; } @@ -73,6 +73,10 @@ div.recent-connections .protocol { vertical-align: middle; } +.caption > * { + display: inline-block; +} + .caption .name { margin-left: 0.25em; } diff --git a/guacamole/src/main/webapp/app/index/styles/ui.css b/guacamole/src/main/webapp/app/index/styles/ui.css index 433b38178..d2d928f54 100644 --- a/guacamole/src/main/webapp/app/index/styles/ui.css +++ b/guacamole/src/main/webapp/app/index/styles/ui.css @@ -72,8 +72,8 @@ h2 ~ h2 { } div.section { - margin: 0; - padding: 1em; + margin: 1em; + padding: 0; } /* diff --git a/guacamole/src/main/webapp/app/manage/controllers/connectionEditModalController.js b/guacamole/src/main/webapp/app/manage/controllers/connectionEditModalController.js deleted file mode 100644 index fea764da2..000000000 --- a/guacamole/src/main/webapp/app/manage/controllers/connectionEditModalController.js +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * The controller for the connection edit modal. - */ -angular.module('manage').controller('connectionEditModalController', ['$scope', '$injector', - function connectionEditModalController($scope, $injector) { - - var connectionEditModal = $injector.get('connectionEditModal'); - var connectionService = $injector.get('connectionService'); - var Connection = $injector.get('Connection'); - var HistoryEntryWrapper = $injector.get('HistoryEntryWrapper'); - - // Make a copy of the old connection so that we can copy over the changes when done - var oldConnection = $scope.connection; - - // Copy data into a new conection object in case the user doesn't want to save - $scope.connection = new Connection($scope.connection); - - var newConnection = !$scope.connection.identifier; - - // Set it to VNC by default - if(!$scope.connection.protocol) - $scope.connection.protocol = "vnc"; - - $scope.historyEntryWrappers = []; - - // Wrap all the history entries - if (!newConnection) { - connectionService.getConnectionHistory($scope.connection.identifier).success(function wrapHistoryEntries(historyEntries) { - historyEntries.forEach(function wrapHistoryEntry(historyEntry) { - $scope.historyEntryWrappers.push(new HistoryEntryWrapper(historyEntry)); - }); - }); - } - - /** - * Close the modal. - */ - $scope.close = function close() { - connectionEditModal.deactivate(); - }; - - /** - * Save the connection and close the modal. - */ - $scope.save = function save() { - connectionService.saveConnection($scope.connection).success(function successfullyUpdatedConnection() { - - var oldParentID = oldConnection.parentIdentifier; - var newParentID = $scope.connection.parentIdentifier; - - // Copy the data back to the original model - angular.extend(oldConnection, $scope.connection); - - // We have to move this connection - if(oldParentID !== newParentID) - - // New connections are created by default in root - don't try to move it if it's already there. - if(newConnection && newParentID === $scope.rootGroup.identifier) { - $scope.moveItem($scope.connection, oldParentID, newParentID); - } else { - connectionService.moveConnection($scope.connection).then(function moveConnection() { - $scope.moveItem($scope.connection, oldParentID, newParentID); - }); - } - - // Close the modal - connectionEditModal.deactivate(); - }); - }; - - /** - * Delete the connection and close the modal. - */ - $scope['delete'] = function deleteConnection() { - - // Nothing to delete if the connection is new - var newConnection = !$scope.connection.identifier; - if(newConnection) { - // Close the modal - connectionEditModal.deactivate(); - return; - } - - connectionService.deleteConnection($scope.connection).success(function successfullyDeletedConnection() { - var oldParentID = oldConnection.parentIdentifier; - - // We have to remove this connection from the heirarchy - $scope.moveItem($scope.connection, oldParentID); - - // Close the modal - connectionEditModal.deactivate(); - }); - }; - -}]); - - - diff --git a/guacamole/src/main/webapp/app/manage/controllers/connectionGroupEditModalController.js b/guacamole/src/main/webapp/app/manage/controllers/connectionGroupEditModalController.js deleted file mode 100644 index 68ba74f51..000000000 --- a/guacamole/src/main/webapp/app/manage/controllers/connectionGroupEditModalController.js +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * The controller for the connection group edit modal. - */ -angular.module('manage').controller('connectionGroupEditModalController', ['$scope', '$injector', - function connectionEditModalController($scope, $injector) { - - var connectionGroupEditModal = $injector.get('connectionGroupEditModal'); - var connectionGroupService = $injector.get('connectionGroupService'); - - // Make a copy of the old connection group so that we can copy over the changes when done - var oldConnectionGroup = $scope.connectionGroup; - - // Copy data into a new conection group object in case the user doesn't want to save - $scope.connectionGroup = angular.copy($scope.connectionGroup); - - var newConnectionGroup = !$scope.connectionGroup.identifier; - - $scope.types = [ - { - label: "organizational", - value: "ORGANIZATIONAL" - }, - { - label: "balancing", - value: "BALANCING" - } - ]; - - // Set it to organizational by default - if(!$scope.connectionGroup.type) - $scope.connectionGroup.type = $scope.types[0].value; - - /** - * Close the modal. - */ - $scope.close = function close() { - connectionGroupEditModal.deactivate(); - }; - - /** - * Save the connection and close the modal. - */ - $scope.save = function save() { - connectionGroupService.saveConnectionGroup($scope.connectionGroup).success(function successfullyUpdatedConnectionGroup() { - - var oldParentID = oldConnectionGroup.parentIdentifier; - var newParentID = $scope.connectionGroup.parentIdentifier; - - // Copy the data back to the original model - angular.extend(oldConnectionGroup, $scope.connectionGroup); - - // New groups are created by default in root - don't try to move it if it's already there. - if(newConnectionGroup && newParentID === $scope.rootGroup.identifier) { - $scope.moveItem($scope.connectionGroup, oldParentID, newParentID); - } else { - connectionGroupService.moveConnectionGroup($scope.connectionGroup).then(function moveConnectionGroup() { - $scope.moveItem($scope.connectionGroup, oldParentID, newParentID); - }); - } - - // Close the modal - connectionGroupEditModal.deactivate(); - }); - }; - - /** - * Delete the connection and close the modal. - */ - $scope['delete'] = function deleteConnectionGroup() { - - // Nothing to delete if the connection is new - if(newConnectionGroup) - // Close the modal - connectionGroupEditModal.deactivate(); - - connectionGroupService.deleteConnectionGroup($scope.connectionGroup).success(function successfullyDeletedConnectionGroup() { - var oldParentID = oldConnectionGroup.parentIdentifier; - - // We have to remove this connection group from the heirarchy - $scope.moveItem($scope.connectionGroup, oldParentID); - - // Close the modal - connectionGroupEditModal.deactivate(); - }); - } -}]); - - - diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js new file mode 100644 index 000000000..22d6c8837 --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * The controller for editing or creating connections. + */ +angular.module('manage').controller('manageConnectionController', ['$scope', '$injector', + 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'); + + // Required services + var $location = $injector.get('$location'); + var $routeParams = $injector.get('$routeParams'); + var connectionService = $injector.get('connectionService'); + var connectionGroupService = $injector.get('connectionGroupService'); + var protocolService = $injector.get('protocolService'); + + /** + * An action to be provided along with the object sent to showStatus which + * closes the currently-shown status dialog. + */ + var ACKNOWLEDGE_ACTION = { + name : "manage.error.action.acknowledge", + // Handle action + callback : function acknowledgeCallback() { + $scope.showStatus(false); + } + }; + + /** + * The identifier of the connection being edited. If a new connection is + * being created, this will not be defined. + * + * @type String + */ + var identifier = $routeParams.id; + + // Pull connection group hierarchy + connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, PermissionSet.ObjectPermissionType.UPDATE) + .success(function connectionGroupReceived(rootGroup) { + $scope.rootGroup = rootGroup; + }); + + // Get protocol metadata + protocolService.getProtocols().success(function protocolsReceived(protocols) { + $scope.protocols = protocols; + }); + + // If we are editing an existing connection, pull its data + if (identifier) { + + // Pull data from existing connection + connectionService.getConnection(identifier).success(function connectionRetrieved(connection) { + $scope.connection = connection; + }); + + // Pull connection history + connectionService.getConnectionHistory(identifier).success(function historyReceived(historyEntries) { + + // Wrap all history entries for sake of display + $scope.historyEntryWrappers = []; + historyEntries.forEach(function wrapHistoryEntry(historyEntry) { + $scope.historyEntryWrappers.push(new HistoryEntryWrapper(historyEntry)); + }); + + }); + + // Pull connection parameters + connectionService.getConnectionParameters(identifier).success(function parametersReceived(parameters) { + $scope.parameters = parameters; + }); + + } + + // If we are creating a new connection, populate skeleton connection data + else { + $scope.connection = new Connection({ protocol: 'vnc' }); + $scope.historyEntryWrappers = []; + $scope.parameters = {}; + } + + /** + * Cancels all pending edits, returning to the management page. + */ + $scope.cancel = function cancel() { + $location.path('/manage/'); + }; + + /** + * Saves the connection, creating a new connection or updating the existing + * connection. + */ + $scope.saveConnection = function saveConnection() { + + $scope.connection.parameters = $scope.parameters; + + // Save the connection + connectionService.saveConnection($scope.connection) + .success(function savedConnection() { + $location.path('/manage/'); + }) + + // Notify of any errors + .error(function connectionSaveFailed(error) { + $scope.showStatus({ + 'className' : 'error', + 'title' : 'manage.error.title', + 'text' : error.message, + 'actions' : [ ACKNOWLEDGE_ACTION ] + }); + }); + + }; + + /** + * An action to be provided along with the object sent to showStatus which + * immediately deletes the current connection. + */ + var DELETE_ACTION = { + name : "manage.edit.connection.delete", + className : "danger", + // Handle action + callback : function deleteCallback() { + deleteConnectionImmediately(); + $scope.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.edit.connection.cancel", + // Handle action + callback : function cancelCallback() { + $scope.showStatus(false); + } + }; + + /** + * Immediately deletes the current connection, without prompting the user + * for confirmation. + */ + var deleteConnectionImmediately = function deleteConnectionImmediately() { + + // Delete the connection + connectionService.deleteConnection($scope.connection) + .success(function deletedConnection() { + $location.path('/manage/'); + }) + + // Notify of any errors + .error(function connectionDeletionFailed(error) { + $scope.showStatus({ + 'className' : 'error', + 'title' : 'manage.error.title', + 'text' : error.message, + 'actions' : [ ACKNOWLEDGE_ACTION ] + }); + }); + + }; + + /** + * Deletes the connection, prompting the user first to confirm that + * deletion is desired. + */ + $scope.deleteConnection = function deleteConnection() { + + // Confirm deletion request + $scope.showStatus({ + 'title' : 'manage.edit.connection.confirmDelete.title', + 'text' : 'manage.edit.connection.confirmDelete.text', + 'actions' : [ DELETE_ACTION, CANCEL_ACTION] + }); + + }; + +}]); diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js new file mode 100644 index 000000000..2aa6a2177 --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * The controller for editing or creating connection groups. + */ +angular.module('manage').controller('manageConnectionGroupController', ['$scope', '$injector', + function manageConnectionGroupController($scope, $injector) { + + // Required types + var ConnectionGroup = $injector.get('ConnectionGroup'); + var PermissionSet = $injector.get('PermissionSet'); + + // Required services + var $location = $injector.get('$location'); + var $routeParams = $injector.get('$routeParams'); + var connectionGroupService = $injector.get('connectionGroupService'); + + /** + * An action to be provided along with the object sent to showStatus which + * closes the currently-shown status dialog. + */ + var ACKNOWLEDGE_ACTION = { + name : "manage.error.action.acknowledge", + // Handle action + callback : function acknowledgeCallback() { + $scope.showStatus(false); + } + }; + + /** + * The identifier of the connection group being edited. If a new connection + * group is being created, this will not be defined. + * + * @type String + */ + var identifier = $routeParams.id; + + // Pull connection group hierarchy + connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, PermissionSet.ObjectPermissionType.UPDATE) + .success(function connectionGroupReceived(rootGroup) { + $scope.rootGroup = rootGroup; + }); + + // If we are editing an existing connection group, pull its data + if (identifier) { + connectionGroupService.getConnectionGroup(identifier).success(function connectionGroupReceived(connectionGroup) { + $scope.connectionGroup = connectionGroup; + }); + } + + // If we are creating a new connection group, populate skeleton connection group data + else + $scope.connectionGroup = new ConnectionGroup(); + + /** + * Available connection group types, as translation string / internal value + * pairs. + * + * @type Object[] + */ + $scope.types = [ + { + label: "organizational", + value: ConnectionGroup.Type.ORGANIZATIONAL + }, + { + label : "balancing", + value : ConnectionGroup.Type.BALANCING + } + ]; + + /** + * Cancels all pending edits, returning to the management page. + */ + $scope.cancel = function cancel() { + $location.path('/manage/'); + }; + + /** + * Saves the connection group, creating a new connection group or updating + * the existing connection group. + */ + $scope.saveConnectionGroup = function saveConnectionGroup() { + + // Save the connection + connectionGroupService.saveConnectionGroup($scope.connectionGroup) + .success(function savedConnectionGroup() { + $location.path('/manage/'); + }) + + // Notify of any errors + .error(function connectionGroupSaveFailed(error) { + $scope.showStatus({ + 'className' : 'error', + 'title' : 'manage.error.title', + 'text' : error.message, + 'actions' : [ ACKNOWLEDGE_ACTION ] + }); + }); + + }; + + /** + * An action to be provided along with the object sent to showStatus which + * immediately deletes the current connection group. + */ + var DELETE_ACTION = { + name : "manage.edit.connectionGroup.delete", + className : "danger", + // Handle action + callback : function deleteCallback() { + deleteConnectionGroupImmediately(); + $scope.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.edit.connectionGroup.cancel", + // Handle action + callback : function cancelCallback() { + $scope.showStatus(false); + } + }; + + /** + * Immediately deletes the current connection group, without prompting the + * user for confirmation. + */ + var deleteConnectionGroupImmediately = function deleteConnectionGroupImmediately() { + + // Delete the connection group + connectionGroupService.deleteConnectionGroup($scope.connectionGroup) + .success(function deletedConnectionGroup() { + $location.path('/manage/'); + }) + + // Notify of any errors + .error(function connectionGroupDeletionFailed(error) { + $scope.showStatus({ + 'className' : 'error', + 'title' : 'manage.error.title', + 'text' : error.message, + 'actions' : [ ACKNOWLEDGE_ACTION ] + }); + }); + + }; + + /** + * Deletes the connection group, prompting the user first to confirm that + * deletion is desired. + */ + $scope.deleteConnectionGroup = function deleteConnectionGroup() { + + // Confirm deletion request + $scope.showStatus({ + 'title' : 'manage.edit.connectionGroup.confirmDelete.title', + 'text' : 'manage.edit.connectionGroup.confirmDelete.text', + 'actions' : [ DELETE_ACTION, CANCEL_ACTION] + }); + + }; + +}]); diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageController.js b/guacamole/src/main/webapp/app/manage/controllers/manageController.js index 1efcde5d4..d83073cd3 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageController.js @@ -27,147 +27,78 @@ angular.module('manage').controller('manageController', ['$scope', '$injector', function manageController($scope, $injector) { // Required types - var PermissionSet = $injector.get('PermissionSet'); var ConnectionGroup = $injector.get('ConnectionGroup'); + var PermissionSet = $injector.get('PermissionSet'); + var User = $injector.get('User'); // Required services - var connectionGroupService = $injector.get('connectionGroupService'); - var connectionEditModal = $injector.get('connectionEditModal'); - var connectionGroupEditModal = $injector.get('connectionGroupEditModal'); - var userEditModal = $injector.get('userEditModal'); - var protocolService = $injector.get('protocolService'); - var userService = $injector.get('userService'); - - // Set status to loading until we have all the connections, groups, and users have loaded - $scope.loadingUsers = true; - $scope.loadingConnections = true; - - $scope.basicPermissionsLoaded.then(function basicPermissionsHaveBeenLoaded() { + var connectionGroupService = $injector.get('connectionGroupService'); + var userService = $injector.get('userService'); - // Retrieve all users for whom we have UPDATE permission - connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, PermissionSet.ObjectPermissionType.UPDATE) - .success(function connectionGroupReceived(rootGroup) { - $scope.rootGroup = rootGroup; - $scope.loadingConnections = false; - }); - - // Retrieve all users for whom we have UPDATE permission - userService.getUsers(PermissionSet.ObjectPermissionType.UPDATE) - .success(function usersReceived(users) { - $scope.users = users; - $scope.loadingUsers = false; - }); - - }); - - $scope.protocols = {}; - - // Get the protocol information from the server and copy it into the scope - protocolService.getProtocols().success(function fetchProtocols(protocols) { - $scope.protocols = protocols; - }); - - // Expose object edit functions to group list template - $scope.groupListContext = { - - /** - * Open a modal to edit the given connection. - * - * @param {Connection} connection - * The connection to edit. - */ - editConnection : function editConnection(connection) { - connectionEditModal.activate({ - connection : connection, - protocols : $scope.protocols, - rootGroup : $scope.rootGroup - }); - }, - - /** - * Open a modal to edit the given connection group. - * - * @param {ConnectionGroup} connectionGroup - * The connection group to edit. - */ - editConnectionGroup : function editConnectionGroup(connectionGroup) { - connectionGroupEditModal.activate({ - connectionGroup : connectionGroup, - rootGroup : $scope.rootGroup - }); + /** + * An action to be provided along with the object sent to showStatus which + * closes the currently-shown status dialog. + */ + var ACKNOWLEDGE_ACTION = { + name : "manage.error.action.acknowledge", + // Handle action + callback : function acknowledgeCallback() { + $scope.showStatus(false); } + }; - }; - /** - * Open a modal to create a new connection. + * The name of the new user to create, if any, when user creation is + * requested via newUser(). + * + * @type String */ - $scope.newConnection = function newConnection() { - connectionEditModal.activate( - { - connection : {}, - protocols : $scope.protocols, - rootGroup : $scope.rootGroup - }); - }; - - /** - * Open a modal to create a new connection group. - */ - $scope.newConnectionGroup = function newConnectionGroup() { - connectionGroupEditModal.activate( - { - connectionGroup : {}, - rootGroup : $scope.rootGroup - }); - }; - - // Remove the user from the current list of users - function removeUser(user) { - for(var i = 0; i < $scope.users.length; i++) { - if($scope.users[i].username === user.username) { - $scope.users.splice(i, 1); - break; - } - } - } - - /** - * Open a modal to edit the user. - * - * @param {object} user The user to edit. - */ - $scope.editUser = function editUser(user) { - userEditModal.activate( - { - user : user, - rootGroup : $scope.rootGroup, - removeUser : removeUser - }); - }; - $scope.newUsername = ""; + // Retrieve all connections for which we have UPDATE permission + connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, PermissionSet.ObjectPermissionType.UPDATE) + .success(function connectionGroupReceived(rootGroup) { + $scope.rootGroup = rootGroup; + }); + + // Retrieve all users for whom we have UPDATE permission + userService.getUsers(PermissionSet.ObjectPermissionType.UPDATE) + .success(function usersReceived(users) { + $scope.users = users; + }); + /** - * Open a modal to edit the user. - * - * @param {object} user The user to edit. + * Creates a new user having the username specified in the user creation + * interface. */ $scope.newUser = function newUser() { - if($scope.newUsername) { - var newUser = { - username: $scope.newUsername - }; - - userService.createUser(newUser).success(function addUserToList() { - $scope.users.push(newUser); + + // Create user skeleton + var user = new User({ + username: $scope.newUsername || '' + }); + + // Create specified user + userService.createUser(user) + + // Add user to visible list upon success + .success(function userCreated() { + $scope.users.push(user); + }) + + // Notify of any errors + .error(function userCreationFailed(error) { + $scope.showStatus({ + 'className' : 'error', + 'title' : 'manage.error.title', + 'text' : error.message, + 'actions' : [ ACKNOWLEDGE_ACTION ] }); - - $scope.newUsername = ""; - } + }); + + // Reset username + $scope.newUsername = ""; + }; }]); - - - diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js new file mode 100644 index 000000000..a45fef809 --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -0,0 +1,437 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * The controller for editing users. + */ +angular.module('manage').controller('manageUserController', ['$scope', '$injector', + function manageUserController($scope, $injector) { + + // Required types + var ConnectionGroup = $injector.get('ConnectionGroup'); + var PermissionFlagSet = $injector.get('PermissionFlagSet'); + var PermissionSet = $injector.get('PermissionSet'); + + // Required services + var $location = $injector.get('$location'); + var $routeParams = $injector.get('$routeParams'); + var connectionGroupService = $injector.get('connectionGroupService'); + var userService = $injector.get('userService'); + var permissionService = $injector.get('permissionService'); + + /** + * An action to be provided along with the object sent to showStatus which + * closes the currently-shown status dialog. + */ + var ACKNOWLEDGE_ACTION = { + name : "manage.error.action.acknowledge", + // Handle action + callback : function acknowledgeCallback() { + $scope.showStatus(false); + } + }; + + /** + * The username of the user being edited. + * + * @type String + */ + var username = $routeParams.id; + + // Pull user data + userService.getUser(username).success(function userReceived(user) { + $scope.user = user; + }); + + // Pull user permissions + permissionService.getPermissions(username).success(function gotPermissions(permissions) { + $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(permissions); + }); + + // Retrieve all connections for which we have UPDATE permission + connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, PermissionSet.ObjectPermissionType.ADMINISTER) + .success(function connectionGroupReceived(rootGroup) { + $scope.rootGroup = rootGroup; + }); + + /** + * Available system permission types, as translation string / internal + * value pairs. + * + * @type Object[] + */ + $scope.systemPermissionTypes = [ + { + label: "manage.edit.user.administerSystem", + value: PermissionSet.SystemPermissionType.ADMINISTER + }, + { + label: "manage.edit.user.createUser", + value: PermissionSet.SystemPermissionType.CREATE_USER + }, + { + label: "manage.edit.user.createConnection", + value: PermissionSet.SystemPermissionType.CREATE_CONNECTION + }, + { + label: "manage.edit.user.createConnectionGroup", + value: PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP + } + ]; + + /** + * 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 + */ + var 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 + */ + 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 remove, 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 add, 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 of a change to the selected system permissions 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 value = $scope.permissionFlags.systemPermissions[type]; + + // Add/remove permission depending on flag state + if (value) + addSystemPermission(type); + else + removeSystemPermission(type); + + }; + + /** + * 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); + + }; + + // 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 of a change to the selected 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 value = $scope.permissionFlags.connectionPermissions.READ[identifier]; + + // Add/remove permission depending on flag state + if (value) + addConnectionPermission(identifier); + else + removeConnectionPermission(identifier); + + }, + + /** + * Notifies of a change to the selected 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 value = $scope.permissionFlags.connectionGroupPermissions.READ[identifier]; + + // Add/remove permission depending on flag state + if (value) + addConnectionGroupPermission(identifier); + else + removeConnectionGroupPermission(identifier); + + } + + }; + + /** + * Cancels all pending edits, returning to the management page. + */ + $scope.cancel = function cancel() { + $location.path('/manage/'); + }; + + /** + * Saves the user, updating the existing user only. + */ + $scope.saveUser = function saveUser() { + + // Verify passwords match + if ($scope.passwordMatch !== $scope.user.password) { + $scope.showStatus({ + 'className' : 'error', + 'title' : 'manage.error.title', + 'text' : 'manage.edit.user.passwordMismatch', + 'actions' : [ ACKNOWLEDGE_ACTION ] + }); + return; + } + + // Save the user + userService.saveUser($scope.user) + .success(function savedUser() { + + // Upon success, save any changed permissions + permissionService.patchPermissions($scope.user.username, permissionsAdded, permissionsRemoved) + .success(function patchedUserPermissions() { + $location.path('/manage/'); + }) + + // Notify of any errors + .error(function userPermissionsPatchFailed(error) { + $scope.showStatus({ + 'className' : 'error', + 'title' : 'manage.error.title', + 'text' : error.message, + 'actions' : [ ACKNOWLEDGE_ACTION ] + }); + }); + + }) + + // Notify of any errors + .error(function userSaveFailed(error) { + $scope.showStatus({ + 'className' : 'error', + 'title' : 'manage.error.title', + 'text' : error.message, + 'actions' : [ ACKNOWLEDGE_ACTION ] + }); + }); + + }; + + /** + * An action to be provided along with the object sent to showStatus which + * immediately deletes the current user. + */ + var DELETE_ACTION = { + name : "manage.edit.user.delete", + className : "danger", + // Handle action + callback : function deleteCallback() { + deleteUserImmediately(); + $scope.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.edit.user.cancel", + // Handle action + callback : function cancelCallback() { + $scope.showStatus(false); + } + }; + + /** + * Immediately deletes the current user, without prompting the user for + * confirmation. + */ + var deleteUserImmediately = function deleteUserImmediately() { + + // Delete the user + userService.deleteUser($scope.user) + .success(function deletedUser() { + $location.path('/manage/'); + }) + + // Notify of any errors + .error(function userDeletionFailed(error) { + $scope.showStatus({ + 'className' : 'error', + 'title' : 'manage.error.title', + 'text' : error.message, + 'actions' : [ ACKNOWLEDGE_ACTION ] + }); + }); + + }; + + /** + * Deletes the user, prompting the user first to confirm that deletion is + * desired. + */ + $scope.deleteUser = function deleteUser() { + + // Confirm deletion request + $scope.showStatus({ + 'title' : 'manage.edit.user.confirmDelete.title', + 'text' : 'manage.edit.user.confirmDelete.text', + 'actions' : [ DELETE_ACTION, CANCEL_ACTION] + }); + + }; + +}]); diff --git a/guacamole/src/main/webapp/app/manage/controllers/userEditModalController.js b/guacamole/src/main/webapp/app/manage/controllers/userEditModalController.js deleted file mode 100644 index 3d7cadd6c..000000000 --- a/guacamole/src/main/webapp/app/manage/controllers/userEditModalController.js +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * The controller for the connection edit modal. - */ -angular.module('manage').controller('userEditModalController', ['$scope', '$injector', - function userEditModalController($scope, $injector) { - - var userEditModal = $injector.get('userEditModal'); - var userService = $injector.get('userService'); - var permissionService = $injector.get('permissionService'); - - // Make a copy of the old user so that we can copy over the changes when done - var oldUser = $scope.user; - - // Copy data into a new conection object in case the user doesn't want to save - $scope.user = angular.copy($scope.user); - - /** - * Close the modal. - */ - $scope.close = function close() { - userEditModal.deactivate(); - }; - - /* - * All the permissions that have been modified since this modal was opened. - * Maps of type or id to value. - */ - $scope.modifiedSystemPermissions = {}; - $scope.modifiedConnectionPermissions = {}; - $scope.modifiedConnectionGroupPermissions = {}; - - $scope.markSystemPermissionModified = function markSystemPermissionModified(type) { - $scope.modifiedSystemPermissions[type] = $scope.systemPermissions[type]; - }; - - $scope.markConnectionPermissionModified = function markConnectionPermissionModified(id) { - $scope.modifiedConnectionPermissions[id] = $scope.connectionPermissions[id]; - }; - - $scope.markConnectionGroupPermissionModified = function markConnectionGroupPermissionModified(id) { - $scope.modifiedConnectionGroupPermissions[id] = $scope.connectionGroupPermissions[id]; - }; - - /** - * Save the user and close the modal. - */ - $scope.save = function save() { - - if($scope.passwordMatch !== $scope.user.password) { - //TODO: Display an error - return; - } - - userService.saveUser($scope.user).success(function successfullyUpdatedUser() { - - //Figure out what permissions have changed - var connectionPermissionsToCreate = [], - connectionPermissionsToDelete = [], - connectionGroupPermissionsToCreate = [], - connectionGroupPermissionsToDelete = [], - systemPermissionsToCreate = [], - systemPermissionsToDelete = []; - - for(var type in $scope.modifiedSystemPermissions) { - // It was added - if($scope.modifiedSystemPermissions[type] && !originalSystemPermissions[type]) { - systemPermissionsToCreate.push(type); - } - // It was removed - else if(!$scope.modifiedSystemPermissions[type] && originalSystemPermissions[type]) { - systemPermissionsToDelete.push(type); - } - } - - for(var id in $scope.modifiedConnectionPermissions) { - // It was added - if($scope.modifiedConnectionPermissions[id] && !originalConnectionPermissions[id]) { - connectionPermissionsToCreate.push(id); - } - // It was removed - else if(!$scope.modifiedConnectionPermissions[id] && originalConnectionPermissions[id]) { - connectionPermissionsToDelete.push(id); - } - } - - for(var id in $scope.modifiedConnectionGroupPermissions) { - // It was added - if($scope.modifiedConnectionGroupPermissions[id] && !originalConnectionGroupPermissions[id]) { - connectionGroupPermissionsToCreate.push(id); - } - // It was removed - else if(!$scope.modifiedConnectionGroupPermissions[id] && originalConnectionGroupPermissions[id]) { - connectionGroupPermissionsToDelete.push(id); - } - } - - var permissionsToAdd = []; - var permissionsToRemove = []; - - // Create new connection permissions - for(var i = 0; i < connectionPermissionsToCreate.length; i++) { - permissionsToAdd.push({ - objectType : "CONNECTION", - objectIdentifier : connectionPermissionsToCreate[i], - permissionType : "READ" - }); - } - - // Delete old connection permissions - for(var i = 0; i < connectionPermissionsToDelete.length; i++) { - permissionsToRemove.push({ - objectType : "CONNECTION", - objectIdentifier : connectionPermissionsToDelete[i], - permissionType : "READ" - }); - } - - // Create new connection group permissions - for(var i = 0; i < connectionGroupPermissionsToCreate.length; i++) { - permissionsToAdd.push({ - objectType : "CONNECTION_GROUP", - objectIdentifier : connectionGroupPermissionsToCreate[i], - permissionType : "READ" - }); - } - - // Delete old connection group permissions - for(var i = 0; i < connectionGroupPermissionsToDelete.length; i++) { - permissionsToRemove.push({ - objectType : "CONNECTION_GROUP", - objectIdentifier : connectionGroupPermissionsToDelete[i], - permissionType : "READ" - }); - } - - // Create new system permissions - for(var i = 0; i < systemPermissionsToCreate.length; i++) { - permissionsToAdd.push({ - objectType : "SYSTEM", - permissionType : systemPermissionsToCreate[i] - }); - } - - // Delete old system permissions - for(var i = 0; i < systemPermissionsToDelete.length; i++) { - permissionsToRemove.push({ - objectType : "SYSTEM", - permissionType : systemPermissionsToDelete[i] - }); - } - - function completeSaveProcess() { - // Close the modal - userEditModal.deactivate(); - } - - function handleFailure() { - //TODO: Handle the permission API call failure - } - - if(permissionsToAdd.length || permissionsToRemove.length) { - // Make the call to update the permissions - permissionService.patchPermissions( - $scope.user.username, permissionsToAdd, permissionsToRemove) - .success(completeSaveProcess).error(handleFailure); - } else { - completeSaveProcess(); - } - - }); - }; - - $scope.permissions = []; - - // Maps of connection and connection group IDs to access permission booleans - $scope.connectionPermissions = {}; - $scope.connectionGroupPermissions = {}; - $scope.systemPermissions = {}; - - // The original permissions to compare against - var originalConnectionPermissions, - originalConnectionGroupPermissions, - originalSystemPermissions; - - // Get the permissions for the user we are editing - permissionService.getPermissions($scope.user.username).success(function gotPermissions(permissions) { - $scope.permissions = permissions; - - // Figure out if the user has any system level permissions - for(var i = 0; i < $scope.permissions.length; i++) { - var permission = $scope.permissions[i]; - if(permission.objectType === "SYSTEM") { - - $scope.systemPermissions[permission.permissionType] = true; - - // Only READ permission is editable via this UI - } else if (permission.permissionType === "READ") { - switch(permission.objectType) { - case "CONNECTION": - $scope.connectionPermissions[permission.objectIdentifier] = true; - break; - case "CONNECTION_GROUP": - $scope.connectionGroupPermissions[permission.objectIdentifier] = true; - break; - } - } - } - - // Copy the original permissions so we can compare later - originalConnectionPermissions = angular.copy($scope.connectionPermissions); - originalConnectionGroupPermissions = angular.copy($scope.connectionGroupPermissions); - originalSystemPermissions = angular.copy($scope.systemPermissions); - - }); - - /** - * Delete the user and close the modal. - */ - $scope['delete'] = function deleteUser() { - userService.deleteUser($scope.user).success(function successfullyDeletedUser() { - - // Remove the user from the list - $scope.removeUser($scope.user); - - // Close the modal - userEditModal.deactivate(); - }); - } - - /** - * Toggle the open/closed status of the connectionGroup. - * - * @param {object} connectionGroup The connection group to toggle. - */ - $scope.toggleExpanded = function toggleExpanded(connectionGroup) { - connectionGroup.expanded = !connectionGroup.expanded; - }; -}]); - - - diff --git a/guacamole/src/main/webapp/app/manage/directives/connectionParameter.js b/guacamole/src/main/webapp/app/manage/directives/connectionParameter.js index f3f931017..c720ecc17 100644 --- a/guacamole/src/main/webapp/app/manage/directives/connectionParameter.js +++ b/guacamole/src/main/webapp/app/manage/directives/connectionParameter.js @@ -31,55 +31,128 @@ angular.module('manage').directive('guacConnectionParameter', [function connecti restrict: 'E', replace: true, scope: { - parameter: '=parameter', - connection: '=connection', + + /** + * The protocol this parameter is associated with. + * + * @type Protocol + */ + protocol : '=', + + /** + * The unique name of this parameter within the protocol + * definition. + * + * @type String + */ + name : '=', + + /** + * The current map of parameter names to their corresponding string + * values. + * + * @type Object. + */ + parameters : '=' + }, templateUrl: 'app/manage/templates/connectionParameter.html', - controller: ['$scope', function connectionParameterController($scope) { - $scope.connectionParameters = $scope.connection.parameters; - $scope.parameterType = $scope.parameter.type; - $scope.parameterName = $scope.parameter.name; - - // Coerce numeric strings to numbers - if ($scope.parameterType === 'NUMERIC') { - - // If a value exists, coerce it to a number - if ($scope.connectionParameters[$scope.parameterName]) - $scope.parameterValue = Number($scope.connectionParameters[$scope.parameterName]); - else - $scope.parameterValue = null; - - } - - // Coerce boolean strings to boolean values - else if ($scope.parameterType === 'BOOLEAN') { - // TODO: Use defined checked value from protocol description - $scope.parameterValue = $scope.connectionParameters[$scope.parameterName] === 'true'; - } - - // All other parameter types are represented internally as strings - else - $scope.parameterValue = $scope.connectionParameters[$scope.parameterName]; - - // Update internal representation as model is changed - $scope.$watch('parameterValue', function parameterValueChanges(value) { - - // Convert numeric values back into strings - if ($scope.parameterType === 'NUMERIC') { - if (value === null || typeof value === 'undefined') - $scope.connectionParameters[$scope.parameterName] = ''; + controller: ['$scope', '$q', function connectionParameterController($scope, $q) { + + /** + * Deferred load of the parameter definition, pending availability + * of the protocol definition as a whole. + * + * @type Deferred + */ + var parameterDefinitionAvailable = $q.defer(); + + /** + * Populates the parameter definition on the scope as + * $scope.parameter if both the parameter name and + * protocol definition are available. If either are unavailable, + * this function has no effect. + */ + var retrieveParameterDefinition = function retrieveParameterDefinition() { + + // Both name and protocol are needed to retrieve the parameter definition + if (!$scope.name || !$scope.protocol) + return; + + // Once protocol definition is available, locate parameter definition by name + $scope.protocol.parameters.forEach(function findParameter(parameter) { + if (parameter.name === $scope.name) { + $scope.parameter = parameter; + parameterDefinitionAvailable.resolve(parameter); + } + }); + + }; + + // Load parameter definition once protocol definition is available. + $scope.$watch('name', retrieveParameterDefinition); + $scope.$watch('protocol', retrieveParameterDefinition); + + // Update typed value when parameter set is changed + $scope.$watch('parameters', function setParameters(parameters) { + + // Don't bother if no parameters were provided + if (!parameters) + return; + + // Wait for parameter definition + parameterDefinitionAvailable.promise.then(function setTypedValue() { + + // Pull parameter value + var value = parameters[$scope.name]; + + // Coerce numeric strings to numbers + if ($scope.parameter.type === 'NUMERIC') + $scope.typedValue = (value ? Number(value) : null); + + // Coerce boolean strings to boolean values + else if ($scope.parameter.type === 'BOOLEAN') + $scope.typedValue = (value === $scope.parameter.value); + + // All other parameter types are represented internally as strings else - $scope.connectionParameters[$scope.parameterName] = value.toString(); - } - - // TODO: Transform BOOLEAN input fields back into strings based on protocol description - - // All other parameter types are already strings - else - $scope.connectionParameters[$scope.parameterName] = value; - + $scope.typedValue = value || ''; + + }); + }); - }] + + // Update string value in parameter set when typed value is changed + $scope.$watch('typedValue', function typedValueChanged(typedValue) { + + // Don't bother if there's nothing to set + if (!$scope.parameters) + return; + + // Wait for parameter definition + parameterDefinitionAvailable.promise.then(function setValue() { + + // Convert numeric values back into strings + if ($scope.parameter.type === 'NUMERIC') { + if (!typedValue) + $scope.parameters[$scope.name] = ''; + else + $scope.parameters[$scope.name] = typedValue.toString(); + } + + // Convert boolean values back into strings based on protocol description + else if ($scope.parameter.type === 'BOOLEAN') + $scope.parameters[$scope.name] = (typedValue ? $scope.parameter.value : ''); + + // All other parameter types are already strings + else + $scope.parameters[$scope.name] = typedValue || ''; + + }); + + }); // end watch typedValue + + }] // end controller }; }]); \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/manage/directives/locationChooser.js b/guacamole/src/main/webapp/app/manage/directives/locationChooser.js index 9b0961bbf..9f4b912d3 100644 --- a/guacamole/src/main/webapp/app/manage/directives/locationChooser.js +++ b/guacamole/src/main/webapp/app/manage/directives/locationChooser.js @@ -72,13 +72,6 @@ angular.module('manage').directive('locationChooser', [function locationChooser( }; - // Map all known groups - mapConnectionGroups($scope.rootGroup); - - // If no value is specified, default to the root identifier - if (!$scope.value || !($scope.value in connectionGroups)) - $scope.value = $scope.rootGroup.identifier; - /** * Whether the group list menu is currently open. * @@ -92,7 +85,7 @@ angular.module('manage').directive('locationChooser', [function locationChooser( * * @type String */ - $scope.chosenConnectionGroupName = connectionGroups[$scope.value].name; + $scope.chosenConnectionGroupName = null; /** * Toggle the current state of the menu listing connection groups. @@ -125,6 +118,24 @@ angular.module('manage').directive('locationChooser', [function locationChooser( }; + $scope.$watch('rootGroup', function setRootGroup(rootGroup) { + + connectionGroups = {}; + + if (!rootGroup) + return; + + // Map all known groups + mapConnectionGroups(rootGroup); + + // If no value is specified, default to the root identifier + if (!$scope.value || !($scope.value in connectionGroups)) + $scope.value = rootGroup.identifier; + + $scope.chosenConnectionGroupName = connectionGroups[$scope.value].name; + + }); + }] }; diff --git a/guacamole/src/main/webapp/app/manage/services/connectionGroupEditModal.js b/guacamole/src/main/webapp/app/manage/services/connectionGroupEditModal.js deleted file mode 100644 index 70f57903a..000000000 --- a/guacamole/src/main/webapp/app/manage/services/connectionGroupEditModal.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * A modal for editing a connection group. - */ -angular.module('manage').factory('connectionGroupEditModal', ['btfModal', - function connectionGroupEditModal(btfModal) { - - // Create the modal object to be used later to actually create the modal - return btfModal({ - controller: 'connectionGroupEditModalController', - controllerAs: 'modal', - templateUrl: 'app/manage/templates/editableConnectionGroup.html', - }); -}]); diff --git a/guacamole/src/main/webapp/app/manage/styles/buttons.css b/guacamole/src/main/webapp/app/manage/styles/buttons.css index 20f2ce3f0..0e4925855 100644 --- a/guacamole/src/main/webapp/app/manage/styles/buttons.css +++ b/guacamole/src/main/webapp/app/manage/styles/buttons.css @@ -31,7 +31,7 @@ button.add-user { } -button.add-connection { +a.button.add-connection { background-image: url('images/action-icons/guac-monitor-add.png'); background-repeat: no-repeat; @@ -42,7 +42,7 @@ button.add-connection { } -button.add-connection-group { +a.button.add-connection-group { background-image: url('images/action-icons/guac-group-add.png'); background-repeat: no-repeat; diff --git a/guacamole/src/main/webapp/app/manage/services/userEditModal.js b/guacamole/src/main/webapp/app/manage/styles/forms.css similarity index 72% rename from guacamole/src/main/webapp/app/manage/services/userEditModal.js rename to guacamole/src/main/webapp/app/manage/styles/forms.css index 5dd04d8ad..0724562e2 100644 --- a/guacamole/src/main/webapp/app/manage/services/userEditModal.js +++ b/guacamole/src/main/webapp/app/manage/styles/forms.css @@ -20,16 +20,13 @@ * THE SOFTWARE. */ -/** - * A modal for editing a connection. - */ -angular.module('manage').factory('userEditModal', ['btfModal', - function userEditModal(btfModal) { - - // Create the modal object to be used later to actually create the modal - return btfModal({ - controller: 'userEditModalController', - controllerAs: 'modal', - templateUrl: 'app/manage/templates/editableUser.html', - }); -}]); +.manage table.properties th { + text-align: left; + font-weight: normal; + padding-right: 1em; +} + +.manage .action-buttons { + text-align: center; + margin-bottom: 1em; +} diff --git a/guacamole/src/main/webapp/app/manage/services/connectionEditModal.js b/guacamole/src/main/webapp/app/manage/styles/locationChooser.css similarity index 71% rename from guacamole/src/main/webapp/app/manage/services/connectionEditModal.js rename to guacamole/src/main/webapp/app/manage/styles/locationChooser.css index cc251798a..836142270 100644 --- a/guacamole/src/main/webapp/app/manage/services/connectionEditModal.js +++ b/guacamole/src/main/webapp/app/manage/styles/locationChooser.css @@ -20,16 +20,19 @@ * THE SOFTWARE. */ -/** - * A modal for editing a connection. - */ -angular.module('manage').factory('connectionEditModal', ['btfModal', - function connectionEditModal(btfModal) { - - // Create the modal object to be used later to actually create the modal - return btfModal({ - controller: 'connectionEditModalController', - controllerAs: 'modal', - templateUrl: 'app/manage/templates/editableConnection.html', - }); -}]); +.location-chooser .dropdown { + + position: absolute; + z-index: 2; + margin-top: -1px; + + width: 3in; + max-height: 2in; + overflow: auto; + + border: 1px solid rgba(0, 0, 0, 0.5); + background: white; + + font-size: 10pt; + +} diff --git a/guacamole/src/main/webapp/app/manage/templates/connection.html b/guacamole/src/main/webapp/app/manage/templates/connection.html index 578c8b654..b2d1c770b 100644 --- a/guacamole/src/main/webapp/app/manage/templates/connection.html +++ b/guacamole/src/main/webapp/app/manage/templates/connection.html @@ -1,4 +1,4 @@ -
+ -
- - -
-
-
- - - {{item.name}} - + +
+
-
+ + + {{item.name}} + +
diff --git a/guacamole/src/main/webapp/app/manage/templates/connectionGroup.html b/guacamole/src/main/webapp/app/manage/templates/connectionGroup.html index 93acc8148..50408c1eb 100644 --- a/guacamole/src/main/webapp/app/manage/templates/connectionGroup.html +++ b/guacamole/src/main/webapp/app/manage/templates/connectionGroup.html @@ -1,4 +1,4 @@ - + - {{item.name}} - + {{item.name}} + diff --git a/guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html b/guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html new file mode 100644 index 000000000..86ebcaf48 --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/templates/connectionGroupPermission.html @@ -0,0 +1,28 @@ +
+ + + + + {{item.name}} +
diff --git a/guacamole/src/main/webapp/app/manage/templates/connectionParameter.html b/guacamole/src/main/webapp/app/manage/templates/connectionParameter.html index 4249f3a3a..540f7e5fa 100644 --- a/guacamole/src/main/webapp/app/manage/templates/connectionParameter.html +++ b/guacamole/src/main/webapp/app/manage/templates/connectionParameter.html @@ -20,10 +20,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --> - - - - - - + + + + + + + \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/manage/templates/connectionPermission.html b/guacamole/src/main/webapp/app/manage/templates/connectionPermission.html new file mode 100644 index 000000000..37985d723 --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/templates/connectionPermission.html @@ -0,0 +1,36 @@ +
+ + + +
+
+
+ + + + + + {{item.name}} + +
diff --git a/guacamole/src/main/webapp/app/manage/templates/editableConnection.html b/guacamole/src/main/webapp/app/manage/templates/editableConnection.html deleted file mode 100644 index a46883c2d..000000000 --- a/guacamole/src/main/webapp/app/manage/templates/editableConnection.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - -
-
- - -
-

{{connection.name}}

-
- - -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - -
{{'manage.edit.connection.name' | translate}}
{{'manage.edit.connection.location' | translate}} - -
{{'manage.edit.connection.protocol' | translate}} - -
-
- -
- - - - - - - -
{{'protocol.' + connection.protocol + '.parameters.' + parameter.name + '.label' | translate}}: - -
-
- - -
{{'manage.edit.connection.history.usageHistory' | translate}}
- -
-

{{'manage.edit.connection.history.connectionNotUsed' | translate}}

-
- - -
- - - - - - - - - - - - - -
{{'manage.edit.connection.history.username' | translate}}{{'manage.edit.connection.history.startTime' | translate}}{{'manage.edit.connection.history.duration' | translate}}
{{wrapper.entry.username}}{{wrapper.entry.startDate | date:'short'}}{{wrapper.durationText | translate:"{VALUE: wrapper.duration.value, UNIT: wrapper.duration.unit}"}}
-
-
-
-
-
- - - -
-
\ No newline at end of file diff --git a/guacamole/src/main/webapp/app/manage/templates/editableConnectionGroup.html b/guacamole/src/main/webapp/app/manage/templates/editableConnectionGroup.html deleted file mode 100644 index f10a348db..000000000 --- a/guacamole/src/main/webapp/app/manage/templates/editableConnectionGroup.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - -
-
- - -
-

{{connectionGroup.name}}

-
- - -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - -
{{'manage.edit.connectionGroup.name' | translate}}
{{'manage.edit.connectionGroup.location' | translate}} - -
{{'manage.edit.connectionGroup.type.label' | translate}} - -
-
-
-
-
-
- - - -
-
\ No newline at end of file diff --git a/guacamole/src/main/webapp/app/manage/templates/editableUser.html b/guacamole/src/main/webapp/app/manage/templates/editableUser.html deleted file mode 100644 index c85e13c5e..000000000 --- a/guacamole/src/main/webapp/app/manage/templates/editableUser.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - -
-
-
-

{{user.username}}

-
- -
-
-
-
- - -
{{'manage.edit.user.properties' | translate}}
- -
- - - - - - - - - - - - -
{{'manage.edit.user.password' | translate}}
{{'manage.edit.user.passwordMatch' | translate}}
-
- - -
{{'manage.edit.user.permissions' | translate}}
- -
- - - - - - - - - - - - - - - - - - - - - - - - -
{{'manage.edit.user.administerSystem' | translate}}
{{'manage.edit.user.createUser' | translate}}
{{'manage.edit.user.createConnection' | translate}}
{{'manage.edit.user.createConnectionGroup' | translate}}
-
- - -
{{'manage.edit.user.connections' | translate}}
-
-
-
-
-
-
-
-
-
-
-
- - - -
-
\ No newline at end of file diff --git a/guacamole/src/main/webapp/app/manage/templates/locationChooser.html b/guacamole/src/main/webapp/app/manage/templates/locationChooser.html index 8f7b793d5..967e9a36b 100644 --- a/guacamole/src/main/webapp/app/manage/templates/locationChooser.html +++ b/guacamole/src/main/webapp/app/manage/templates/locationChooser.html @@ -1,4 +1,4 @@ -
+
- +
{{chosenConnectionGroupName}}
- + +
diff --git a/guacamole/src/main/webapp/app/manage/templates/manage.html b/guacamole/src/main/webapp/app/manage/templates/manage.html index 9a428e58d..3a45f3f24 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manage.html +++ b/guacamole/src/main/webapp/app/manage/templates/manage.html @@ -40,11 +40,11 @@ THE SOFTWARE.
-
-
+
+
- {{user.username}} + {{user.username}}
@@ -57,14 +57,13 @@ THE SOFTWARE.
- - + {{'manage.newConnection' | translate}} + {{'manage.newGroup' | translate}}
-
+
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageConnection.html b/guacamole/src/main/webapp/app/manage/templates/manageConnection.html new file mode 100644 index 000000000..e204f501c --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/templates/manageConnection.html @@ -0,0 +1,102 @@ + + + + + +

{{'manage.edit.connection.title' | translate}}

+
+ + + + + + + + + + + + + + + + + + + + + + +
{{'manage.edit.connection.name' | translate}}
{{'manage.edit.connection.location' | translate}} + +
{{'manage.edit.connection.protocol' | translate}} + +
+
+ + +

{{'manage.edit.connection.parameters' | translate}}

+
+ + + + + + + +
{{'protocol.' + connection.protocol + '.parameters.' + parameter.name + '.label' | translate}}: + +
+
+ + +
+ + + +
+ + +

{{'manage.edit.connection.history.usageHistory' | translate}}

+
+

{{'manage.edit.connection.history.connectionNotUsed' | translate}}

+ + + + + + + + + + + + + + + +
{{'manage.edit.connection.history.username' | translate}}{{'manage.edit.connection.history.startTime' | translate}}{{'manage.edit.connection.history.duration' | translate}}
{{wrapper.entry.username}}{{wrapper.entry.startDate | date:'short'}}{{wrapper.durationText | translate:"{VALUE: wrapper.duration.value, UNIT: wrapper.duration.unit}"}}
+
diff --git a/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html b/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html new file mode 100644 index 000000000..56a6ddc96 --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html @@ -0,0 +1,65 @@ + + + + + +

{{'manage.edit.connectionGroup.title' | translate}}

+
+ + + + + + + + + + + + + + + + + + + + + + +
{{'manage.edit.connectionGroup.name' | translate}}
{{'manage.edit.connectionGroup.location' | translate}} + +
{{'manage.edit.connectionGroup.type.label' | translate}} + +
+
+ + +
+ + + +
\ No newline at end of file diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUser.html b/guacamole/src/main/webapp/app/manage/templates/manageUser.html new file mode 100644 index 000000000..e11d5224c --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/templates/manageUser.html @@ -0,0 +1,77 @@ + + + + + +

{{'manage.edit.user.title' | translate}}

+
+ + + + + + + + + + + + + + + + +
{{'manage.edit.user.username' | translate}}{{user.username}}
{{'manage.edit.user.password' | translate}}
{{'manage.edit.user.passwordMatch' | translate}}
+
+ + +

{{'manage.edit.user.permissions' | translate}}

+
+ + + + + +
{{systemPermissionType.label | translate}}
+
+ + +

{{'manage.edit.user.connections' | translate}}

+
+ +
+ + +
+ + + +
diff --git a/guacamole/src/main/webapp/app/notification/templates/guacNotification.html b/guacamole/src/main/webapp/app/notification/templates/guacNotification.html index 50d484fb2..a31cff250 100644 --- a/guacamole/src/main/webapp/app/notification/templates/guacNotification.html +++ b/guacamole/src/main/webapp/app/notification/templates/guacNotification.html @@ -41,7 +41,7 @@
- +
diff --git a/guacamole/src/main/webapp/app/notification/types/NotificationAction.js b/guacamole/src/main/webapp/app/notification/types/NotificationAction.js index d0e75cb6d..8a49adc52 100644 --- a/guacamole/src/main/webapp/app/notification/types/NotificationAction.js +++ b/guacamole/src/main/webapp/app/notification/types/NotificationAction.js @@ -35,8 +35,11 @@ angular.module('notification').factory('NotificationAction', [function defineNot * * @param {Function} callback * The callback to call when the user elects to perform this action. + * + * @param {String} className + * The CSS class to associate with this action, if any. */ - var NotificationAction = function NotificationAction(name, callback) { + var NotificationAction = function NotificationAction(name, callback, className) { /** * Reference to this NotificationAction. @@ -45,6 +48,13 @@ angular.module('notification').factory('NotificationAction', [function defineNot */ var action = this; + /** + * The CSS class associated with this action. + * + * @type String + */ + this.className = className; + /** * The name of this action. * diff --git a/guacamole/src/main/webapp/app/rest/services/protocolService.js b/guacamole/src/main/webapp/app/rest/services/protocolService.js index 1a863107a..b03acd53b 100644 --- a/guacamole/src/main/webapp/app/rest/services/protocolService.js +++ b/guacamole/src/main/webapp/app/rest/services/protocolService.js @@ -30,12 +30,12 @@ angular.module('rest').factory('protocolService', ['$http', 'authenticationServi /** * Makes a request to the REST API to get the list of protocols, returning - * a promise that provides an array of @link{Protocol} objects if - * successful. + * a promise that provides a map of @link{Protocol} objects by protocol + * name if successful. * - * @returns {Promise.} - * A promise which will resolve with an array of @link{Protocol} - * objects upon success. + * @returns {Promise.>} + * A promise which will resolve with a map of @link{Protocol} + * objects by protocol name upon success. */ service.getProtocols = function getProtocols() { diff --git a/guacamole/src/main/webapp/app/rest/types/PermissionFlagSet.js b/guacamole/src/main/webapp/app/rest/types/PermissionFlagSet.js new file mode 100644 index 000000000..197752379 --- /dev/null +++ b/guacamole/src/main/webapp/app/rest/types/PermissionFlagSet.js @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * A service for defining the PermissionFlagSet class. + */ +angular.module('rest').factory('PermissionFlagSet', ['PermissionSet', + function definePermissionFlagSet(PermissionSet) { + + /** + * Alternative view of a @link{PermissionSet} which allows manipulation of + * each permission through the setting (or retrieval) of boolean property + * values. + * + * @constructor + * @param {PermissionFlagSet|Object} template + * The object whose properties should be copied within the new + * PermissionFlagSet. + */ + var PermissionFlagSet = function PermissionFlagSet(template) { + + // Use empty object by default + template = template || {}; + + /** + * The granted state of each system permission, as a map of system + * permission type string to boolean value. A particular permission is + * granted if its corresponding boolean value is set to true. Valid + * permission type strings are defined within + * PermissionSet.SystemPermissionType. Permissions which are not + * granted may be set to false, but this is not required. + * + * @type Object. + */ + this.systemPermissions = template.systemPermissions || {}; + + /** + * The granted state of each permission for each connection, as a map + * of object permission type string to permission map. The permission + * map is, in turn, a map of connection identifier to boolean value. A + * particular permission is granted if its corresponding boolean value + * is set to true. Valid permission type strings are defined within + * PermissionSet.ObjectPermissionType. Permissions which are not + * granted may be set to false, but this is not required. + * + * @type Object.> + */ + this.connectionPermissions = template.connectionPermissions || {}; + + /** + * The granted state of each permission for each connection group, as a + * map of object permission type string to permission map. The + * permission map is, in turn, a map of connection group identifier to + * boolean value. A particular permission is granted if its + * corresponding boolean value is set to true. Valid permission type + * strings are defined within PermissionSet.ObjectPermissionType. + * Permissions which are not granted may be set to false, but this is + * not required. + * + * @type Object.> + */ + this.connectionGroupPermissions = template.connectionGroupPermissions || {}; + + /** + * The granted state of each permission for each user, as a map of + * object permission type string to permission map. The permission map + * is, in turn, a map of username to boolean value. A particular + * permission is granted if its corresponding boolean value is set to + * true. Valid permission type strings are defined within + * PermissionSet.ObjectPermissionType. Permissions which are not + * granted may be set to false, but this is not required. + * + * @type Object.> + */ + this.userPermissions = template.userPermissions || {}; + + }; + + var addObjectPermissions = function addObjectPermissions(permMap, flagMap) { + + // For each defined identifier in the permission map + for (var identifier in permMap) { + + // Pull the permission array and loop through each permission + var permissions = permMap[identifier]; + permissions.forEach(function addObjectPermission(type) { + + // Get identifier/flag mapping, creating first if necessary + var objectFlags = flagMap[type] = flagMap[type] || {}; + + // Set flag for current permission + objectFlags[identifier] = true; + + }); + + } + + }; + + /** + * Creates a new PermissionFlagSet, populating it with all the permissions + * indicated as granted within the given PermissionSet. + * + * @param {PermissionSet} permissionSet + * The PermissionSet containing the permissions to be copied into a new + * PermissionFlagSet. + * + * @returns {PermissionFlagSet} + * A new PermissionFlagSet containing flags representing all granted + * permissions from the given PermissionSet. + */ + PermissionFlagSet.fromPermissionSet = function fromPermissionSet(permissionSet) { + + var permissionFlagSet = new PermissionFlagSet(); + + // Add all granted system permissions + permissionSet.systemPermissions.forEach(function addSystemPermission(type) { + permissionFlagSet.systemPermissions[type] = true; + }); + + // Add all granted connection permissions + addObjectPermissions(permissionSet.connectionPermissions, permissionFlagSet.connectionPermissions); + + // Add all granted connection group permissions + addObjectPermissions(permissionSet.connectionGroupPermissions, permissionFlagSet.connectionGroupPermissions); + + // Add all granted user permissions + addObjectPermissions(permissionSet.userPermissions, permissionFlagSet.userPermissions); + + return permissionFlagSet; + + }; + + return PermissionFlagSet; + +}]); \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/rest/types/PermissionSet.js b/guacamole/src/main/webapp/app/rest/types/PermissionSet.js index 740b9437f..869f14523 100644 --- a/guacamole/src/main/webapp/app/rest/types/PermissionSet.js +++ b/guacamole/src/main/webapp/app/rest/types/PermissionSet.js @@ -275,6 +275,284 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet() return permSet.systemPermissions.indexOf(type) !== -1; }; + /** + * Adds the given system permission to the given permission set, if not + * already present. If the permission is already present, this function has + * no effect. + * + * @param {PermissionSet} permSet + * The permission set to modify. + * + * @param {String} type + * The permission to add, as defined by + * PermissionSet.SystemPermissionType. + * + * @returns {Boolean} + * true if the permission was added, false if the permission was + * already present in the given permission set. + */ + PermissionSet.addSystemPermission = function addSystemPermission(permSet, type) { + + // Add permission, if it doesn't already exist + if (permSet.systemPermissions.indexOf(type) === -1) { + permSet.systemPermissions.push(type); + return true; + } + + // Permission already present + return false; + + }; + + /** + * Removes the given system permission from the given permission set, if + * present. If the permission is not present, this function has no effect. + * + * @param {PermissionSet} permSet + * The permission set to modify. + * + * @param {String} type + * The permission to remove, as defined by + * PermissionSet.SystemPermissionType. + * + * @returns {Boolean} + * true if the permission was removed, false if the permission was not + * present in the given permission set. + */ + PermissionSet.removeSystemPermission = function removeSystemPermission(permSet, type) { + + // Remove permission, if it exists + var permLocation = permSet.systemPermissions.indexOf(type); + if (permLocation !== -1) { + permSet.systemPermissions.splice(permLocation, 1); + return true; + } + + // Permission not present + return false; + + }; + + /** + * Adds the given permission applying to the arbitrary object with the + * given ID to the given permission set, if not already present. If the + * permission is already present, this function has no effect. + * + * @param {Object.} permMap + * The permission map to modify, where each entry maps an object + * identifer to the array of granted permissions. + * + * @param {String} type + * The permission to add, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the arbitrary object to which the permission + * applies. + * + * @returns {Boolean} + * true if the permission was added, false if the permission was + * already present in the given permission set. + */ + var addObjectPermission = function addObjectPermission(permMap, type, identifier) { + + // Pull array of permissions, creating it if necessary + var permArray = permMap[identifier] = permMap[identifier] || []; + + // Add permission, if it doesn't already exist + if (permArray.indexOf(type) === -1) { + permArray.push(type); + return true; + } + + // Permission already present + return false; + + }; + + /** + * Removes the given permission applying to the arbitrary object with the + * given ID from the given permission set, if present. If the permission is + * not present, this function has no effect. + * + * @param {Object.} permMap + * The permission map to modify, where each entry maps an object + * identifer to the array of granted permissions. + * + * @param {String} type + * The permission to remove, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the arbitrary object to which the permission + * applies. + * + * @returns {Boolean} + * true if the permission was removed, false if the permission was not + * present in the given permission set. + */ + var removeObjectPermission = function removeObjectPermission(permMap, type, identifier) { + + // Pull array of permissions + var permArray = permMap[identifier]; + + // If no permissions present at all, nothing to remove + if (!(identifier in permMap)) + return false; + + // Remove permission, if it exists + var permLocation = permArray.indexOf(type); + if (permLocation !== -1) { + permArray.splice(permLocation, 1); + return true; + } + + // Permission not present + return false; + + }; + + /** + * Adds the given connection permission applying to the connection with + * the given ID to the given permission set, if not already present. If the + * permission is already present, this function has no effect. + * + * @param {PermissionSet} permSet + * The permission set to modify. + * + * @param {String} type + * The permission to add, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the connection to which the permission applies. + * + * @returns {Boolean} + * true if the permission was added, false if the permission was + * already present in the given permission set. + */ + PermissionSet.addConnectionPermission = function addConnectionPermission(permSet, type, identifier) { + return addObjectPermission(permSet.connectionPermissions, type, identifier); + }; + + /** + * Removes the given connection permission applying to the connection with + * the given ID from the given permission set, if present. If the + * permission is not present, this function has no effect. + * + * @param {PermissionSet} permSet + * The permission set to modify. + * + * @param {String} type + * The permission to remove, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the connection to which the permission applies. + * + * @returns {Boolean} + * true if the permission was removed, false if the permission was not + * present in the given permission set. + */ + PermissionSet.removeConnectionPermission = function removeConnectionPermission(permSet, type, identifier) { + return removeObjectPermission(permSet.connectionPermissions, type, identifier); + }; + + /** + * Adds the given connection group permission applying to the connection + * group with the given ID to the given permission set, if not already + * present. If the permission is already present, this function has no + * effect. + * + * @param {PermissionSet} permSet + * The permission set to modify. + * + * @param {String} type + * The permission to add, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the connection group to which the permission + * applies. + * + * @returns {Boolean} + * true if the permission was added, false if the permission was + * already present in the given permission set. + */ + PermissionSet.addConnectionGroupPermission = function addConnectionGroupPermission(permSet, type, identifier) { + return addObjectPermission(permSet.connectionGroupPermissions, type, identifier); + }; + + /** + * Removes the given connection group permission applying to the connection + * group with the given ID from the given permission set, if present. If + * the permission is not present, this function has no effect. + * + * @param {PermissionSet} permSet + * The permission set to modify. + * + * @param {String} type + * The permission to remove, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the connection group to which the permission + * applies. + * + * @returns {Boolean} + * true if the permission was removed, false if the permission was not + * present in the given permission set. + */ + PermissionSet.removeConnectionGroupPermission = function removeConnectionGroupPermission(permSet, type, identifier) { + return removeObjectPermission(permSet.connectionGroupPermissions, type, identifier); + }; + + /** + * Adds the given user permission applying to the user with the given ID to + * the given permission set, if not already present. If the permission is + * already present, this function has no effect. + * + * @param {PermissionSet} permSet + * The permission set to modify. + * + * @param {String} type + * The permission to add, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the user to which the permission applies. + * + * @returns {Boolean} + * true if the permission was added, false if the permission was + * already present in the given permission set. + */ + PermissionSet.addUserPermission = function addUserPermission(permSet, type, identifier) { + return addObjectPermission(permSet.userPermissions, type, identifier); + }; + + /** + * Removes the given user permission applying to the user with the given ID + * from the given permission set, if present. If the permission is not + * present, this function has no effect. + * + * @param {PermissionSet} permSet + * The permission set to modify. + * + * @param {String} type + * The permission to remove, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the user to whom the permission applies. + * + * @returns {Boolean} + * true if the permission was removed, false if the permission was not + * present in the given permission set. + */ + PermissionSet.removeUserPermission = function removeUserPermission(permSet, type, identifier) { + return removeObjectPermission(permSet.userPermissions, type, identifier); + }; + return PermissionSet; }]); \ No newline at end of file diff --git a/guacamole/src/main/webapp/translations/en_US.json b/guacamole/src/main/webapp/translations/en_US.json index 16d282093..de2127c9a 100644 --- a/guacamole/src/main/webapp/translations/en_US.json +++ b/guacamole/src/main/webapp/translations/en_US.json @@ -39,16 +39,22 @@ "newGroup" : "New Group", "edit": { "connection": { + "title" : "Edit Connection", "cancel" : "Cancel", "save" : "Save", "delete" : "Delete", + "confirmDelete" : { + "title" : "Delete Connection", + "text" : "Connections cannot be restored after they have been deleted. Are you sure you want to delete this connection?" + }, "protocol" : "Protocol:", "root" : "ROOT", "location" : "Location:", "name" : "Name:", + "parameters" : "Parameters", "history" : { "connectionNotUsed" : "This connection has not yet been used.", - "usageHistory" : "Usage History:", + "usageHistory" : "Usage History", "username" : "Username", "startTime" : "Start Time", "duration" : "Duration", @@ -58,9 +64,14 @@ } }, "connectionGroup": { + "title" : "Edit Connection Group", "cancel" : "Cancel", "save" : "Save", "delete" : "Delete", + "confirmDelete" : { + "title" : "Delete Connection", + "text" : "Connection groups cannot be restored after they have been deleted. Are you sure you want to delete this connection group?" + }, "usageHistory" : "Usage History:", "type" : { "label" : "Type", @@ -72,18 +83,30 @@ "name" : "Name:" }, "user": { + "title" : "Edit User", "cancel" : "Cancel", "save" : "Save", "delete" : "Delete", - "properties" : "Properties:", + "confirmDelete" : { + "title" : "Delete User", + "text" : "Users cannot be restored after they have been deleted. Are you sure you want to delete this user?" + }, "password" : "Password:", "passwordMatch" : "Re-enter Password:", - "permissions" : "Permissions:", + "passwordMismatch" : "The provided passwords do not match.", + "permissions" : "Permissions", + "username" : "Username:", "administerSystem" : "Administer system:", "createUser" : "Create new users:", "createConnection" : "Create new connections:", "createConnectionGroup" : "Create new connection groups:", - "connections" : "Connections:" + "connections" : "Connections" + } + }, + "error": { + "title" : "Error", + "action": { + "acknowledge" : "OK" } } },