GUACAMOLE-724: Control only the currently-focused client with client-specific menu options.

This commit is contained in:
Michael Jumper
2021-06-16 21:59:10 -07:00
parent 3f4c6a4cd1
commit d0b1fb7d7f
5 changed files with 101 additions and 43 deletions

View File

@@ -156,10 +156,20 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
*/ */
$scope.applyParameterChanges = function applyParameterChanges() { $scope.applyParameterChanges = function applyParameterChanges() {
angular.forEach($scope.menu.connectionParameters, function sendArgv(value, name) { angular.forEach($scope.menu.connectionParameters, function sendArgv(value, name) {
ManagedClient.setArgument($scope.client, name, value); if ($scope.focusedClient)
ManagedClient.setArgument($scope.focusedClient, name, value);
}); });
}; };
/**
* The currently-focused client within the current ManagedClientGroup. If
* there is no current group, no client is focused, or multiple clients are
* focused, this will be null.
*
* @type ManagedClient
*/
$scope.focusedClient = null;
/** /**
* The set of clients that should be attached to the client UI. This will * The set of clients that should be attached to the client UI. This will
* be immediately initialized by a call to updateAttachedClients() below. * be immediately initialized by a call to updateAttachedClients() below.
@@ -173,6 +183,11 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
*/ */
$scope.getName = ManagedClientGroup.getName; $scope.getName = ManagedClientGroup.getName;
/**
* @borrows ManagedClientGroup.getTitle
*/
$scope.getTitle = ManagedClientGroup.getTitle;
/** /**
* Reloads the contents of $scope.clientGroup to reflect the client IDs * Reloads the contents of $scope.clientGroup to reflect the client IDs
* currently listed in the URL. * currently listed in the URL.
@@ -231,6 +246,27 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
// reloading the route // reloading the route
$scope.$on('$routeUpdate', updateAttachedClients); $scope.$on('$routeUpdate', updateAttachedClients);
/**
* Returns the currently-focused ManagedClient. If there is no such client,
* or multiple clients are focused, null is returned.
*
* @returns {ManagedClient}
* The currently-focused client, or null if there are no focused
* clients or if multiple clients are focused.
*/
$scope.getFocusedClient = function getFocusedClient() {
var managedClientGroup = $scope.clientGroup;
if (managedClientGroup) {
var focusedClients = _.filter(managedClientGroup.clients, client => client.clientProperties.focused);
if (focusedClients.length === 1)
return focusedClients[0];
}
return null;
};
/** /**
* The root connection groups of the connection hierarchy that should be * The root connection groups of the connection hierarchy that should be
* presented to the user for selecting a different connection, as a map of * presented to the user for selecting a different connection, as a map of
@@ -456,11 +492,6 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
$scope.applyParameterChanges(); $scope.applyParameterChanges();
} }
// Obtain snapshot of current editable connection parameters when menu
// is opened
else if (menuShown)
$scope.menu.connectionParameters = ManagedClient.getArgumentModel($scope.client);
// Disable client keyboard if the menu is shown // Disable client keyboard if the menu is shown
angular.forEach($scope.clientGroup.clients, function updateKeyboardEnabled(client) { angular.forEach($scope.clientGroup.clients, function updateKeyboardEnabled(client) {
client.clientProperties.keyboardEnabled = !menuShown; client.clientProperties.keyboardEnabled = !menuShown;
@@ -468,8 +499,25 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
}); });
// Automatically track and cache the currently-focused client
$scope.$watch('getFocusedClient()', function focusedClientChanged(client) {
// Apply any parameter changes when focus is changing (as
// applyParameterChanges() depends on the value of focusedClient, this
// must be called BEFORE updating focusedClient
$scope.applyParameterChanges();
$scope.focusedClient = client;
// Update available connection parameters, if there is a focused
// client
$scope.menu.connectionParameters = $scope.focusedClient ?
ManagedClient.getArgumentModel($scope.focusedClient) : {};
});
// Update page icon when thumbnail changes // Update page icon when thumbnail changes
$scope.$watch('client.thumbnail.canvas', function thumbnailChanged(canvas) { $scope.$watch('focusedClient.thumbnail.canvas', function thumbnailChanged(canvas) {
iconService.setIcons(canvas); iconService.setIcons(canvas);
}); });
@@ -487,7 +535,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
}); });
// Pull sharing profiles once the tunnel UUID is known // Pull sharing profiles once the tunnel UUID is known
$scope.$watch('client.tunnel.uuid', function retrieveSharingProfiles(uuid) { $scope.$watch('focusedClient.tunnel.uuid', function retrieveSharingProfiles(uuid) {
// Only pull sharing profiles if tunnel UUID is actually available // Only pull sharing profiles if tunnel UUID is actually available
if (!uuid) if (!uuid)
@@ -510,7 +558,8 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
* The sharing profile to use to generate the sharing link. * The sharing profile to use to generate the sharing link.
*/ */
$scope.share = function share(sharingProfile) { $scope.share = function share(sharingProfile) {
ManagedClient.createShareLink($scope.client, sharingProfile); if ($scope.focusedClient)
ManagedClient.createShareLink($scope.focusedClient, sharingProfile);
}; };
/** /**
@@ -521,7 +570,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
* link, false otherwise. * link, false otherwise.
*/ */
$scope.isShared = function isShared() { $scope.isShared = function isShared() {
return ManagedClient.isShared($scope.client); return !!$scope.focusedClient && ManagedClient.isShared($scope.focusedClient);
}; };
/** /**
@@ -534,9 +583,12 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
*/ */
$scope.getShareLinkCount = function getShareLinkCount() { $scope.getShareLinkCount = function getShareLinkCount() {
if (!$scope.focusedClient)
return 0;
// Count total number of links within the ManagedClient's share link map // Count total number of links within the ManagedClient's share link map
var linkCount = 0; var linkCount = 0;
for (var dummy in $scope.client.shareLinks) for (var dummy in $scope.focusedClient.shareLinks)
linkCount++; linkCount++;
return linkCount; return linkCount;
@@ -625,7 +677,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
}); });
// Update page title when client title changes // Update page title when client title changes
$scope.$watch('client.title', function clientTitleChanged(title) { $scope.$watch('getTitle(clientGroup)', function clientTitleChanged(title) {
$scope.page.title = title; $scope.page.title = title;
}); });
@@ -678,13 +730,17 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
}; };
/** /**
* Immediately disconnects the currently-connected client, if any. * Immediately disconnects all currently-focused clients, if any.
*/ */
$scope.disconnect = function disconnect() { $scope.disconnect = function disconnect() {
// Disconnect if client is available // Disconnect if client is available
if ($scope.client) if ($scope.clientGroup) {
$scope.client.client.disconnect(); $scope.clientGroup.clients.forEach(client => {
if (client.clientProperties.focused)
client.client.disconnect();
});
}
// Hide menu // Hide menu
$scope.menu.shown = false; $scope.menu.shown = false;
@@ -804,13 +860,9 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
*/ */
$scope.uploadFiles = function uploadFiles(files) { $scope.uploadFiles = function uploadFiles(files) {
// Ignore file uploads if no attached client
if (!$scope.client)
return;
// Upload each file // Upload each file
for (var i = 0; i < files.length; i++) for (var i = 0; i < files.length; i++)
ManagedClient.uploadFile($scope.client, files[i], $scope.filesystemMenuContents); ManagedClient.uploadFile($scope.filesystemMenuContents.client, files[i], $scope.filesystemMenuContents);
}; };

View File

@@ -28,14 +28,6 @@ angular.module('client').directive('guacFileBrowser', [function guacFileBrowser(
replace: true, replace: true,
scope: { scope: {
/**
* The client whose file transfers should be managed by this
* directive.
*
* @type ManagedClient
*/
client : '=',
/** /**
* @type ManagedFilesystem * @type ManagedFilesystem
*/ */
@@ -116,7 +108,7 @@ angular.module('client').directive('guacFileBrowser', [function guacFileBrowser(
* The file to download. * The file to download.
*/ */
$scope.downloadFile = function downloadFile(file) { $scope.downloadFile = function downloadFile(file) {
ManagedFilesystem.downloadFile($scope.client, $scope.filesystem, file.streamName); ManagedFilesystem.downloadFile($scope.filesystem, file.streamName);
}; };
/** /**

View File

@@ -87,7 +87,7 @@
translate="CLIENT.HELP_SHARE_LINK" translate="CLIENT.HELP_SHARE_LINK"
translate-values="{LINKS : getShareLinkCount()}"></p> translate-values="{LINKS : getShareLinkCount()}"></p>
<table> <table>
<tr ng-repeat="link in client.shareLinks | toArray | orderBy: value.name"> <tr ng-repeat="link in focusedClient.shareLinks | toArray | orderBy: value.name">
<th>{{link.value.name}}</th> <th>{{link.value.name}}</th>
<td><a href="{{link.value.href}}" target="_blank">{{link.value.href}}</a></td> <td><a href="{{link.value.href}}" target="_blank">{{link.value.href}}</a></td>
</tr> </tr>
@@ -105,19 +105,19 @@
</div> </div>
<!-- Devices --> <!-- Devices -->
<div class="menu-section" id="devices" ng-show="client.filesystems.length"> <div class="menu-section" id="devices" ng-if="focusedClient.filesystems.length">
<h3>{{'CLIENT.SECTION_HEADER_DEVICES' | translate}}</h3> <h3>{{'CLIENT.SECTION_HEADER_DEVICES' | translate}}</h3>
<div class="content"> <div class="content">
<div class="device filesystem" ng-repeat="filesystem in client.filesystems" ng-click="showFilesystemMenu(filesystem)"> <div class="device filesystem" ng-repeat="filesystem in focusedClient.filesystems" ng-click="showFilesystemMenu(filesystem)">
{{filesystem.name}} {{filesystem.name}}
</div> </div>
</div> </div>
</div> </div>
<!-- Connection parameters which may be modified while the connection is open --> <!-- Connection parameters which may be modified while the connection is open -->
<div class="menu-section connection-parameters" id="connection-settings" ng-show="client.protocol"> <div class="menu-section connection-parameters" id="connection-settings" ng-if="focusedClient.protocol">
<guac-form namespace="getProtocolNamespace(client.protocol)" <guac-form namespace="getProtocolNamespace(focusedClient.protocol)"
content="client.forms" content="focusedClient.forms"
model="menu.connectionParameters" model="menu.connectionParameters"
model-only="true"></guac-form> model-only="true"></guac-form>
</div> </div>

View File

@@ -20,7 +20,7 @@
/** /**
* Provides the ManagedClientGroup class used by the guacClientManager service. * Provides the ManagedClientGroup class used by the guacClientManager service.
*/ */
angular.module('client').factory('ManagedClientGroup', [function defineManagedClientGroup() { angular.module('client').factory('ManagedClientGroup', ['$injector', function defineManagedClientGroup($injector) {
/** /**
* Object which serves as a grouping of ManagedClients. Each * Object which serves as a grouping of ManagedClients. Each
@@ -201,7 +201,15 @@ angular.module('client').factory('ManagedClientGroup', [function defineManagedCl
* ManagedClientGroup. * ManagedClientGroup.
*/ */
ManagedClientGroup.getName = function getName(group) { ManagedClientGroup.getName = function getName(group) {
return _.filter(group.clients, (client => !!client.name)).map(client => client.name).join(', ') || '...';
// Generate a name from ONLY the focused clients, unless there are no
// focused clients
var relevantClients = _.filter(group.clients, client => client.clientProperties.focused);
if (!relevantClients.length)
relevantClients = group.clients;
return _.filter(relevantClients, (client => !!client.name)).map(client => client.name).join(', ') || '...';
}; };
return ManagedClientGroup; return ManagedClientGroup;

View File

@@ -42,6 +42,14 @@ angular.module('client').factory('ManagedFilesystem', ['$rootScope', '$injector'
// Use empty object by default // Use empty object by default
template = template || {}; template = template || {};
/**
* The client that originally received the "filesystem" instruction
* that resulted in the creation of this ManagedFilesystem.
*
* @type ManagedClient
*/
this.client = template.client;
/** /**
* The Guacamole filesystem object, as received via a "filesystem" * The Guacamole filesystem object, as received via a "filesystem"
* instruction. * instruction.
@@ -171,10 +179,11 @@ angular.module('client').factory('ManagedFilesystem', ['$rootScope', '$injector'
* @returns {ManagedFilesystem} * @returns {ManagedFilesystem}
* The newly-created ManagedFilesystem. * The newly-created ManagedFilesystem.
*/ */
ManagedFilesystem.getInstance = function getInstance(object, name) { ManagedFilesystem.getInstance = function getInstance(client, object, name) {
// Init new filesystem object // Init new filesystem object
var managedFilesystem = new ManagedFilesystem({ var managedFilesystem = new ManagedFilesystem({
client : client,
object : object, object : object,
name : name, name : name,
root : new ManagedFilesystem.File({ root : new ManagedFilesystem.File({
@@ -196,9 +205,6 @@ angular.module('client').factory('ManagedFilesystem', ['$rootScope', '$injector'
* client and filesystem. The browser will automatically start the * client and filesystem. The browser will automatically start the
* download upon completion of this function. * download upon completion of this function.
* *
* @param {ManagedClient} managedClient
* The ManagedClient from which the file is to be downloaded.
*
* @param {ManagedFilesystem} managedFilesystem * @param {ManagedFilesystem} managedFilesystem
* The ManagedFilesystem from which the file is to be downloaded. Any * The ManagedFilesystem from which the file is to be downloaded. Any
* path information provided must be relative to this filesystem. * path information provided must be relative to this filesystem.
@@ -206,7 +212,7 @@ angular.module('client').factory('ManagedFilesystem', ['$rootScope', '$injector'
* @param {String} path * @param {String} path
* The full, absolute path of the file to download. * The full, absolute path of the file to download.
*/ */
ManagedFilesystem.downloadFile = function downloadFile(managedClient, managedFilesystem, path) { ManagedFilesystem.downloadFile = function downloadFile(managedFilesystem, path) {
// Request download // Request download
managedFilesystem.object.requestInputStream(path, function downloadStreamReceived(stream, mimetype) { managedFilesystem.object.requestInputStream(path, function downloadStreamReceived(stream, mimetype) {
@@ -215,7 +221,7 @@ angular.module('client').factory('ManagedFilesystem', ['$rootScope', '$injector'
var filename = path.match(/(.*[\\/])?(.*)/)[2]; var filename = path.match(/(.*[\\/])?(.*)/)[2];
// Start download // Start download
tunnelService.downloadStream(managedClient.tunnel.uuid, stream, mimetype, filename); tunnelService.downloadStream(managedFilesystem.client.tunnel.uuid, stream, mimetype, filename);
}); });