GUAC-1132: Implement session deletion within management UI.

This commit is contained in:
Michael Jumper
2015-03-19 12:49:03 -07:00
parent ea7e88279e
commit dd78341cbd
5 changed files with 129 additions and 46 deletions

View File

@@ -25,20 +25,24 @@ package org.glyptodon.guacamole.net.basic.rest.tunnel;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import org.glyptodon.guacamole.GuacamoleClientException;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleUnsupportedException;
import org.glyptodon.guacamole.net.GuacamoleTunnel;
import org.glyptodon.guacamole.net.auth.ConnectionRecord;
import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.net.basic.rest.APIPatch;
import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure;
import org.glyptodon.guacamole.net.basic.rest.PATCH;
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -73,7 +77,8 @@ public class TunnelRESTService {
* performing the operation.
*
* @return
* The tunnels of all active connections visible to the current user.
* A map of the tunnels of all active connections visible to the
* current user, where the key of each entry is the tunnel's UUID.
*
* @throws GuacamoleException
* If an error occurs while retrieving the tunnels.
@@ -81,19 +86,21 @@ public class TunnelRESTService {
@GET
@Path("/")
@AuthProviderRESTExposure
public List<APITunnel> getTunnels(@QueryParam("token") String authToken)
public Map<String, APITunnel> getTunnels(@QueryParam("token") String authToken)
throws GuacamoleException {
UserContext userContext = authenticationService.getUserContext(authToken);
// Retrieve all active tunnels
List<APITunnel> apiTunnels = new ArrayList<APITunnel>();
Map<String, APITunnel> apiTunnels = new HashMap<String, APITunnel>();
for (ConnectionRecord record : userContext.getActiveConnections()) {
// Locate associated tunnel and UUID
GuacamoleTunnel tunnel = record.getTunnel();
if (tunnel != null)
apiTunnels.add(new APITunnel(record, tunnel.getUUID().toString()));
if (tunnel != null) {
APITunnel apiTunnel = new APITunnel(record, tunnel.getUUID().toString());
apiTunnels.put(apiTunnel.getUUID(), apiTunnel);
}
}
@@ -109,24 +116,41 @@ public class TunnelRESTService {
* The authentication token that is used to authenticate the user
* performing the operation.
*
* @param tunnelUUIDs
* The UUIDs associated with the tunnels being deleted.
* @param patches
* The tunnel patches to apply for this request.
*
* @throws GuacamoleException
* If an error occurs while deleting the tunnels.
*/
@DELETE
@PATCH
@Path("/")
@AuthProviderRESTExposure
public void deleteTunnels(@QueryParam("token") String authToken,
@QueryParam("tunnelUUID") Collection<String> tunnelUUIDs)
throws GuacamoleException {
public void patchTunnels(@QueryParam("token") String authToken,
List<APIPatch<String>> patches) throws GuacamoleException {
// Attempt to get all requested tunnels
UserContext userContext = authenticationService.getUserContext(authToken);
Collection<ConnectionRecord> records = userContext.getActiveConnections(tunnelUUIDs);
// Build list of tunnels to delete
Collection<String> tunnelUUIDs = new ArrayList<String>(patches.size());
for (APIPatch<String> patch : patches) {
// Only remove is supported
if (patch.getOp() != APIPatch.Operation.remove)
throw new GuacamoleUnsupportedException("Only the \"remove\" operation is supported when patching tunnels.");
// Retrieve and validate path
String path = patch.getPath();
if (!path.startsWith("/"))
throw new GuacamoleClientException("Patch paths must start with \"/\".");
// Add UUID
tunnelUUIDs.add(path.substring(1));
}
// Close each tunnel, if not already closed
Collection<ConnectionRecord> records = userContext.getActiveConnections(tunnelUUIDs);
for (ConnectionRecord record : records) {
GuacamoleTunnel tunnel = record.getTunnel();

View File

@@ -74,11 +74,11 @@ angular.module('manage').controller('manageSessionsController', ['$scope', '$inj
$scope.connections = {};
/**
* The count of currently selected tunnel wrappers.
* Map of all currently-selected tunnel wrappers by UUID.
*
* @type Number
* @type Object.<String, ActiveTunnelWrapper>
*/
var selectedWrapperCount = 0;
var selectedWrappers = {};
/**
* Adds the given connection to the internal set of visible
@@ -126,9 +126,9 @@ angular.module('manage').controller('manageSessionsController', ['$scope', '$inj
// Wrap all active tunnels for sake of display
$scope.wrappers = [];
tunnels.forEach(function wrapActiveTunnel(tunnel) {
$scope.wrappers.push(new ActiveTunnelWrapper(tunnel));
});
for (var tunnelUUID in tunnels) {
$scope.wrappers.push(new ActiveTunnelWrapper(tunnels[tunnelUUID]));
}
});
@@ -147,12 +147,24 @@ angular.module('manage').controller('manageSessionsController', ['$scope', '$inj
};
/**
* An action to be provided along with the object sent to showStatus which
* closes the currently-shown status dialog.
*/
var ACKNOWLEDGE_ACTION = {
name : "MANAGE_SESSION.ACTION_ACKNOWLEDGE",
// Handle action
callback : function acknowledgeCallback() {
guacNotification.showStatus(false);
}
};
/**
* An action to be provided along with the object sent to showStatus which
* closes the currently-shown status dialog.
*/
var CANCEL_ACTION = {
name : "MANAGE_USER.ACTION_CANCEL",
name : "MANAGE_SESSION.ACTION_CANCEL",
// Handle action
callback : function cancelCallback() {
guacNotification.showStatus(false);
@@ -178,7 +190,31 @@ angular.module('manage').controller('manageSessionsController', ['$scope', '$inj
* confirmation.
*/
var deleteSessionsImmediately = function deleteSessionsImmediately() {
// TODO: Use a batch delete function to delete the sessions.
// Perform deletion
tunnelService.deleteActiveTunnels(Object.keys(selectedWrappers))
.success(function tunnelsDeleted() {
// Remove deleted tunnels from wrapper array
$scope.wrappers = $scope.wrappers.filter(function tunnelStillExists(wrapper) {
return !(wrapper.tunnel.uuid in selectedWrappers);
});
// Clear selection
selectedWrappers = {};
})
// Notify of any errors
.error(function tunnelDeletionFailed(error) {
guacNotification.showStatus({
'className' : 'error',
'title' : 'MANAGE_SESSION.DIALOG_HEADER_ERROR',
'text' : error.message,
'actions' : [ ACKNOWLEDGE_ACTION ]
});
});
};
/**
@@ -201,20 +237,31 @@ angular.module('manage').controller('manageSessionsController', ['$scope', '$inj
* true if selected sessions can be deleted, false otherwise.
*/
$scope.canDeleteSessions = function canDeleteSessions() {
return selectedWrapperCount > 0;
// We can delete sessions if at least one is selected
for (var tunnelUUID in selectedWrappers)
return true;
return false;
};
/**
* Called whenever a tunnel wrapper changes selected status.
*
* @param {Boolean} selected
* Whether the wrapper is now selected.
* @param {ActiveTunnelWrapper} wrapper
* The wrapper whose selected status has changed.
*/
$scope.wrapperSelectionChange = function wrapperSelectionChange(selected) {
if (selected)
selectedWrapperCount++;
$scope.wrapperSelectionChange = function wrapperSelectionChange(wrapper) {
// Add wrapper to map if selected
if (wrapper.checked)
selectedWrappers[wrapper.tunnel.uuid] = wrapper;
// Otherwise, remove wrapper from map
else
selectedWrapperCount--;
delete selectedWrappers[wrapper.tunnel.uuid];
};
}]);

View File

@@ -56,7 +56,7 @@ THE SOFTWARE.
<tbody>
<tr ng-repeat="wrapper in wrapperPage" class="session">
<td>
<input ng-change="wrapperSelectionChange(wrapper.checked)" type="checkbox" ng-model="wrapper.checked" />
<input ng-change="wrapperSelectionChange(wrapper)" type="checkbox" ng-model="wrapper.checked" />
</td>
<td>
{{wrapper.tunnel.username}}

View File

@@ -30,12 +30,12 @@ angular.module('rest').factory('tunnelService', ['$http', 'authenticationService
/**
* Makes a request to the REST API to get the list of active tunnels,
* returning a promise that provides an array of @link{ActiveTunnel}
* returning a promise that provides a map of @link{ActiveTunnel}
* objects if successful.
*
* @returns {Promise.<ActiveTunnel[]>}
* A promise which will resolve with an array of @link{ActiveTunnel}
* objects upon success.
* @returns {Promise.<Object.<String, ActiveTunnel>>}
* A promise which will resolve with a map of @link{ActiveTunnel}
* objects, where each key is the UUID of the corresponding tunnel.
*/
service.getActiveTunnels = function getActiveTunnels() {
@@ -54,29 +54,39 @@ angular.module('rest').factory('tunnelService', ['$http', 'authenticationService
};
/**
* Makes a request to the REST API to delete the tunnel having the given
* UUID, effectively disconnecting the tunnel, returning a promise that can
* be used for processing the results of the call.
* Makes a request to the REST API to delete the tunnels having the given
* UUIDs, effectively disconnecting the tunnels, returning a promise that
* can be used for processing the results of the call.
*
* @param {String} uuid
* The UUID of the tunnel to delete.
* @param {String[]} uuids
* The UUIDs of the tunnels to delete.
*
* @returns {Promise}
* A promise for the HTTP call which will succeed if and only if the
* delete operation is successful.
*/
service.deleteActiveTunnel = function deleteActiveTunnel(uuid) {
service.deleteActiveTunnels = function deleteActiveTunnels(uuids) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Delete connection
// Convert provided array of UUIDs to a patch
var tunnelPatch = [];
uuids.forEach(function addTunnelPatch(uuid) {
tunnelPatch.push({
op : 'remove',
path : '/' + uuid
});
});
// Perform tunnel deletion via PATCH
return $http({
method : 'DELETE',
url : 'api/tunnels/' + encodeURIComponent(uuid),
params : httpParameters
method : 'PATCH',
url : 'api/tunnels',
params : httpParameters,
data : tunnelPatch
});
};

View File

@@ -236,6 +236,8 @@
"MANAGE_SESSION" : {
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
"ACTION_CANCEL" : "@:APP.ACTION_CANCEL",
"ACTION_DELETE" : "Kill all selected sessions",
"DIALOG_HEADER_CONFIRM_DELETE" : "Kill Sessions",