Merge 1.3.0 changes back to master.

This commit is contained in:
Virtually Nick
2020-11-02 09:47:26 -05:00
11 changed files with 303 additions and 10 deletions

View File

@@ -714,6 +714,18 @@ Guacamole.Client = function(tunnel) {
*/ */
this.onpipe = null; this.onpipe = null;
/**
* Fired when a "required" instruction is received. A required instruction
* indicates that additional parameters are required for the connection to
* continue, such as user credentials.
*
* @event
* @param {String[]} parameters
* The names of the connection parameters that are required to be
* provided for the connection to continue.
*/
this.onrequired = null;
/** /**
* Fired whenever a sync instruction is received from the server, indicating * Fired whenever a sync instruction is received from the server, indicating
* that the server is finished processing any input from the client and * that the server is finished processing any input from the client and
@@ -1338,6 +1350,10 @@ Guacamole.Client = function(tunnel) {
}, },
"required": function required(parameters) {
if (guac_client.onrequired) guac_client.onrequired(parameters);
},
"reset": function(parameters) { "reset": function(parameters) {
var layer = getLayer(parseInt(parameters[0])); var layer = getLayer(parseInt(parameters[0]));

View File

@@ -41,6 +41,15 @@ public enum GuacamoleProtocolCapability {
*/ */
PROTOCOL_VERSION_DETECTION(GuacamoleProtocolVersion.VERSION_1_1_0), PROTOCOL_VERSION_DETECTION(GuacamoleProtocolVersion.VERSION_1_1_0),
/**
* Support for the "required" instruction. The "required" instruction
* allows the server to explicitly request connection parameters from the
* client without which the connection cannot continue, such as user
* credentials. Support for this instruction was introduced in
* {@link GuacamoleProtocolVersion#VERSION_1_3_0}.
*/
REQUIRED_INSTRUCTION(GuacamoleProtocolVersion.VERSION_1_3_0),
/** /**
* Support for the "timezone" handshake instruction. The "timezone" * Support for the "timezone" handshake instruction. The "timezone"
* instruction allows the client to request that the server forward their * instruction allows the client to request that the server forward their

View File

@@ -46,11 +46,18 @@ public class GuacamoleProtocolVersion {
*/ */
public static final GuacamoleProtocolVersion VERSION_1_1_0 = new GuacamoleProtocolVersion(1, 1, 0); public static final GuacamoleProtocolVersion VERSION_1_1_0 = new GuacamoleProtocolVersion(1, 1, 0);
/**
* Protocol version 1.3.0, which introduces the "required" instruction
* allowing the server to explicitly request connection parameters from the
* client.
*/
public static final GuacamoleProtocolVersion VERSION_1_3_0 = new GuacamoleProtocolVersion(1, 3, 0);
/** /**
* The most recent version of the Guacamole protocol at the time this * The most recent version of the Guacamole protocol at the time this
* version of GuacamoleProtocolVersion was built. * version of GuacamoleProtocolVersion was built.
*/ */
public static final GuacamoleProtocolVersion LATEST = VERSION_1_1_0; public static final GuacamoleProtocolVersion LATEST = VERSION_1_3_0;
/** /**
* A regular expression that matches the VERSION_X_Y_Z pattern, where * A regular expression that matches the VERSION_X_Y_Z pattern, where

View File

@@ -732,8 +732,14 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
return $scope.client && $scope.client.clientState.tunnelUnstable; 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 // Hide any existing status
guacNotification.showStatus(false); guacNotification.showStatus(false);
@@ -835,6 +841,105 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
else else
guacNotification.showStatus(false); 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.<String, String>} 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() { $scope.zoomIn = function zoomIn() {

View File

@@ -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;
}

View File

@@ -166,6 +166,16 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
data : '' 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.<String, String>
*/
this.requiredParameters = null;
/** /**
* All uploaded files. As files are uploaded, their progress can be * All uploaded files. As files are uploaded, their progress can be
* observed through the elements of this array. It is intended that * 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 // Manage the client display
managedClient.managedDisplay = ManagedDisplay.getInstance(client.getDisplay()); managedClient.managedDisplay = ManagedDisplay.getInstance(client.getDisplay());
@@ -736,6 +756,29 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
delete managedClient.arguments[name]; 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.<String, String>} 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 * 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 * set of name/value pairs suitable for use as the model of a form which

View File

@@ -101,8 +101,9 @@ angular.module('index').controller('indexController', ['$scope', '$injector',
// Broadcast keydown events // Broadcast keydown events
keyboard.onkeydown = function onkeydown(keysym) { keyboard.onkeydown = function onkeydown(keysym) {
// Do not handle key events if not logged in // Do not handle key events if not logged in or if a notification is
if ($scope.expectedCredentials) // shown
if ($scope.expectedCredentials || guacNotification.getStatus())
return true; return true;
// Warn of pending keydown // Warn of pending keydown
@@ -119,8 +120,9 @@ angular.module('index').controller('indexController', ['$scope', '$injector',
// Broadcast keyup events // Broadcast keyup events
keyboard.onkeyup = function onkeyup(keysym) { keyboard.onkeyup = function onkeyup(keysym) {
// Do not handle key events if not logged in // Do not handle key events if not logged in or if a notification is
if ($scope.expectedCredentials) // shown
if ($scope.expectedCredentials || guacNotification.getStatus())
return; return;
// Warn of pending keyup // Warn of pending keyup

View File

@@ -101,3 +101,44 @@
.notification .progress .text { .notification .progress .text {
position: relative; position: relative;
} }
.notification .parameters {
width: 100%;
}
.notification .parameters .fields {
display: table;
width: 100%;
}
.notification .parameters .fields .labeled-field {
display: table-row;
}
.notification .parameters .fields .field-header,
.notification .parameters .fields .form-field {
text-align: left;
display: table-cell;
padding: .125em;
vertical-align: top;
}
.notification .parameters .fields .field-header {
padding-right: 1em;
}
.notification .parameters .fields .field-header {
width: 0;
}
.notification .parameters .fields .form-field {
width: 100%;
}
.notification .parameters input[type=text],
.notification .parameters input[type=email],
.notification .parameters input[type=number],
.notification .parameters input[type=password],
.notification .parameters textarea {
max-width: 100%;
}

View File

@@ -1,4 +1,5 @@
<div class="notification" ng-class="notification.className"> <form class="notification" ng-class="notification.className"
ng-submit="notification.formSubmitCallback()">
<!-- Notification title --> <!-- Notification title -->
<div ng-show="notification.title" class="title-bar"> <div ng-show="notification.title" class="title-bar">
@@ -12,6 +13,15 @@
translate="{{notification.text.key}}" translate="{{notification.text.key}}"
translate-values="{{notification.text.variables}}"></p> translate-values="{{notification.text.variables}}"></p>
<!-- Arbitrary parameters -->
<div class="parameters" ng-show="notification.forms">
<guac-form
namespace="notification.formNamespace"
content="notification.forms"
model="notification.formModel"
model-only="true"></guac-form>
</div>
<!-- Current progress --> <!-- Current progress -->
<div class="progress" ng-show="notification.progress"><div class="bar" ng-show="progressPercent" ng-style="{'width': progressPercent + '%'}"></div><div <div class="progress" ng-show="notification.progress"><div class="bar" ng-show="progressPercent" ng-style="{'width': progressPercent + '%'}"></div><div
ng-show="notification.progress.text" ng-show="notification.progress.text"

View File

@@ -57,6 +57,41 @@ angular.module('notification').factory('Notification', [function defineNotificat
*/ */
this.text = template.text; this.text = template.text;
/**
* The translation namespace of the translation strings that will
* be generated for all fields within the notification. This namespace
* is absolutely required if form fields will be included in the
* notification.
*
* @type String
*/
this.formNamespace = template.formNamespace;
/**
* Optional form content to display. This may be a form, an array of
* forms, or a simple array of fields.
*
* @type Form[]|Form|Field[]|Field
*/
this.forms = template.forms;
/**
* The object which will receive all field values. Each field value
* will be assigned to the property of this object having the same
* name.
*
* @type Object.<String, String>
*/
this.formModel = template.model;
/**
* The function to invoke when the form is submitted, if form fields
* are present within the notification.
*
* @type Function
*/
this.formSubmitCallback = template.formSubmitCallback;
/** /**
* An array of all actions available to the user in response to this * An array of all actions available to the user in response to this
* notification. * notification.

View File

@@ -54,7 +54,9 @@
"CLIENT" : { "CLIENT" : {
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
"ACTION_CANCEL" : "@:APP.ACTION_CANCEL",
"ACTION_CLEAR_COMPLETED_TRANSFERS" : "Clear", "ACTION_CLEAR_COMPLETED_TRANSFERS" : "Clear",
"ACTION_CONTINUE" : "@:APP.ACTION_CONTINUE",
"ACTION_DISCONNECT" : "Disconnect", "ACTION_DISCONNECT" : "Disconnect",
"ACTION_LOGOUT" : "@:APP.ACTION_LOGOUT", "ACTION_LOGOUT" : "@:APP.ACTION_LOGOUT",
"ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK", "ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK",