From f3101688e253c7eac136821900c706e40bec1bab Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 1 Nov 2020 20:22:52 -0800 Subject: [PATCH] GUACAMOLE-221: Prompt user to provide additional parameters when "required" instruction is received. --- .../client/controllers/clientController.js | 109 +++++++++++++++++- .../webapp/app/client/styles/notification.css | 23 ++++ .../webapp/app/client/types/ManagedClient.js | 43 +++++++ .../app/index/controllers/indexController.js | 10 +- .../src/main/webapp/translations/en.json | 2 + 5 files changed, 181 insertions(+), 6 deletions(-) create mode 100644 guacamole/src/main/webapp/app/client/styles/notification.css diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js index 326d868a5..451adeeb9 100644 --- a/guacamole/src/main/webapp/app/client/controllers/clientController.js +++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js @@ -732,8 +732,14 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams return $scope.client && $scope.client.clientState.tunnelUnstable; }; - // Show status dialog when connection status changes - $scope.$watch('client.clientState.connectionState', function clientStateChanged(connectionState) { + /** + * Notifies the user that the connection state has changed. + * + * @param {String} connectionState + * The current connection state, as defined by + * ManagedClientState.ConnectionState. + */ + var notifyConnectionState = function notifyConnectionState(connectionState) { // Hide any existing status guacNotification.showStatus(false); @@ -835,6 +841,105 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams else guacNotification.showStatus(false); + }; + + /** + * Prompts the user to enter additional connection parameters. If the + * protocol and associated parameters of the underlying connection are not + * yet known, this function has no effect and should be re-invoked once + * the parameters are known. + * + * @param {Object.} requiredParameters + * The set of all parameters requested by the server via "required" + * instructions, where each object key is the name of a requested + * parameter and each value is the current value entered by the user. + */ + var notifyParametersRequired = function notifyParametersRequired(requiredParameters) { + + /** + * Action which submits the current set of parameter values, requesting + * that the connection continue. + */ + var SUBMIT_PARAMETERS = { + name : "CLIENT.ACTION_CONTINUE", + className : "button", + callback : function submitParameters() { + if ($scope.client) { + var params = $scope.client.requiredParameters; + $scope.client.requiredParameters = null; + ManagedClient.sendArguments($scope.client, params); + } + } + }; + + /** + * Action which cancels submission of additional parameters and + * disconnects from the current connection. + */ + var CANCEL_PARAMETER_SUBMISSION = { + name : "CLIENT.ACTION_CANCEL", + className : "button", + callback : function cancelSubmission() { + $scope.client.requiredParameters = null; + $scope.disconnect(); + } + }; + + // Attempt to prompt for parameters only if the parameters that apply + // to the underlying connection are known + if (!$scope.client.protocol || !$scope.client.forms) + return; + + // Hide any existing status + guacNotification.showStatus(false); + + // Prompt for parameters + guacNotification.showStatus({ + formNamespace : Protocol.getNamespace($scope.client.protocol), + forms : $scope.client.forms, + formModel : requiredParameters, + formSubmitCallback : SUBMIT_PARAMETERS.callback, + actions : [ SUBMIT_PARAMETERS, CANCEL_PARAMETER_SUBMISSION ] + }); + + }; + + /** + * Returns whether the given connection state allows for submission of + * connection parameters via "argv" instructions. + * + * @param {String} connectionState + * The connection state to test, as defined by + * ManagedClientState.ConnectionState. + * + * @returns {boolean} + * true if the given connection state allows submission of connection + * parameters via "argv" instructions, false otherwise. + */ + var canSubmitParameters = function canSubmitParameters(connectionState) { + return (connectionState === ManagedClientState.ConnectionState.WAITING || + connectionState === ManagedClientState.ConnectionState.CONNECTED); + }; + + // Show status dialog when connection status changes + $scope.$watchGroup([ + 'client.clientState.connectionState', + 'client.requiredParameters', + 'client.protocol', + 'client.forms' + ], function clientStateChanged(newValues) { + + var connectionState = newValues[0]; + var requiredParameters = newValues[1]; + + // Prompt for parameters only if parameters can actually be submitted + if (requiredParameters && canSubmitParameters(connectionState)) + notifyParametersRequired(requiredParameters); + + // Otherwise, just show general connection state + else + notifyConnectionState(connectionState); + }); $scope.zoomIn = function zoomIn() { diff --git a/guacamole/src/main/webapp/app/client/styles/notification.css b/guacamole/src/main/webapp/app/client/styles/notification.css new file mode 100644 index 000000000..77a0a641a --- /dev/null +++ b/guacamole/src/main/webapp/app/client/styles/notification.css @@ -0,0 +1,23 @@ +/* + * 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. + */ + +.client .notification .parameters h3, +.client .notification .parameters .password-field .toggle-password { + display: none; +} diff --git a/guacamole/src/main/webapp/app/client/types/ManagedClient.js b/guacamole/src/main/webapp/app/client/types/ManagedClient.js index c1eccdddc..b40a94994 100644 --- a/guacamole/src/main/webapp/app/client/types/ManagedClient.js +++ b/guacamole/src/main/webapp/app/client/types/ManagedClient.js @@ -166,6 +166,16 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', data : '' }); + /** + * The current state of all parameters requested by the server via + * "required" instructions, where each object key is the name of a + * requested parameter and each value is the current value entered by + * the user or null if no parameters are currently being requested. + * + * @type Object. + */ + this.requiredParameters = null; + /** * All uploaded files. As files are uploaded, their progress can be * observed through the elements of this array. It is intended that @@ -578,6 +588,16 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', }); }; + // Handle any received prompts + client.onrequired = function onrequired(parameters) { + $rootScope.$apply(function promptUser() { + managedClient.requiredParameters = {}; + angular.forEach(parameters, function populateParameter(name) { + managedClient.requiredParameters[name] = ''; + }); + }); + }; + // Manage the client display managedClient.managedDisplay = ManagedDisplay.getInstance(client.getDisplay()); @@ -736,6 +756,29 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', delete managedClient.arguments[name]; }; + /** + * Sends the given connection parameter values using "argv" streams, + * updating the behavior of the connection in real-time if the server is + * expecting or requiring these parameters. + * + * @param {ManagedClient} managedClient + * The ManagedClient instance associated with the active connection + * being modified. + * + * @param {Object.} values + * The set of values to attempt to assign to corresponding connection + * parameters, where each object key is the connection parameter being + * set. + */ + ManagedClient.sendArguments = function sendArguments(managedClient, values) { + angular.forEach(values, function sendArgument(value, name) { + var stream = managedClient.client.createArgumentValueStream("text/plain", name); + var writer = new Guacamole.StringWriter(stream); + writer.sendText(value); + writer.sendEnd(); + }); + }; + /** * 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 diff --git a/guacamole/src/main/webapp/app/index/controllers/indexController.js b/guacamole/src/main/webapp/app/index/controllers/indexController.js index ac2fec451..9b9078235 100644 --- a/guacamole/src/main/webapp/app/index/controllers/indexController.js +++ b/guacamole/src/main/webapp/app/index/controllers/indexController.js @@ -101,8 +101,9 @@ angular.module('index').controller('indexController', ['$scope', '$injector', // Broadcast keydown events keyboard.onkeydown = function onkeydown(keysym) { - // Do not handle key events if not logged in - if ($scope.expectedCredentials) + // Do not handle key events if not logged in or if a notification is + // shown + if ($scope.expectedCredentials || guacNotification.getStatus()) return true; // Warn of pending keydown @@ -119,8 +120,9 @@ angular.module('index').controller('indexController', ['$scope', '$injector', // Broadcast keyup events keyboard.onkeyup = function onkeyup(keysym) { - // Do not handle key events if not logged in - if ($scope.expectedCredentials) + // Do not handle key events if not logged in or if a notification is + // shown + if ($scope.expectedCredentials || guacNotification.getStatus()) return; // Warn of pending keyup diff --git a/guacamole/src/main/webapp/translations/en.json b/guacamole/src/main/webapp/translations/en.json index 47e4b717b..caf1b0a2b 100644 --- a/guacamole/src/main/webapp/translations/en.json +++ b/guacamole/src/main/webapp/translations/en.json @@ -54,7 +54,9 @@ "CLIENT" : { "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", "ACTION_CLEAR_COMPLETED_TRANSFERS" : "Clear", + "ACTION_CONTINUE" : "@:APP.ACTION_CONTINUE", "ACTION_DISCONNECT" : "Disconnect", "ACTION_LOGOUT" : "@:APP.ACTION_LOGOUT", "ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK",