Merge pull request #28 from glyptodon/cleanup-angular

GUAC-932: Finish cleanup and restoration of feature set.
This commit is contained in:
Mike Jumper
2014-12-25 00:57:33 -08:00
43 changed files with 2114 additions and 1291 deletions

View File

@@ -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<String, User> 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<String, Connection> 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<String, ConnectionGroup> directory = rootGroup.getConnectionGroupDirectory();
ConnectionGroup connectionGroup = directory.get(identifier);
if (connectionGroup == null)
throw new GuacamoleResourceNotFoundException("No such connection group: \"" + identifier + "\"");
return connectionGroup;
}
}

View File

@@ -38,6 +38,7 @@ public class RESTModule extends AbstractModule {
// Bind generic low-level services
bind(ProtocolRetrievalService.class);
bind(ObjectRetrievalService.class);
}

View File

@@ -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<String, Connection> 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<String, Connection> 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<String, Connection> 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<String, Connection> 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<String, ConnectionGroup> 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<String, Connection> connectionDirectory = parentConnectionGroup.getConnectionDirectory();
@@ -340,12 +289,10 @@ public class ConnectionRESTService {
Directory<String, Connection> 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());
}
}

View File

@@ -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();

View File

@@ -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<String, ConnectionGroup> 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<String, ConnectionGroup> 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<String, ConnectionGroup> 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<String, ConnectionGroup> 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<String, ConnectionGroup> 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());
}

View File

@@ -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<String, User> 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)

View File

@@ -25,7 +25,9 @@
<!-- Connection -->
<div class="connection" ng-show="item.isConnection">
<ng-include src="connectionTemplate"/>
<div class="caption">
<ng-include src="connectionTemplate"/>
</div>
</div>
<!-- Connection group -->

View File

@@ -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: '/'
});
}]);

View File

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

View File

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

View File

@@ -72,8 +72,8 @@ h2 ~ h2 {
}
div.section {
margin: 0;
padding: 1em;
margin: 1em;
padding: 0;
}
/*

View File

@@ -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();
});
};
}]);

View File

@@ -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();
});
}
}]);

View File

@@ -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]
});
};
}]);

View File

@@ -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]
});
};
}]);

View File

@@ -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 = "";
};
}]);

View File

@@ -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]
});
};
}]);

View File

@@ -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;
};
}]);

View File

@@ -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.<String, String>
*/
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
* <code>$scope.parameter</code> 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
};
}]);

View File

@@ -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;
});
}]
};

View File

@@ -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',
});
}]);

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
<div ng-click="context.editConnection(item.wrappedItem)">
<a ng-href="#/manage/connections/{{item.identifier}}">
<!--
Copyright (C) 2014 Glyptodon LLC
@@ -21,15 +21,12 @@
THE SOFTWARE.
-->
<div class="caption">
<!-- Connection icon -->
<div class="protocol">
<div class="icon type" ng-class="item.protocol"></div>
</div>
<!-- Connection name -->
<span class="name">{{item.name}}</span>
<!-- Connection icon -->
<div class="protocol">
<div class="icon type" ng-class="item.protocol"></div>
</div>
</div>
<!-- Connection name -->
<span class="name">{{item.name}}</span>
</a>

View File

@@ -1,4 +1,4 @@
<span class="name" ng-click="context.editConnectionGroup(item.wrappedItem)">
<a ng-href="#/manage/connectionGroups/{{item.identifier}}">
<!--
Copyright (C) 2014 Glyptodon LLC
@@ -21,5 +21,5 @@
THE SOFTWARE.
-->
{{item.name}}
</span>
<span class="name">{{item.name}}</span>
</a>

View File

@@ -0,0 +1,28 @@
<div class="choice">
<!--
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.
-->
<input type="checkbox" ng-model="context.getPermissionFlags().connectionGroupPermissions.READ[item.identifier]"
ng-change="context.connectionGroupPermissionChanged(item.identifier)"/>
<span class="name">{{item.name}}</span>
</div>

View File

