mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-724: Abstract away groups of running clients within their own type.
This commit is contained in:
@@ -26,6 +26,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
||||
// Required types
|
||||
var ConnectionGroup = $injector.get('ConnectionGroup');
|
||||
var ManagedClient = $injector.get('ManagedClient');
|
||||
var ManagedClientGroup = $injector.get('ManagedClientGroup');
|
||||
var ManagedClientState = $injector.get('ManagedClientState');
|
||||
var ManagedFilesystem = $injector.get('ManagedFilesystem');
|
||||
var Protocol = $injector.get('Protocol');
|
||||
@@ -152,37 +153,66 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
||||
};
|
||||
|
||||
/**
|
||||
* The client which should be attached to the client UI.
|
||||
* The set of clients that should be attached to the client UI. This will
|
||||
* be immediately initialized by a call to updateAttachedClients() below.
|
||||
*
|
||||
* @type ManagedClient[]
|
||||
* @type ManagedClientGroup[]
|
||||
*/
|
||||
$scope.clients = [];
|
||||
$scope.clientGroup = null;
|
||||
|
||||
/**
|
||||
* All active clients which are not any current client ($scope.clients).
|
||||
* Each key is the ID of the connection used by that client.
|
||||
*
|
||||
* @type Object.<String, ManagedClient>
|
||||
* @borrows ManagedClientGroup.getName
|
||||
*/
|
||||
$scope.otherClients = {};
|
||||
$scope.getName = ManagedClientGroup.getName;
|
||||
|
||||
/**
|
||||
* Reloads the contents of $scope.clients and $scope.otherClients to
|
||||
* reflect the client IDs currently listed in the URL.
|
||||
* Reloads the contents of $scope.clientGroup to reflect the client IDs
|
||||
* currently listed in the URL.
|
||||
*/
|
||||
var updateAttachedClients = function updateAttachedClients() {
|
||||
|
||||
var ids = $routeParams.id.split(/[ +]/);
|
||||
var previousClients = $scope.clientGroup ? $scope.clientGroup.clients.slice() : [];
|
||||
detachCurrentGroup();
|
||||
|
||||
$scope.clients = [];
|
||||
$scope.otherClients = angular.extend({}, guacClientManager.getManagedClients());
|
||||
$scope.clientGroup = guacClientManager.getManagedClientGroup($routeParams.id);
|
||||
$scope.clientGroup.attached = true;
|
||||
|
||||
// Separate active clients by whether they should be displayed within
|
||||
// the current view
|
||||
ids.forEach(function groupClients(id) {
|
||||
$scope.clients.push(guacClientManager.getManagedClient(id));
|
||||
delete $scope.otherClients[id];
|
||||
});
|
||||
// Ensure menu is closed if updated view is not a modification of the
|
||||
// current view (has no clients in common). The menu should remain open
|
||||
// only while the current view is being modified, not when navigating
|
||||
// to an entirely different view.
|
||||
if (_.isEmpty(_.intersection(previousClients, $scope.clientGroup.clients)))
|
||||
$scope.menu.shown = false;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Detaches the ManagedClientGroup currently attached to the client
|
||||
* interface via $scope.clientGroup such that the interface can be safely
|
||||
* cleaned up or another ManagedClientGroup can take its place.
|
||||
*/
|
||||
var detachCurrentGroup = function detachCurrentGroup() {
|
||||
|
||||
var managedClientGroup = $scope.clientGroup;
|
||||
if (managedClientGroup) {
|
||||
|
||||
// Flag group as detached
|
||||
managedClientGroup.attached = false;
|
||||
|
||||
// Remove all disconnected clients from management (the user has
|
||||
// seen their status)
|
||||
_.filter(managedClientGroup.clients, client => {
|
||||
|
||||
var connectionState = client.clientState.connectionState;
|
||||
return connectionState === ManagedClientState.ConnectionState.DISCONNECTED
|
||||
|| connectionState === ManagedClientState.ConnectionState.TUNNEL_ERROR
|
||||
|| connectionState === ManagedClientState.ConnectionState.CLIENT_ERROR;
|
||||
|
||||
}).forEach(client => {
|
||||
guacClientManager.removeManagedClient(client.id);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -424,7 +454,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
||||
$scope.menu.connectionParameters = ManagedClient.getArgumentModel($scope.client);
|
||||
|
||||
// Disable client keyboard if the menu is shown
|
||||
angular.forEach($scope.clients, function updateKeyboardEnabled(client) {
|
||||
angular.forEach($scope.clientGroup.clients, function updateKeyboardEnabled(client) {
|
||||
client.clientProperties.keyboardEnabled = !menuShown;
|
||||
});
|
||||
|
||||
@@ -606,15 +636,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
||||
* otherwise.
|
||||
*/
|
||||
$scope.isConnectionUnstable = function isConnectionUnstable() {
|
||||
|
||||
var unstable = false;
|
||||
|
||||
angular.forEach($scope.clients, function checkStability(client) {
|
||||
unstable |= client.clientState.tunnelUnstable;
|
||||
});
|
||||
|
||||
return unstable;
|
||||
|
||||
return _.findIndex($scope.clientGroup.clients, client => client.clientState.tunnelUnstable) !== -1;
|
||||
};
|
||||
|
||||
|
||||
@@ -830,22 +852,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
||||
|
||||
// Clean up when view destroyed
|
||||
$scope.$on('$destroy', function clientViewDestroyed() {
|
||||
|
||||
// Remove client from client manager if no longer connected
|
||||
var managedClient = $scope.client;
|
||||
if (managedClient) {
|
||||
|
||||
// Get current connection state
|
||||
var connectionState = managedClient.clientState.connectionState;
|
||||
|
||||
// If disconnected, remove from management
|
||||
if (connectionState === ManagedClientState.ConnectionState.DISCONNECTED
|
||||
|| connectionState === ManagedClientState.ConnectionState.TUNNEL_ERROR
|
||||
|| connectionState === ManagedClientState.ConnectionState.CLIENT_ERROR)
|
||||
guacClientManager.removeManagedClient(managedClient.id);
|
||||
|
||||
}
|
||||
|
||||
detachCurrentGroup();
|
||||
});
|
||||
|
||||
}]);
|
||||
|
@@ -29,6 +29,7 @@ angular.module('client').directive('guacClientPanel', ['$injector', function gua
|
||||
var sessionStorageFactory = $injector.get('sessionStorageFactory');
|
||||
|
||||
// Required types
|
||||
var ManagedClientGroup = $injector.get('ManagedClientGroup');
|
||||
var ManagedClientState = $injector.get('ManagedClientState');
|
||||
|
||||
/**
|
||||
@@ -49,12 +50,12 @@ angular.module('client').directive('guacClientPanel', ['$injector', function gua
|
||||
scope: {
|
||||
|
||||
/**
|
||||
* The ManagedClient instances associated with the active
|
||||
* The ManagedClientGroup instances associated with the active
|
||||
* connections to be displayed within this panel.
|
||||
*
|
||||
* @type ManagedClient[]|Object.<String, ManagedClient>
|
||||
* @type ManagedClientGroup[]
|
||||
*/
|
||||
clients : '='
|
||||
clientGroups : '='
|
||||
|
||||
},
|
||||
templateUrl: 'app/client/templates/guacClientPanel.html',
|
||||
@@ -75,71 +76,68 @@ angular.module('client').directive('guacClientPanel', ['$injector', function gua
|
||||
$scope.panelHidden = panelHidden;
|
||||
|
||||
/**
|
||||
* Returns whether this panel currently has any clients associated
|
||||
* with it.
|
||||
* Returns whether this panel currently has any client groups
|
||||
* associated with it.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* true if at least one client is associated with this panel,
|
||||
* false otherwise.
|
||||
* true if at least one client group is associated with this
|
||||
* panel, false otherwise.
|
||||
*/
|
||||
$scope.hasClients = function hasClients() {
|
||||
return !!_.find($scope.clients, $scope.isManaged);
|
||||
$scope.hasClientGroups = function hasClientGroups() {
|
||||
return $scope.clientGroups && $scope.clientGroups.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the status of the given client has changed in a
|
||||
* way that requires the user's attention. This may be due to an
|
||||
* error, or due to a server-initiated disconnect.
|
||||
* @borrows ManagedClientGroup.getIdentifier
|
||||
*/
|
||||
$scope.getIdentifier = ManagedClientGroup.getIdentifier;
|
||||
|
||||
/**
|
||||
* @borrows ManagedClientGroup.getTitle
|
||||
*/
|
||||
$scope.getTitle = ManagedClientGroup.getTitle;
|
||||
|
||||
/**
|
||||
* Returns whether the status of any client within the given client
|
||||
* group has changed in a way that requires the user's attention.
|
||||
* This may be due to an error, or due to a server-initiated
|
||||
* disconnect.
|
||||
*
|
||||
* @param {ManagedClient} client
|
||||
* The client to test.
|
||||
* @param {ManagedClientGroup} clientGroup
|
||||
* The client group to test.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the given client requires the user's attention,
|
||||
* false otherwise.
|
||||
*/
|
||||
$scope.hasStatusUpdate = function hasStatusUpdate(client) {
|
||||
$scope.hasStatusUpdate = function hasStatusUpdate(clientGroup) {
|
||||
return _.findIndex(clientGroup.clients, (client) => {
|
||||
|
||||
// Test whether the client has encountered an error
|
||||
switch (client.clientState.connectionState) {
|
||||
case ManagedClientState.ConnectionState.CONNECTION_ERROR:
|
||||
case ManagedClientState.ConnectionState.TUNNEL_ERROR:
|
||||
case ManagedClientState.ConnectionState.DISCONNECTED:
|
||||
return true;
|
||||
}
|
||||
// Test whether the client has encountered an error
|
||||
switch (client.clientState.connectionState) {
|
||||
case ManagedClientState.ConnectionState.CONNECTION_ERROR:
|
||||
case ManagedClientState.ConnectionState.TUNNEL_ERROR:
|
||||
case ManagedClientState.ConnectionState.DISCONNECTED:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
|
||||
}) !== -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the given client is currently being managed by
|
||||
* the guacClientManager service.
|
||||
* Initiates an orderly disconnect of all clients within the given
|
||||
* group. The clients are removed from management such that
|
||||
* attempting to connect to any of the same connections will result
|
||||
* in new connections being established, rather than displaying a
|
||||
* notification that the connection has ended.
|
||||
*
|
||||
* @param {ManagedClient} client
|
||||
* The client to test.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the given client is being managed by the
|
||||
* guacClientManager service, false otherwise.
|
||||
* @param {ManagedClientGroup} clientGroup
|
||||
* The group of clients to disconnect.
|
||||
*/
|
||||
$scope.isManaged = function isManaged(client) {
|
||||
return !!guacClientManager.getManagedClients()[client.id];
|
||||
};
|
||||
|
||||
/**
|
||||
* Initiates an orderly disconnect of the given client. The client
|
||||
* is removed from management such that attempting to connect to
|
||||
* the same connection will result in a new connection being
|
||||
* established, rather than displaying a notification that the
|
||||
* connection has ended.
|
||||
*
|
||||
* @param {type} client
|
||||
* @returns {undefined}
|
||||
*/
|
||||
$scope.disconnect = function disconnect(client) {
|
||||
client.client.disconnect();
|
||||
guacClientManager.removeManagedClient(client.id);
|
||||
$scope.disconnect = function disconnect(clientGroup) {
|
||||
guacClientManager.removeManagedClientGroup(ManagedClientGroup.getIdentifier(clientGroup));
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -32,50 +32,17 @@ angular.module('client').directive('guacTiledClients', [function guacTiledClient
|
||||
directive.scope = {
|
||||
|
||||
/**
|
||||
* The Guacamole clients that should be displayed in an evenly-tiled
|
||||
* grid arrangement.
|
||||
* The group of Guacamole clients that should be displayed in an
|
||||
* evenly-tiled grid arrangement.
|
||||
*
|
||||
* @type ManagedClient[]
|
||||
* @type ManagedClientGroup
|
||||
*/
|
||||
clients : '='
|
||||
clientGroup : '='
|
||||
|
||||
};
|
||||
|
||||
directive.controller = ['$scope', '$injector', '$element',
|
||||
function guacTiledListController($scope, $injector, $element) {
|
||||
|
||||
/**
|
||||
* Returns the number of columns that should be used to evenly arrange
|
||||
* all provided clients in a tiled grid.
|
||||
*
|
||||
* @returns {Number}
|
||||
* The number of columns that should be used for the grid of
|
||||
* clients.
|
||||
*/
|
||||
var getColumns = function getColumns() {
|
||||
|
||||
if (!$scope.clients || !$scope.clients.length)
|
||||
return 0;
|
||||
|
||||
return Math.ceil(Math.sqrt($scope.clients.length));
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the number of rows that should be used to evenly arrange all
|
||||
* provided clients in a tiled grid.
|
||||
*
|
||||
* @returns {Number}
|
||||
* The number of rows that should be used for the grid of clients.
|
||||
*/
|
||||
var getRows = function getRows() {
|
||||
|
||||
if (!$scope.clients || !$scope.clients.length)
|
||||
return 0;
|
||||
|
||||
return Math.ceil($scope.clients.length / getColumns());
|
||||
|
||||
};
|
||||
function guacTiledClientsController($scope, $injector, $element) {
|
||||
|
||||
/**
|
||||
* Assigns keyboard focus to the given client, allowing that client to
|
||||
@@ -86,7 +53,14 @@ angular.module('client').directive('guacTiledClients', [function guacTiledClient
|
||||
* The client that should receive keyboard focus.
|
||||
*/
|
||||
$scope.assignFocus = function assignFocus(client) {
|
||||
|
||||
// Clear focus of all other clients
|
||||
$scope.clientGroup.clients.forEach(client => {
|
||||
client.clientProperties.focused = false;
|
||||
});
|
||||
|
||||
client.clientProperties.focused = true;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -98,10 +72,10 @@ angular.module('client').directive('guacTiledClients', [function guacTiledClient
|
||||
* otherwise.
|
||||
*/
|
||||
$scope.hasMultipleClients = function hasMultipleClients() {
|
||||
return $scope.clients && $scope.clients.length > 1;
|
||||
return $scope.clientGroup && $scope.clientGroup.clients.length > 1;
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Returns the CSS width that should be applied to each tile to
|
||||
* achieve an even arrangement.
|
||||
*
|
||||
@@ -109,7 +83,7 @@ angular.module('client').directive('guacTiledClients', [function guacTiledClient
|
||||
* The CSS width that should be applied to each tile.
|
||||
*/
|
||||
$scope.getTileWidth = function getTileWidth() {
|
||||
return Math.floor(100 / getColumns()) + '%';
|
||||
return Math.floor(100 / $scope.clientGroup.columns) + '%';
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -120,22 +94,7 @@ angular.module('client').directive('guacTiledClients', [function guacTiledClient
|
||||
* The CSS height that should be applied to each tile.
|
||||
*/
|
||||
$scope.getTileHeight = function getTileHeight() {
|
||||
return Math.floor(100 / getRows()) + '%';
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the display title of the given Guacamole client. If the
|
||||
* title is not yet known, a placeholder title will be returned.
|
||||
*
|
||||
* @param {ManagedClient} client
|
||||
* The client whose title should be retrieved.
|
||||
*
|
||||
* @returns {String}
|
||||
* The title of the given client, or a placeholder title if the
|
||||
* client's title is not yet known.
|
||||
*/
|
||||
$scope.getClientTitle = function getClientTitle(client) {
|
||||
return client.title || '...';
|
||||
return Math.floor(100 / $scope.clientGroup.rows) + '%';
|
||||
};
|
||||
|
||||
}];
|
||||
|
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A directive for displaying a group of Guacamole clients as a non-interactive
|
||||
* thumbnail of tiled client displays.
|
||||
*/
|
||||
angular.module('client').directive('guacTiledThumbnails', [function guacTiledThumbnails() {
|
||||
|
||||
var directive = {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'app/client/templates/guacTiledThumbnails.html'
|
||||
};
|
||||
|
||||
directive.scope = {
|
||||
|
||||
/**
|
||||
* The group of clients to display as a thumbnail of tiled client
|
||||
* displays.
|
||||
*
|
||||
* @type ManagedClientGroup
|
||||
*/
|
||||
clientGroup : '='
|
||||
|
||||
};
|
||||
|
||||
directive.controller = ['$scope', '$injector', '$element',
|
||||
function guacTiledThumbnailsController($scope, $injector, $element) {
|
||||
|
||||
/**
|
||||
* Returns the CSS width that should be applied to each tile to
|
||||
* achieve an even arrangement.
|
||||
*
|
||||
* @returns {String}
|
||||
* The CSS width that should be applied to each tile.
|
||||
*/
|
||||
$scope.getTileWidth = function getTileWidth() {
|
||||
return Math.floor(100 / $scope.clientGroup.columns) + '%';
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the CSS height that should be applied to each tile to
|
||||
* achieve an even arrangement.
|
||||
*
|
||||
* @returns {String}
|
||||
* The CSS height that should be applied to each tile.
|
||||
*/
|
||||
$scope.getTileHeight = function getTileHeight() {
|
||||
return Math.floor(100 / $scope.clientGroup.rows) + '%';
|
||||
};
|
||||
|
||||
}];
|
||||
|
||||
return directive;
|
||||
|
||||
}]);
|
@@ -24,7 +24,8 @@ angular.module('client').factory('guacClientManager', ['$injector',
|
||||
function guacClientManager($injector) {
|
||||
|
||||
// Required types
|
||||
var ManagedClient = $injector.get('ManagedClient');
|
||||
var ManagedClient = $injector.get('ManagedClient');
|
||||
var ManagedClientGroup = $injector.get('ManagedClientGroup');
|
||||
|
||||
// Required services
|
||||
var $window = $injector.get('$window');
|
||||
@@ -56,6 +57,53 @@ angular.module('client').factory('guacClientManager', ['$injector',
|
||||
return storedManagedClients();
|
||||
};
|
||||
|
||||
/**
|
||||
* Getter/setter which retrieves or sets the array of all active managed
|
||||
* client groups.
|
||||
*
|
||||
* @type Function
|
||||
*/
|
||||
var storedManagedClientGroups = sessionStorageFactory.create([], function destroyClientGroupStorage() {
|
||||
|
||||
// Disconnect all clients when storage is destroyed
|
||||
service.clear();
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns an array of all managed client groups.
|
||||
*
|
||||
* @returns {ManagedClientGroup[]>}
|
||||
* An array of all active managed client groups.
|
||||
*/
|
||||
service.getManagedClientGroups = function getManagedClientGroups() {
|
||||
return storedManagedClientGroups();
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the ManagedClient with the given ID from all
|
||||
* ManagedClientGroups, automatically adjusting the tile size of the
|
||||
* clients that remain in each group. All client groups that are empty
|
||||
* after the client is removed will also be removed.
|
||||
*
|
||||
* @param {string} id
|
||||
* The ID of the ManagedClient to remove.
|
||||
*/
|
||||
var ungroupManagedClient = function ungroupManagedClient(id) {
|
||||
|
||||
var managedClientGroups = storedManagedClientGroups();
|
||||
|
||||
// Remove client from all groups
|
||||
managedClientGroups.forEach(group => {
|
||||
_.remove(group.clients, client => (client.id === id));
|
||||
ManagedClientGroup.recalculateTiles(group);
|
||||
});
|
||||
|
||||
// Remove any groups that are now empty
|
||||
_.remove(managedClientGroups, group => !group.clients.length);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the existing ManagedClient associated with the connection having
|
||||
* the given ID, if any. If no such a ManagedClient already exists, this
|
||||
@@ -67,13 +115,16 @@ angular.module('client').factory('guacClientManager', ['$injector',
|
||||
* @returns {Boolean}
|
||||
* true if an existing client was removed, false otherwise.
|
||||
*/
|
||||
service.removeManagedClient = function replaceManagedClient(id) {
|
||||
service.removeManagedClient = function removeManagedClient(id) {
|
||||
|
||||
var managedClients = storedManagedClients();
|
||||
|
||||
// Remove client if it exists
|
||||
if (id in managedClients) {
|
||||
|
||||
// Pull client out of any containing groups
|
||||
ungroupManagedClient(id);
|
||||
|
||||
// Disconnect and remove
|
||||
managedClients[id].client.disconnect();
|
||||
delete managedClients[id];
|
||||
@@ -102,11 +153,31 @@ angular.module('client').factory('guacClientManager', ['$injector',
|
||||
*/
|
||||
service.replaceManagedClient = function replaceManagedClient(id) {
|
||||
|
||||
// Disconnect any existing client
|
||||
service.removeManagedClient(id);
|
||||
var managedClients = storedManagedClients();
|
||||
var managedClientGroups = storedManagedClientGroups();
|
||||
|
||||
// Set new client
|
||||
return storedManagedClients()[id] = ManagedClient.getInstance(id);
|
||||
// Remove client if it exists
|
||||
if (id in managedClients) {
|
||||
|
||||
var hadFocus = managedClients[id].clientProperties.focused;
|
||||
managedClients[id].client.disconnect();
|
||||
delete managedClients[id];
|
||||
|
||||
// Remove client from all groups
|
||||
managedClientGroups.forEach(group => {
|
||||
|
||||
var index = _.findIndex(group.clients, client => (client.id === id));
|
||||
if (index === -1)
|
||||
return;
|
||||
|
||||
group.clients[index] = managedClients[id] = ManagedClient.getInstance(id);
|
||||
managedClients[id].clientProperties.focused = hadFocus;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return managedClients[id];
|
||||
|
||||
};
|
||||
|
||||
@@ -126,6 +197,10 @@ angular.module('client').factory('guacClientManager', ['$injector',
|
||||
|
||||
var managedClients = storedManagedClients();
|
||||
|
||||
// Ensure any existing client is removed from its containing group
|
||||
// prior to being returned
|
||||
ungroupManagedClient(id);
|
||||
|
||||
// Create new managed client if it doesn't already exist
|
||||
if (!(id in managedClients))
|
||||
managedClients[id] = ManagedClient.getInstance(id);
|
||||
@@ -136,7 +211,81 @@ angular.module('client').factory('guacClientManager', ['$injector',
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnects and removes all currently-connected clients.
|
||||
* Returns the ManagedClientGroup having the given ID. If no such
|
||||
* ManagedClientGroup exists, a new ManagedClientGroup is created by
|
||||
* extracting the relevant connections from the ID.
|
||||
*
|
||||
* @param {String} id
|
||||
* The ID of the ManagedClientGroup to retrieve or create.
|
||||
*
|
||||
* @returns {ManagedClientGroup}
|
||||
* The ManagedClientGroup having the given ID.
|
||||
*/
|
||||
service.getManagedClientGroup = function getManagedClientGroup(id) {
|
||||
|
||||
var clients = [];
|
||||
var clientIds = ManagedClientGroup.getClientIdentifiers(id);
|
||||
|
||||
// Separate active clients by whether they should be displayed within
|
||||
// the current view
|
||||
clientIds.forEach(function groupClients(id) {
|
||||
clients.push(service.getManagedClient(id));
|
||||
});
|
||||
|
||||
if (clients.length === 1) {
|
||||
clients[0].clientProperties.focused = true;
|
||||
}
|
||||
|
||||
var group = new ManagedClientGroup({
|
||||
clients : clients
|
||||
});
|
||||
|
||||
var managedClientGroups = storedManagedClientGroups();
|
||||
managedClientGroups.push(group);
|
||||
return group;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the existing ManagedClientGroup having the given ID, if any,
|
||||
* disconnecting and removing all ManagedClients associated with that
|
||||
* group. If no such a ManagedClientGroup currently exists, this function
|
||||
* has no effect.
|
||||
*
|
||||
* @param {String} id
|
||||
* The ID of the ManagedClientGroup to remove.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if a ManagedClientGroup was removed, false otherwise.
|
||||
*/
|
||||
service.removeManagedClientGroup = function removeManagedClientGroup(id) {
|
||||
|
||||
var managedClients = storedManagedClients();
|
||||
var managedClientGroups = storedManagedClientGroups();
|
||||
|
||||
// Remove all matching groups (there SHOULD only be one)
|
||||
var removed = _.remove(managedClientGroups, (group) => ManagedClientGroup.getIdentifier(group) === id);
|
||||
|
||||
// Disconnect all clients associated with the removed group(s)
|
||||
removed.forEach((group) => {
|
||||
group.clients.forEach((client) => {
|
||||
|
||||
var id = client.id;
|
||||
if (managedClients[id]) {
|
||||
managedClients[id].client.disconnect();
|
||||
delete managedClients[id];
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
return !!removed.length;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnects and removes all currently-connected clients and client
|
||||
* groups.
|
||||
*/
|
||||
service.clear = function clear() {
|
||||
|
||||
@@ -146,8 +295,9 @@ angular.module('client').factory('guacClientManager', ['$injector',
|
||||
for (var id in managedClients)
|
||||
managedClients[id].client.disconnect();
|
||||
|
||||
// Clear managed clients
|
||||
// Clear managed clients and client groups
|
||||
storedManagedClients({});
|
||||
storedManagedClientGroups([]);
|
||||
|
||||
};
|
||||
|
||||
|
@@ -9,12 +9,7 @@
|
||||
<div class="client-body" guac-touch-drag="clientDrag" guac-touch-pinch="clientPinch">
|
||||
|
||||
<!-- All connections in current display -->
|
||||
<guac-tiled-clients clients="clients"></guac-tiled-clients>
|
||||
|
||||
<!-- All other active connections -->
|
||||
<div id="other-connections">
|
||||
<guac-client-panel clients="otherClients"></guac-client-panel>
|
||||
</div>
|
||||
<guac-tiled-clients client-group="clientGroup"></guac-tiled-clients>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -52,9 +47,9 @@
|
||||
|
||||
<!-- Stationary header -->
|
||||
<div class="header">
|
||||
<h2 ng-hide="rootConnectionGroups">{{client.name}}</h2>
|
||||
<h2 ng-hide="rootConnectionGroups">{{ getName(clientGroup) }}</h2>
|
||||
<h2 class="connection-select-menu" ng-show="rootConnectionGroups">
|
||||
<guac-menu menu-title="client.name" interactive="true">
|
||||
<guac-menu menu-title="getName(clientGroup)" interactive="true">
|
||||
<div class="all-connections">
|
||||
<guac-group-list-filter connection-groups="rootConnectionGroups"
|
||||
filtered-connection-groups="filteredRootConnectionGroups"
|
||||
|
@@ -1,29 +1,27 @@
|
||||
<div class="client-panel"
|
||||
ng-class="{ 'has-clients': hasClients(), 'hidden' : panelHidden() }">
|
||||
ng-class="{ 'has-clients': hasClientGroups(), 'hidden' : panelHidden() }">
|
||||
|
||||
<!-- Toggle panel visibility -->
|
||||
<div class="client-panel-handle" ng-click="togglePanel()"></div>
|
||||
|
||||
<!-- List of connection thumbnails -->
|
||||
<ul class="client-panel-connection-list">
|
||||
<li ng-repeat="client in clients | toArray | orderBy: [ '-value.lastUsed', 'value.title' ]"
|
||||
ng-class="{ 'needs-attention' : hasStatusUpdate(client.value) }"
|
||||
ng-show="isManaged(client.value)"
|
||||
<li ng-repeat="clientGroup in clientGroups"
|
||||
ng-if="!clientGroup.attached"
|
||||
ng-class="{ 'needs-attention' : hasStatusUpdate(clientGroup) }"
|
||||
class="client-panel-connection">
|
||||
|
||||
<!-- Close connection -->
|
||||
<button class="close-other-connection" ng-click="disconnect(client.value)">
|
||||
<button class="close-other-connection" ng-click="disconnect(clientGroup)">
|
||||
<img ng-attr-alt="{{ 'CLIENT.ACTION_DISCONNECT' | translate }}"
|
||||
ng-attr-title="{{ 'CLIENT.ACTION_DISCONNECT' | translate }}"
|
||||
src="images/x.png">
|
||||
</button>
|
||||
|
||||
<!-- Thumbnail -->
|
||||
<a href="#/client/{{client.value.id}}">
|
||||
<div class="thumbnail">
|
||||
<guac-thumbnail client="client.value"></guac-thumbnail>
|
||||
</div>
|
||||
<div class="name">{{ client.value.title }}</div>
|
||||
<a href="#/client/{{ getIdentifier(clientGroup) }}">
|
||||
<guac-tiled-thumbnails client-group="clientGroup"></guac-tiled-thumbnails>
|
||||
<div class="name">{{ getTitle(clientGroup) }}</div>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
@@ -1,12 +1,12 @@
|
||||
<ul class="tiled-client-list" ng-class="{ 'multiple-clients' : hasMultipleClients() }">
|
||||
|
||||
<li class="client-tile"
|
||||
ng-repeat="client in clients"
|
||||
ng-repeat="client in clientGroup.clients"
|
||||
ng-style="{ 'width' : getTileWidth(), 'height' : getTileHeight() }"
|
||||
ng-class="{ 'focused' : client.clientProperties.focused }"
|
||||
ng-click="assignFocus(client)">
|
||||
|
||||
<h3>{{ getClientTitle(client) }}</h3>
|
||||
<h3>{{ client.title }}</h3>
|
||||
<guac-client client="client"></guac-client>
|
||||
|
||||
<!-- Client-specific status/error dialog -->
|
||||
|
@@ -0,0 +1,7 @@
|
||||
<ul class="tiled-client-list">
|
||||
<li class="client-tile"
|
||||
ng-repeat="client in clientGroup.clients"
|
||||
ng-style="{ 'width' : getTileWidth(), 'height' : getTileHeight() }">
|
||||
<guac-thumbnail client="client"></guac-thumbnail>
|
||||
</li>
|
||||
</ul>
|
@@ -62,8 +62,9 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
||||
|
||||
/**
|
||||
* Object which serves as a surrogate interface, encapsulating a Guacamole
|
||||
* client while it is active, allowing it to be detached and reattached
|
||||
* from different client views.
|
||||
* client while it is active, allowing it to be maintained in the
|
||||
* background. One or more ManagedClients are grouped within
|
||||
* ManagedClientGroups before being attached to the client view.
|
||||
*
|
||||
* @constructor
|
||||
* @param {ManagedClient|Object} [template={}]
|
||||
|
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides the ManagedClientGroup class used by the guacClientManager service.
|
||||
*/
|
||||
angular.module('client').factory('ManagedClientGroup', [function defineManagedClientGroup() {
|
||||
|
||||
/**
|
||||
* Object which serves as a grouping of ManagedClients. Each
|
||||
* ManagedClientGroup may be attached, detached, and reattached dynamically
|
||||
* from different client views, with its contents automatically displayed
|
||||
* in a tiled arrangment if needed.
|
||||
*
|
||||
* @constructor
|
||||
* @param {ManagedClientGroup|Object} [template={}]
|
||||
* The object whose properties should be copied within the new
|
||||
* ManagedClientGroup.
|
||||
*/
|
||||
var ManagedClientGroup = function ManagedClientGroup(template) {
|
||||
|
||||
// Use empty object by default
|
||||
template = template || {};
|
||||
|
||||
/**
|
||||
* Whether this ManagedClientGroup is currently attached to the client
|
||||
* interface (true) or is running in the background (false).
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
*/
|
||||
this.attached = template.attached || false;
|
||||
|
||||
/**
|
||||
* The clients that should be displayed within the client interface
|
||||
* when this group is attached.
|
||||
*
|
||||
* @type {ManagedClient[]}
|
||||
* @default []
|
||||
*/
|
||||
this.clients = template.clients || [];
|
||||
|
||||
/**
|
||||
* The number of rows that should be used when arranging the clients
|
||||
* within this group in a grid. By default, this value is automatically
|
||||
* calculated from the number of clients.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
this.rows = template.rows || ManagedClientGroup.getRows(this);
|
||||
|
||||
/**
|
||||
* The number of columns that should be used when arranging the clients
|
||||
* within this group in a grid. By default, this value is automatically
|
||||
* calculated from the number of clients.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
this.columns = template.columns || ManagedClientGroup.getColumns(this);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the number of rows and columns stored within the given
|
||||
* ManagedClientGroup such that the clients within the group are evenly
|
||||
* distributed. This function should be called whenever the size of a
|
||||
* group changes.
|
||||
*
|
||||
* @param {ManagedClientGroup} group
|
||||
* The ManagedClientGroup that should be updated.
|
||||
*/
|
||||
ManagedClientGroup.recalculateTiles = function recalculateTiles(group) {
|
||||
|
||||
var recalculated = new ManagedClientGroup({
|
||||
clients : group.clients
|
||||
});
|
||||
|
||||
group.rows = recalculated.rows;
|
||||
group.columns = recalculated.columns;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the unique ID representing the given ManagedClientGroup. The ID
|
||||
* of each ManagedClientGroup consists simply of the IDs of all its
|
||||
* ManagedClients, separated by periods.
|
||||
*
|
||||
* @param {ManagedClientGroup} group
|
||||
* The ManagedClientGroup to determine the ID of.
|
||||
*
|
||||
* @returns {string}
|
||||
* The unique ID representing the given ManagedClientGroup.
|
||||
*/
|
||||
ManagedClientGroup.getIdentifier = function getIdentifier(group) {
|
||||
return _.map(group.clients, client => client.id).join('.');
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of client identifiers for all clients contained within
|
||||
* the given ManagedClientGroup. Order of the identifiers is preserved
|
||||
* with respect to the order of the clients within the group.
|
||||
*
|
||||
* @param {ManagedClientGroup|string} group
|
||||
* The ManagedClientGroup to retrieve the client identifiers from,
|
||||
* or its ID.
|
||||
*
|
||||
* @returns {string[]}
|
||||
* The client identifiers of all clients contained within the given
|
||||
* ManagedClientGroup.
|
||||
*/
|
||||
ManagedClientGroup.getClientIdentifiers = function getClientIdentifiers(group) {
|
||||
|
||||
if (_.isString(group))
|
||||
return group.split(/\./);
|
||||
|
||||
return group.clients.map(client => client.id);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the number of columns that should be used to evenly arrange
|
||||
* all provided clients in a tiled grid.
|
||||
*
|
||||
* @returns {Number}
|
||||
* The number of columns that should be used for the grid of
|
||||
* clients.
|
||||
*/
|
||||
ManagedClientGroup.getColumns = function getColumns(group) {
|
||||
|
||||
if (!group.clients.length)
|
||||
return 0;
|
||||
|
||||
return Math.ceil(Math.sqrt(group.clients.length));
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the number of rows that should be used to evenly arrange all
|
||||
* provided clients in a tiled grid.
|
||||
*
|
||||
* @returns {Number}
|
||||
* The number of rows that should be used for the grid of clients.
|
||||
*/
|
||||
ManagedClientGroup.getRows = function getRows(group) {
|
||||
|
||||
if (!group.clients.length)
|
||||
return 0;
|
||||
|
||||
return Math.ceil(group.clients.length / ManagedClientGroup.getColumns(group));
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the title which should be displayed as the page title if the
|
||||
* given client group is attached to the interface.
|
||||
*
|
||||
* @param {ManagedClientGroup} group
|
||||
* The ManagedClientGroup to determine the title of.
|
||||
*
|
||||
* @returns {string}
|
||||
* The title of the given ManagedClientGroup.
|
||||
*/
|
||||
ManagedClientGroup.getTitle = function getTitle(group) {
|
||||
|
||||
// Use client-specific title if only one client
|
||||
if (group.clients.length === 1)
|
||||
return group.clients[0].title;
|
||||
|
||||
// With multiple clients, somehow combining multiple page titles would
|
||||
// be confusing. Instead, use the combined names.
|
||||
return ManagedClientGroup.getName(group);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the combined names of all clients within the given
|
||||
* ManagedClientGroup, as determined by the names of the associated
|
||||
* connections or connection groups.
|
||||
*
|
||||
* @param {ManagedClientGroup} group
|
||||
* The ManagedClientGroup to determine the name of.
|
||||
*
|
||||
* @returns {string}
|
||||
* The combined names of all clients within the given
|
||||
* ManagedClientGroup.
|
||||
*/
|
||||
ManagedClientGroup.getName = function getName(group) {
|
||||
return _.filter(group.clients, (client => !!client.name)).map(client => client.name).join(', ') || '...';
|
||||
};
|
||||
|
||||
return ManagedClientGroup;
|
||||
|
||||
}]);
|
@@ -18,7 +18,8 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* A directive which displays the contents of a connection group.
|
||||
* A directive which displays the recently-accessed connections nested beneath
|
||||
* each of the given connection groups.
|
||||
*/
|
||||
angular.module('home').directive('guacRecentConnections', [function guacRecentConnections() {
|
||||
|
||||
@@ -44,21 +45,12 @@ angular.module('home').directive('guacRecentConnections', [function guacRecentCo
|
||||
controller: ['$scope', '$injector', function guacRecentConnectionsController($scope, $injector) {
|
||||
|
||||
// Required types
|
||||
var ActiveConnection = $injector.get('ActiveConnection');
|
||||
var ClientIdentifier = $injector.get('ClientIdentifier');
|
||||
var RecentConnection = $injector.get('RecentConnection');
|
||||
|
||||
// Required services
|
||||
var guacClientManager = $injector.get('guacClientManager');
|
||||
var guacHistory = $injector.get('guacHistory');
|
||||
|
||||
/**
|
||||
* Array of all known and visible active connections.
|
||||
*
|
||||
* @type ActiveConnection[]
|
||||
*/
|
||||
$scope.activeConnections = [];
|
||||
|
||||
/**
|
||||
* Array of all known and visible recently-used connections.
|
||||
*
|
||||
@@ -68,16 +60,12 @@ angular.module('home').directive('guacRecentConnections', [function guacRecentCo
|
||||
|
||||
/**
|
||||
* Returns whether recent connections are available for display.
|
||||
* Note that, for the sake of this directive, recent connections
|
||||
* include any currently-active connections, even if they are not
|
||||
* yet in the history.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if recent (or active) connections are present, false
|
||||
* otherwise.
|
||||
* true if recent connections are present, false otherwise.
|
||||
*/
|
||||
$scope.hasRecentConnections = function hasRecentConnections() {
|
||||
return !!($scope.activeConnections.length || $scope.recentConnections.length);
|
||||
return !!$scope.recentConnections.length;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -149,7 +137,6 @@ angular.module('home').directive('guacRecentConnections', [function guacRecentCo
|
||||
$scope.$watch("rootGroups", function setRootGroups(rootGroups) {
|
||||
|
||||
// Clear connection arrays
|
||||
$scope.activeConnections = [];
|
||||
$scope.recentConnections = [];
|
||||
|
||||
// Produce collection of visible objects
|
||||
@@ -160,29 +147,11 @@ angular.module('home').directive('guacRecentConnections', [function guacRecentCo
|
||||
});
|
||||
}
|
||||
|
||||
var managedClients = guacClientManager.getManagedClients();
|
||||
|
||||
// Add all active connections
|
||||
for (var id in managedClients) {
|
||||
|
||||
// Get corresponding managed client
|
||||
var client = managedClients[id];
|
||||
|
||||
// Add active connections for clients with associated visible objects
|
||||
if (id in visibleObjects) {
|
||||
|
||||
var object = visibleObjects[id];
|
||||
$scope.activeConnections.push(new ActiveConnection(object.name, client));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Add any recent connections that are visible
|
||||
guacHistory.recentConnections.forEach(function addRecentConnection(historyEntry) {
|
||||
|
||||
// Add recent connections for history entries with associated visible objects
|
||||
if (historyEntry.id in visibleObjects && !(historyEntry.id in managedClients)) {
|
||||
if (historyEntry.id in visibleObjects) {
|
||||
|
||||
var object = visibleObjects[historyEntry.id];
|
||||
$scope.recentConnections.push(new RecentConnection(object.name, historyEntry));
|
||||
|
@@ -3,23 +3,6 @@
|
||||
<!-- Text displayed if no recent connections exist -->
|
||||
<p class="placeholder" ng-hide="hasRecentConnections()">{{'HOME.INFO_NO_RECENT_CONNECTIONS' | translate}}</p>
|
||||
|
||||
<!-- All active connections -->
|
||||
<div ng-repeat="activeConnection in activeConnections" class="connection">
|
||||
<a href="#/client/{{activeConnection.client.id}}">
|
||||
|
||||
<!-- Connection thumbnail -->
|
||||
<div class="thumbnail">
|
||||
<guac-thumbnail client="activeConnection.client"></guac-thumbnail>
|
||||
</div>
|
||||
|
||||
<!-- Connection name -->
|
||||
<div class="caption">
|
||||
<span class="name">{{activeConnection.name}}</span>
|
||||
</div>
|
||||
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- All recent connections -->
|
||||
<div ng-repeat="recentConnection in recentConnections" class="connection">
|
||||
<a href="#/client/{{recentConnection.entry.id}}">
|
||||
|
@@ -24,11 +24,12 @@ angular.module('index').controller('indexController', ['$scope', '$injector',
|
||||
function indexController($scope, $injector) {
|
||||
|
||||
// Required services
|
||||
var $document = $injector.get('$document');
|
||||
var $route = $injector.get('$route');
|
||||
var $window = $injector.get('$window');
|
||||
var clipboardService = $injector.get('clipboardService');
|
||||
var guacNotification = $injector.get('guacNotification');
|
||||
var $document = $injector.get('$document');
|
||||
var $route = $injector.get('$route');
|
||||
var $window = $injector.get('$window');
|
||||
var clipboardService = $injector.get('clipboardService');
|
||||
var guacNotification = $injector.get('guacNotification');
|
||||
var guacClientManager = $injector.get('guacClientManager');
|
||||
|
||||
/**
|
||||
* The error that prevents the current page from rendering at all. If no
|
||||
@@ -43,6 +44,14 @@ angular.module('index').controller('indexController', ['$scope', '$injector',
|
||||
*/
|
||||
$scope.guacNotification = guacNotification;
|
||||
|
||||
/**
|
||||
* All currently-active connections, grouped into their corresponding
|
||||
* tiled views.
|
||||
*
|
||||
* @type ManagedClientGroup[]
|
||||
*/
|
||||
$scope.getManagedClientGroups = guacClientManager.getManagedClientGroups;
|
||||
|
||||
/**
|
||||
* The message to display to the user as instructions for the login
|
||||
* process.
|
||||
|
@@ -20,7 +20,7 @@
|
||||
#other-connections .client-panel {
|
||||
|
||||
display: none;
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
@@ -80,6 +80,11 @@
|
||||
<div id="content" ng-view>
|
||||
</div>
|
||||
|
||||
<!-- All active connections -->
|
||||
<div id="other-connections">
|
||||
<guac-client-panel client-groups="getManagedClientGroups()"></guac-client-panel>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Polyfills -->
|
||||
|
Reference in New Issue
Block a user