mirror of
				https://github.com/gyurix1968/guacamole-client.git
				synced 2025-10-30 00:23:21 +00:00 
			
		
		
		
	GUACAMOLE-630: Allow parameters received via "argv" streams to be edited within the Guacamole menu.
This commit is contained in:
		| @@ -27,6 +27,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams | |||||||
|     var ManagedClient      = $injector.get('ManagedClient'); |     var ManagedClient      = $injector.get('ManagedClient'); | ||||||
|     var ManagedClientState = $injector.get('ManagedClientState'); |     var ManagedClientState = $injector.get('ManagedClientState'); | ||||||
|     var ManagedFilesystem  = $injector.get('ManagedFilesystem'); |     var ManagedFilesystem  = $injector.get('ManagedFilesystem'); | ||||||
|  |     var Protocol           = $injector.get('Protocol'); | ||||||
|     var ScrollState        = $injector.get('ScrollState'); |     var ScrollState        = $injector.get('ScrollState'); | ||||||
|  |  | ||||||
|     // Required services |     // Required services | ||||||
| @@ -248,7 +249,15 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams | |||||||
|          * |          * | ||||||
|          * @type ScrollState |          * @type ScrollState | ||||||
|          */ |          */ | ||||||
|         scrollState : new ScrollState() |         scrollState : new ScrollState(), | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * The current desired values of all editable connection parameters as | ||||||
|  |          * a set of name/value pairs, including any changes made by the user. | ||||||
|  |          * | ||||||
|  |          * @type {Object.<String, String>} | ||||||
|  |          */ | ||||||
|  |         connectionParameters : {} | ||||||
|  |  | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -257,6 +266,16 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams | |||||||
|         $scope.menu.shown = false; |         $scope.menu.shown = false; | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Applies any changes to connection parameters made by the user within the | ||||||
|  |      * Guacamole menu. | ||||||
|  |      */ | ||||||
|  |     $scope.applyParameterChanges = function applyParameterChanges() { | ||||||
|  |         angular.forEach($scope.menu.connectionParameters, function sendArgv(value, name) { | ||||||
|  |             ManagedClient.setArgument($scope.client, name, value); | ||||||
|  |         }); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * The client which should be attached to the client UI. |      * The client which should be attached to the client UI. | ||||||
|      * |      * | ||||||
| @@ -429,11 +448,19 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams | |||||||
|  |  | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     // Update client state/behavior as visibility of the Guacamole menu changes | ||||||
|     $scope.$watch('menu.shown', function menuVisibilityChanged(menuShown, menuShownPreviousState) { |     $scope.$watch('menu.shown', function menuVisibilityChanged(menuShown, menuShownPreviousState) { | ||||||
|          |          | ||||||
|         // Send clipboard data if menu is hidden |         // Send clipboard and argument value data once menu is hidden | ||||||
|         if (!menuShown && menuShownPreviousState) |         if (!menuShown && menuShownPreviousState) { | ||||||
|             $scope.$broadcast('guacClipboard', $scope.client.clipboardData); |             $scope.$broadcast('guacClipboard', $scope.client.clipboardData); | ||||||
|  |             $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 | ||||||
|         $scope.client.clientProperties.keyboardEnabled = !menuShown; |         $scope.client.clientProperties.keyboardEnabled = !menuShown; | ||||||
| @@ -805,6 +832,11 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams | |||||||
|     // Set client-specific menu actions |     // Set client-specific menu actions | ||||||
|     $scope.clientMenuActions = [ DISCONNECT_MENU_ACTION ]; |     $scope.clientMenuActions = [ DISCONNECT_MENU_ACTION ]; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @borrows Protocol.getNamespace | ||||||
|  |      */ | ||||||
|  |     $scope.getProtocolNamespace = Protocol.getNamespace; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * The currently-visible filesystem within the filesystem menu, if the |      * The currently-visible filesystem within the filesystem menu, if the | ||||||
|      * filesystem menu is open. If no filesystem is currently visible, this |      * filesystem menu is open. If no filesystem is currently visible, this | ||||||
|   | |||||||
| @@ -96,6 +96,14 @@ | |||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|  |  | ||||||
|  |                 <!-- Connection parameters which may be modified while the connection is open --> | ||||||
|  |                 <div class="menu-section connection-parameters" id="connection-settings" ng-show="client.protocol"> | ||||||
|  |                     <guac-form namespace="getProtocolNamespace(client.protocol)" | ||||||
|  |                                content="client.forms" | ||||||
|  |                                model="menu.connectionParameters" | ||||||
|  |                                model-only="true"></guac-form> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|                 <!-- Input method --> |                 <!-- Input method --> | ||||||
|                 <div class="menu-section" id="keyboard-settings"> |                 <div class="menu-section" id="keyboard-settings"> | ||||||
|                     <h3>{{'CLIENT.SECTION_HEADER_INPUT_METHOD' | translate}}</h3> |                     <h3>{{'CLIENT.SECTION_HEADER_INPUT_METHOD' | translate}}</h3> | ||||||
|   | |||||||
							
								
								
									
										152
									
								
								guacamole/src/main/webapp/app/client/types/ManagedArgument.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								guacamole/src/main/webapp/app/client/types/ManagedArgument.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | |||||||
|  | /* | ||||||
|  |  * 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 ManagedArgument class used by ManagedClient. | ||||||
|  |  */ | ||||||
|  | angular.module('client').factory('ManagedArgument', ['$q', function defineManagedArgument($q) { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Object which represents an argument (connection parameter) which may be | ||||||
|  |      * changed by the user while the connection is open. | ||||||
|  |      *  | ||||||
|  |      * @constructor | ||||||
|  |      * @param {ManagedArgument|Object} [template={}] | ||||||
|  |      *     The object whose properties should be copied within the new | ||||||
|  |      *     ManagedArgument. | ||||||
|  |      */ | ||||||
|  |     var ManagedArgument = function ManagedArgument(template) { | ||||||
|  |  | ||||||
|  |         // Use empty object by default | ||||||
|  |         template = template || {}; | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * The name of the connection parameter. | ||||||
|  |          * | ||||||
|  |          * @type {String} | ||||||
|  |          */ | ||||||
|  |         this.name = template.name; | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * The current value of the connection parameter. | ||||||
|  |          * | ||||||
|  |          * @type {String} | ||||||
|  |          */ | ||||||
|  |         this.value = template.value; | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * A valid, open output stream which may be used to apply a new value | ||||||
|  |          * to the connection parameter. | ||||||
|  |          * | ||||||
|  |          * @type {Guacamole.OutputStream} | ||||||
|  |          */ | ||||||
|  |         this.stream = template.stream; | ||||||
|  |  | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Requests editable access to a given connection parameter, returning a | ||||||
|  |      * promise which is resolved with a ManagedArgument instance that provides | ||||||
|  |      * such access if the parameter is indeed editable. | ||||||
|  |      * | ||||||
|  |      * @param {ManagedClient} managedClient | ||||||
|  |      *     The ManagedClient instance associated with the connection for which | ||||||
|  |      *     an editable version of the connection parameter is being retrieved. | ||||||
|  |      * | ||||||
|  |      * @param {String} name | ||||||
|  |      *     The name of the connection parameter. | ||||||
|  |      * | ||||||
|  |      * @param {String} value | ||||||
|  |      *     The current value of the connection parameter, as received from a | ||||||
|  |      *     prior, inbound "argv" stream. | ||||||
|  |      * | ||||||
|  |      * @returns {Promise.<ManagedArgument>} | ||||||
|  |      *     A promise which is resolved with the new ManagedArgument instance | ||||||
|  |      *     once the requested parameter has been verified as editable. | ||||||
|  |      */ | ||||||
|  |     ManagedArgument.getInstance = function getInstance(managedClient, name, value) { | ||||||
|  |  | ||||||
|  |         var deferred = $q.defer(); | ||||||
|  |  | ||||||
|  |         // Create internal, fully-populated instance of ManagedArgument, to be | ||||||
|  |         // returned only once mutability of the associated connection parameter | ||||||
|  |         // has been verified | ||||||
|  |         var managedArgument = new ManagedArgument({ | ||||||
|  |             name   : name, | ||||||
|  |             value  : value, | ||||||
|  |             stream : managedClient.client.createArgumentValueStream('text/plain', name) | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // The connection parameter is editable only if a successful "ack" is | ||||||
|  |         // received | ||||||
|  |         managedArgument.stream.onack = function ackReceived(status) { | ||||||
|  |             if (status.isError()) | ||||||
|  |                 deferred.reject(status); | ||||||
|  |             else | ||||||
|  |                 deferred.resolve(managedArgument); | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         return deferred.promise; | ||||||
|  |  | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Sets the given editable argument (connection parameter) to the given | ||||||
|  |      * value, updating the behavior of the associated connection in real-time. | ||||||
|  |      * If successful, the ManagedArgument provided cannot be used for future | ||||||
|  |      * calls to setValue() and must be replaced with a new instance. This | ||||||
|  |      * function only has an effect if the new parameter value is different from | ||||||
|  |      * the current value. | ||||||
|  |      * | ||||||
|  |      * @param {ManagedArgument} managedArgument | ||||||
|  |      *     The ManagedArgument instance associated with the connection | ||||||
|  |      *     parameter being modified. | ||||||
|  |      * | ||||||
|  |      * @param {String} value | ||||||
|  |      *     The new value to assign to the connection parameter. | ||||||
|  |      * | ||||||
|  |      * @returns {Boolean} | ||||||
|  |      *     true if the connection parameter was sent and the provided | ||||||
|  |      *     ManagedArgument instance may no longer be used for future setValue() | ||||||
|  |      *     calls, false if the connection parameter was NOT sent as it has not | ||||||
|  |      *     changed. | ||||||
|  |      */ | ||||||
|  |     ManagedArgument.setValue = function setValue(managedArgument, value) { | ||||||
|  |  | ||||||
|  |         // Stream new value only if value has changed | ||||||
|  |         if (value !== managedArgument.value) { | ||||||
|  |  | ||||||
|  |             var writer = new Guacamole.StringWriter(managedArgument.stream); | ||||||
|  |             writer.sendText(value); | ||||||
|  |             writer.sendEnd(); | ||||||
|  |  | ||||||
|  |             // ManagedArgument instance is no longer usable | ||||||
|  |             return true; | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // No parameter value change was attempted and the ManagedArgument | ||||||
|  |         // instance may be reused | ||||||
|  |         return false; | ||||||
|  |  | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     return ManagedArgument; | ||||||
|  |  | ||||||
|  | }]); | ||||||
| @@ -27,6 +27,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', | |||||||
|     var ClientProperties       = $injector.get('ClientProperties'); |     var ClientProperties       = $injector.get('ClientProperties'); | ||||||
|     var ClientIdentifier       = $injector.get('ClientIdentifier'); |     var ClientIdentifier       = $injector.get('ClientIdentifier'); | ||||||
|     var ClipboardData          = $injector.get('ClipboardData'); |     var ClipboardData          = $injector.get('ClipboardData'); | ||||||
|  |     var ManagedArgument        = $injector.get('ManagedArgument'); | ||||||
|     var ManagedClientState     = $injector.get('ManagedClientState'); |     var ManagedClientState     = $injector.get('ManagedClientState'); | ||||||
|     var ManagedClientThumbnail = $injector.get('ManagedClientThumbnail'); |     var ManagedClientThumbnail = $injector.get('ManagedClientThumbnail'); | ||||||
|     var ManagedDisplay         = $injector.get('ManagedDisplay'); |     var ManagedDisplay         = $injector.get('ManagedDisplay'); | ||||||
| @@ -44,6 +45,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', | |||||||
|     var connectionService      = $injector.get('connectionService'); |     var connectionService      = $injector.get('connectionService'); | ||||||
|     var preferenceService      = $injector.get('preferenceService'); |     var preferenceService      = $injector.get('preferenceService'); | ||||||
|     var requestService         = $injector.get('requestService'); |     var requestService         = $injector.get('requestService'); | ||||||
|  |     var schemaService          = $injector.get('schemaService'); | ||||||
|     var tunnelService          = $injector.get('tunnelService'); |     var tunnelService          = $injector.get('tunnelService'); | ||||||
|     var guacAudio              = $injector.get('guacAudio'); |     var guacAudio              = $injector.get('guacAudio'); | ||||||
|     var guacHistory            = $injector.get('guacHistory'); |     var guacHistory            = $injector.get('guacHistory'); | ||||||
| @@ -117,6 +119,23 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', | |||||||
|          */ |          */ | ||||||
|         this.title = template.title; |         this.title = template.title; | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * The name which uniquely identifies the protocol of the connection in | ||||||
|  |          * use. If the protocol cannot be determined, such as when a connection | ||||||
|  |          * group is in use, this will be null. | ||||||
|  |          * | ||||||
|  |          * @type {String} | ||||||
|  |          */ | ||||||
|  |         this.protocol = template.protocol || null; | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * An array of forms describing all known parameters for the connection | ||||||
|  |          * in use, including those which may not be editable. | ||||||
|  |          * | ||||||
|  |          * @type {Form[]} | ||||||
|  |          */ | ||||||
|  |         this.forms = template.forms || []; | ||||||
|  |  | ||||||
|         /** |         /** | ||||||
|          * The most recently-generated thumbnail for this connection, as |          * The most recently-generated thumbnail for this connection, as | ||||||
|          * stored within the local connection history. If no thumbnail is |          * stored within the local connection history. If no thumbnail is | ||||||
| @@ -179,6 +198,17 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', | |||||||
|          */ |          */ | ||||||
|         this.clientProperties = template.clientProperties || new ClientProperties(); |         this.clientProperties = template.clientProperties || new ClientProperties(); | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * All editable arguments (connection parameters), stored by their | ||||||
|  |          * names. Arguments will only be present within this set if their | ||||||
|  |          * current values have been exposed by the server via an inbound "argv" | ||||||
|  |          * stream and the server has confirmed that the value may be changed | ||||||
|  |          * through a successful "ack" to an outbound "argv" stream. | ||||||
|  |          * | ||||||
|  |          * @type {Object.<String, ManagedArgument>} | ||||||
|  |          */ | ||||||
|  |         this.arguments = template.arguments || {}; | ||||||
|  |  | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -448,6 +478,33 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', | |||||||
|  |  | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|  |         // Test for argument mutability whenever an argument value is | ||||||
|  |         // received | ||||||
|  |         client.onargv = function clientArgumentValueReceived(stream, mimetype, name) { | ||||||
|  |  | ||||||
|  |             // Ignore arguments which do not use a mimetype currently supported | ||||||
|  |             // by the web application | ||||||
|  |             if (mimetype !== 'text/plain') | ||||||
|  |                 return; | ||||||
|  |  | ||||||
|  |             var reader = new Guacamole.StringReader(stream); | ||||||
|  |  | ||||||
|  |             // Assemble received data into a single string | ||||||
|  |             var value = ''; | ||||||
|  |             reader.ontext = function textReceived(text) { | ||||||
|  |                 value += text; | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             // Test mutability once stream is finished, storing the current | ||||||
|  |             // value for the argument only if it is mutable | ||||||
|  |             reader.onend = function textComplete() { | ||||||
|  |                 ManagedArgument.getInstance(managedClient, name, value).then(function argumentIsMutable(argument) { | ||||||
|  |                     managedClient.arguments[name] = argument; | ||||||
|  |                 }, function ignoreImmutableArguments() {}); | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |         }; | ||||||
|  |  | ||||||
|         // Handle any received clipboard data |         // Handle any received clipboard data | ||||||
|         client.onclipboard = function clientClipboardReceived(stream, mimetype) { |         client.onclipboard = function clientClipboardReceived(stream, mimetype) { | ||||||
|  |  | ||||||
| @@ -522,11 +579,16 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', | |||||||
|             client.connect(connectString); |             client.connect(connectString); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         // If using a connection, pull connection name |         // If using a connection, pull connection name and protocol information | ||||||
|         if (clientIdentifier.type === ClientIdentifier.Types.CONNECTION) { |         if (clientIdentifier.type === ClientIdentifier.Types.CONNECTION) { | ||||||
|             connectionService.getConnection(clientIdentifier.dataSource, clientIdentifier.id) |             $q.all({ | ||||||
|             .then(function connectionRetrieved(connection) { |                 connection : connectionService.getConnection(clientIdentifier.dataSource, clientIdentifier.id), | ||||||
|                 managedClient.name = managedClient.title = connection.name; |                 protocols  : schemaService.getProtocols(clientIdentifier.dataSource) | ||||||
|  |             }) | ||||||
|  |             .then(function dataRetrieved(values) { | ||||||
|  |                 managedClient.name = managedClient.title = values.connection.name; | ||||||
|  |                 managedClient.protocol = values.connection.protocol; | ||||||
|  |                 managedClient.forms = values.protocols[values.connection.protocol].connectionForms; | ||||||
|             }, requestService.WARN); |             }, requestService.WARN); | ||||||
|         } |         } | ||||||
|          |          | ||||||
| @@ -619,6 +681,52 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', | |||||||
|  |  | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Assigns the given value to the connection parameter having the given | ||||||
|  |      * name, updating the behavior of the connection in real-time. If the | ||||||
|  |      * connection parameter is not editable, this function has no effect. | ||||||
|  |      * | ||||||
|  |      * @param {ManagedClient} managedClient | ||||||
|  |      *     The ManagedClient instance associated with the active connection | ||||||
|  |      *     being modified. | ||||||
|  |      * | ||||||
|  |      * @param {String} name | ||||||
|  |      *     The name of the connection parameter to modify. | ||||||
|  |      * | ||||||
|  |      * @param {String} value | ||||||
|  |      *     The value to attempt to assign to the given connection parameter. | ||||||
|  |      */ | ||||||
|  |     ManagedClient.setArgument = function setArgument(managedClient, name, value) { | ||||||
|  |         var managedArgument = managedClient.arguments[name]; | ||||||
|  |         if (managedArgument && ManagedArgument.setValue(managedArgument, value)) | ||||||
|  |             delete managedClient.arguments[name]; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Retrieves the current values of all editable connection parameters as a | ||||||
|  |      * set of name/value pairs suitable for use as the model of a form which | ||||||
|  |      * edits those parameters. | ||||||
|  |      * | ||||||
|  |      * @param {ManagedClient} client | ||||||
|  |      *     The ManagedClient instance associated with the active connection | ||||||
|  |      *     whose parameter values are being retrieved. | ||||||
|  |      * | ||||||
|  |      * @returns {Object.<String, String>} | ||||||
|  |      *     A new set of name/value pairs containing the current values of all | ||||||
|  |      *     editable parameters. | ||||||
|  |      */ | ||||||
|  |     ManagedClient.getArgumentModel = function getArgumentModel(client) { | ||||||
|  |  | ||||||
|  |         var model = {}; | ||||||
|  |  | ||||||
|  |         angular.forEach(client.arguments, function addModelEntry(managedArgument) { | ||||||
|  |             model[managedArgument.name] = managedArgument.value; | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         return model; | ||||||
|  |  | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Produces a sharing link for the given ManagedClient using the given |      * Produces a sharing link for the given ManagedClient using the given | ||||||
|      * sharing profile. The resulting sharing link, and any required login |      * sharing profile. The resulting sharing link, and any required login | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user