@@ -20,10 +20,11 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<input ng-show="parameterType === 'TEXT'" type="text" ng-model="parameterValue"/>
<input ng-show="parameterType === 'NUMERIC'" type="number" ng-model="parameterValue"/>
<input ng-show="parameterType === 'USERNAME'" type="text" ng-model="parameterValue"/>
<input ng-show="parameterType === 'PASSWORD'" type="password" ng-model="parameterValue"/>
<input ng-show="parameterType === 'BOOLEAN'" type="checkbox" ng-model="parameterValue"/>
<select ng-show="parameterType === 'ENUM'" ng-model="parameterValue" ng-options="option.value as 'protocol.' + connection.protocol + '.parameters.' + parameter.name + '.options.' + (option.value || 'empty') | translate for option in parameter.options | orderBy: value"></select>
<input ng-show="parameter.type === 'TEXT'" type="text" ng-model="typedValue"/>
<input ng-show="parameter.type === 'NUMERIC'" type="number" ng-model="typedValue"/>
<input ng-show="parameter.type === 'USERNAME'" type="text" ng-model="typedValue"/>
<input ng-show="parameter.type === 'PASSWORD'" type="password" ng-model="typedValue"/>
<input ng-show="parameter.type === 'BOOLEAN'" type="checkbox" ng-model="typedValue"/>
<select ng-show="parameter.type === 'ENUM'" ng-model="typedValue" ng-options="option.value as 'protocol.' + protocol.name + '.parameters.' + parameter.name + '.options.' + (option.value || 'empty') | translate for option in parameter.options | orderBy: value"></select>
</span>

View File

@@ -0,0 +1,36 @@
<div class="choice">
<!--
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.
-->
<!-- Connection icon -->
<div class="protocol">
<div class="icon type" ng-class="item.protocol"></div>
</div>
<!-- Checkbox -->
<input type="checkbox" ng-model="context.getPermissionFlags().connectionPermissions.READ[item.identifier]"
ng-change="context.connectionPermissionChanged(item.identifier)"/>
<!-- Connection name -->
<span class="name">{{item.name}}</span>
</div>

View File

@@ -1,117 +0,0 @@
<!--
Copyright 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.
-->
<!-- Dialog container for the modal -->
<div class="dialog-container">
<div class="dialog edit">
<!-- Connection name -->
<div class="header">
<h2>{{connection.name}}</h2>
</div>
<!-- Main connection edit section -->
<div class="body">
<div class="form">
<div class="settings section">
<dl>
<dd>
<table class="fields section">
<!-- Edit connection name -->
<tr>
<th>{{'manage.edit.connection.name' | translate}}</th>
<td><input type="text" ng-model="connection.name"/></td>
</tr>
<!-- Edit connection location -->
<tr>
<th>{{'manage.edit.connection.location' | translate}}</th>
<td>
<location-chooser value="connection.parentIdentifier" root-group="rootGroup"/>
</td>
</tr>
<!-- Edit connection protocol -->
<tr>
<th>{{'manage.edit.connection.protocol' | translate}}</th>
<td>
<select ng-model="connection.protocol" ng-options="name as 'protocol.' + protocol.name + '.label' | translate for (name, protocol) in protocols | orderBy: name"></select>
</td>
</tr>
</table>
</dd>
<dd>
<table class="fields section">
<!-- All the different possible editable field types -->
<tr ng-repeat="parameter in protocols[connection.protocol].parameters">
<th>{{'protocol.' + connection.protocol + '.parameters.' + parameter.name + '.label' | translate}}:</th>
<td>
<guac-connection-parameter parameter="parameter" connection="connection" />
</td>
</tr>
</table>
</dd>
<!-- History connection area -->
<dt>{{'manage.edit.connection.history.usageHistory' | translate}}</dt>
<dd ng-hide="connection.history.length">
<p>{{'manage.edit.connection.history.connectionNotUsed' | translate}}</p>
</dd>
<!-- History connection list -->
<dd ng-show="connection.history.length">
<table class="history section">
<tr>
<th>{{'manage.edit.connection.history.username' | translate}}</th>
<th>{{'manage.edit.connection.history.startTime' | translate}}</th>
<th>{{'manage.edit.connection.history.duration' | translate}}</th>
</tr>
<tbody>
<tr ng-repeat="wrapper in historyEntryWrappers">
<td class="username">{{wrapper.entry.username}}</td>
<td class="start">{{wrapper.entry.startDate | date:'short'}}</td>
<td class="duration">{{wrapper.durationText | translate:"{VALUE: wrapper.duration.value, UNIT: wrapper.duration.unit}"}}</td>
</tr>
</tbody>
</table>
</dd>
</dl>
</div>
</div>
</div>
<!-- Control buttons -->
<div class="footer">
<button ng-click="save()">{{'manage.edit.connection.save' | translate}}</button>
<button ng-click="close()">{{'manage.edit.connection.cancel' | translate}}</button>
<button ng-click="delete()" class="danger">{{'manage.edit.connection.delete' | translate}}</button>
</div>
</div>
</div>

