GUACAMOLE-234: Merge migration from JLDAP to Apache Directory API.
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/form/Field.java b/guacamole-ext/src/main/java/org/apache/guacamole/form/Field.java
index 9fe76a4..d35facd 100644
--- a/guacamole-ext/src/main/java/org/apache/guacamole/form/Field.java
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/form/Field.java
@@ -117,6 +117,12 @@
*/
public static String QUERY_PARAMETER = "QUERY_PARAMETER";
+ /**
+ * A color scheme accepted by the Guacamole server terminal emulator
+ * and protocols which leverage it.
+ */
+ public static String TERMINAL_COLOR_SCHEME = "TERMINAL_COLOR_SCHEME";
+
}
/**
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/form/TerminalColorSchemeField.java b/guacamole-ext/src/main/java/org/apache/guacamole/form/TerminalColorSchemeField.java
new file mode 100644
index 0000000..ccd7ce5
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/form/TerminalColorSchemeField.java
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+package org.apache.guacamole.form;
+
+/**
+ * Represents a terminal color scheme field. The field may contain only valid
+ * terminal color schemes as used by the Guacamole server terminal emulator
+ * and protocols which leverage it (SSH, telnet, Kubernetes).
+ */
+public class TerminalColorSchemeField extends Field {
+
+ /**
+ * Creates a new TerminalColorSchemeField with the given name.
+ *
+ * @param name
+ * The unique name to associate with this field.
+ */
+ public TerminalColorSchemeField(String name) {
+ super(name, Field.Type.TERMINAL_COLOR_SCHEME);
+ }
+
+}
diff --git a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/kubernetes.json b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/kubernetes.json
index 94ec738..17df48a 100644
--- a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/kubernetes.json
+++ b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/kubernetes.json
@@ -68,7 +68,7 @@
"fields" : [
{
"name" : "color-scheme",
- "type" : "TEXT",
+ "type" : "TERMINAL_COLOR_SCHEME",
"options" : [ "", "black-white", "gray-black", "green-black", "white-black" ]
},
{
diff --git a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/ssh.json b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/ssh.json
index 8eef774..6a91279 100644
--- a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/ssh.json
+++ b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/ssh.json
@@ -47,7 +47,7 @@
"fields" : [
{
"name" : "color-scheme",
- "type" : "TEXT",
+ "type" : "TERMINAL_COLOR_SCHEME",
"options" : [ "", "black-white", "gray-black", "green-black", "white-black" ]
},
{
diff --git a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/telnet.json b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/telnet.json
index 9b39168..2526096 100644
--- a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/telnet.json
+++ b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/telnet.json
@@ -51,7 +51,7 @@
"fields" : [
{
"name" : "color-scheme",
- "type" : "TEXT",
+ "type" : "TERMINAL_COLOR_SCHEME",
"options" : [ "", "black-white", "gray-black", "green-black", "white-black" ]
},
{
diff --git a/guacamole/pom.xml b/guacamole/pom.xml
index 7a68606..8f90f27 100644
--- a/guacamole/pom.xml
+++ b/guacamole/pom.xml
@@ -501,6 +501,13 @@
<version>1.0.10</version>
</dependency>
+ <!-- Pickr (JavaScript color picker) -->
+ <dependency>
+ <groupId>org.webjars.npm</groupId>
+ <artifactId>simonwep__pickr</artifactId>
+ <version>1.2.6</version>
+ </dependency>
+
</dependencies>
</project>
diff --git a/guacamole/src/licenses/LICENSE b/guacamole/src/licenses/LICENSE
index 1e228d2..7c696e5 100644
--- a/guacamole/src/licenses/LICENSE
+++ b/guacamole/src/licenses/LICENSE
@@ -688,6 +688,37 @@
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+Pickr (https://simonwep.github.io/pickr/)
+-----------------------------------------
+
+ Version: 1.2.6
+ From: 'Simon Reinisch' (https://github.com/Simonwep/)
+ License(s):
+ MIT (bundled/pickr-1.2.6/LICENSE)
+
+MIT License
+
+Copyright (c) 2019 Simon Reinisch
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
Simple Logging Facade for Java (http://slf4j.org/)
--------------------------------------------------
diff --git a/guacamole/src/licenses/bundled/pickr-1.2.6/LICENSE b/guacamole/src/licenses/bundled/pickr-1.2.6/LICENSE
new file mode 100644
index 0000000..e02b384
--- /dev/null
+++ b/guacamole/src/licenses/bundled/pickr-1.2.6/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Simon Reinisch
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js
index 41c6ba6..b90f263 100644
--- a/guacamole/src/main/webapp/app/client/controllers/clientController.js
+++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js
@@ -27,6 +27,7 @@
var ManagedClient = $injector.get('ManagedClient');
var ManagedClientState = $injector.get('ManagedClientState');
var ManagedFilesystem = $injector.get('ManagedFilesystem');
+ var Protocol = $injector.get('Protocol');
var ScrollState = $injector.get('ScrollState');
// Required services
@@ -248,7 +249,15 @@
*
* @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 : {}
};
@@ -258,6 +267,16 @@
};
/**
+ * 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.
*
* @type ManagedClient
@@ -429,12 +448,20 @@
});
+ // Update client state/behavior as visibility of the Guacamole menu changes
$scope.$watch('menu.shown', function menuVisibilityChanged(menuShown, menuShownPreviousState) {
- // Send clipboard data if menu is hidden
- if (!menuShown && menuShownPreviousState)
+ // Send clipboard and argument value data once menu is hidden
+ if (!menuShown && menuShownPreviousState) {
$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
$scope.client.clientProperties.keyboardEnabled = !menuShown;
@@ -806,6 +833,11 @@
$scope.clientMenuActions = [ DISCONNECT_MENU_ACTION ];
/**
+ * @borrows Protocol.getNamespace
+ */
+ $scope.getProtocolNamespace = Protocol.getNamespace;
+
+ /**
* The currently-visible filesystem within the filesystem menu, if the
* filesystem menu is open. If no filesystem is currently visible, this
* will be null.
diff --git a/guacamole/src/main/webapp/app/client/templates/client.html b/guacamole/src/main/webapp/app/client/templates/client.html
index ad85f23..7690e75 100644
--- a/guacamole/src/main/webapp/app/client/templates/client.html
+++ b/guacamole/src/main/webapp/app/client/templates/client.html
@@ -96,6 +96,14 @@
</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 -->
<div class="menu-section" id="keyboard-settings">
<h3>{{'CLIENT.SECTION_HEADER_INPUT_METHOD' | translate}}</h3>
diff --git a/guacamole/src/main/webapp/app/client/types/ManagedArgument.js b/guacamole/src/main/webapp/app/client/types/ManagedArgument.js
new file mode 100644
index 0000000..247d9f6
--- /dev/null
+++ b/guacamole/src/main/webapp/app/client/types/ManagedArgument.js
@@ -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;
+
+}]);
\ No newline at end of file
diff --git a/guacamole/src/main/webapp/app/client/types/ManagedClient.js b/guacamole/src/main/webapp/app/client/types/ManagedClient.js
index b4637e9..4551208 100644
--- a/guacamole/src/main/webapp/app/client/types/ManagedClient.js
+++ b/guacamole/src/main/webapp/app/client/types/ManagedClient.js
@@ -27,6 +27,7 @@
var ClientProperties = $injector.get('ClientProperties');
var ClientIdentifier = $injector.get('ClientIdentifier');
var ClipboardData = $injector.get('ClipboardData');
+ var ManagedArgument = $injector.get('ManagedArgument');
var ManagedClientState = $injector.get('ManagedClientState');
var ManagedClientThumbnail = $injector.get('ManagedClientThumbnail');
var ManagedDisplay = $injector.get('ManagedDisplay');
@@ -44,6 +45,7 @@
var connectionService = $injector.get('connectionService');
var preferenceService = $injector.get('preferenceService');
var requestService = $injector.get('requestService');
+ var schemaService = $injector.get('schemaService');
var tunnelService = $injector.get('tunnelService');
var guacAudio = $injector.get('guacAudio');
var guacHistory = $injector.get('guacHistory');
@@ -118,6 +120,23 @@
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
* stored within the local connection history. If no thumbnail is
* stored, this will be null.
@@ -179,6 +198,17 @@
*/
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 @@
};
+ // 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
client.onclipboard = function clientClipboardReceived(stream, mimetype) {
@@ -522,11 +579,16 @@
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) {
- connectionService.getConnection(clientIdentifier.dataSource, clientIdentifier.id)
- .then(function connectionRetrieved(connection) {
- managedClient.name = managedClient.title = connection.name;
+ $q.all({
+ connection : connectionService.getConnection(clientIdentifier.dataSource, clientIdentifier.id),
+ 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);
}
@@ -620,6 +682,52 @@
};
/**
+ * 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
* sharing profile. The resulting sharing link, and any required login
* information, can be retrieved from the <code>shareLinks</code> property
diff --git a/guacamole/src/main/webapp/app/form/controllers/terminalColorSchemeFieldController.js b/guacamole/src/main/webapp/app/form/controllers/terminalColorSchemeFieldController.js
new file mode 100644
index 0000000..fb85a50
--- /dev/null
+++ b/guacamole/src/main/webapp/app/form/controllers/terminalColorSchemeFieldController.js
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+
+
+/**
+ * Controller for terminal color scheme fields.
+ */
+angular.module('form').controller('terminalColorSchemeFieldController', ['$scope', '$injector',
+ function terminalColorSchemeFieldController($scope, $injector) {
+
+ // Required types
+ var ColorScheme = $injector.get('ColorScheme');
+
+ /**
+ * The currently selected color scheme. If a pre-defined color scheme is
+ * selected, this will be the connection parameter value associated with
+ * that color scheme. If a custom color scheme is selected, this will be
+ * the string "custom".
+ *
+ * @type String
+ */
+ $scope.selectedColorScheme = '';
+
+ /**
+ * The current custom color scheme, if a custom color scheme has been
+ * specified. If no custom color scheme has yet been specified, this will
+ * be a ColorScheme instance that has been initialized to the default
+ * colors.
+ *
+ * @type ColorScheme
+ */
+ $scope.customColorScheme = new ColorScheme();
+
+ /**
+ * The array of colors to include within the color picker as pre-defined
+ * options for convenience.
+ *
+ * @type String[]
+ */
+ $scope.defaultPalette = new ColorScheme().colors;
+
+ /**
+ * Whether the raw details of the custom color scheme should be shown. By
+ * default, such details are hidden.
+ *
+ * @type Boolean
+ */
+ $scope.detailsShown = false;
+
+ /**
+ * The palette indices of all colors which are considered low-intensity.
+ *
+ * @type Number[]
+ */
+ $scope.lowIntensity = [ 0, 1, 2, 3, 4, 5, 6, 7 ];
+
+ /**
+ * The palette indices of all colors which are considered high-intensity.
+ *
+ * @type Number[]
+ */
+ $scope.highIntensity = [ 8, 9, 10, 11, 12, 13, 14, 15 ];
+
+ /**
+ * The string value which is assigned to selectedColorScheme if a custom
+ * color scheme is selected.
+ *
+ * @constant
+ * @type String
+ */
+ var CUSTOM_COLOR_SCHEME = 'custom';
+
+ /**
+ * Returns whether a custom color scheme has been selected.
+ *
+ * @returns {Boolean}
+ * true if a custom color scheme has been selected, false otherwise.
+ */
+ $scope.isCustom = function isCustom() {
+ return $scope.selectedColorScheme === CUSTOM_COLOR_SCHEME;
+ };
+
+ /**
+ * Shows the raw details of the custom color scheme. If the details are
+ * already shown, this function has no effect.
+ */
+ $scope.showDetails = function showDetails() {
+ $scope.detailsShown = true;
+ };
+
+ /**
+ * Hides the raw details of the custom color scheme. If the details are
+ * already hidden, this function has no effect.
+ */
+ $scope.hideDetails = function hideDetails() {
+ $scope.detailsShown = false;
+ };
+
+ // Keep selected color scheme and custom color scheme in sync with changes
+ // to model
+ $scope.$watch('model', function modelChanged(model) {
+ if ($scope.selectedColorScheme === CUSTOM_COLOR_SCHEME || (model && !_.includes($scope.field.options, model))) {
+ $scope.customColorScheme = ColorScheme.fromString(model);
+ $scope.selectedColorScheme = CUSTOM_COLOR_SCHEME;
+ }
+ else
+ $scope.selectedColorScheme = model || '';
+ });
+
+ // Keep model in sync with changes to selected color scheme
+ $scope.$watch('selectedColorScheme', function selectedColorSchemeChanged(selectedColorScheme) {
+ if (!selectedColorScheme)
+ $scope.model = '';
+ else if (selectedColorScheme === CUSTOM_COLOR_SCHEME)
+ $scope.model = ColorScheme.toString($scope.customColorScheme);
+ else
+ $scope.model = selectedColorScheme;
+ });
+
+ // Keep model in sync with changes to custom color scheme
+ $scope.$watch('customColorScheme', function customColorSchemeChanged(customColorScheme) {
+ if ($scope.selectedColorScheme === CUSTOM_COLOR_SCHEME)
+ $scope.model = ColorScheme.toString(customColorScheme);
+ }, true);
+
+}]);
diff --git a/guacamole/src/main/webapp/app/form/controllers/textFieldController.js b/guacamole/src/main/webapp/app/form/controllers/textFieldController.js
index b5bc753..8dd134a 100644
--- a/guacamole/src/main/webapp/app/form/controllers/textFieldController.js
+++ b/guacamole/src/main/webapp/app/form/controllers/textFieldController.js
@@ -35,6 +35,6 @@
// Generate unique ID for datalist, if applicable
if ($scope.field.options && $scope.field.options.length)
- $scope.dataListId = $scope.field.name + '-datalist';
+ $scope.dataListId = $scope.fieldId + '-datalist';
}]);
diff --git a/guacamole/src/main/webapp/app/form/directives/formField.js b/guacamole/src/main/webapp/app/form/directives/formField.js
index ea0f35f..41db0c1 100644
--- a/guacamole/src/main/webapp/app/form/directives/formField.js
+++ b/guacamole/src/main/webapp/app/form/directives/formField.js
@@ -73,6 +73,18 @@
var fieldContent = $element.find('.form-field');
/**
+ * An ID value which is reasonably likely to be unique relative to
+ * other elements on the page. This ID should be used to associate
+ * the relevant input element with the label provided by the
+ * guacFormField directive, if there is such an input element.
+ *
+ * @type String
+ */
+ $scope.fieldId = 'guac-field-XXXXXXXXXXXXXXXX'.replace(/X/g, function getRandomCharacter() {
+ return Math.floor(Math.random() * 36).toString(36);
+ }) + '-' + new Date().getTime().toString(36);
+
+ /**
* Produces the translation string for the header of the current
* field. The translation string will be of the form:
*
@@ -158,4 +170,4 @@
}] // end controller
};
-}]);
\ No newline at end of file
+}]);
diff --git a/guacamole/src/main/webapp/app/form/directives/guacInputColor.js b/guacamole/src/main/webapp/app/form/directives/guacInputColor.js
new file mode 100644
index 0000000..3b010cc
--- /dev/null
+++ b/guacamole/src/main/webapp/app/form/directives/guacInputColor.js
@@ -0,0 +1,202 @@
+/*
+ * 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 which implements a color input field, leveraging the "Pickr"
+ * color picker. If the "Picker" color picker cannot be used because it relies
+ * on JavaScript features not supported by the browser (Internet Explorer), a
+ * "guacInputColorUnavailable" event will be emitted up the scope, and this
+ * directive will become read-only, functioning essentially as a color preview.
+ */
+angular.module('form').directive('guacInputColor', [function guacInputColor() {
+
+ var config = {
+ restrict: 'E',
+ replace: true,
+ templateUrl: 'app/form/templates/guacInputColor.html',
+ transclude: true
+ };
+
+ config.scope = {
+
+ /**
+ * The current selected color value, in standard 6-digit hexadecimal
+ * RGB notation. When the user selects a different color using this
+ * directive, this value will updated accordingly.
+ *
+ * @type String
+ */
+ model: '=',
+
+ /**
+ * An optional array of colors to include within the color picker as a
+ * convenient selection of pre-defined colors. The colors within the
+ * array must be in standard 6-digit hexadecimal RGB notation.
+ *
+ * @type String[]
+ */
+ palette: '='
+
+ };
+
+ config.controller = ['$scope', '$element', '$injector',
+ function guacInputColorController($scope, $element, $injector) {
+
+ // Required services
+ var $q = $injector.get('$q');
+ var $translate = $injector.get('$translate');
+
+ /**
+ * Whether the color picker ("Pickr") cannot be used. In general, all
+ * browsers should support Pickr with the exception of Internet
+ * Explorer.
+ *
+ * @type Boolean
+ */
+ $scope.colorPickerUnavailable = false;
+
+ /**
+ * Returns whether the color currently selected is "dark" in the sense
+ * that the color white will have higher contrast against it than the
+ * color black.
+ *
+ * @returns {Boolean}
+ * true if the currently selected color is relatively dark (white
+ * text would provide better contrast than black), false otherwise.
+ */
+ $scope.isDark = function isDark() {
+
+ // Assume not dark if color is invalid or undefined
+ var rgb = $scope.model && /^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/.exec($scope.model);
+ if (!rgb)
+ return false;
+
+ // Parse color component values as hexadecimal
+ var red = parseInt(rgb[1], 16);
+ var green = parseInt(rgb[2], 16);
+ var blue = parseInt(rgb[3], 16);
+
+ // Convert RGB to luminance in HSL space (as defined by the
+ // relative luminance formula given by the W3C for accessibility)
+ var luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue;
+
+ // Consider the background to be dark if white text over that
+ // background would provide better contrast than black
+ return luminance <= 153; // 153 is the component value 0.6 converted from 0-1 to the 0-255 range
+
+ };
+
+ // Init color picker after required translation strings are available
+ $q.all({
+ 'save' : $translate('APP.ACTION_SAVE'),
+ 'cancel' : $translate('APP.ACTION_CANCEL')
+ }).then(function stringsRetrieved(strings) {
+
+ try {
+
+ /**
+ * An instance of the "Pickr" color picker, bound to the underlying
+ * element of this directive.
+ *
+ * @type Pickr
+ */
+ var pickr = Pickr.create({
+
+ // Bind color picker to the underlying element of this directive
+ el : $element[0],
+
+ // Wrap color picker dialog in Guacamole-specific class for
+ // sake of additional styling
+ appClass : 'guac-input-color-picker',
+
+ // Display color details as hex
+ defaultRepresentation : 'HEX',
+
+ // Use "monolith" theme, as a nice balance between "nano" (does
+ // not work in Internet Explorer) and "classic" (too big)
+ theme : 'monolith',
+
+ // Leverage the container element as the button which shows the
+ // picker, relying on our own styling for that button
+ useAsButton : true,
+ appendToBody : true,
+
+ // Do not include opacity controls
+ lockOpacity : true,
+
+ // Include a selection of palette entries for convenience and
+ // reference
+ swatches : $scope.palette || [],
+
+ components: {
+
+ // Include hue and color preview controls
+ preview : true,
+ hue : true,
+
+ // Display only a text color input field and the save and
+ // cancel buttons (no clear button)
+ interaction: {
+ input : true,
+ save : true,
+ cancel : true
+ }
+
+ },
+
+ // Use translation strings for buttons
+ strings : strings
+
+ });
+
+ // Hide color picker after user clicks "cancel"
+ pickr.on('cancel', function colorChangeCanceled() {
+ pickr.hide();
+ });
+
+ // Keep model in sync with changes to the color picker
+ pickr.on('save', function colorChanged(color) {
+ $scope.$evalAsync(function updateModel() {
+ $scope.model = color.toHEXA().toString();
+ });
+ });
+
+ // Keep color picker in sync with changes to the model
+ pickr.on('init', function pickrReady(color) {
+ $scope.$watch('model', function modelChanged(model) {
+ pickr.setColor(model);
+ });
+ });
+
+ }
+
+ // If the "Pickr" color picker cannot be loaded (Internet Explorer),
+ // let the scope above us know
+ catch (e) {
+ $scope.colorPickerUnavailable = true;
+ $scope.$emit('guacInputColorUnavailable', e);
+ }
+
+ }, angular.noop);
+
+ }];
+
+ return config;
+
+}]);
diff --git a/guacamole/src/main/webapp/app/form/services/formService.js b/guacamole/src/main/webapp/app/form/services/formService.js
index 6019e74..cc0a240 100644
--- a/guacamole/src/main/webapp/app/form/services/formService.js
+++ b/guacamole/src/main/webapp/app/form/services/formService.js
@@ -179,6 +179,19 @@
module : 'form',
controller : 'timeFieldController',
templateUrl : 'app/form/templates/timeField.html'
+ },
+
+ /**
+ * Field type which allows selection of color schemes accepted by the
+ * Guacamole server terminal emulator and protocols which leverage it.
+ *
+ * @see {@link Field.Type.TERMINAL_COLOR_SCHEME}
+ * @type FieldType
+ */
+ 'TERMINAL_COLOR_SCHEME' : {
+ module : 'form',
+ controller : 'terminalColorSchemeFieldController',
+ templateUrl : 'app/form/templates/terminalColorSchemeField.html'
}
};
@@ -221,6 +234,11 @@
* A String which defines the unique namespace associated the
* translation strings used by the form using a field of this type.
*
+ * fieldId:
+ * A String value which is reasonably likely to be unique and may
+ * be used to associate the main element of the field with its
+ * label.
+ *
* field:
* The Field object that is being rendered, representing a field of
* this type.
diff --git a/guacamole/src/main/webapp/app/form/styles/terminal-color-scheme-field.css b/guacamole/src/main/webapp/app/form/styles/terminal-color-scheme-field.css
new file mode 100644
index 0000000..01eac1a
--- /dev/null
+++ b/guacamole/src/main/webapp/app/form/styles/terminal-color-scheme-field.css
@@ -0,0 +1,158 @@
+/*
+ * 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.
+ */
+
+.terminal-color-scheme-field {
+ max-width: 320px;
+}
+
+.terminal-color-scheme-field select {
+ width: 100%;
+}
+
+.terminal-color-scheme-field .custom-color-scheme {
+ background: #EEE;
+ padding: 0.5em;
+ border: 1px solid silver;
+ border-spacing: 0;
+ margin-top: -2px;
+ width: 100%;
+}
+
+.terminal-color-scheme-field .custom-color-scheme-section {
+ display: -ms-flexbox;
+ display: -moz-box;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: flex;
+}
+
+.terminal-color-scheme-field .guac-input-color {
+
+ display: block;
+ margin: 2px;
+ width: 1.5em;
+ height: 1.5em;
+ min-width: 1.25em;
+ border-radius: 0.15em;
+ line-height: 1.5em;
+ text-align: center;
+ font-size: 0.75em;
+ cursor: pointer;
+ color: black;
+
+ -ms-flex: 1;
+ -moz-box-flex: 1;
+ -webkit-box-flex: 1;
+ -webkit-flex: 1;
+ flex: 1;
+
+}
+
+.terminal-color-scheme-field .guac-input-color.read-only {
+ cursor: not-allowed;
+}
+
+.terminal-color-scheme-field .guac-input-color.dark {
+ color: white;
+}
+
+.terminal-color-scheme-field .palette .guac-input-color {
+ font-weight: bold;
+}
+
+/* Hide palette numbers unless color scheme details are visible */
+.terminal-color-scheme-field.custom-color-scheme-details-hidden .custom-color-scheme .palette .guac-input-color {
+ color: transparent;
+}
+
+/*
+ * Custom color scheme details header
+ */
+
+.terminal-color-scheme-field .custom-color-scheme-details-header {
+ font-size: 0.8em;
+ margin: 0.5em 0;
+ padding: 0;
+}
+
+.terminal-color-scheme-field .custom-color-scheme-details-header::before {
+ content: 'â–¸ ';
+}
+
+.terminal-color-scheme-field.custom-color-scheme-details-visible .custom-color-scheme-details-header::before {
+ content: 'â–¾ ';
+}
+
+/*
+ * Details show/hide link
+ */
+
+/* Render show/hide as a link */
+.terminal-color-scheme-field .custom-color-scheme-hide-details,
+.terminal-color-scheme-field .custom-color-scheme-show-details {
+ color: blue;
+ text-decoration: underline;
+ cursor: pointer;
+ margin: 0 0.25em;
+ font-weight: normal;
+}
+
+.terminal-color-scheme-field .custom-color-scheme-hide-details {
+ display: none;
+}
+
+.terminal-color-scheme-field.custom-color-scheme-details-visible .custom-color-scheme-hide-details {
+ display: inline;
+}
+
+.terminal-color-scheme-field.custom-color-scheme-details-visible .custom-color-scheme-show-details {
+ display: none;
+}
+
+/*
+ * Color scheme details
+ */
+
+.terminal-color-scheme-field .custom-color-scheme-details {
+ display: none;
+}
+
+.terminal-color-scheme-field.custom-color-scheme-details-visible .custom-color-scheme-details {
+ display: block;
+ width: 100%;
+ margin: 0.5em 0;
+}
+
+/*
+ * Color picker
+ */
+
+/* Increase width of color picker to allow two even rows of eight color
+ * swatches */
+.guac-input-color-picker[data-theme="monolith"] {
+ width: 16.25em;
+}
+
+/* Remove Guacamole-specific styles inherited from the generic button rules */
+.guac-input-color-picker[data-theme="monolith"] button {
+ min-width: 0;
+ padding: 0;
+ margin: 0;
+ box-shadow: none;
+}
diff --git a/guacamole/src/main/webapp/app/form/templates/checkboxField.html b/guacamole/src/main/webapp/app/form/templates/checkboxField.html
index ad9d8e0..110ab14 100644
--- a/guacamole/src/main/webapp/app/form/templates/checkboxField.html
+++ b/guacamole/src/main/webapp/app/form/templates/checkboxField.html
@@ -1 +1,5 @@
-<input type="checkbox" ng-model="typedValue" autocorrect="off" autocapitalize="off"/>
\ No newline at end of file
+<input type="checkbox"
+ ng-attr-id="{{ fieldId }}"
+ ng-model="typedValue"
+ autocorrect="off"
+ autocapitalize="off"/>
diff --git a/guacamole/src/main/webapp/app/form/templates/dateField.html b/guacamole/src/main/webapp/app/form/templates/dateField.html
index a186e19..2da96a1 100644
--- a/guacamole/src/main/webapp/app/form/templates/dateField.html
+++ b/guacamole/src/main/webapp/app/form/templates/dateField.html
@@ -1,5 +1,6 @@
<div class="date-field">
<input type="date"
+ ng-attr-id="{{ fieldId }}"
ng-model="typedValue"
ng-model-options="modelOptions"
guac-lenient-date
diff --git a/guacamole/src/main/webapp/app/form/templates/emailField.html b/guacamole/src/main/webapp/app/form/templates/emailField.html
index db6d3be..4ef14f4 100644
--- a/guacamole/src/main/webapp/app/form/templates/emailField.html
+++ b/guacamole/src/main/webapp/app/form/templates/emailField.html
@@ -1,8 +1,9 @@
<div class="email-field">
<input type="email"
+ ng-attr-id="{{ fieldId }}"
ng-model="model"
ng-hide="readOnly"
autocorrect="off"
autocapitalize="off"/>
<a href="mailto:{{model}}" ng-show="readOnly">{{model}}</a>
-</div>
\ No newline at end of file
+</div>
diff --git a/guacamole/src/main/webapp/app/form/templates/formField.html b/guacamole/src/main/webapp/app/form/templates/formField.html
index 45cf6b9..3e45d4c 100644
--- a/guacamole/src/main/webapp/app/form/templates/formField.html
+++ b/guacamole/src/main/webapp/app/form/templates/formField.html
@@ -1,9 +1,11 @@
-<label class="labeled-field" ng-class="{empty: !model}" ng-show="isFieldVisible()">
+<div class="labeled-field" ng-class="{empty: !model}" ng-show="isFieldVisible()">
<!-- Field header -->
- <span class="field-header">{{getFieldHeader() | translate}}</span>
+ <div class="field-header">
+ <label ng-attr-for="{{ fieldId }}">{{getFieldHeader() | translate}}</label>
+ </div>
<!-- Field content -->
<div class="form-field"></div>
-</label>
+</div>
diff --git a/guacamole/src/main/webapp/app/form/templates/guacInputColor.html b/guacamole/src/main/webapp/app/form/templates/guacInputColor.html
new file mode 100644
index 0000000..fc6e675
--- /dev/null
+++ b/guacamole/src/main/webapp/app/form/templates/guacInputColor.html
@@ -0,0 +1,10 @@
+<div class="guac-input-color"
+ ng-class="{
+ 'dark' : isDark(),
+ 'read-only' : colorPickerUnavailable
+ }"
+ ng-style="{
+ 'background-color' : model
+ }">
+ <ng-transclude></ng-transclude>
+</div>
\ No newline at end of file
diff --git a/guacamole/src/main/webapp/app/form/templates/languageField.html b/guacamole/src/main/webapp/app/form/templates/languageField.html
index 404f74e..b1f25e8 100644
--- a/guacamole/src/main/webapp/app/form/templates/languageField.html
+++ b/guacamole/src/main/webapp/app/form/templates/languageField.html
@@ -1 +1,3 @@
-<select ng-model="model" ng-options="language.key as language.value for language in languages | toArray | orderBy: key"></select>
\ No newline at end of file
+<select ng-attr-id="{{ fieldId }}"
+ ng-model="model"
+ ng-options="language.key as language.value for language in languages | toArray | orderBy: key"></select>
diff --git a/guacamole/src/main/webapp/app/form/templates/numberField.html b/guacamole/src/main/webapp/app/form/templates/numberField.html
index 3d6312e..f802b4a 100644
--- a/guacamole/src/main/webapp/app/form/templates/numberField.html
+++ b/guacamole/src/main/webapp/app/form/templates/numberField.html
@@ -1 +1,5 @@
-<input type="number" ng-model="typedValue" autocorrect="off" autocapitalize="off"/>
\ No newline at end of file
+<input type="number"
+ ng-attr-id="{{ fieldId }}"
+ ng-model="typedValue"
+ autocorrect="off"
+ autocapitalize="off"/>
diff --git a/guacamole/src/main/webapp/app/form/templates/passwordField.html b/guacamole/src/main/webapp/app/form/templates/passwordField.html
index 506d8b6..69d67e8 100644
--- a/guacamole/src/main/webapp/app/form/templates/passwordField.html
+++ b/guacamole/src/main/webapp/app/form/templates/passwordField.html
@@ -1,4 +1,9 @@
<div class="password-field">
- <input type="{{passwordInputType}}" ng-model="model" ng-trim="false" autocorrect="off" autocapitalize="off"/>
+ <input type="{{passwordInputType}}"
+ ng-attr-id="{{ fieldId }}"
+ ng-model="model"
+ ng-trim="false"
+ autocorrect="off"
+ autocapitalize="off"/>
<div class="icon toggle-password" ng-click="togglePassword()" title="{{getTogglePasswordHelpText() | translate}}"></div>
-</div>
\ No newline at end of file
+</div>
diff --git a/guacamole/src/main/webapp/app/form/templates/selectField.html b/guacamole/src/main/webapp/app/form/templates/selectField.html
index 3bd2bb8..e5ce0e9 100644
--- a/guacamole/src/main/webapp/app/form/templates/selectField.html
+++ b/guacamole/src/main/webapp/app/form/templates/selectField.html
@@ -1 +1,3 @@
-<select ng-model="model" ng-options="option as getFieldOption(option) | translate for option in field.options | orderBy: value"></select>
\ No newline at end of file
+<select ng-attr-id="{{ fieldId }}"
+ ng-model="model"
+ ng-options="option as getFieldOption(option) | translate for option in field.options | orderBy: value"></select>
diff --git a/guacamole/src/main/webapp/app/form/templates/terminalColorSchemeField.html b/guacamole/src/main/webapp/app/form/templates/terminalColorSchemeField.html
new file mode 100644
index 0000000..a8425e4
--- /dev/null
+++ b/guacamole/src/main/webapp/app/form/templates/terminalColorSchemeField.html
@@ -0,0 +1,63 @@
+<div class="terminal-color-scheme-field" ng-class="{
+ 'custom-color-scheme-details-visible' : detailsShown,
+ 'custom-color-scheme-details-hidden' : !detailsShown
+ }">
+
+ <!-- Pre-defined color scheme options -->
+ <select ng-attr-id="{{ fieldId }}" ng-model="selectedColorScheme">
+ <option ng-repeat="option in field.options | orderBy: value"
+ ng-value="option">{{ getFieldOption(option) | translate }}</option>
+ <option value="custom">{{ 'COLOR_SCHEME.FIELD_OPTION_CUSTOM' | translate }}</option>
+ </select>
+
+ <!-- Custom color scheme -->
+ <div class="custom-color-scheme" ng-show="isCustom()">
+
+ <!-- Default foreground color -->
+ <div class="custom-color-scheme-section default-color foreground">
+ <guac-input-color model="customColorScheme.foreground"
+ palette="defaultPalette">
+ {{ 'COLOR_SCHEME.FIELD_HEADER_FOREGROUND' | translate }}
+ </guac-input-color>
+ </div>
+
+ <!-- Default background color -->
+ <div class="custom-color-scheme-section default-color background">
+ <guac-input-color model="customColorScheme.background"
+ palette="defaultPalette">
+ {{ 'COLOR_SCHEME.FIELD_HEADER_BACKGROUND' | translate }}
+ </guac-input-color>
+ </div>
+
+ <!-- Low intensity portion of 16-color palette -->
+ <div class="custom-color-scheme-section palette low-intensity">
+ <guac-input-color ng-repeat="index in lowIntensity"
+ model="customColorScheme.colors[index]"
+ palette="defaultPalette">
+ {{ index }}
+ </guac-input-color>
+ </div>
+
+ <!-- High intensity portion of 16-color palette -->
+ <div class="custom-color-scheme-section palette high-intensity">
+ <guac-input-color ng-repeat="index in highIntensity"
+ model="customColorScheme.colors[index]"
+ palette="defaultPalette">
+ {{ index }}
+ </guac-input-color>
+ </div>
+
+ </div>
+
+ <!-- Show/hide details -->
+ <h4 class="custom-color-scheme-details-header" ng-show="isCustom()">
+ {{'COLOR_SCHEME.SECTION_HEADER_DETAILS' | translate}}
+ <a class="custom-color-scheme-show-details" ng-click="showDetails()">{{'COLOR_SCHEME.ACTION_SHOW_DETAILS' | translate}}</a>
+ <a class="custom-color-scheme-hide-details" ng-click="hideDetails()">{{'COLOR_SCHEME.ACTION_HIDE_DETAILS' | translate}}</a>
+ </h4>
+
+ <!-- Custom color scheme details (internal representation -->
+ <textarea class="custom-color-scheme-details" spellcheck="false"
+ ng-model="model" ng-show="isCustom()"></textarea>
+
+</div>
diff --git a/guacamole/src/main/webapp/app/form/templates/textAreaField.html b/guacamole/src/main/webapp/app/form/templates/textAreaField.html
index 082476f..2d4144b 100644
--- a/guacamole/src/main/webapp/app/form/templates/textAreaField.html
+++ b/guacamole/src/main/webapp/app/form/templates/textAreaField.html
@@ -1 +1,4 @@
-<textarea ng-model="model" autocorrect="off" autocapitalize="off"></textarea>
\ No newline at end of file
+<textarea ng-attr-id="{{ fieldId }}"
+ ng-model="model"
+ autocorrect="off"
+ autocapitalize="off"></textarea>
diff --git a/guacamole/src/main/webapp/app/form/templates/textField.html b/guacamole/src/main/webapp/app/form/templates/textField.html
index a338db4..938b81c 100644
--- a/guacamole/src/main/webapp/app/form/templates/textField.html
+++ b/guacamole/src/main/webapp/app/form/templates/textField.html
@@ -1,7 +1,12 @@
<div class="text-field">
- <input type="text" ng-model="model" autocorrect="off" autocapitalize="off" ng-attr-list="{{ dataListId }}"/>
- <datalist ng-if="dataListId" id="{{ dataListId }}">
+ <input type="text"
+ ng-attr-id="{{ fieldId }}"
+ ng-attr-list="{{ dataListId }}"
+ ng-model="model"
+ autocorrect="off"
+ autocapitalize="off"/>
+ <datalist ng-if="dataListId" ng-attr-id="{{ dataListId }}">
<option ng-repeat="option in field.options | orderBy: option"
value="{{ option }}">{{ getFieldOption(option) | translate }}</option>
</datalist>
-</div>
\ No newline at end of file
+</div>
diff --git a/guacamole/src/main/webapp/app/form/templates/timeField.html b/guacamole/src/main/webapp/app/form/templates/timeField.html
index 24ae968..292e44c 100644
--- a/guacamole/src/main/webapp/app/form/templates/timeField.html
+++ b/guacamole/src/main/webapp/app/form/templates/timeField.html
@@ -1,5 +1,6 @@
<div class="time-field">
<input type="time"
+ ng-attr-id="{{ fieldId }}"
ng-model="typedValue"
ng-model-options="modelOptions"
guac-lenient-time
diff --git a/guacamole/src/main/webapp/app/form/templates/timeZoneField.html b/guacamole/src/main/webapp/app/form/templates/timeZoneField.html
index 15fd4d6..ed960af 100644
--- a/guacamole/src/main/webapp/app/form/templates/timeZoneField.html
+++ b/guacamole/src/main/webapp/app/form/templates/timeZoneField.html
@@ -2,6 +2,7 @@
<!-- Available time zone regions -->
<select class="time-zone-region"
+ ng-attr-id="{{ fieldId }}"
ng-model="region"
ng-options="name for name in regions | orderBy: name"></select>
diff --git a/guacamole/src/main/webapp/app/form/types/ColorScheme.js b/guacamole/src/main/webapp/app/form/types/ColorScheme.js
new file mode 100644
index 0000000..f51a667
--- /dev/null
+++ b/guacamole/src/main/webapp/app/form/types/ColorScheme.js
@@ -0,0 +1,949 @@
+/*
+ * 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.
+ */
+
+/**
+ * Service which defines the ColorScheme class.
+ */
+angular.module('form').factory('ColorScheme', [function defineColorScheme() {
+
+ /**
+ * Intermediate representation of a custom color scheme which can be
+ * converted to the color scheme format used by Guacamole's terminal
+ * emulator. All colors must be represented in the six-digit hexadecimal
+ * RGB notation used by HTML ("#000000" for black, etc.).
+ *
+ * @constructor
+ * @param {ColorScheme|Object} [template={}]
+ * The object whose properties should be copied within the new
+ * ColorScheme.
+ */
+ var ColorScheme = function ColorScheme(template) {
+
+ // Use empty object by default
+ template = template || {};
+
+ /**
+ * The terminal background color. This will be the default foreground
+ * color of the Guacamole terminal emulator ("#000000") by default.
+ *
+ * @type {String}
+ */
+ this.background = template.background || '#000000';
+
+ /**
+ * The terminal foreground color. This will be the default foreground
+ * color of the Guacamole terminal emulator ("#999999") by default.
+ *
+ * @type {String}
+ */
+ this.foreground = template.foreground || '#999999';
+
+ /**
+ * The terminal color palette. Default values are provided for the
+ * normal 16 terminal colors using the default values of the Guacamole
+ * terminal emulator, however the terminal emulator and this
+ * representation support up to 256 colors.
+ *
+ * @type {String[]}
+ */
+ this.colors = template.colors || [
+
+ // Normal colors
+ '#000000', // Black
+ '#993E3E', // Red
+ '#3E993E', // Green
+ '#99993E', // Brown
+ '#3E3E99', // Blue
+ '#993E99', // Magenta
+ '#3E9999', // Cyan
+ '#999999', // White
+
+ // Intense colors
+ '#3E3E3E', // Black
+ '#FF6767', // Red
+ '#67FF67', // Green
+ '#FFFF67', // Brown
+ '#6767FF', // Blue
+ '#FF67FF', // Magenta
+ '#67FFFF', // Cyan
+ '#FFFFFF' // White
+
+ ];
+
+ /**
+ * The string which was parsed to produce this ColorScheme instance, if
+ * ColorScheme.fromString() was used to produce this ColorScheme.
+ *
+ * @private
+ * @type {String}
+ */
+ this._originalString = template._originalString;
+
+ };
+
+ /**
+ * Given a color string in the standard 6-digit hexadecimal RGB format,
+ * returns a X11 color spec which represents the same color.
+ *
+ * @param {String} color
+ * The hexadecimal color string to convert.
+ *
+ * @returns {String}
+ * The X11 color spec representing the same color as the given
+ * hexadecimal string, or null if the given string is not a valid
+ * 6-digit hexadecimal RGB color.
+ */
+ var fromHexColor = function fromHexColor(color) {
+
+ var groups = /^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/.exec(color);
+ if (!groups)
+ return null;
+
+ return 'rgb:' + groups[1] + '/' + groups[2] + '/' + groups[3];
+
+ };
+
+ /**
+ * Parses the same subset of the X11 color spec supported by the Guacamole
+ * terminal emulator (the "rgb:*" format), returning the equivalent 6-digit
+ * hexadecimal color string supported by the ColorScheme representation.
+ * The X11 color spec defined by Xlib's XParseColor(). The human-readable
+ * color names supported by the Guacamole terminal emulator (the same color
+ * names as supported by xterm) may also be used.
+ *
+ * @param {String} color
+ * The X11 color spec to parse, or the name of a known named color.
+ *
+ * @returns {String}
+ * The 6-digit hexadecimal color string which represents the same color
+ * as the given X11 color spec/name, or null if the given spec/name is
+ * invalid.
+ */
+ var toHexColor = function toHexColor(color) {
+
+ /**
+ * Shifts or truncates the given hexadecimal string such that it
+ * contains exactly two hexadecimal digits, as required by any
+ * individual color component of the 6-digit hexadecimal RGB format.
+ *
+ * @param {String} component
+ * The hexadecimal string to shift or truncate to two digits.
+ *
+ * @returns {String}
+ * A new 2-digit hexadecimal string containing the same digits as
+ * the provided string, shifted or truncated as necessary to fit
+ * within the 2-digit length limit.
+ */
+ var toHexComponent = function toHexComponent(component) {
+ return (component + '0').substring(0, 2).toUpperCase();
+ };
+
+ // Attempt to parse any non-RGB color as a named color
+ var groups = /^rgb:([0-9A-Fa-f]{1,4})\/([0-9A-Fa-f]{1,4})\/([0-9A-Fa-f]{1,4})$/.exec(color);
+ if (!groups)
+ return ColorScheme.NAMED_COLORS[color.toLowerCase()] || null;
+
+ // Convert to standard 6-digit hexadecimal RGB format
+ return '#' + toHexComponent(groups[1]) + toHexComponent(groups[2]) + toHexComponent(groups[3]);
+
+ };
+
+ /**
+ * Converts the given string representation of a color scheme which is
+ * supported by the Guacamole terminal emulator to a corresponding,
+ * intermediate ColorScheme object.
+ *
+ * @param {String} str
+ * An arbitrary color scheme, in the string format supported by the
+ * Guacamole terminal emulator.
+ *
+ * @returns {ColorScheme}
+ * A new ColorScheme instance which represents the same color scheme as
+ * the given string.
+ */
+ ColorScheme.fromString = function fromString(str) {
+
+ var scheme = new ColorScheme({ _originalString : str });
+
+ // For each semicolon-separated statement in the provided color scheme
+ var statements = str.split(/;/);
+ for (var i = 0; i < statements.length; i++) {
+
+ // Skip any statements which cannot be parsed
+ var statement = statements[i];
+ var groups = /^\s*(background|foreground|color([0-9]+))\s*:\s*(\S*)\s*$/.exec(statement);
+ if (!groups)
+ continue;
+
+ // If the statement is valid and contains a valid color, map that
+ // color to the appropriate property of the ColorScheme object
+ var color = toHexColor(groups[3]);
+ if (color) {
+ if (groups[1] === 'background')
+ scheme.background = color;
+ else if (groups[1] === 'foreground')
+ scheme.foreground = color;
+ else
+ scheme.colors[parseInt(groups[2])] = color;
+ }
+
+ }
+
+ return scheme;
+
+ };
+
+ /**
+ * Returns whether the two given color schemes define the exact same
+ * colors.
+ *
+ * @param {ColorScheme} a
+ * The first ColorScheme to compare.
+ *
+ * @param {ColorScheme} b
+ * The second ColorScheme to compare.
+ *
+ * @returns {Boolean}
+ * true if both color schemes contain the same colors, false otherwise.
+ */
+ ColorScheme.equals = function equals(a, b) {
+ return a.foreground === b.foreground
+ && a.background === b.background
+ && _.isEqual(a.colors, b.colors);
+ };
+
+ /**
+ * Converts the given ColorScheme to a string representation which is
+ * supported by the Guacamole terminal emulator.
+ *
+ * @param {ColorScheme} scheme
+ * The ColorScheme to convert to a string.
+ *
+ * @returns {String}
+ * The given color scheme, converted to the string format supported by
+ * the Guacamole terminal emulator.
+ */
+ ColorScheme.toString = function toString(scheme) {
+
+ // Use originally-provided string if it equates to the exact same color scheme
+ if (!_.isUndefined(scheme._originalString) && ColorScheme.equals(scheme, ColorScheme.fromString(scheme._originalString)))
+ return scheme._originalString;
+
+ // Add background and foreground
+ var str = 'background: ' + fromHexColor(scheme.background) + ';\n'
+ + 'foreground: ' + fromHexColor(scheme.foreground) + ';';
+
+ // Add color definitions for each palette entry
+ for (var index in scheme.colors)
+ str += '\ncolor' + index + ': ' + fromHexColor(scheme.colors[index]) + ';';
+
+ return str;
+
+ };
+
+ /**
+ * The set of all named colors supported by the Guacamole terminal
+ * emulator and their corresponding 6-digit hexadecimal RGB
+ * representations. This set should contain all colors supported by xterm.
+ *
+ * @constant
+ * @type {Object.<String, String>}
+ */
+ ColorScheme.NAMED_COLORS = {
+ 'aliceblue' : '#F0F8FF',
+ 'antiquewhite' : '#FAEBD7',
+ 'antiquewhite1' : '#FFEFDB',
+ 'antiquewhite2' : '#EEDFCC',
+ 'antiquewhite3' : '#CDC0B0',
+ 'antiquewhite4' : '#8B8378',
+ 'aqua' : '#00FFFF',
+ 'aquamarine' : '#7FFFD4',
+ 'aquamarine1' : '#7FFFD4',
+ 'aquamarine2' : '#76EEC6',
+ 'aquamarine3' : '#66CDAA',
+ 'aquamarine4' : '#458B74',
+ 'azure' : '#F0FFFF',
+ 'azure1' : '#F0FFFF',
+ 'azure2' : '#E0EEEE',
+ 'azure3' : '#C1CDCD',
+ 'azure4' : '#838B8B',
+ 'beige' : '#F5F5DC',
+ 'bisque' : '#FFE4C4',
+ 'bisque1' : '#FFE4C4',
+ 'bisque2' : '#EED5B7',
+ 'bisque3' : '#CDB79E',
+ 'bisque4' : '#8B7D6B',
+ 'black' : '#000000',
+ 'blanchedalmond' : '#FFEBCD',
+ 'blue' : '#0000FF',
+ 'blue1' : '#0000FF',
+ 'blue2' : '#0000EE',
+ 'blue3' : '#0000CD',
+ 'blue4' : '#00008B',
+ 'blueviolet' : '#8A2BE2',
+ 'brown' : '#A52A2A',
+ 'brown1' : '#FF4040',
+ 'brown2' : '#EE3B3B',
+ 'brown3' : '#CD3333',
+ 'brown4' : '#8B2323',
+ 'burlywood' : '#DEB887',
+ 'burlywood1' : '#FFD39B',
+ 'burlywood2' : '#EEC591',
+ 'burlywood3' : '#CDAA7D',
+ 'burlywood4' : '#8B7355',
+ 'cadetblue' : '#5F9EA0',
+ 'cadetblue1' : '#98F5FF',
+ 'cadetblue2' : '#8EE5EE',
+ 'cadetblue3' : '#7AC5CD',
+ 'cadetblue4' : '#53868B',
+ 'chartreuse' : '#7FFF00',
+ 'chartreuse1' : '#7FFF00',
+ 'chartreuse2' : '#76EE00',
+ 'chartreuse3' : '#66CD00',
+ 'chartreuse4' : '#458B00',
+ 'chocolate' : '#D2691E',
+ 'chocolate1' : '#FF7F24',
+ 'chocolate2' : '#EE7621',
+ 'chocolate3' : '#CD661D',
+ 'chocolate4' : '#8B4513',
+ 'coral' : '#FF7F50',
+ 'coral1' : '#FF7256',
+ 'coral2' : '#EE6A50',
+ 'coral3' : '#CD5B45',
+ 'coral4' : '#8B3E2F',
+ 'cornflowerblue' : '#6495ED',
+ 'cornsilk' : '#FFF8DC',
+ 'cornsilk1' : '#FFF8DC',
+ 'cornsilk2' : '#EEE8CD',
+ 'cornsilk3' : '#CDC8B1',
+ 'cornsilk4' : '#8B8878',
+ 'crimson' : '#DC143C',
+ 'cyan' : '#00FFFF',
+ 'cyan1' : '#00FFFF',
+ 'cyan2' : '#00EEEE',
+ 'cyan3' : '#00CDCD',
+ 'cyan4' : '#008B8B',
+ 'darkblue' : '#00008B',
+ 'darkcyan' : '#008B8B',
+ 'darkgoldenrod' : '#B8860B',
+ 'darkgoldenrod1' : '#FFB90F',
+ 'darkgoldenrod2' : '#EEAD0E',
+ 'darkgoldenrod3' : '#CD950C',
+ 'darkgoldenrod4' : '#8B6508',
+ 'darkgray' : '#A9A9A9',
+ 'darkgreen' : '#006400',
+ 'darkgrey' : '#A9A9A9',
+ 'darkkhaki' : '#BDB76B',
+ 'darkmagenta' : '#8B008B',
+ 'darkolivegreen' : '#556B2F',
+ 'darkolivegreen1' : '#CAFF70',
+ 'darkolivegreen2' : '#BCEE68',
+ 'darkolivegreen3' : '#A2CD5A',
+ 'darkolivegreen4' : '#6E8B3D',
+ 'darkorange' : '#FF8C00',
+ 'darkorange1' : '#FF7F00',
+ 'darkorange2' : '#EE7600',
+ 'darkorange3' : '#CD6600',
+ 'darkorange4' : '#8B4500',
+ 'darkorchid' : '#9932CC',
+ 'darkorchid1' : '#BF3EFF',
+ 'darkorchid2' : '#B23AEE',
+ 'darkorchid3' : '#9A32CD',
+ 'darkorchid4' : '#68228B',
+ 'darkred' : '#8B0000',
+ 'darksalmon' : '#E9967A',
+ 'darkseagreen' : '#8FBC8F',
+ 'darkseagreen1' : '#C1FFC1',
+ 'darkseagreen2' : '#B4EEB4',
+ 'darkseagreen3' : '#9BCD9B',
+ 'darkseagreen4' : '#698B69',
+ 'darkslateblue' : '#483D8B',
+ 'darkslategray' : '#2F4F4F',
+ 'darkslategray1' : '#97FFFF',
+ 'darkslategray2' : '#8DEEEE',
+ 'darkslategray3' : '#79CDCD',
+ 'darkslategray4' : '#528B8B',
+ 'darkslategrey' : '#2F4F4F',
+ 'darkturquoise' : '#00CED1',
+ 'darkviolet' : '#9400D3',
+ 'deeppink' : '#FF1493',
+ 'deeppink1' : '#FF1493',
+ 'deeppink2' : '#EE1289',
+ 'deeppink3' : '#CD1076',
+ 'deeppink4' : '#8B0A50',
+ 'deepskyblue' : '#00BFFF',
+ 'deepskyblue1' : '#00BFFF',
+ 'deepskyblue2' : '#00B2EE',
+ 'deepskyblue3' : '#009ACD',
+ 'deepskyblue4' : '#00688B',
+ 'dimgray' : '#696969',
+ 'dimgrey' : '#696969',
+ 'dodgerblue' : '#1E90FF',
+ 'dodgerblue1' : '#1E90FF',
+ 'dodgerblue2' : '#1C86EE',
+ 'dodgerblue3' : '#1874CD',
+ 'dodgerblue4' : '#104E8B',
+ 'firebrick' : '#B22222',
+ 'firebrick1' : '#FF3030',
+ 'firebrick2' : '#EE2C2C',
+ 'firebrick3' : '#CD2626',
+ 'firebrick4' : '#8B1A1A',
+ 'floralwhite' : '#FFFAF0',
+ 'forestgreen' : '#228B22',
+ 'fuchsia' : '#FF00FF',
+ 'gainsboro' : '#DCDCDC',
+ 'ghostwhite' : '#F8F8FF',
+ 'gold' : '#FFD700',
+ 'gold1' : '#FFD700',
+ 'gold2' : '#EEC900',
+ 'gold3' : '#CDAD00',
+ 'gold4' : '#8B7500',
+ 'goldenrod' : '#DAA520',
+ 'goldenrod1' : '#FFC125',
+ 'goldenrod2' : '#EEB422',
+ 'goldenrod3' : '#CD9B1D',
+ 'goldenrod4' : '#8B6914',
+ 'gray' : '#BEBEBE',
+ 'gray0' : '#000000',
+ 'gray1' : '#030303',
+ 'gray10' : '#1A1A1A',
+ 'gray100' : '#FFFFFF',
+ 'gray11' : '#1C1C1C',
+ 'gray12' : '#1F1F1F',
+ 'gray13' : '#212121',
+ 'gray14' : '#242424',
+ 'gray15' : '#262626',
+ 'gray16' : '#292929',
+ 'gray17' : '#2B2B2B',
+ 'gray18' : '#2E2E2E',
+ 'gray19' : '#303030',
+ 'gray2' : '#050505',
+ 'gray20' : '#333333',
+ 'gray21' : '#363636',
+ 'gray22' : '#383838',
+ 'gray23' : '#3B3B3B',
+ 'gray24' : '#3D3D3D',
+ 'gray25' : '#404040',
+ 'gray26' : '#424242',
+ 'gray27' : '#454545',
+ 'gray28' : '#474747',
+ 'gray29' : '#4A4A4A',
+ 'gray3' : '#080808',
+ 'gray30' : '#4D4D4D',
+ 'gray31' : '#4F4F4F',
+ 'gray32' : '#525252',
+ 'gray33' : '#545454',
+ 'gray34' : '#575757',
+ 'gray35' : '#595959',
+ 'gray36' : '#5C5C5C',
+ 'gray37' : '#5E5E5E',
+ 'gray38' : '#616161',
+ 'gray39' : '#636363',
+ 'gray4' : '#0A0A0A',
+ 'gray40' : '#666666',
+ 'gray41' : '#696969',
+ 'gray42' : '#6B6B6B',
+ 'gray43' : '#6E6E6E',
+ 'gray44' : '#707070',
+ 'gray45' : '#737373',
+ 'gray46' : '#757575',
+ 'gray47' : '#787878',
+ 'gray48' : '#7A7A7A',
+ 'gray49' : '#7D7D7D',
+ 'gray5' : '#0D0D0D',
+ 'gray50' : '#7F7F7F',
+ 'gray51' : '#828282',
+ 'gray52' : '#858585',
+ 'gray53' : '#878787',
+ 'gray54' : '#8A8A8A',
+ 'gray55' : '#8C8C8C',
+ 'gray56' : '#8F8F8F',
+ 'gray57' : '#919191',
+ 'gray58' : '#949494',
+ 'gray59' : '#969696',
+ 'gray6' : '#0F0F0F',
+ 'gray60' : '#999999',
+ 'gray61' : '#9C9C9C',
+ 'gray62' : '#9E9E9E',
+ 'gray63' : '#A1A1A1',
+ 'gray64' : '#A3A3A3',
+ 'gray65' : '#A6A6A6',
+ 'gray66' : '#A8A8A8',
+ 'gray67' : '#ABABAB',
+ 'gray68' : '#ADADAD',
+ 'gray69' : '#B0B0B0',
+ 'gray7' : '#121212',
+ 'gray70' : '#B3B3B3',
+ 'gray71' : '#B5B5B5',
+ 'gray72' : '#B8B8B8',
+ 'gray73' : '#BABABA',
+ 'gray74' : '#BDBDBD',
+ 'gray75' : '#BFBFBF',
+ 'gray76' : '#C2C2C2',
+ 'gray77' : '#C4C4C4',
+ 'gray78' : '#C7C7C7',
+ 'gray79' : '#C9C9C9',
+ 'gray8' : '#141414',
+ 'gray80' : '#CCCCCC',
+ 'gray81' : '#CFCFCF',
+ 'gray82' : '#D1D1D1',
+ 'gray83' : '#D4D4D4',
+ 'gray84' : '#D6D6D6',
+ 'gray85' : '#D9D9D9',
+ 'gray86' : '#DBDBDB',
+ 'gray87' : '#DEDEDE',
+ 'gray88' : '#E0E0E0',
+ 'gray89' : '#E3E3E3',
+ 'gray9' : '#171717',
+ 'gray90' : '#E5E5E5',
+ 'gray91' : '#E8E8E8',
+ 'gray92' : '#EBEBEB',
+ 'gray93' : '#EDEDED',
+ 'gray94' : '#F0F0F0',
+ 'gray95' : '#F2F2F2',
+ 'gray96' : '#F5F5F5',
+ 'gray97' : '#F7F7F7',
+ 'gray98' : '#FAFAFA',
+ 'gray99' : '#FCFCFC',
+ 'green' : '#00FF00',
+ 'green1' : '#00FF00',
+ 'green2' : '#00EE00',
+ 'green3' : '#00CD00',
+ 'green4' : '#008B00',
+ 'greenyellow' : '#ADFF2F',
+ 'grey' : '#BEBEBE',
+ 'grey0' : '#000000',
+ 'grey1' : '#030303',
+ 'grey10' : '#1A1A1A',
+ 'grey100' : '#FFFFFF',
+ 'grey11' : '#1C1C1C',
+ 'grey12' : '#1F1F1F',
+ 'grey13' : '#212121',
+ 'grey14' : '#242424',
+ 'grey15' : '#262626',
+ 'grey16' : '#292929',
+ 'grey17' : '#2B2B2B',
+ 'grey18' : '#2E2E2E',
+ 'grey19' : '#303030',
+ 'grey2' : '#050505',
+ 'grey20' : '#333333',
+ 'grey21' : '#363636',
+ 'grey22' : '#383838',
+ 'grey23' : '#3B3B3B',
+ 'grey24' : '#3D3D3D',
+ 'grey25' : '#404040',
+ 'grey26' : '#424242',
+ 'grey27' : '#454545',
+ 'grey28' : '#474747',
+ 'grey29' : '#4A4A4A',
+ 'grey3' : '#080808',
+ 'grey30' : '#4D4D4D',
+ 'grey31' : '#4F4F4F',
+ 'grey32' : '#525252',
+ 'grey33' : '#545454',
+ 'grey34' : '#575757',
+ 'grey35' : '#595959',
+ 'grey36' : '#5C5C5C',
+ 'grey37' : '#5E5E5E',
+ 'grey38' : '#616161',
+ 'grey39' : '#636363',
+ 'grey4' : '#0A0A0A',
+ 'grey40' : '#666666',
+ 'grey41' : '#696969',
+ 'grey42' : '#6B6B6B',
+ 'grey43' : '#6E6E6E',
+ 'grey44' : '#707070',
+ 'grey45' : '#737373',
+ 'grey46' : '#757575',
+ 'grey47' : '#787878',
+ 'grey48' : '#7A7A7A',
+ 'grey49' : '#7D7D7D',
+ 'grey5' : '#0D0D0D',
+ 'grey50' : '#7F7F7F',
+ 'grey51' : '#828282',
+ 'grey52' : '#858585',
+ 'grey53' : '#878787',
+ 'grey54' : '#8A8A8A',
+ 'grey55' : '#8C8C8C',
+ 'grey56' : '#8F8F8F',
+ 'grey57' : '#919191',
+ 'grey58' : '#949494',
+ 'grey59' : '#969696',
+ 'grey6' : '#0F0F0F',
+ 'grey60' : '#999999',
+ 'grey61' : '#9C9C9C',
+ 'grey62' : '#9E9E9E',
+ 'grey63' : '#A1A1A1',
+ 'grey64' : '#A3A3A3',
+ 'grey65' : '#A6A6A6',
+ 'grey66' : '#A8A8A8',
+ 'grey67' : '#ABABAB',
+ 'grey68' : '#ADADAD',
+ 'grey69' : '#B0B0B0',
+ 'grey7' : '#121212',
+ 'grey70' : '#B3B3B3',
+ 'grey71' : '#B5B5B5',
+ 'grey72' : '#B8B8B8',
+ 'grey73' : '#BABABA',
+ 'grey74' : '#BDBDBD',
+ 'grey75' : '#BFBFBF',
+ 'grey76' : '#C2C2C2',
+ 'grey77' : '#C4C4C4',
+ 'grey78' : '#C7C7C7',
+ 'grey79' : '#C9C9C9',
+ 'grey8' : '#141414',
+ 'grey80' : '#CCCCCC',
+ 'grey81' : '#CFCFCF',
+ 'grey82' : '#D1D1D1',
+ 'grey83' : '#D4D4D4',
+ 'grey84' : '#D6D6D6',
+ 'grey85' : '#D9D9D9',
+ 'grey86' : '#DBDBDB',
+ 'grey87' : '#DEDEDE',
+ 'grey88' : '#E0E0E0',
+ 'grey89' : '#E3E3E3',
+ 'grey9' : '#171717',
+ 'grey90' : '#E5E5E5',
+ 'grey91' : '#E8E8E8',
+ 'grey92' : '#EBEBEB',
+ 'grey93' : '#EDEDED',
+ 'grey94' : '#F0F0F0',
+ 'grey95' : '#F2F2F2',
+ 'grey96' : '#F5F5F5',
+ 'grey97' : '#F7F7F7',
+ 'grey98' : '#FAFAFA',
+ 'grey99' : '#FCFCFC',
+ 'honeydew' : '#F0FFF0',
+ 'honeydew1' : '#F0FFF0',
+ 'honeydew2' : '#E0EEE0',
+ 'honeydew3' : '#C1CDC1',
+ 'honeydew4' : '#838B83',
+ 'hotpink' : '#FF69B4',
+ 'hotpink1' : '#FF6EB4',
+ 'hotpink2' : '#EE6AA7',
+ 'hotpink3' : '#CD6090',
+ 'hotpink4' : '#8B3A62',
+ 'indianred' : '#CD5C5C',
+ 'indianred1' : '#FF6A6A',
+ 'indianred2' : '#EE6363',
+ 'indianred3' : '#CD5555',
+ 'indianred4' : '#8B3A3A',
+ 'indigo' : '#4B0082',
+ 'ivory' : '#FFFFF0',
+ 'ivory1' : '#FFFFF0',
+ 'ivory2' : '#EEEEE0',
+ 'ivory3' : '#CDCDC1',
+ 'ivory4' : '#8B8B83',
+ 'khaki' : '#F0E68C',
+ 'khaki1' : '#FFF68F',
+ 'khaki2' : '#EEE685',
+ 'khaki3' : '#CDC673',
+ 'khaki4' : '#8B864E',
+ 'lavender' : '#E6E6FA',
+ 'lavenderblush' : '#FFF0F5',
+ 'lavenderblush1' : '#FFF0F5',
+ 'lavenderblush2' : '#EEE0E5',
+ 'lavenderblush3' : '#CDC1C5',
+ 'lavenderblush4' : '#8B8386',
+ 'lawngreen' : '#7CFC00',
+ 'lemonchiffon' : '#FFFACD',
+ 'lemonchiffon1' : '#FFFACD',
+ 'lemonchiffon2' : '#EEE9BF',
+ 'lemonchiffon3' : '#CDC9A5',
+ 'lemonchiffon4' : '#8B8970',
+ 'lightblue' : '#ADD8E6',
+ 'lightblue1' : '#BFEFFF',
+ 'lightblue2' : '#B2DFEE',
+ 'lightblue3' : '#9AC0CD',
+ 'lightblue4' : '#68838B',
+ 'lightcoral' : '#F08080',
+ 'lightcyan' : '#E0FFFF',
+ 'lightcyan1' : '#E0FFFF',
+ 'lightcyan2' : '#D1EEEE',
+ 'lightcyan3' : '#B4CDCD',
+ 'lightcyan4' : '#7A8B8B',
+ 'lightgoldenrod' : '#EEDD82',
+ 'lightgoldenrod1' : '#FFEC8B',
+ 'lightgoldenrod2' : '#EEDC82',
+ 'lightgoldenrod3' : '#CDBE70',
+ 'lightgoldenrod4' : '#8B814C',
+ 'lightgoldenrodyellow' : '#FAFAD2',
+ 'lightgray' : '#D3D3D3',
+ 'lightgreen' : '#90EE90',
+ 'lightgrey' : '#D3D3D3',
+ 'lightpink' : '#FFB6C1',
+ 'lightpink1' : '#FFAEB9',
+ 'lightpink2' : '#EEA2AD',
+ 'lightpink3' : '#CD8C95',
+ 'lightpink4' : '#8B5F65',
+ 'lightsalmon' : '#FFA07A',
+ 'lightsalmon1' : '#FFA07A',
+ 'lightsalmon2' : '#EE9572',
+ 'lightsalmon3' : '#CD8162',
+ 'lightsalmon4' : '#8B5742',
+ 'lightseagreen' : '#20B2AA',
+ 'lightskyblue' : '#87CEFA',
+ 'lightskyblue1' : '#B0E2FF',
+ 'lightskyblue2' : '#A4D3EE',
+ 'lightskyblue3' : '#8DB6CD',
+ 'lightskyblue4' : '#607B8B',
+ 'lightslateblue' : '#8470FF',
+ 'lightslategray' : '#778899',
+ 'lightslategrey' : '#778899',
+ 'lightsteelblue' : '#B0C4DE',
+ 'lightsteelblue1' : '#CAE1FF',
+ 'lightsteelblue2' : '#BCD2EE',
+ 'lightsteelblue3' : '#A2B5CD',
+ 'lightsteelblue4' : '#6E7B8B',
+ 'lightyellow' : '#FFFFE0',
+ 'lightyellow1' : '#FFFFE0',
+ 'lightyellow2' : '#EEEED1',
+ 'lightyellow3' : '#CDCDB4',
+ 'lightyellow4' : '#8B8B7A',
+ 'lime' : '#00FF00',
+ 'limegreen' : '#32CD32',
+ 'linen' : '#FAF0E6',
+ 'magenta' : '#FF00FF',
+ 'magenta1' : '#FF00FF',
+ 'magenta2' : '#EE00EE',
+ 'magenta3' : '#CD00CD',
+ 'magenta4' : '#8B008B',
+ 'maroon' : '#B03060',
+ 'maroon1' : '#FF34B3',
+ 'maroon2' : '#EE30A7',
+ 'maroon3' : '#CD2990',
+ 'maroon4' : '#8B1C62',
+ 'mediumaquamarine' : '#66CDAA',
+ 'mediumblue' : '#0000CD',
+ 'mediumorchid' : '#BA55D3',
+ 'mediumorchid1' : '#E066FF',
+ 'mediumorchid2' : '#D15FEE',
+ 'mediumorchid3' : '#B452CD',
+ 'mediumorchid4' : '#7A378B',
+ 'mediumpurple' : '#9370DB',
+ 'mediumpurple1' : '#AB82FF',
+ 'mediumpurple2' : '#9F79EE',
+ 'mediumpurple3' : '#8968CD',
+ 'mediumpurple4' : '#5D478B',
+ 'mediumseagreen' : '#3CB371',
+ 'mediumslateblue' : '#7B68EE',
+ 'mediumspringgreen' : '#00FA9A',
+ 'mediumturquoise' : '#48D1CC',
+ 'mediumvioletred' : '#C71585',
+ 'midnightblue' : '#191970',
+ 'mintcream' : '#F5FFFA',
+ 'mistyrose' : '#FFE4E1',
+ 'mistyrose1' : '#FFE4E1',
+ 'mistyrose2' : '#EED5D2',
+ 'mistyrose3' : '#CDB7B5',
+ 'mistyrose4' : '#8B7D7B',
+ 'moccasin' : '#FFE4B5',
+ 'navajowhite' : '#FFDEAD',
+ 'navajowhite1' : '#FFDEAD',
+ 'navajowhite2' : '#EECFA1',
+ 'navajowhite3' : '#CDB38B',
+ 'navajowhite4' : '#8B795E',
+ 'navy' : '#000080',
+ 'navyblue' : '#000080',
+ 'oldlace' : '#FDF5E6',
+ 'olive' : '#808000',
+ 'olivedrab' : '#6B8E23',
+ 'olivedrab1' : '#C0FF3E',
+ 'olivedrab2' : '#B3EE3A',
+ 'olivedrab3' : '#9ACD32',
+ 'olivedrab4' : '#698B22',
+ 'orange' : '#FFA500',
+ 'orange1' : '#FFA500',
+ 'orange2' : '#EE9A00',
+ 'orange3' : '#CD8500',
+ 'orange4' : '#8B5A00',
+ 'orangered' : '#FF4500',
+ 'orangered1' : '#FF4500',
+ 'orangered2' : '#EE4000',
+ 'orangered3' : '#CD3700',
+ 'orangered4' : '#8B2500',
+ 'orchid' : '#DA70D6',
+ 'orchid1' : '#FF83FA',
+ 'orchid2' : '#EE7AE9',
+ 'orchid3' : '#CD69C9',
+ 'orchid4' : '#8B4789',
+ 'palegoldenrod' : '#EEE8AA',
+ 'palegreen' : '#98FB98',
+ 'palegreen1' : '#9AFF9A',
+ 'palegreen2' : '#90EE90',
+ 'palegreen3' : '#7CCD7C',
+ 'palegreen4' : '#548B54',
+ 'paleturquoise' : '#AFEEEE',
+ 'paleturquoise1' : '#BBFFFF',
+ 'paleturquoise2' : '#AEEEEE',
+ 'paleturquoise3' : '#96CDCD',
+ 'paleturquoise4' : '#668B8B',
+ 'palevioletred' : '#DB7093',
+ 'palevioletred1' : '#FF82AB',
+ 'palevioletred2' : '#EE799F',
+ 'palevioletred3' : '#CD6889',
+ 'palevioletred4' : '#8B475D',
+ 'papayawhip' : '#FFEFD5',
+ 'peachpuff' : '#FFDAB9',
+ 'peachpuff1' : '#FFDAB9',
+ 'peachpuff2' : '#EECBAD',
+ 'peachpuff3' : '#CDAF95',
+ 'peachpuff4' : '#8B7765',
+ 'peru' : '#CD853F',
+ 'pink' : '#FFC0CB',
+ 'pink1' : '#FFB5C5',
+ 'pink2' : '#EEA9B8',
+ 'pink3' : '#CD919E',
+ 'pink4' : '#8B636C',
+ 'plum' : '#DDA0DD',
+ 'plum1' : '#FFBBFF',
+ 'plum2' : '#EEAEEE',
+ 'plum3' : '#CD96CD',
+ 'plum4' : '#8B668B',
+ 'powderblue' : '#B0E0E6',
+ 'purple' : '#A020F0',
+ 'purple1' : '#9B30FF',
+ 'purple2' : '#912CEE',
+ 'purple3' : '#7D26CD',
+ 'purple4' : '#551A8B',
+ 'rebeccapurple' : '#663399',
+ 'red' : '#FF0000',
+ 'red1' : '#FF0000',
+ 'red2' : '#EE0000',
+ 'red3' : '#CD0000',
+ 'red4' : '#8B0000',
+ 'rosybrown' : '#BC8F8F',
+ 'rosybrown1' : '#FFC1C1',
+ 'rosybrown2' : '#EEB4B4',
+ 'rosybrown3' : '#CD9B9B',
+ 'rosybrown4' : '#8B6969',
+ 'royalblue' : '#4169E1',
+ 'royalblue1' : '#4876FF',
+ 'royalblue2' : '#436EEE',
+ 'royalblue3' : '#3A5FCD',
+ 'royalblue4' : '#27408B',
+ 'saddlebrown' : '#8B4513',
+ 'salmon' : '#FA8072',
+ 'salmon1' : '#FF8C69',
+ 'salmon2' : '#EE8262',
+ 'salmon3' : '#CD7054',
+ 'salmon4' : '#8B4C39',
+ 'sandybrown' : '#F4A460',
+ 'seagreen' : '#2E8B57',
+ 'seagreen1' : '#54FF9F',
+ 'seagreen2' : '#4EEE94',
+ 'seagreen3' : '#43CD80',
+ 'seagreen4' : '#2E8B57',
+ 'seashell' : '#FFF5EE',
+ 'seashell1' : '#FFF5EE',
+ 'seashell2' : '#EEE5DE',
+ 'seashell3' : '#CDC5BF',
+ 'seashell4' : '#8B8682',
+ 'sienna' : '#A0522D',
+ 'sienna1' : '#FF8247',
+ 'sienna2' : '#EE7942',
+ 'sienna3' : '#CD6839',
+ 'sienna4' : '#8B4726',
+ 'silver' : '#C0C0C0',
+ 'skyblue' : '#87CEEB',
+ 'skyblue1' : '#87CEFF',
+ 'skyblue2' : '#7EC0EE',
+ 'skyblue3' : '#6CA6CD',
+ 'skyblue4' : '#4A708B',
+ 'slateblue' : '#6A5ACD',
+ 'slateblue1' : '#836FFF',
+ 'slateblue2' : '#7A67EE',
+ 'slateblue3' : '#6959CD',
+ 'slateblue4' : '#473C8B',
+ 'slategray' : '#708090',
+ 'slategray1' : '#C6E2FF',
+ 'slategray2' : '#B9D3EE',
+ 'slategray3' : '#9FB6CD',
+ 'slategray4' : '#6C7B8B',
+ 'slategrey' : '#708090',
+ 'snow' : '#FFFAFA',
+ 'snow1' : '#FFFAFA',
+ 'snow2' : '#EEE9E9',
+ 'snow3' : '#CDC9C9',
+ 'snow4' : '#8B8989',
+ 'springgreen' : '#00FF7F',
+ 'springgreen1' : '#00FF7F',
+ 'springgreen2' : '#00EE76',
+ 'springgreen3' : '#00CD66',
+ 'springgreen4' : '#008B45',
+ 'steelblue' : '#4682B4',
+ 'steelblue1' : '#63B8FF',
+ 'steelblue2' : '#5CACEE',
+ 'steelblue3' : '#4F94CD',
+ 'steelblue4' : '#36648B',
+ 'tan' : '#D2B48C',
+ 'tan1' : '#FFA54F',
+ 'tan2' : '#EE9A49',
+ 'tan3' : '#CD853F',
+ 'tan4' : '#8B5A2B',
+ 'teal' : '#008080',
+ 'thistle' : '#D8BFD8',
+ 'thistle1' : '#FFE1FF',
+ 'thistle2' : '#EED2EE',
+ 'thistle3' : '#CDB5CD',
+ 'thistle4' : '#8B7B8B',
+ 'tomato' : '#FF6347',
+ 'tomato1' : '#FF6347',
+ 'tomato2' : '#EE5C42',
+ 'tomato3' : '#CD4F39',
+ 'tomato4' : '#8B3626',
+ 'turquoise' : '#40E0D0',
+ 'turquoise1' : '#00F5FF',
+ 'turquoise2' : '#00E5EE',
+ 'turquoise3' : '#00C5CD',
+ 'turquoise4' : '#00868B',
+ 'violet' : '#EE82EE',
+ 'violetred' : '#D02090',
+ 'violetred1' : '#FF3E96',
+ 'violetred2' : '#EE3A8C',
+ 'violetred3' : '#CD3278',
+ 'violetred4' : '#8B2252',
+ 'webgray' : '#808080',
+ 'webgreen' : '#008000',
+ 'webgrey' : '#808080',
+ 'webmaroon' : '#800000',
+ 'webpurple' : '#800080',
+ 'wheat' : '#F5DEB3',
+ 'wheat1' : '#FFE7BA',
+ 'wheat2' : '#EED8AE',
+ 'wheat3' : '#CDBA96',
+ 'wheat4' : '#8B7E66',
+ 'white' : '#FFFFFF',
+ 'whitesmoke' : '#F5F5F5',
+ 'x11gray' : '#BEBEBE',
+ 'x11green' : '#00FF00',
+ 'x11grey' : '#BEBEBE',
+ 'x11maroon' : '#B03060',
+ 'x11purple' : '#A020F0',
+ 'yellow' : '#FFFF00',
+ 'yellow1' : '#FFFF00',
+ 'yellow2' : '#EEEE00',
+ 'yellow3' : '#CDCD00',
+ 'yellow4' : '#8B8B00',
+ 'yellowgreen' : '#9ACD32'
+ };
+
+ return ColorScheme;
+
+}]);
diff --git a/guacamole/src/main/webapp/app/manage/styles/connection-parameter.css b/guacamole/src/main/webapp/app/manage/styles/connection-parameter.css
index 8fe19d6..c5645fb 100644
--- a/guacamole/src/main/webapp/app/manage/styles/connection-parameter.css
+++ b/guacamole/src/main/webapp/app/manage/styles/connection-parameter.css
@@ -29,6 +29,7 @@
display: table;
padding-left: .5em;
border-left: 3px solid rgba(0,0,0,0.125);
+ width: 100%;
}
.connection-parameters .form .fields .labeled-field {
@@ -40,8 +41,11 @@
display: table-cell;
padding: 0.125em;
vertical-align: top;
+ width: 100%;
}
.connection-parameters .form .fields .field-header {
padding-right: 1em;
+ width: 0;
+ white-space: nowrap;
}
diff --git a/guacamole/src/main/webapp/app/rest/types/Field.js b/guacamole/src/main/webapp/app/rest/types/Field.js
index 84dfe13..195db82 100644
--- a/guacamole/src/main/webapp/app/rest/types/Field.js
+++ b/guacamole/src/main/webapp/app/rest/types/Field.js
@@ -168,7 +168,16 @@
*
* @type String
*/
- QUERY_PARAMETER : 'QUERY_PARAMETER'
+ QUERY_PARAMETER : 'QUERY_PARAMETER',
+
+ /**
+ * The type string associated with parameters that may contain color
+ * schemes accepted by the Guacamole server terminal emulator and
+ * protocols which leverage it.
+ *
+ * @type String
+ */
+ TERMINAL_COLOR_SCHEME : 'TERMINAL_COLOR_SCHEME'
};
diff --git a/guacamole/src/main/webapp/index.html b/guacamole/src/main/webapp/index.html
index e675546..309f114 100644
--- a/guacamole/src/main/webapp/index.html
+++ b/guacamole/src/main/webapp/index.html
@@ -27,6 +27,7 @@
<link rel="icon" type="image/png" href="images/logo-64.png"/>
<link rel="icon" type="image/png" sizes="144x144" href="images/logo-144.png"/>
<link rel="apple-touch-icon" type="image/png" href="images/logo-144.png"/>
+ <link rel="stylesheet" type="text/css" href="webjars/simonwep__pickr/1.2.6/dist/themes/monolith.min.css"/>
<link rel="stylesheet" type="text/css" href="app.css?v=${project.version}">
<title ng-bind="page.title | translate"></title>
</head>
@@ -87,7 +88,10 @@
<!-- JSTZ -->
<script type="text/javascript" src="webjars/jstz/1.0.10/dist/jstz.min.js"></script>
-
+
+ <!-- Pickr (color picker) -->
+ <script type="text/javascript" src="webjars/simonwep__pickr/1.2.6/dist/pickr.es5.min.js"></script>
+
<!-- Polyfills for the "datalist" element, Blob and the FileSaver API -->
<script type="text/javascript" src="webjars/blob-polyfill/1.0.20150320/Blob.js"></script>
<script type="text/javascript" src="webjars/datalist-polyfill/1.14.0/datalist-polyfill.min.js"></script>
diff --git a/guacamole/src/main/webapp/translations/en.json b/guacamole/src/main/webapp/translations/en.json
index 8592c9e..7b29549 100644
--- a/guacamole/src/main/webapp/translations/en.json
+++ b/guacamole/src/main/webapp/translations/en.json
@@ -148,6 +148,22 @@
},
+ "COLOR_SCHEME" : {
+
+ "ACTION_CANCEL" : "@:APP.ACTION_CANCEL",
+ "ACTION_HIDE_DETAILS" : "Hide",
+ "ACTION_SAVE" : "@:APP.ACTION_SAVE",
+ "ACTION_SHOW_DETAILS" : "Show",
+
+ "FIELD_HEADER_BACKGROUND" : "Background",
+ "FIELD_HEADER_FOREGROUND" : "Foreground",
+
+ "FIELD_OPTION_CUSTOM" : "Custom...",
+
+ "SECTION_HEADER_DETAILS" : "Details:"
+
+ },
+
"DATA_SOURCE_DEFAULT" : {
"NAME" : "Default (XML)"
},