mirror of
				https://github.com/gyurix1968/guacamole-client.git
				synced 2025-10-31 00:53:21 +00:00 
			
		
		
		
	GUACAMOLE-1293: Merge add list/count of current users joined to a connection.
This commit is contained in:
		| @@ -690,7 +690,10 @@ Guacamole.Client = function(tunnel) { | ||||
|      | ||||
|     /** | ||||
|      * Fired when an arbitrary message is received from the tunnel that should | ||||
|      * be processed by the client. | ||||
|      * be processed by the client. By default, additional message-specific | ||||
|      * events such as "onjoin" and "onleave" will fire for the received message | ||||
|      * after this event has been processed. An event handler for "onmsg" need | ||||
|      * not be supplied if "onjoin" and/or "onleave" will be used. | ||||
|      *  | ||||
|      * @event | ||||
|      * @param {!number} msgcode | ||||
| @@ -700,9 +703,49 @@ Guacamole.Client = function(tunnel) { | ||||
|      * @param {string[]} args | ||||
|      *     An array of arguments to be processed with the message sent to the | ||||
|      *     client. | ||||
|      * | ||||
|      * @return {boolean} | ||||
|      *     true if message-specific events such as "onjoin" and | ||||
|      *     "onleave" should be fired for this message, false otherwise. If | ||||
|      *     no value is returned, message-specific events will be allowed to | ||||
|      *     fire. | ||||
|      */ | ||||
|     this.onmsg = null; | ||||
|  | ||||
|     /** | ||||
|      * Fired when a user joins a shared connection. | ||||
|      * | ||||
|      * @event | ||||
|      * @param {!string} userID | ||||
|      *     A unique value representing this specific user's connection to the | ||||
|      *     shared connection. This value is generated by the server and is | ||||
|      *     guaranteed to be unique relative to other users of the connection. | ||||
|      * | ||||
|      * @param {!string} name | ||||
|      *     A human-readable name representing the user that joined, such as | ||||
|      *     their username. This value is provided by the web application during | ||||
|      *     the connection handshake and is not necessarily unique relative to | ||||
|      *     other users of the connection. | ||||
|      */ | ||||
|     this.onjoin = null; | ||||
|  | ||||
|     /** | ||||
|      * Fired when a user leaves a shared connection. | ||||
|      * | ||||
|      * @event | ||||
|      * @param {!string} userID | ||||
|      *     A unique value representing this specific user's connection to the | ||||
|      *     shared connection. This value is generated by the server and is | ||||
|      *     guaranteed to be unique relative to other users of the connection. | ||||
|      * | ||||
|      * @param {!string} name | ||||
|      *     A human-readable name representing the user that left, such as their | ||||
|      *     username. This value is provided by the web application during the | ||||
|      *     connection handshake and is not necessarily unique relative to other | ||||
|      *     users of the connection. | ||||
|      */ | ||||
|     this.onleave = null; | ||||
|  | ||||
|     /** | ||||
|      * Fired when a audio stream is created. The stream provided to this event | ||||
|      * handler will contain its own event handlers for received data. | ||||
| @@ -1417,7 +1460,39 @@ Guacamole.Client = function(tunnel) { | ||||
|          | ||||
|         "msg" : function(parameters) { | ||||
|  | ||||
|             if (guac_client.onmsg) guac_client.onmsg(parseInt(parameters[0]), parameters.slice(1)); | ||||
|             var userID; | ||||
|             var username; | ||||
|  | ||||
|             // Fire general message handling event first | ||||
|             var allowDefault = true; | ||||
|             var msgid = parseInt(parameters[0]); | ||||
|             if (guac_client.onmsg) { | ||||
|                 allowDefault = guac_client.onmsg(msgid, parameters.slice(1)); | ||||
|                 if (allowDefault === undefined) | ||||
|                     allowDefault = true; | ||||
|             } | ||||
|  | ||||
|             // Fire message-specific convenience events if not prevented by the | ||||
|             // "onmsg" handler | ||||
|             if (allowDefault) { | ||||
|                 switch (msgid) { | ||||
|  | ||||
|                     case Guacamole.Client.Message.USER_JOINED: | ||||
|                         userID = parameters[1]; | ||||
|                         username = parameters[2]; | ||||
|                         if (guac_client.onjoin) | ||||
|                             guac_client.onjoin(userID, username); | ||||
|                         break; | ||||
|  | ||||
|                     case Guacamole.Client.Message.USER_LEFT: | ||||
|                         userID = parameters[1]; | ||||
|                         username = parameters[2]; | ||||
|                         if (guac_client.onleave) | ||||
|                             guac_client.onleave(userID, username); | ||||
|                         break; | ||||
|  | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|         }, | ||||
|  | ||||
|   | ||||
| @@ -792,24 +792,6 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams | ||||
|  | ||||
|     }; | ||||
|      | ||||
|     /** | ||||
|      * Determines whether the attached client group has any associated client | ||||
|      * messages to display. | ||||
|      *  | ||||
|      * @returns {Boolean} | ||||
|      *     true if there are messages to display; otherwise false. | ||||
|      */ | ||||
|     $scope.hasMessages = function hasMessages() { | ||||
|          | ||||
|         // No client group means no messages | ||||
|         if (!$scope.clientGroup) | ||||
|             return false; | ||||
|          | ||||
|         // Otherwise, find messages within the clients in the group. | ||||
|         return _.findIndex($scope.clientGroup.clients, ManagedClient.hasMessages) !== -1; | ||||
|          | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Determines whether the attached client group has any associated file | ||||
|      * transfers, regardless of those file transfers' state. | ||||
|   | ||||
| @@ -1,87 +0,0 @@ | ||||
| /* | ||||
|  * 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. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Directive which displays a message for the client. | ||||
|  */ | ||||
| angular.module('client').directive('guacClientMessage', [function guacClientMessage() { | ||||
|  | ||||
|     return { | ||||
|         restrict: 'E', | ||||
|         replace: true, | ||||
|         scope: { | ||||
|  | ||||
|             /** | ||||
|              * The message to display to the client. | ||||
|              *  | ||||
|              * @type {!ManagedClientMessage} | ||||
|              */ | ||||
|             message : '=' | ||||
|  | ||||
|         }, | ||||
|  | ||||
|         templateUrl: 'app/client/templates/guacClientMessage.html', | ||||
|          | ||||
|         controller: ['$scope', '$injector', '$element', | ||||
|                 function guacClientMessageController($scope, $injector, $element) { | ||||
|              | ||||
|             // Required types | ||||
|             const ManagedClientMessage = $injector.get('ManagedClientMessage'); | ||||
|              | ||||
|             // Required services | ||||
|             var translationStringService = $injector.get('translationStringService'); | ||||
|              | ||||
|             /** | ||||
|              * Uses the msgcode to retrieve the correct translation key for | ||||
|              * the client message. | ||||
|              *  | ||||
|              * @returns {string} | ||||
|              */ | ||||
|             $scope.getMessageKey = function getMessageKey() { | ||||
|                  | ||||
|                 let msgString = "DEFAULT"; | ||||
|                 if (Object.values(Guacamole.Client.Message).includes($scope.message.msgcode)) | ||||
|                     msgString = Object.keys(Guacamole.Client.Message).find(key => Guacamole.Client.Message[key] === $scope.message.msgcode); | ||||
|                  | ||||
|                 return "CLIENT.MESSAGE_" + translationStringService.canonicalize(msgString); | ||||
|             }; | ||||
|              | ||||
|             /** | ||||
|              * Returns a set of key/value object pairs that represent the | ||||
|              * arguments provided as part of the message in the form | ||||
|              * "ARGS_0 = value". Guacamole's translation system relies on | ||||
|              * the arguments being available in this format in order to be able | ||||
|              * to handle substituting values for an arbitrary list of arguments. | ||||
|              *  | ||||
|              * @returns {Object} | ||||
|              */ | ||||
|             $scope.getMessageArgs = function getMessageArgs() { | ||||
|                 return $scope.message.args.reduce( | ||||
|                     function(acc, value, index) { | ||||
|                         acc[`ARGS_${index}`] = value; | ||||
|                         return acc; | ||||
|                     }, | ||||
|                     {} | ||||
|                 ); | ||||
|             }; | ||||
|                  | ||||
|         }] | ||||
|  | ||||
|     }; | ||||
| }]); | ||||
| @@ -0,0 +1,196 @@ | ||||
| /* | ||||
|  * 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 that displays a status indicator showing the number of users | ||||
|  * joined to a connection. The specific usernames of those users are visible in | ||||
|  * a tooltip on mouseover, and small notifications are displayed as users | ||||
|  * join/leave the connection. | ||||
|  */ | ||||
| angular.module('client').directive('guacClientUserCount', [function guacClientUserCount() { | ||||
|  | ||||
|     const directive = { | ||||
|         restrict: 'E', | ||||
|         replace: true, | ||||
|         templateUrl: 'app/client/templates/guacClientUserCount.html' | ||||
|     }; | ||||
|  | ||||
|     directive.scope = { | ||||
|  | ||||
|         /** | ||||
|          * The client whose current users should be displayed. | ||||
|          *  | ||||
|          * @type ManagedClient | ||||
|          */ | ||||
|         client : '=' | ||||
|          | ||||
|     }; | ||||
|  | ||||
|     directive.controller = ['$scope', '$injector', '$element', | ||||
|         function guacClientUserCountController($scope, $injector, $element) { | ||||
|  | ||||
|         // Required services | ||||
|         var $translate = $injector.get('$translate'); | ||||
|  | ||||
|         /** | ||||
|          * The maximum number of messages displayed by this directive at any | ||||
|          * given time. Old messages will be discarded as necessary to ensure | ||||
|          * the number of messages displayed never exceeds this value. | ||||
|          * | ||||
|          * @constant | ||||
|          * @type number | ||||
|          */ | ||||
|         var MAX_MESSAGES = 3; | ||||
|  | ||||
|         /** | ||||
|          * The list that should contain any notifications regarding users | ||||
|          * joining or leaving the connection. | ||||
|          * | ||||
|          * @type HTMLUListElement | ||||
|          */ | ||||
|         var messages = $element.find('.client-user-count-messages')[0]; | ||||
|  | ||||
|         /** | ||||
|          * Map of the usernames of all users of the current connection to the | ||||
|          * number of concurrent connections those users have to the current | ||||
|          * connection. | ||||
|          * | ||||
|          * @type Object.<string, number> | ||||
|          */ | ||||
|         $scope.userCounts = {}; | ||||
|  | ||||
|         /** | ||||
|          * Displays a message noting that a change related to a particular user | ||||
|          * of this connection has occurred. | ||||
|          * | ||||
|          * @param {!string} str | ||||
|          *     The key of the translation string containing the message to | ||||
|          *     display. This translation key must accept "USERNAME" as the | ||||
|          *     name of the translation parameter containing the username of | ||||
|          *     the user in question. | ||||
|          * | ||||
|          * @param {!string} username | ||||
|          *     The username of the user in question. | ||||
|          */ | ||||
|         var notify = function notify(str, username) { | ||||
|             $translate(str, { 'USERNAME' : username }).then(function translationReady(text) { | ||||
|  | ||||
|                 if (messages.childNodes.length === 3) | ||||
|                     messages.removeChild(messages.lastChild); | ||||
|  | ||||
|                 var message = document.createElement('li'); | ||||
|                 message.className = 'client-user-count-message'; | ||||
|                 message.textContent = text; | ||||
|                 messages.insertBefore(message, messages.firstChild); | ||||
|  | ||||
|                 // Automatically remove the notification after its "fadeout" | ||||
|                 // animation ends. NOTE: This will not fire if the element is | ||||
|                 // not visible at all. | ||||
|                 message.addEventListener('animationend', function animationEnded() { | ||||
|                     messages.removeChild(message); | ||||
|                 }); | ||||
|  | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Displays a message noting that a particular user has joined the | ||||
|          * current connection. | ||||
|          *  | ||||
|          * @param {!string} username | ||||
|          *     The username of the user that joined. | ||||
|          */ | ||||
|         var notifyUserJoined = function notifyUserJoined(username) { | ||||
|             notify('CLIENT.TEXT_USER_JOINED', username); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Displays a message noting that a particular user has left the | ||||
|          * current connection. | ||||
|          *  | ||||
|          * @param {!string} username | ||||
|          *     The username of the user that left. | ||||
|          */ | ||||
|         var notifyUserLeft = function notifyUserLeft(username) { | ||||
|             notify('CLIENT.TEXT_USER_LEFT', username); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * The ManagedClient attached to this directive at the time the | ||||
|          * notification update scope watch was last invoked. This is necessary | ||||
|          * as $scope.$watchGroup() does not allow for the callback to know | ||||
|          * whether the scope was previously uninitialized (it's "oldValues" | ||||
|          * parameter receives a copy of the new values if there are no old | ||||
|          * values). | ||||
|          * | ||||
|          * @type ManagedClient | ||||
|          */ | ||||
|         var oldClient = null; | ||||
|  | ||||
|         // Update visible notifications as users join/leave | ||||
|         $scope.$watchGroup([ 'client', 'client.userCount' ], function usersChanged() { | ||||
|  | ||||
|             // Resynchronize directive with state of any attached client when | ||||
|             // the client changes, to ensure notifications are only shown for | ||||
|             // future changes in users present | ||||
|             if (oldClient !== $scope.client) { | ||||
|  | ||||
|                 $scope.userCounts = {}; | ||||
|                 oldClient = $scope.client; | ||||
|  | ||||
|                 angular.forEach($scope.client.users, function initUsers(connections, username) { | ||||
|                     var count = Object.keys(connections).length; | ||||
|                     $scope.userCounts[username] = count; | ||||
|                 }); | ||||
|  | ||||
|                 return; | ||||
|  | ||||
|             } | ||||
|  | ||||
|             // Display join/leave notifications for users who are currently | ||||
|             // connected but whose connection counts have changed | ||||
|             angular.forEach($scope.client.users, function addNewUsers(connections, username) { | ||||
|  | ||||
|                 var count = Object.keys(connections).length; | ||||
|                 var known = $scope.userCounts[username] || 0; | ||||
|  | ||||
|                 if (count > known) | ||||
|                     notifyUserJoined(username); | ||||
|                 else if (count < known) | ||||
|                     notifyUserLeft(username); | ||||
|  | ||||
|                 $scope.userCounts[username] = count; | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             // Display leave notifications for users who are no longer connected | ||||
|             angular.forEach($scope.userCounts, function removeOldUsers(count, username) { | ||||
|                 if (!$scope.client.users[username]) { | ||||
|                     notifyUserLeft(username); | ||||
|                     delete $scope.userCounts[username]; | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|  | ||||
|     }]; | ||||
|  | ||||
|     return directive; | ||||
|  | ||||
| }]); | ||||
| @@ -1,76 +0,0 @@ | ||||
| /* | ||||
|  * 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. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Directive which displays all client messages. | ||||
|  */ | ||||
| angular.module('client').directive('guacMessageDialog', [function guacMessageDialog() { | ||||
|  | ||||
|     return { | ||||
|         restrict: 'E', | ||||
|         replace: true, | ||||
|         scope: { | ||||
|  | ||||
|             /** | ||||
|              * The client group whose messages should be managed by this | ||||
|              * directive. | ||||
|              *  | ||||
|              * @type ManagedClientGroup | ||||
|              */ | ||||
|             clientGroup : '=' | ||||
|  | ||||
|         }, | ||||
|  | ||||
|         templateUrl: 'app/client/templates/guacMessageDialog.html', | ||||
|         controller: ['$scope', '$injector', function guacMessageDialogController($scope, $injector) { | ||||
|  | ||||
|             // Required types | ||||
|             const ManagedClient            = $injector.get('ManagedClient'); | ||||
|             const ManagedClientGroup       = $injector.get('ManagedClientGroup'); | ||||
|  | ||||
|             /** | ||||
|              * Removes all messages. | ||||
|              */ | ||||
|             $scope.clearAllMessages = function clearAllMessages() { | ||||
|                  | ||||
|                 // Nothing to clear if no client group attached | ||||
|                 if (!$scope.clientGroup) | ||||
|                     return; | ||||
|  | ||||
|                 // Remove each client's messages | ||||
|                 $scope.clientGroup.clients.forEach(client =>  { | ||||
|                     client.messages = []; | ||||
|                 }); | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * @borrows ManagedClientGroup.hasMultipleClients | ||||
|              */ | ||||
|             $scope.hasMultipleClients = ManagedClientGroup.hasMultipleClients; | ||||
|  | ||||
|             /** | ||||
|              * @borrows ManagedClient.hasMessages | ||||
|              */ | ||||
|             $scope.hasMessages = ManagedClient.hasMessages; | ||||
|  | ||||
|         }] | ||||
|  | ||||
|     }; | ||||
| }]); | ||||
| @@ -1,23 +0,0 @@ | ||||
| /* | ||||
|  * 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. | ||||
|  */ | ||||
|  | ||||
| p.client-message-text { | ||||
|     margin: 5px; | ||||
| } | ||||
|  | ||||
| @@ -1,122 +0,0 @@ | ||||
| /* | ||||
|  * 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. | ||||
|  */ | ||||
|  | ||||
| #message-dialog { | ||||
|  | ||||
|     position: absolute; | ||||
|     right: 0; | ||||
|     bottom: 0; | ||||
|     z-index: 20; | ||||
|  | ||||
|     font-size: 0.8em; | ||||
|  | ||||
|     width: 3in; | ||||
|     max-width: 100%; | ||||
|     max-height: 3in; | ||||
|      | ||||
|     background: white; | ||||
|     opacity: 0.75; | ||||
|  | ||||
| } | ||||
|  | ||||
| #message-dialog .message-dialog-box { | ||||
|  | ||||
|     /* IE10 */ | ||||
|     display: -ms-flexbox; | ||||
|     -ms-flex-align: stretch; | ||||
|     -ms-flex-direction: column; | ||||
|  | ||||
|     /* Ancient Mozilla */ | ||||
|     display: -moz-box; | ||||
|     -moz-box-align: stretch; | ||||
|     -moz-box-orient: vertical; | ||||
|  | ||||
|     /* Ancient WebKit */ | ||||
|     display: -webkit-box; | ||||
|     -webkit-box-align: stretch; | ||||
|     -webkit-box-orient: vertical; | ||||
|  | ||||
|     /* Old WebKit */ | ||||
|     display: -webkit-flex; | ||||
|     -webkit-align-items: stretch; | ||||
|     -webkit-flex-direction: column; | ||||
|  | ||||
|     /* W3C */ | ||||
|     display: flex; | ||||
|     align-items: stretch; | ||||
|     flex-direction: column; | ||||
|  | ||||
|     max-width: inherit; | ||||
|     max-height: inherit; | ||||
|  | ||||
|     border: 1px solid rgba(0, 0, 0, 0.5); | ||||
|     box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.25); | ||||
|  | ||||
| } | ||||
|  | ||||
| #message-dialog .message-dialog-box .header { | ||||
|     -ms-flex: 0 0 auto; | ||||
|     -moz-box-flex: 0; | ||||
|     -webkit-box-flex: 0; | ||||
|     -webkit-flex: 0 0 auto; | ||||
|     flex: 0 0 auto; | ||||
|     margin-bottom: 5px; | ||||
| } | ||||
|  | ||||
| #message-dialog .message-dialog-box .client-message-body { | ||||
|  | ||||
|     -ms-flex: 1 1 auto; | ||||
|     -moz-box-flex: 1; | ||||
|     -webkit-box-flex: 1; | ||||
|     -webkit-flex: 1 1 auto; | ||||
|     flex: 1 1 auto; | ||||
|  | ||||
|     overflow: auto; | ||||
|  | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Shrink maximum height if viewport is too small for default 3in dialog. | ||||
|  */ | ||||
| @media all and (max-height: 3in) { | ||||
|  | ||||
|     #message-dialog { | ||||
|         max-height: 1.5in; | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * If viewport is too small for even the 1.5in dialog, fit all available space. | ||||
|  */ | ||||
| @media all and (max-height: 1.5in) { | ||||
|  | ||||
|     #message-dialog { | ||||
|         height: 100%; | ||||
|     } | ||||
|  | ||||
|     #message-dialog .message-dialog-box { | ||||
|         position: absolute; | ||||
|         left:   0.5em; | ||||
|         top:    0.5em; | ||||
|         right:  0.5em; | ||||
|         bottom: 0.5em; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -110,10 +110,15 @@ | ||||
| } | ||||
|  | ||||
| .tiled-client-grid .client-tile .client-tile-header .client-tile-name { | ||||
|  | ||||
|     -webkit-box-flex: 1; | ||||
|     -webkit-flex: 1; | ||||
|     -ms-flex: 1; | ||||
|     flex: 1; | ||||
|  | ||||
|     padding: 0 0.5em; | ||||
|     margin-bottom: -0.125em; | ||||
|  | ||||
| } | ||||
|  | ||||
| .tiled-client-grid .client-tile .main { | ||||
| @@ -136,3 +141,132 @@ | ||||
| .tiled-client-grid .shared .client-tile-shared-indicator { | ||||
|     display: inline; | ||||
| } | ||||
|  | ||||
| .tiled-client-grid .client-user-count { | ||||
|  | ||||
|     visibility: hidden; | ||||
|  | ||||
|     display: block; | ||||
|     position: absolute; | ||||
|     right: 0; | ||||
|     top: 0; | ||||
|     z-index: 1; | ||||
|  | ||||
|     border-radius: 0.25em; | ||||
|     padding: 0.125em 0.75em; | ||||
|     margin: 0.5em; | ||||
|  | ||||
|     background: #055; | ||||
|     color: white; | ||||
|     font-weight: bold; | ||||
|     font-size: 0.8em; | ||||
|  | ||||
| } | ||||
|  | ||||
| .tiled-client-grid .client-user-count::before { | ||||
|  | ||||
|     content: ' '; | ||||
|     display: inline-block; | ||||
|  | ||||
|     margin-bottom: -0.2em; | ||||
|     padding-right: 0.25em; | ||||
|     width: 1em; | ||||
|     height: 1em; | ||||
|  | ||||
|     background: center / contain no-repeat url('images/user-icons/guac-user-white.svg'); | ||||
|     background-size: contain; | ||||
|     background-position: center; | ||||
|     background-repeat: no-repeat; | ||||
|  | ||||
| } | ||||
|  | ||||
| .tiled-client-grid .client-user-count .client-user-count-users, | ||||
| .tiled-client-grid .client-user-count .client-user-count-messages { | ||||
|  | ||||
|     position: absolute; | ||||
|     right: 0; | ||||
|  | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
|     margin-top: 0.5em; | ||||
|     list-style: none; | ||||
|  | ||||
| } | ||||
|  | ||||
| .tiled-client-grid .client-user-count .client-user-count-users, | ||||
| .tiled-client-grid .client-user-count .client-user-count-message { | ||||
|     border-radius: 0.25em; | ||||
|     background: black; | ||||
|     color: white; | ||||
|     padding: 0.5em; | ||||
| } | ||||
|  | ||||
| .tiled-client-grid .client-user-count .client-user-count-message { | ||||
|     white-space: nowrap; | ||||
|     animation: 1s linear 3s fadeout; | ||||
| } | ||||
|  | ||||
| .tiled-client-grid .client-tile-header .client-user-count { | ||||
|     display: inline-block; | ||||
|     position: relative; | ||||
|     white-space: nowrap; | ||||
|     background: black; | ||||
|     padding-left: 0.5em; | ||||
|     padding-right: 0.75em; | ||||
| } | ||||
|  | ||||
| .tiled-client-grid .client-tile-header .client-user-count::before { | ||||
|     padding-right: 0.75em; | ||||
| } | ||||
|  | ||||
| .tiled-client-grid .joined .client-user-count { | ||||
|     visibility: visible; | ||||
| } | ||||
|  | ||||
| .tiled-client-grid .client-user-count .client-user-count-users { | ||||
|     display: none; | ||||
| } | ||||
|  | ||||
| .tiled-client-grid .client-user-count:hover .client-user-count-users { | ||||
|     display: block; | ||||
| } | ||||
|  | ||||
| .tiled-client-grid .client-user-count .client-user-count-user::after { | ||||
|     content: ', '; | ||||
|     margin-right: 0.25em; | ||||
| } | ||||
|  | ||||
| .tiled-client-grid .client-user-count .client-user-count-user:last-child::after { | ||||
|     content: none; | ||||
| } | ||||
|  | ||||
| .tiled-client-grid .client-user-count .client-user-count-user { | ||||
|     display: inline-block; | ||||
| } | ||||
|  | ||||
| .tiled-client-grid .client-user-count .client-user-count-users { | ||||
|     width: 256px; | ||||
|     max-width: 75vw; | ||||
|     white-space: normal; | ||||
|     border: 1px solid #333; | ||||
| } | ||||
|  | ||||
| .tiled-client-grid .client-user-count .client-user-count-users::before { | ||||
|  | ||||
|     content: ' '; | ||||
|     display: block; | ||||
|  | ||||
|     position: absolute; | ||||
|     right: 0.5em; | ||||
|     top: -0.5em; | ||||
|  | ||||
|     width: 1em; | ||||
|     height: 1em; | ||||
|  | ||||
|     background: black; | ||||
|     border: 1px solid #333; | ||||
|     border-right: none; | ||||
|     border-bottom: none; | ||||
|     transform: rotate(45deg); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -45,11 +45,6 @@ | ||||
|         {{'CLIENT.TEXT_CLIENT_STATUS_UNSTABLE' | translate}} | ||||
|     </div> | ||||
|      | ||||
|     <!-- Message dialog --> | ||||
|     <div id="message-dialog" ng-show="hasMessages()"> | ||||
|         <guac-message-dialog client-group="clientGroup"></guac-message-dialog> | ||||
|     </div> | ||||
|  | ||||
|     <!-- Menu --> | ||||
|     <div class="menu" ng-class="{open: menu.shown}" id="guac-menu"> | ||||
|         <div class="menu-content" ng-if="menu.shown" guac-touch-drag="menuDrag"> | ||||
|   | ||||
| @@ -1,8 +0,0 @@ | ||||
| <div class="client-message" ng-click="clear()"> | ||||
|  | ||||
|     <!-- Message text --> | ||||
|     <p class="client-message-text" | ||||
|        translate="{{ getMessageKey() }}" | ||||
|        translate-values="{{ getMessageArgs() }}"></p> | ||||
|  | ||||
| </div> | ||||
| @@ -0,0 +1,9 @@ | ||||
| <div class="client-user-count" title="{{ instance }}"> | ||||
|     <span class="client-user-count-value">{{ client.userCount }}</span> | ||||
|     <ul class="client-user-count-messages"></ul> | ||||
|     <ul class="client-user-count-users"> | ||||
|         <li class="client-user-count-user" ng-repeat="user in userCounts | toArray | orderBy: key" | ||||
|             translate="CLIENT.INFO_USER_COUNT" | ||||
|             translate-values="{ USERNAME : user.key, COUNT : user.value }"></li> | ||||
|     </ul> | ||||
| </div> | ||||
| @@ -1,21 +0,0 @@ | ||||
| <div class="message-dialog-box"> | ||||
|      | ||||
|     <!-- Message dialog header --> | ||||
|     <div class="header"> | ||||
|         <h2>{{'CLIENT.SECTION_HEADER_CLIENT_MESSAGES' | translate}}</h2> | ||||
|         <button ng-click="clearAllMessages()">{{'CLIENT.ACTION_CLEAR_CLIENT_MESSAGES' | translate}}</button> | ||||
|     </div> | ||||
|  | ||||
|     <!-- Received messages --> | ||||
|     <div class="client-messages-body"> | ||||
|         <div class="client-messages-body-section" ng-repeat="client in clientGroup.clients" ng-show="hasMessages(client)"> | ||||
|             <h3 ng-show="hasMultipleClients(clientGroup)">{{ client.name }}</h3> | ||||
|             <div class="messages"> | ||||
|                 <guac-client-message | ||||
|                     message="message" | ||||
|                     ng-repeat="message in client.messages"> | ||||
|                 </guac-client-message> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -5,12 +5,14 @@ | ||||
|             <div class="client-tile" ng-if="client" | ||||
|                 ng-class="{ | ||||
|                     'focused' : client.clientProperties.focused, | ||||
|                     'shared'  : isShared(client) | ||||
|                     'shared'  : isShared(client), | ||||
|                     'joined'  : client.userCount | ||||
|                 }" | ||||
|                 guac-click="getFocusAssignmentCallback(client)"> | ||||
|                 <h3 class="client-tile-header" ng-if="hasMultipleClients(clientGroup)"> | ||||
|                     <img class="client-tile-shared-indicator" src="images/share-white.svg"> | ||||
|                     <span class="client-tile-name">{{ client.title }}</span> | ||||
|                     <guac-client-user-count client="client"></guac-client-user-count> | ||||
|                     <img ng-click="onClose({ '$client' : client })" | ||||
|                          class="client-tile-disconnect" | ||||
|                          ng-attr-alt="{{ 'CLIENT.ACTION_DISCONNECT' | translate }}" | ||||
| @@ -21,6 +23,9 @@ | ||||
|  | ||||
|                 <!-- Client-specific status/error dialog --> | ||||
|                 <guac-client-notification client="client"></guac-client-notification> | ||||
|  | ||||
|                 <guac-client-user-count client="client" ng-if="!hasMultipleClients(clientGroup)"></guac-client-user-count> | ||||
|  | ||||
|             </div> | ||||
|  | ||||
|         </div> | ||||
|   | ||||
| @@ -17,6 +17,8 @@ | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| /* global Guacamole, _ */ | ||||
|  | ||||
| /** | ||||
|  * Provides the ManagedClient class used by the guacClientManager service. | ||||
|  */ | ||||
| @@ -28,7 +30,6 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', | ||||
|     const ClientIdentifier       = $injector.get('ClientIdentifier'); | ||||
|     const ClipboardData          = $injector.get('ClipboardData'); | ||||
|     const ManagedArgument        = $injector.get('ManagedArgument'); | ||||
|     const ManagedClientMessage   = $injector.get('ManagedClientMessage'); | ||||
|     const ManagedClientState     = $injector.get('ManagedClientState'); | ||||
|     const ManagedClientThumbnail = $injector.get('ManagedClientThumbnail'); | ||||
|     const ManagedDisplay         = $injector.get('ManagedDisplay'); | ||||
| @@ -176,12 +177,23 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', | ||||
|         this.filesystems = template.filesystems || []; | ||||
|  | ||||
|         /** | ||||
|          * All messages that have been sent to the client that should be | ||||
|          * displayed. | ||||
|          *  | ||||
|          * @type ManagedClientMessage[] | ||||
|          * The current number of users sharing this connection, excluding the | ||||
|          * user that originally started the connection. Duplicate connections | ||||
|          * from the same user are included in this total. | ||||
|          */ | ||||
|         this.messages = template.messages || []; | ||||
|         this.userCount = template.userCount || 0; | ||||
|  | ||||
|         /** | ||||
|          * All users currently sharing this connection, excluding the user that | ||||
|          * originally started the connection. If the connection is not shared, | ||||
|          * this object will be empty. This map consists of key/value pairs | ||||
|          * where each key is the user's username and each value is an object | ||||
|          * tracking the unique connections currently used by that user (a map | ||||
|          * of Guacamole protocol user IDs to boolean values). | ||||
|          * | ||||
|          * @type Object.<string, Object.<string, boolean>> | ||||
|          */ | ||||
|         this.users = template.users || {}; | ||||
|  | ||||
|         /** | ||||
|          * All available share links generated for the this ManagedClient via | ||||
| @@ -496,17 +508,34 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         // Handle messages received from guacd to display to the client. | ||||
|         client.onmsg = function clientMessage(msgcode, args) { | ||||
|         // Update user count when a new user joins | ||||
|         client.onjoin = function userJoined(id, username) { | ||||
|             $rootScope.$apply(function usersChanged() { | ||||
|  | ||||
|             msg = new ManagedClientMessage(); | ||||
|             msg.msgcode = msgcode; | ||||
|             msg.args = args; | ||||
|                 var connections = managedClient.users[username] || {}; | ||||
|                 managedClient.users[username] = connections; | ||||
|  | ||||
|                 managedClient.userCount++; | ||||
|                 connections[id] = true; | ||||
|  | ||||
|             $rootScope.$apply(function updateMessages() { | ||||
|                 managedClient.messages.push(msg); | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         // Update user count when a user leaves | ||||
|         client.onleave = function userLeft(id, username) { | ||||
|             $rootScope.$apply(function usersChanged() { | ||||
|  | ||||
|                 var connections = managedClient.users[username] || {}; | ||||
|                 managedClient.users[username] = connections; | ||||
|  | ||||
|                 managedClient.userCount--; | ||||
|                 delete connections[id]; | ||||
|  | ||||
|                 // Delete user entry after no connections remain | ||||
|                 if (_.isEmpty(connections)) | ||||
|                     delete managedClient.users[username]; | ||||
|  | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         // Automatically update the client thumbnail | ||||
| @@ -915,19 +944,6 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', | ||||
|  | ||||
|     }; | ||||
|      | ||||
|     /** | ||||
|      * Returns whether the given client has any associated messages to display. | ||||
|      *  | ||||
|      * @param {GuacamoleClient} client | ||||
|      *     The client for which messages should be checked. | ||||
|      *      | ||||
|      * @returns {Boolean} | ||||
|      *     true if the given client has any messages, otherwise false. | ||||
|      */ | ||||
|     ManagedClient.hasMessages = function hasMessages(client) { | ||||
|         return !!(client && client.messages && client.messages.length); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Returns whether the given client has any associated file transfers, | ||||
|      * regardless of those file transfers' state. | ||||
|   | ||||
| @@ -1,59 +0,0 @@ | ||||
| /* | ||||
|  * 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 ManagedClientMessage class used for messages displayed in | ||||
|  * a ManagedClient. | ||||
|  */ | ||||
| angular.module('client').factory('ManagedClientMessage', [function defineManagedClientMessage() { | ||||
|  | ||||
|     /** | ||||
|      * Object which represents a message to be displayed to a Guacamole client. | ||||
|      * | ||||
|      * @constructor | ||||
|      * @param {ManagedClientMessage|Object} [template={}] | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     ManagedClientMessage. | ||||
|      */ | ||||
|     var ManagedClientMessage = function ManagedClientMessage(template) { | ||||
|  | ||||
|         // Use empty object by default | ||||
|         template = template || {}; | ||||
|  | ||||
|         /** | ||||
|          * The message code sent by the server that will be used to locate the | ||||
|          * message within the Guacamole translation framework. | ||||
|          *  | ||||
|          * @type Number | ||||
|          */ | ||||
|         this.msgcode = template.msgcode; | ||||
|          | ||||
|         /** | ||||
|          * Any arguments that should be passed through the translation system | ||||
|          * and displayed as part of the message. | ||||
|          *  | ||||
|          * @type String[] | ||||
|          */ | ||||
|         this.args = template.args; | ||||
|          | ||||
|     }; | ||||
|  | ||||
|     return ManagedClientMessage; | ||||
|  | ||||
| }]); | ||||
| @@ -0,0 +1 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path d="M474.341 504.195c.005 31.957-.398 24.504-.398 58.628H208.914c0-52.862-.398-8.867-.398-58.628 0-47.522 67.134-73.133 131.412-73.133 64.277 0 134.405 18.656 134.413 73.133z" style="fill:#fff;fill-opacity:1;stroke:#000;stroke-width:.09023736;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0" transform="translate(-33.342 -44.364) scale(.19138)"/><path d="M48.002 28.63a16.506 16.506 0 1 1-33.012 0 16.506 16.506 0 1 1 33.012 0z" style="fill:#fff;fill-rule:evenodd;stroke:none" transform="translate(.504 -8)"/></svg> | ||||
| After Width: | Height: | Size: 635 B | 
| @@ -128,10 +128,7 @@ | ||||
|  | ||||
|         "INFO_CONNECTION_SHARED" : "This connection is now shared.", | ||||
|         "INFO_NO_FILE_TRANSFERS" : "No file transfers.", | ||||
|  | ||||
|         "MESSAGE_DEFAULT"      : "", | ||||
|         "MESSAGE_USER_JOINED"  : "User {ARGS_1} has joined the connection.", | ||||
|         "MESSAGE_USER_LEFT"    : "User {ARGS_1} has left the connection.", | ||||
|         "INFO_USER_COUNT"        : "{USERNAME}{COUNT, plural, one{} other{ (#)}}", | ||||
|  | ||||
|         "NAME_INPUT_METHOD_NONE"   : "None", | ||||
|         "NAME_INPUT_METHOD_OSK"    : "On-screen keyboard", | ||||
| @@ -158,6 +155,8 @@ | ||||
|         "TEXT_CLIENT_STATUS_DISCONNECTED" : "You have been disconnected.", | ||||
|         "TEXT_CLIENT_STATUS_UNSTABLE"     : "The network connection to the Guacamole server appears unstable.", | ||||
|         "TEXT_CLIENT_STATUS_WAITING"      : "Connected to Guacamole. Waiting for response...", | ||||
|         "TEXT_USER_JOINED"                : "{USERNAME} has joined the connection.", | ||||
|         "TEXT_USER_LEFT"                  : "{USERNAME} has left the connection.", | ||||
|         "TEXT_RECONNECT_COUNTDOWN"        : "Reconnecting in {REMAINING} {REMAINING, plural, one{second} other{seconds}}...", | ||||
|         "TEXT_FILE_TRANSFER_PROGRESS"     : "{PROGRESS} {UNIT, select, b{B} kb{KB} mb{MB} gb{GB} other{}}", | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user