View File

@@ -1,79 +0,0 @@
<!--
Copyright 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.
-->
<!-- Dialog container for the modal -->
<div class="dialog-container">
<div class="dialog edit">
<!-- Connection group name -->
<div class="header">
<h2>{{connectionGroup.name}}</h2>
</div>
<!-- Main connection group edit section -->
<div class="body">
<div class="form">
<div class="settings section">
<dl>
<dd>
<table class="fields section">
<!-- Edit connection group name -->
<tr>
<th>{{'manage.edit.connectionGroup.name' | translate}}</th>
<td><input type="text" ng-model="connectionGroup.name"/></td>
</tr>
<!-- Edit connection group location -->
<tr>
<th>{{'manage.edit.connectionGroup.location' | translate}}</th>
<td>
<location-chooser value="connectionGroup.parentIdentifier" root-group="rootGroup"/>
</td>
</tr>
<!-- Edit connection group type -->
<tr>
<th>{{'manage.edit.connectionGroup.type.label' | translate}}</th>
<td>
<select ng-model="connectionGroup.type" ng-options="type.value as 'manage.edit.connectionGroup.type.' + type.label | translate for type in types | orderBy: name"></select>
</td>
</tr>
</table>
</dd>
</dl>
</div>
</div>
</div>
<!-- Control buttons -->
<div class="footer">
<button ng-click="save()">{{'manage.edit.connectionGroup.save' | translate}}</button>
<button ng-click="close()">{{'manage.edit.connectionGroup.cancel' | translate}}</button>
<button ng-click="delete()" class="danger">{{'manage.edit.connectionGroup.delete' | translate}}</button>
</div>
</div>
</div>

View File

