mirror of
				https://github.com/gyurix1968/guacamole-client.git
				synced 2025-10-31 00:53:21 +00:00 
			
		
		
		
	GUAC-928 Added file transfer support.
This commit is contained in:
		| @@ -68,6 +68,19 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', | ||||
|         0x031D: true | ||||
|     }; | ||||
|      | ||||
|     /** | ||||
|      * The reconnect action to be provided along with the object sent to | ||||
|      * showStatus. | ||||
|      */ | ||||
|     var RECONNECT_ACTION = { | ||||
|         name        : "client.action.reconnect", | ||||
|         // Handle reconnect action | ||||
|         callback    : function reconnectCallback() { | ||||
|             $scope.id = uniqueId; | ||||
|             $scope.showStatus(false); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     // Get DAO for reading connections and groups | ||||
|     var connectionGroupDAO = $injector.get('connectionGroupDAO'); | ||||
|     var connectionDAO      = $injector.get('connectionDAO'); | ||||
| @@ -215,7 +228,7 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', | ||||
|             className: "error", | ||||
|             title: "client.error.connectionErrorTitle", | ||||
|             text: "client.error.clientErrors." + errorName, | ||||
|             actions: [ "client.action.reconnect" ] | ||||
|             actions: [ RECONNECT_ACTION ] | ||||
|         }); | ||||
|  | ||||
|     }); | ||||
| @@ -250,19 +263,11 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', | ||||
|             className: "error", | ||||
|             title: "client.error.connectionErrorTitle", | ||||
|             text: "client.error.tunnelErrors." + errorName, | ||||
|             actions: [ "client.action.reconnect" ] | ||||
|             actions: [ RECONNECT_ACTION ] | ||||
|         }); | ||||
|  | ||||
|     }); | ||||
|  | ||||
|     // Handle reconnect action | ||||
|     $scope.$on('guacAction', function actionListener(event, action) { | ||||
|  | ||||
|         if (action === "client.action.reconnect") | ||||
|             $scope.id = uniqueId; | ||||
|  | ||||
|     }); | ||||
|  | ||||
|     $scope.formattedScale = function formattedScale() { | ||||
|         return Math.round($scope.clientProperties.scale * 100); | ||||
|     }; | ||||
| @@ -293,4 +298,65 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', | ||||
|         return $scope.clientProperties.minZoom >= 1; | ||||
|     }; | ||||
|      | ||||
|     // Mapping of stream index to notification object | ||||
|     var downloadNotifications = {}; | ||||
|      | ||||
|     // Mapping of stream index to notification ID | ||||
|     var downloadNotificationIDs = {}; | ||||
|      | ||||
|     $scope.$on('guacClientFileStart', function handleClientFileStart(event, guacClient, streamIndex, mimetype, filename) { | ||||
|         $scope.safeApply(function() { | ||||
|              | ||||
|             var notification = { | ||||
|                 className  : 'download', | ||||
|                 title      : 'client.fileTransfer.title', | ||||
|                 text       : filename | ||||
|             }; | ||||
|              | ||||
|             downloadNotifications[streamIndex]   = notification; | ||||
|             downloadNotificationIDs[streamIndex] = $scope.addNotification(notification); | ||||
|              | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     $scope.$on('guacClientFileProgress', function handleClientFileProgress(event, guacClient, streamIndex, mimetype, filename, length) { | ||||
|         $scope.safeApply(function() { | ||||
|              | ||||
|             var notification = downloadNotifications[streamIndex]; | ||||
|             if (notification) | ||||
|                 notification.progress = length; | ||||
|              | ||||
|         }); | ||||
|     }); | ||||
|      | ||||
|     $scope.$on('guacClientFileEnd', function handleClientFileEnd(event, guacClient, streamIndex, mimetype, filename, blob) { | ||||
|         $scope.safeApply(function() { | ||||
|  | ||||
|             var notification = downloadNotifications[streamIndex]; | ||||
|             var notificationID = downloadNotificationIDs[streamIndex]; | ||||
|              | ||||
|             /** | ||||
|              * Saves the current file. | ||||
|              */ | ||||
|             var saveFile = function saveFile() { | ||||
|                 saveAs(blob, filename); | ||||
|                 $scope.removeNotification(notificationID); | ||||
|                 delete downloadNotifications[streamIndex]; | ||||
|                 delete downloadNotificationIDs[streamIndex]; | ||||
|             }; | ||||
|              | ||||
|             // Add download action and remove progress indicator | ||||
|             if (notificationID && notification) { | ||||
|                 delete notification.progress; | ||||
|                 notification.actions = [ | ||||
|                     { | ||||
|                         name       : 'client.fileTransfer.save', | ||||
|                         callback   : saveFile | ||||
|                     } | ||||
|                 ]; | ||||
|             } | ||||
|  | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
| }]); | ||||
|   | ||||
| @@ -147,13 +147,13 @@ angular.module('client').factory('guacClientFactory', ['$rootScope', | ||||
|  | ||||
|                     // Update progress as data is received | ||||
|                     blob_reader.onprogress = function onprogress() { | ||||
|                         $scope.$emit('guacClientFileProgress', guacClient, stream.index, mimetype, filename); | ||||
|                         $scope.$emit('guacClientFileProgress', guacClient, stream.index, mimetype, filename, blob_reader.getLength()); | ||||
|                         stream.sendAck("Received", Guacamole.Status.Code.SUCCESS); | ||||
|                     }; | ||||
|  | ||||
|                     // When complete, prompt for download | ||||
|                     blob_reader.onend = function onend() { | ||||
|                         $scope.$emit('guacClientFileEnd', guacClient, stream.index, mimetype, filename); | ||||
|                         $scope.$emit('guacClientFileEnd', guacClient, stream.index, mimetype, filename, blob_reader.getBlob()); | ||||
|                     }; | ||||
|  | ||||
|                     stream.sendAck("Ready", Guacamole.Status.Code.SUCCESS); | ||||
|   | ||||
| @@ -33,56 +33,21 @@ | ||||
|     font-size: 0.7em; | ||||
|     text-align: center; | ||||
|      | ||||
|     border: 1px solid rgba(0, 0, 0, 0.75); | ||||
|     -moz-border-radius: 0.2em; | ||||
|     -webkit-border-radius: 0.2em; | ||||
|     -khtml-border-radius: 0.2em; | ||||
|     border-radius: 0.2em; | ||||
|     border: 1px solid rgba(0, 0, 0, 0.125); | ||||
|     box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.125); | ||||
|     background: white; | ||||
|  | ||||
|     color: black; | ||||
|  | ||||
|     padding: 0.5em; | ||||
|     margin: 1em; | ||||
|     width: 2in; | ||||
|     max-width: 75%; | ||||
|     overflow: hidden; | ||||
|  | ||||
|     box-shadow: 0.1em 0.1em 0.2em rgba(0, 0, 0, 0.25); | ||||
|   | ||||
| } | ||||
|  | ||||
| .notification div { | ||||
|     display: inline-block; | ||||
|     text-align: left; | ||||
| } | ||||
|  | ||||
| .notification .title-bar { | ||||
|     display: block; | ||||
|     white-space: nowrap; | ||||
|     font-weight: bold; | ||||
|  | ||||
|     border-bottom: 1px solid black; | ||||
|     padding-bottom: 0.5em; | ||||
|     margin-bottom: 0.5em; | ||||
| } | ||||
|  | ||||
| .notification .title-bar * { | ||||
|     vertical-align: middle; | ||||
| } | ||||
|  | ||||
| .notification .close { | ||||
|  | ||||
|     background: url('images/action-icons/guac-close.png'); | ||||
|     background-size: 10px 10px; | ||||
|     -moz-background-size: 10px 10px; | ||||
|     -webkit-background-size: 10px 10px; | ||||
|     -khtml-background-size: 10px 10px; | ||||
|  | ||||
|     width: 10px; | ||||
|     height: 10px; | ||||
|  | ||||
|     float: right; | ||||
|     cursor: pointer; | ||||
|  | ||||
| .notification .buttons { | ||||
|     margin: 0; | ||||
| } | ||||
|  | ||||
| @keyframes notification-progress { | ||||
| @@ -95,43 +60,25 @@ | ||||
|     to   {background-position: 64px 0px;} | ||||
| } | ||||
|  | ||||
| .notification .caption, | ||||
| .notification.download .caption { | ||||
| .notification .title-bar { | ||||
|     font-size: 1.25em; | ||||
|     font-weight: bold; | ||||
|  | ||||
|     text-transform: uppercase; | ||||
|     border-bottom: 1px solid rgba(0, 0, 0, 0.125); | ||||
|     box-shadow: 0 1px 2px rgba(0, 0, 0, 0.125); | ||||
|     background: rgba(0, 0, 0, 0.04); | ||||
|  | ||||
|     margin-bottom: 1em; | ||||
| } | ||||
|  | ||||
| .notification .text { | ||||
|     width: 100%; | ||||
|     white-space: nowrap; | ||||
|     overflow: hidden; | ||||
|     text-overflow: ellipsis; | ||||
| } | ||||
|  | ||||
| .notification.upload .status, | ||||
| .notification.download .status { | ||||
|     color: red; | ||||
|     font-size: 1em; | ||||
|     padding: 1em; | ||||
| } | ||||
|  | ||||
| .notification.download .progress, | ||||
| .notification.upload .progress, | ||||
| .notification.download .download { | ||||
|  | ||||
|     margin-top: 1em; | ||||
|     margin-left: 0.75em; | ||||
|     padding: 0.25em; | ||||
|     min-width: 5em; | ||||
|      | ||||
|     border: 1px solid gray; | ||||
|     -moz-border-radius: 0.2em; | ||||
|     -webkit-border-radius: 0.2em; | ||||
|     -khtml-border-radius: 0.2em; | ||||
|     border-radius: 0.2em; | ||||
|  | ||||
|     text-align: center; | ||||
|     float: right; | ||||
|  | ||||
|     position: relative; | ||||
|      | ||||
| } | ||||
|  | ||||
| .notification.upload .progress { | ||||
|     float: none; | ||||
|     width: 80%; | ||||
| @@ -160,6 +107,7 @@ | ||||
| .notification.upload .progress, | ||||
| .notification.download .progress { | ||||
|  | ||||
|     width: 100%; | ||||
|     background: #C2C2C2 url('images/progress.png'); | ||||
|     background-size: 16px 16px; | ||||
|     -moz-background-size: 16px 16px; | ||||
| @@ -176,6 +124,20 @@ | ||||
|     -webkit-animation-timing-function: linear; | ||||
|     -webkit-animation-iteration-count: infinite; | ||||
|  | ||||
|     padding: 0.25em; | ||||
|     min-width: 5em; | ||||
|      | ||||
|     border: 1px solid gray; | ||||
|     -moz-border-radius: 0.2em; | ||||
|     -webkit-border-radius: 0.2em; | ||||
|     -khtml-border-radius: 0.2em; | ||||
|     border-radius: 0.2em; | ||||
|  | ||||
|     text-align: center; | ||||
|     float: right; | ||||
|  | ||||
|     position: relative; | ||||
|      | ||||
| } | ||||
|  | ||||
| .notification.download .download { | ||||
|   | ||||
| @@ -59,6 +59,8 @@ angular.module('index').controller('indexController', ['$scope', '$injector', | ||||
|     $scope.currentUserIsAdmin = false; | ||||
|     $scope.currentUserHasUpdate = false; | ||||
|     $scope.currentUserPermissions = null; | ||||
|     $scope.notifications = []; | ||||
|     var notificationUniqueID = 0; | ||||
|      | ||||
|     // A promise to be fulfilled when all basic user permissions are loaded. | ||||
|     var permissionsLoaded= $q.defer(); | ||||
| @@ -71,23 +73,36 @@ angular.module('index').controller('indexController', ['$scope', '$injector', | ||||
|         $location.path('/login'); | ||||
|  | ||||
|     /** | ||||
|      * Shows or hides the status modal. If a status modal is currently shown, | ||||
|      * no further status modals will be shown until the current status is | ||||
|      * Shows or hides the status. If a status is currently shown, | ||||
|      * no further statuses will be shown until the current status is | ||||
|      * hidden. | ||||
|      * | ||||
|      * @param {Boolean|Object} status The status to show, or false to hide the | ||||
|      *                                current status. | ||||
|      * @param {String} [status.title] The title of the status modal. | ||||
|      * @param {String} [status.text] The body text of the status modal. | ||||
|      * @param {String} [status.className] The CSS class name to apply to the | ||||
|      *                                    modal, in addition to the default | ||||
|      *                                    "dialog" and "status" classes. | ||||
|      * @param {String[]} [status.actions] Array of action names which | ||||
|      *                                    correspond to button captions. Each | ||||
|      *                                    action will be displayed as a button | ||||
|      *                                    within the status modal. Clickin a | ||||
|      *                                    button will fire a guacStatusAction | ||||
|      *                                    event. | ||||
|      * @param {String} [status.title] The title of the status. | ||||
|      * @param {String} [status.text] The body text of the status. | ||||
|      * @param {String} [status.className] The CSS class name to apply. | ||||
|      * @param {Object[]} [status.actions] Array of action objects which | ||||
|      *                                    contain an action name and callback to | ||||
|      *                                    be executed when that action is | ||||
|      *                                    invoked.  | ||||
|      *                                     | ||||
|      * @example | ||||
|      *  | ||||
|      * // To show a status message with actions | ||||
|      * $scope.showStatus({ | ||||
|      *     'title'      : 'Disconnected', | ||||
|      *     'text'       : 'You have been disconnected!', | ||||
|      *     'actions'    : { | ||||
|      *         'name'       : 'reconnect', | ||||
|      *         'callback'   : function () { | ||||
|      *             // Reconnection code goes here | ||||
|      *         } | ||||
|      *     } | ||||
|      * }); | ||||
|      *  | ||||
|      * // To hide the status message | ||||
|      * $scope.showStatus(false); | ||||
|      */ | ||||
|     $scope.showStatus = function showStatus(status) { | ||||
|         if (!$scope.status || !status) | ||||
| @@ -95,19 +110,56 @@ angular.module('index').controller('indexController', ['$scope', '$injector', | ||||
|     }; | ||||
|      | ||||
|     /** | ||||
|      * Fires a guacStatusAction event signalling a chosen action. The status | ||||
|      * modal will be cloased prior to firing the action event. | ||||
|      * Adds a notification to the the list of notifications shown. | ||||
|      *  | ||||
|      * @param {String} action The name of the action. | ||||
|      * @param {Object} notification The notification to add. | ||||
|      * @param {String} [notification.title] The title of the notification. | ||||
|      * @param {String} [notification.text] The body text of the status modal. | ||||
|      * @param {String} [notification.className] The CSS class name to apply. | ||||
|      * @param {Object[]} [notification.actions] Array of action objects which | ||||
|      *                                    contain an action name and callback to | ||||
|      *                                    be executed when that action is | ||||
|      *                                    invoked.  | ||||
|      * @returns {Number} A unique ID for the notification that's just been added. | ||||
|      *  | ||||
|      *                         | ||||
|      * @example | ||||
|      *  | ||||
|      * var id = $scope.addNotification({ | ||||
|      *     'title'      : 'Download', | ||||
|      *     'text'       : 'You have a file ready for download!', | ||||
|      *     'actions'    : { | ||||
|      *         'name'       : 'download', | ||||
|      *         'callback'   : function () { | ||||
|      *             // download the file and remove the notification here | ||||
|      *         } | ||||
|      *     } | ||||
|      * }); | ||||
|      */ | ||||
|     $scope.fireAction = function fireAction(action) { | ||||
|     $scope.addNotification = function addNotification(notification) { | ||||
|         var id = ++notificationUniqueID; | ||||
|          | ||||
|         // Hide status modal | ||||
|         $scope.status = false; | ||||
|         $scope.notifications.push({ | ||||
|             notification    : notification, | ||||
|             id              : id | ||||
|         }); | ||||
|          | ||||
|         // Fire action event | ||||
|         $scope.$broadcast('guacAction', action); | ||||
|         return id; | ||||
|     }; | ||||
|      | ||||
|     /** | ||||
|      * Remove a notification by unique ID. | ||||
|      *  | ||||
|      * @param {type} id The unique ID of the notification to remove. This ID is | ||||
|      *                  retrieved from the initial call to addNotification. | ||||
|      */ | ||||
|     $scope.removeNotification = function removeNotification(id) { | ||||
|         for(var i = 0; i < $scope.notifications.length; i++) { | ||||
|             if($scope.notifications[i].id === id) { | ||||
|                 $scope.notifications.splice(i, 1); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|             | ||||
|     // Allow the permissions to be reloaded elsewhere if needed | ||||
|   | ||||
| @@ -47,7 +47,7 @@ THE SOFTWARE. | ||||
|                  | ||||
|                     <!-- All action buttons, if any --> | ||||
|                     <div ng-show="status.actions && status.actions.length" class="buttons"> | ||||
|                         <button ng-repeat="action in status.actions" ng-click="fireAction(action)">{{action | translate}}</button> | ||||
|                         <button ng-repeat="action in status.actions" ng-click="action.callback()">{{action.name | translate}}</button> | ||||
|                     </div> | ||||
|                  | ||||
|                 </div> | ||||
| @@ -57,6 +57,28 @@ THE SOFTWARE. | ||||
|         <div id="content" ng-view> | ||||
|         </div> | ||||
|          | ||||
|         <!-- Notification area --> | ||||
|         <div id="notificationArea"> | ||||
|             <div ng-repeat="wrapper in notifications" ng-class="wrapper.notification.className" class="notification"> | ||||
|                  | ||||
|                 <!-- Notification title --> | ||||
|                 <div ng-show="wrapper.notification.title" class="title-bar"> | ||||
|                     <div class="title">{{wrapper.notification.title | translate}}</div> | ||||
|                 </div> | ||||
|                  | ||||
|                 <!-- Notification text --> | ||||
|                 <p ng-show="wrapper.notification.text" class="text">{{wrapper.notification.text}}</p> | ||||
|                  | ||||
|                 <div ng-show="wrapper.notification.progress" class="progress"> | ||||
|                     {{wrapper.notification.progress}} | ||||
|                 </div> | ||||
|                  | ||||
|                 <div ng-show="wrapper.notification.actions && wrapper.notification.actions.length" class="buttons"> | ||||
|                     <button ng-repeat="action in wrapper.notification.actions" ng-click="action.callback()">{{action.name | translate}}</button> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|          | ||||
|         <script type="text/javascript" src="guacamole.min.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
|   | ||||
| @@ -268,6 +268,10 @@ | ||||
|         }, | ||||
|         "action" : { | ||||
|             "reconnect" : "Reconnect" | ||||
|         }, | ||||
|         "fileTransfer" : { | ||||
|             "title"     : "File Transfer", | ||||
|             "save"      : "Save" | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user