@@ -1,141 +0,0 @@
<!--
Copyright 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.
-->
<!-- Hierarchical connection and connection group permission selector -->
<script type="text/ng-template" id="nestedUserPermissionEditGroup.html">
<!-- Connection -->
<div class="choice" ng-show="item.isConnection">
<input type="checkbox" ng-model="connectionPermissions[item.identifier]" ng-change="markConnectionPermissionModified(item.identifier)"/>
<div class="connection list-item">
<div class="caption">
<div class="protocol">
<div class="icon" ng-class="item.protocol"></div>
</div><span class="name">{{item.name}}</span>
</div>
</div>
</div>
<!-- Connection group -->
<div class="choice" ng-show="!item.isConnection">
<input type="checkbox" ng-model="connectionGroupPermissions[item.identifier]" ng-change="markConnectionGroupPermissionModified(item.identifier)"/>
<div class="group empty list-item balancer">
<div class="caption">
<div class="icon group" ng-click="toggleExpanded(item)" ng-class="{expanded: item.expanded, empty: !item.children.length, balancer: item.balancer && !item.children.length}"></div>
<span class="name">{{item.name}}</span>
</div>
<!-- Connection group children -->
<div class="children" ng-show="item.expanded">
<div ng-repeat="item in item.children | orderBy : 'name'" ng-include="'nestedUserPermissionEditGroup.html'">
</div>
</div>
</div>
</script>
<!-- User edit modal -->
<div class="dialog-container">
<div class="dialog edit">
<div class="header">
<h2>{{user.username}}</h2>
</div>
<div class="body">
<div class="form">
<div class="settings section">
<dl>
<!-- User properties section -->
<dt>{{'manage.edit.user.properties' | translate}}</dt>
<dd>
<table class="fields section">
<tr>
<th>{{'manage.edit.user.password' | translate}}</th>
<td><input ng-model="user.password" type="password" /></td>
</tr>
<tr>
<th>{{'manage.edit.user.passwordMatch' | translate}}</th>
<td><input ng-model="passwordMatch" type="password" /></td>
</tr>
</table>
</dd>
<!-- System permissions section -->
<dt>{{'manage.edit.user.permissions' | translate}}</dt>
<dd>
<table class="permissions section">
<tr>
<th>{{'manage.edit.user.administerSystem' | translate}}</th>
<td><input type="checkbox" ng-model="systemPermissions.ADMINISTER" ng-change="markSystemPermissionModified('ADMINISTER')" /></td>
</tr>
<tr>
<th>{{'manage.edit.user.createUser' | translate}}</th>
<td><input type="checkbox" ng-model="systemPermissions.CREATE_USER" ng-change="markSystemPermissionModified('CREATE_USER')" /></td>
</tr>
<tr>
<th>{{'manage.edit.user.createConnection' | translate}}</th>
<td><input type="checkbox" ng-model="systemPermissions.CREATE_CONNECTION" ng-change="markSystemPermissionModified('CREATE_CONNECTION')" /></td>
</tr>
<tr>
<th>{{'manage.edit.user.createConnectionGroup' | translate}}</th>
<td><input type="checkbox" ng-model="systemPermissions.CREATE_CONNECTION_GROUP" ng-change="markSystemPermissionModified('CREATE_CONNECTION_GROUP')" /></td>
</tr>
</table>
</dd>
<!-- Connection and connection group permission section -->
<dt>{{'manage.edit.user.connections' | translate}}</dt>
<dd>
<div class="group-view">
<div class="list">
<div ng-repeat="item in rootGroup.children | orderBy : 'name'" ng-include="'nestedUserPermissionEditGroup.html'"></div>
</div>
</div>
</dd>
</dl>
</div>
</div>
</div>
<!-- Form controls -->
<div class="footer">
<button ng-click="save()">{{'manage.edit.user.save' | translate}}</button>
<button ng-click="close()">{{'manage.edit.user.cancel' | translate}}</button>
<button ng-click="delete()" class="danger">{{'manage.edit.user.delete' | translate}}</button>
</div>
</div>
</div>

View File

@@ -1,4 +1,4 @@
<div>
<div class="location-chooser">
<!--
Copyright 2014 Glyptodon LLC.
@@ -21,10 +21,10 @@
THE SOFTWARE.
-->
<!-- Open the dropdown -->
<!-- Chosen group name -->
<div ng-click="toggleMenu()" class="location">{{chosenConnectionGroupName}}</div>
<!-- Dropdown hierarchical menu of groups -->
<div ng-show="menuOpen" class="dropdown">
<guac-group-list
context="groupListContext"
@@ -32,4 +32,5 @@
connection-group="rootGroup"
connection-group-template="'app/manage/templates/locationChooserConnectionGroup.html'"/>
</div>
</div>

View File

@@ -40,11 +40,11 @@ THE SOFTWARE.
</div>
<!-- List of users this user has access to -->
<div class="user-list" ng-class="{loading: loadingUsers}">
<div ng-click="editUser(user)" ng-repeat="user in users | orderBy : 'username'" class="list-item">
<div class="user-list" ng-class="{loading: !users}">
<div ng-repeat="user in users | orderBy : 'username'" class="user list-item">
<div class="caption">
<div class="icon user"></div>
<span class="name">{{user.username}}</span>
<a class="name" ng-href="#/manage/users/{{user.username}}">{{user.username}}</a>
</div>
</div>
</div>
@@ -57,14 +57,13 @@ THE SOFTWARE.
<!-- Control to create a new connection or group -->
<div class="connection-add-form">
<button ng-click="newConnection()" class="add-connection">{{'manage.newConnection' | translate}}</button>
<button ng-click="newConnectionGroup()" class="add-connection-group">{{'manage.newGroup' | translate}}</button>
<a class="add-connection button" href="#/manage/connections/">{{'manage.newConnection' | translate}}</a>
<a class="add-connection-group button" href="#/manage/connectionGroups/">{{'manage.newGroup' | translate}}</a>
</div>
<!-- List of connections and groups this user has access to -->
<div class="connection-list" ng-class="{loading: loadingConnections}">
<div class="connection-list" ng-class="{loading: !rootGroup}">
<guac-group-list
context="groupListContext"
connection-group="rootGroup"
connection-template="'app/manage/templates/connection.html'"
connection-group-template="'app/manage/templates/connectionGroup.html'"/>

View File

@@ -0,0 +1,102 @@
<!--
Copyright 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.
-->
<div class="logout-panel">
<a class="back button" href="#/manage/">{{'manage.back' | translate}}</a>
<a class="logout button" ng-click="logout()">{{'home.logout' | translate}}</a>
</div>
<!-- Main property editor -->
<h2>{{'manage.edit.connection.title' | translate}}</h2>
<div class="section">
<table class="properties">
<!-- Edit connection name -->
<tr>
<th>{{'manage.edit.connection.name' | translate}}</th>
<td><input type="text" ng-model="connection.name"/></td>
</tr>
<!-- Edit connection location -->
<tr>
<th>{{'manage.edit.connection.location' | translate}}</th>
<td>
<location-chooser value="connection.parentIdentifier" root-group="rootGroup"></location-chooser>
</td>
</tr>
<!-- Edit connection protocol -->
<tr>
<th>{{'manage.edit.connection.protocol' | translate}}</th>
<td>
<select ng-model="connection.protocol" ng-options="name as 'protocol.' + protocol.name + '.label' | translate for (name, protocol) in protocols | orderBy: name"></select>
</td>
</tr>
</table>
</div>
<!-- Connection parameters -->
<h2>{{'manage.edit.connection.parameters' | translate}}</h2>
<div class="section" ng-class="{loading: !parameters}">
<table class="properties">
<!-- All the different possible editable field types -->
<tr ng-repeat="parameter in protocols[connection.protocol].parameters">
<th>{{'protocol.' + connection.protocol + '.parameters.' + parameter.name + '.label' | translate}}:</th>
<td>
<guac-connection-parameter protocol="protocols[connection.protocol]" name="parameter.name" parameters="parameters"/>
</td>
</tr>
</table>
</div>
<!-- Form action buttons -->
<div class="action-buttons">
<button ng-click="saveConnection()">{{'manage.edit.connection.save' | translate}}</button>
<button ng-click="cancel()">{{'manage.edit.connection.cancel' | translate}}</button>
<button ng-click="deleteConnection()" class="danger">{{'manage.edit.connection.delete' | translate}}</button>
</div>
<!-- Connection history -->
<h2>{{'manage.edit.connection.history.usageHistory' | translate}}</h2>
<div class="history section" ng-class="{loading: !historyEntryWrappers}">
<p ng-hide="historyEntryWrappers.length">{{'manage.edit.connection.history.connectionNotUsed' | translate}}</p>
<table ng-show="historyEntryWrappers.length">
<thead>
<tr>
<th>{{'manage.edit.connection.history.username' | translate}}</th>
<th>{{'manage.edit.connection.history.startTime' | translate}}</th>
<th>{{'manage.edit.connection.history.duration' | translate}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="wrapper in historyEntryWrappers">
<td class="username">{{wrapper.entry.username}}</td>
<td class="start">{{wrapper.entry.startDate | date:'short'}}</td>
<td class="duration">{{wrapper.durationText | translate:"{VALUE: wrapper.duration.value, UNIT: wrapper.duration.unit}"}}</td>
</tr>
</tbody>
</table>
</div>

View File

@@ -0,0 +1,65 @@
<!--
Copyright 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.
-->
<div class="logout-panel">
<a class="back button" href="#/manage/">{{'manage.back' | translate}}</a>
<a class="logout button" ng-click="logout()">{{'home.logout' | translate}}</a>
</div>
<!-- Main property editor -->
<h2>{{'manage.edit.connectionGroup.title' | translate}}</h2>
<div class="section">
<table class="properties">
<!-- Edit connection group name -->
<tr>
<th>{{'manage.edit.connectionGroup.name' | translate}}</th>
<td><input type="text" ng-model="connectionGroup.name"/></td>
</tr>
<!-- Edit connection group location -->
<tr>
<th>{{'manage.edit.connectionGroup.location' | translate}}</th>
<td>
<location-chooser value="connectionGroup.parentIdentifier" root-group="rootGroup"/>
</td>
</tr>
<!-- Edit connection group type -->
<tr>
<th>{{'manage.edit.connectionGroup.type.label' | translate}}</th>
<td>
<select ng-model="connectionGroup.type" ng-options="type.value as 'manage.edit.connectionGroup.type.' + type.label | translate for type in types | orderBy: name"></select>
</td>
</tr>
</table>
</div>
<!-- Form action buttons -->
<div class="action-buttons">
<button ng-click="saveConnectionGroup()">{{'manage.edit.connectionGroup.save' | translate}}</button>
<button ng-click="cancel()">{{'manage.edit.connectionGroup.cancel' | translate}}</button>
<button ng-click="deleteConnectionGroup()" class="danger">{{'manage.edit.connectionGroup.delete' | translate}}</button>
</div>

View File

@@ -0,0 +1,77 @@
<!--
Copyright 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.
-->
<div class="logout-panel">
<a class="back button" href="#/manage/">{{'manage.back' | translate}}</a>
<a class="logout button" ng-click="logout()">{{'home.logout' | translate}}</a>
</div>
<!-- Main property editor -->
<h2>{{'manage.edit.user.title' | translate}}</h2>
<div class="section">
<table class="properties">
<tr>
<th>{{'manage.edit.user.username' | translate}}</th>
<td>{{user.username}}</td>
</tr>
<tr>
<th>{{'manage.edit.user.password' | translate}}</th>
<td><input ng-model="user.password" type="password" /></td>
</tr>
<tr>
<th>{{'manage.edit.user.passwordMatch' | translate}}</th>
<td><input ng-model="passwordMatch" type="password" /></td>
</tr>
</table>
</div>
<!-- System permissions section -->
<h2>{{'manage.edit.user.permissions' | translate}}</h2>
<div class="section">
<table class="properties">
<tr ng-repeat="systemPermissionType in systemPermissionTypes">
<th>{{systemPermissionType.label | translate}}</th>
<td><input type="checkbox" ng-model="permissionFlags.systemPermissions[systemPermissionType.value]"
ng-change="systemPermissionChanged(systemPermissionType.value)"/></td>
</tr>
</table>
</div>
<!-- Connection and connection group permission section -->
<h2>{{'manage.edit.user.connections' | translate}}</h2>
<div class="section" ng-class="{loading: !rootGroup}">
<guac-group-list
context="groupListContext"
connection-group="rootGroup"
connection-template="'app/manage/templates/connectionPermission.html'"
connection-group-template="'app/manage/templates/connectionGroupPermission.html'"/>
</div>
<!-- Form action buttons -->
<div class="action-buttons">
<button ng-click="saveUser()">{{'manage.edit.user.save' | translate}}</button>
<button ng-click="cancel()">{{'manage.edit.user.cancel' | translate}}</button>
<button ng-click="deleteUser()" class="danger">{{'manage.edit.user.delete' | translate}}</button>
</div>

View File

@@ -41,7 +41,7 @@
<!-- Buttons -->
<div ng-show="notification.actions.length" class="buttons">
<button ng-repeat="action in notification.actions" ng-click="action.callback()">{{action.name | translate}}</button>
<button ng-repeat="action in notification.actions" ng-click="action.callback()" ng-class="action.className">{{action.name | translate}}</button>
</div>
</div>

View File

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

View File

@@ -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.<Protocol[]>}
* A promise which will resolve with an array of @link{Protocol}
* objects upon success.
* @returns {Promise.<Object.<String, Protocol>>}
* A promise which will resolve with a map of @link{Protocol}
* objects by protocol name upon success.
*/
service.getProtocols = function getProtocols() {

View File

@@ -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.<String, Boolean>
*/
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.<String, Object.<String, Boolean>>
*/
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.<String, Object.<String, Boolean>>
*/
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.<String, Object.<String, Boolean>>
*/
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;
}]);

View File

@@ -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.<String, String[]>} 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.<String, String[]>} 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;
}]);

View File

@@ -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"
}
}
},