mirror of
				https://github.com/gyurix1968/guacamole-client.git
				synced 2025-10-30 00:23:21 +00:00 
			
		
		
		
	GUAC-1053: Create session management tab.
This commit is contained in:
		| @@ -1,349 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright (C) 2015 Glyptodon LLC |  | ||||||
|  * |  | ||||||
|  * 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. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * The controller for the user session administration page. |  | ||||||
|  */ |  | ||||||
| angular.module('manage').controller('manageSessionsController', ['$scope', '$injector',  |  | ||||||
|         function manageSessionsController($scope, $injector) { |  | ||||||
|  |  | ||||||
|     // Required types |  | ||||||
|     var ActiveConnectionWrapper = $injector.get('ActiveConnectionWrapper'); |  | ||||||
|     var ConnectionGroup         = $injector.get('ConnectionGroup'); |  | ||||||
|     var SortOrder               = $injector.get('SortOrder'); |  | ||||||
|  |  | ||||||
|     // Required services |  | ||||||
|     var $filter                 = $injector.get('$filter'); |  | ||||||
|     var $translate              = $injector.get('$translate'); |  | ||||||
|     var activeConnectionService = $injector.get('activeConnectionService'); |  | ||||||
|     var authenticationService   = $injector.get('authenticationService'); |  | ||||||
|     var connectionGroupService  = $injector.get('connectionGroupService'); |  | ||||||
|     var guacNotification        = $injector.get('guacNotification'); |  | ||||||
|     var permissionService       = $injector.get('permissionService'); |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * All permissions associated with the current user, or null if the user's |  | ||||||
|      * permissions have not yet been loaded. |  | ||||||
|      * |  | ||||||
|      * @type PermissionSet |  | ||||||
|      */ |  | ||||||
|     $scope.permissions = null; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * The ActiveConnectionWrappers of all active sessions accessible by the |  | ||||||
|      * current user, or null if the active sessions have not yet been loaded. |  | ||||||
|      * |  | ||||||
|      * @type ActiveConnectionWrapper[] |  | ||||||
|      */ |  | ||||||
|     $scope.wrappers = null; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * SortOrder instance which maintains the sort order of the visible |  | ||||||
|      * connection wrappers. |  | ||||||
|      * |  | ||||||
|      * @type SortOrder |  | ||||||
|      */ |  | ||||||
|     $scope.wrapperOrder = new SortOrder([ |  | ||||||
|         'activeConnection.username', |  | ||||||
|         'startDate', |  | ||||||
|         'activeConnection.remoteHost', |  | ||||||
|         'name' |  | ||||||
|     ]); |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Array of all wrapper properties that are filterable. |  | ||||||
|      * |  | ||||||
|      * @type String[] |  | ||||||
|      */ |  | ||||||
|     $scope.filteredWrapperProperties = [ |  | ||||||
|         'activeConnection.username', |  | ||||||
|         'startDate', |  | ||||||
|         'activeConnection.remoteHost', |  | ||||||
|         'name' |  | ||||||
|     ]; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * All active connections, if known, or null if active connections have not |  | ||||||
|      * yet been loaded. |  | ||||||
|      * |  | ||||||
|      * @type ActiveConnection |  | ||||||
|      */ |  | ||||||
|     var activeConnections = null; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Map of all visible connections by object identifier, or null if visible |  | ||||||
|      * connections have not yet been loaded. |  | ||||||
|      * |  | ||||||
|      * @type Object.<String, Connection> |  | ||||||
|      */ |  | ||||||
|     var connections = null; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * The date format for use for session-related dates. |  | ||||||
|      * |  | ||||||
|      * @type String |  | ||||||
|      */ |  | ||||||
|     var sessionDateFormat = null; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Map of all currently-selected active connection wrappers by identifier. |  | ||||||
|      *  |  | ||||||
|      * @type Object.<String, ActiveConnectionWrapper> |  | ||||||
|      */ |  | ||||||
|     var selectedWrappers = {}; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Adds the given connection to the internal set of visible |  | ||||||
|      * connections. |  | ||||||
|      *  |  | ||||||
|      * @param {Connection} connection |  | ||||||
|      *     The connection to add to the internal set of visible connections. |  | ||||||
|      */ |  | ||||||
|     var addConnection = function addConnection(connection) { |  | ||||||
|  |  | ||||||
|         // Add given connection to set of visible connections |  | ||||||
|         connections[connection.identifier] = connection; |  | ||||||
|  |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Adds all descendant connections of the given connection group to the |  | ||||||
|      * internal set of connections. |  | ||||||
|      *  |  | ||||||
|      * @param {ConnectionGroup} connectionGroup |  | ||||||
|      *     The connection group whose descendant connections should be added to |  | ||||||
|      *     the internal set of connections. |  | ||||||
|      */ |  | ||||||
|     var addDescendantConnections = function addDescendantConnections(connectionGroup) { |  | ||||||
|  |  | ||||||
|         // Add all child connections |  | ||||||
|         if (connectionGroup.childConnections) |  | ||||||
|             connectionGroup.childConnections.forEach(addConnection); |  | ||||||
|  |  | ||||||
|         // Add all child connection groups |  | ||||||
|         if (connectionGroup.childConnectionGroups) |  | ||||||
|             connectionGroup.childConnectionGroups.forEach(addDescendantConnections); |  | ||||||
|  |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Wraps all loaded active connections, storing the resulting array within |  | ||||||
|      * the scope. If required data has not yet finished loading, this function |  | ||||||
|      * has no effect. |  | ||||||
|      */ |  | ||||||
|     var wrapActiveConnections = function wrapActiveConnections() { |  | ||||||
|  |  | ||||||
|         // Abort if not all required data is available |  | ||||||
|         if (!activeConnections || !connections || !sessionDateFormat) |  | ||||||
|             return; |  | ||||||
|  |  | ||||||
|         // Wrap all active connections for sake of display |  | ||||||
|         $scope.wrappers = []; |  | ||||||
|         for (var identifier in activeConnections) { |  | ||||||
|  |  | ||||||
|             var activeConnection = activeConnections[identifier]; |  | ||||||
|             var connection = connections[activeConnection.connectionIdentifier]; |  | ||||||
|  |  | ||||||
|             $scope.wrappers.push(new ActiveConnectionWrapper( |  | ||||||
|                 connection.name, |  | ||||||
|                 $filter('date')(activeConnection.startDate, sessionDateFormat), |  | ||||||
|                 activeConnection |  | ||||||
|             ));  |  | ||||||
|  |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     // Query the user's permissions |  | ||||||
|     permissionService.getPermissions(authenticationService.getCurrentUserID()) |  | ||||||
|             .success(function permissionsReceived(retrievedPermissions) { |  | ||||||
|         $scope.permissions = retrievedPermissions; |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     // Retrieve all connections  |  | ||||||
|     connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER) |  | ||||||
|     .success(function connectionGroupReceived(retrievedRootGroup) { |  | ||||||
|  |  | ||||||
|         // Load connections from retrieved group tree |  | ||||||
|         connections = {}; |  | ||||||
|         addDescendantConnections(retrievedRootGroup); |  | ||||||
|  |  | ||||||
|         // Attempt to produce wrapped list of active connections |  | ||||||
|         wrapActiveConnections(); |  | ||||||
|  |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Query active sessions |  | ||||||
|     activeConnectionService.getActiveConnections().success(function sessionsRetrieved(retrievedActiveConnections) { |  | ||||||
|  |  | ||||||
|         // Store received list |  | ||||||
|         activeConnections = retrievedActiveConnections; |  | ||||||
|  |  | ||||||
|         // Attempt to produce wrapped list of active connections |  | ||||||
|         wrapActiveConnections(); |  | ||||||
|  |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     // Get session date format |  | ||||||
|     $translate('MANAGE_SESSION.FORMAT_STARTDATE').then(function sessionDateFormatReceived(retrievedSessionDateFormat) { |  | ||||||
|  |  | ||||||
|         // Store received date format |  | ||||||
|         sessionDateFormat = retrievedSessionDateFormat; |  | ||||||
|  |  | ||||||
|         // Attempt to produce wrapped list of active connections |  | ||||||
|         wrapActiveConnections(); |  | ||||||
|  |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Returns whether critical data has completed being loaded. |  | ||||||
|      * |  | ||||||
|      * @returns {Boolean} |  | ||||||
|      *     true if enough data has been loaded for the user interface to be |  | ||||||
|      *     useful, false otherwise. |  | ||||||
|      */ |  | ||||||
|     $scope.isLoaded = function isLoaded() { |  | ||||||
|  |  | ||||||
|         return $scope.wrappers          !== null |  | ||||||
|             && $scope.sessionDateFormat !== null |  | ||||||
|             && $scope.permissions       !== null; |  | ||||||
|  |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * An action to be provided along with the object sent to showStatus which |  | ||||||
|      * closes the currently-shown status dialog. |  | ||||||
|      */ |  | ||||||
|     var ACKNOWLEDGE_ACTION = { |  | ||||||
|         name        : "MANAGE_SESSION.ACTION_ACKNOWLEDGE", |  | ||||||
|         // Handle action |  | ||||||
|         callback    : function acknowledgeCallback() { |  | ||||||
|             guacNotification.showStatus(false); |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * An action to be provided along with the object sent to showStatus which |  | ||||||
|      * closes the currently-shown status dialog. |  | ||||||
|      */ |  | ||||||
|     var CANCEL_ACTION = { |  | ||||||
|         name        : "MANAGE_SESSION.ACTION_CANCEL", |  | ||||||
|         // Handle action |  | ||||||
|         callback    : function cancelCallback() { |  | ||||||
|             guacNotification.showStatus(false); |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
|      |  | ||||||
|     /** |  | ||||||
|      * An action to be provided along with the object sent to showStatus which |  | ||||||
|      * immediately deletes the currently selected sessions. |  | ||||||
|      */ |  | ||||||
|     var DELETE_ACTION = { |  | ||||||
|         name        : "MANAGE_SESSION.ACTION_DELETE", |  | ||||||
|         className   : "danger", |  | ||||||
|         // Handle action |  | ||||||
|         callback    : function deleteCallback() { |  | ||||||
|             deleteSessionsImmediately(); |  | ||||||
|             guacNotification.showStatus(false); |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
|      |  | ||||||
|     /** |  | ||||||
|      * Immediately deletes the selected sessions, without prompting the user for |  | ||||||
|      * confirmation. |  | ||||||
|      */ |  | ||||||
|     var deleteSessionsImmediately = function deleteSessionsImmediately() { |  | ||||||
|  |  | ||||||
|         // Perform deletion |  | ||||||
|         activeConnectionService.deleteActiveConnections(Object.keys(selectedWrappers)) |  | ||||||
|         .success(function activeConnectionsDeleted() { |  | ||||||
|  |  | ||||||
|             // Remove deleted connections from wrapper array |  | ||||||
|             $scope.wrappers = $scope.wrappers.filter(function activeConnectionStillExists(wrapper) { |  | ||||||
|                 return !(wrapper.activeConnection.identifier in selectedWrappers); |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|             // Clear selection |  | ||||||
|             selectedWrappers = {}; |  | ||||||
|  |  | ||||||
|         }) |  | ||||||
|  |  | ||||||
|         // Notify of any errors |  | ||||||
|         .error(function activeConnectionDeletionFailed(error) { |  | ||||||
|             guacNotification.showStatus({ |  | ||||||
|                 'className'  : 'error', |  | ||||||
|                 'title'      : 'MANAGE_SESSION.DIALOG_HEADER_ERROR', |  | ||||||
|                 'text'       : error.message, |  | ||||||
|                 'actions'    : [ ACKNOWLEDGE_ACTION ] |  | ||||||
|             }); |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|     };  |  | ||||||
|      |  | ||||||
|     /** |  | ||||||
|      * Delete all selected sessions, prompting the user first to confirm that  |  | ||||||
|      * deletion is desired. |  | ||||||
|      */ |  | ||||||
|     $scope.deleteSessions = function deleteSessions() { |  | ||||||
|         // Confirm deletion request |  | ||||||
|         guacNotification.showStatus({ |  | ||||||
|             'title'      : 'MANAGE_SESSION.DIALOG_HEADER_CONFIRM_DELETE', |  | ||||||
|             'text'       : 'MANAGE_SESSION.TEXT_CONFIRM_DELETE', |  | ||||||
|             'actions'    : [ DELETE_ACTION, CANCEL_ACTION] |  | ||||||
|         }); |  | ||||||
|     }; |  | ||||||
|      |  | ||||||
|     /** |  | ||||||
|      * Returns whether the selected sessions can be deleted. |  | ||||||
|      *  |  | ||||||
|      * @returns {Boolean} |  | ||||||
|      *     true if selected sessions can be deleted, false otherwise. |  | ||||||
|      */ |  | ||||||
|     $scope.canDeleteSessions = function canDeleteSessions() { |  | ||||||
|  |  | ||||||
|         // We can delete sessions if at least one is selected |  | ||||||
|         for (var identifier in selectedWrappers) |  | ||||||
|             return true; |  | ||||||
|  |  | ||||||
|         return false; |  | ||||||
|  |  | ||||||
|     }; |  | ||||||
|      |  | ||||||
|     /** |  | ||||||
|      * Called whenever an active connection wrapper changes selected status. |  | ||||||
|      *  |  | ||||||
|      * @param {ActiveConnectionWrapper} wrapper |  | ||||||
|      *     The wrapper whose selected status has changed. |  | ||||||
|      */ |  | ||||||
|     $scope.wrapperSelectionChange = function wrapperSelectionChange(wrapper) { |  | ||||||
|  |  | ||||||
|         // Add wrapper to map if selected |  | ||||||
|         if (wrapper.checked) |  | ||||||
|             selectedWrappers[wrapper.activeConnection.identifier] = wrapper; |  | ||||||
|  |  | ||||||
|         // Otherwise, remove wrapper from map |  | ||||||
|         else |  | ||||||
|             delete selectedWrappers[wrapper.activeConnection.identifier]; |  | ||||||
|  |  | ||||||
|     }; |  | ||||||
|      |  | ||||||
| }]); |  | ||||||
| @@ -1,86 +0,0 @@ | |||||||
| <!-- |  | ||||||
| Copyright 2015 Glyptodon LLC. |  | ||||||
|  |  | ||||||
| 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. |  | ||||||
| --> |  | ||||||
|  |  | ||||||
| <div class="view" ng-class="{loading: !isLoaded()}"> |  | ||||||
|  |  | ||||||
|     <div class="header"> |  | ||||||
|         <h2>{{'MANAGE_SESSION.SECTION_HEADER_SESSIONS' | translate}}</h2> |  | ||||||
|         <guac-user-menu></guac-user-menu> |  | ||||||
|     </div> |  | ||||||
|  |  | ||||||
|     <!-- User Session management --> |  | ||||||
|     <div class="settings section sessions"> |  | ||||||
|         <p>{{'MANAGE_SESSION.HELP_SESSIONS' | translate}}</p> |  | ||||||
|  |  | ||||||
|         <!-- Form action buttons --> |  | ||||||
|         <div class="action-buttons"> |  | ||||||
|             <button class="delete-sessions danger" ng-disabled="!canDeleteSessions()" ng-click="deleteSessions()">{{'MANAGE_SESSION.ACTION_DELETE' | translate}}</button> |  | ||||||
|         </div> |  | ||||||
|  |  | ||||||
|         <!-- Session filter --> |  | ||||||
|         <guac-filter filtered-items="filteredWrappers" items="wrappers" |  | ||||||
|                      placeholder="'MANAGE_SESSION.FIELD_PLACEHOLDER_FILTER' | translate" |  | ||||||
|                      properties="filteredWrapperProperties"></guac-filter> |  | ||||||
|  |  | ||||||
|         <!-- List of current user sessions --> |  | ||||||
|         <table class="sorted session-list"> |  | ||||||
|             <thead> |  | ||||||
|                 <tr> |  | ||||||
|                     <th class="select-session"></th> |  | ||||||
|                     <th guac-sort-order="wrapperOrder" guac-sort-property="'activeConnection.username'"> |  | ||||||
|                         {{'MANAGE_SESSION.TABLE_HEADER_SESSION_USERNAME' | translate}} |  | ||||||
|                     </th> |  | ||||||
|                     <th guac-sort-order="wrapperOrder" guac-sort-property="'startDate'"> |  | ||||||
|                         {{'MANAGE_SESSION.TABLE_HEADER_SESSION_STARTDATE' | translate}} |  | ||||||
|                     </th> |  | ||||||
|                     <th guac-sort-order="wrapperOrder" guac-sort-property="'activeConnection.remoteHost'"> |  | ||||||
|                         {{'MANAGE_SESSION.TABLE_HEADER_SESSION_REMOTEHOST' | translate}} |  | ||||||
|                     </th> |  | ||||||
|                     <th guac-sort-order="wrapperOrder" guac-sort-property="'name'"> |  | ||||||
|                         {{'MANAGE_SESSION.TABLE_HEADER_SESSION_CONNECTION_NAME' | translate}} |  | ||||||
|                     </th> |  | ||||||
|                 </tr> |  | ||||||
|             </thead> |  | ||||||
|             <tbody> |  | ||||||
|                 <tr ng-repeat="wrapper in wrapperPage" class="session"> |  | ||||||
|                     <td class="select-session"> |  | ||||||
|                         <input ng-change="wrapperSelectionChange(wrapper)" type="checkbox" ng-model="wrapper.checked" /> |  | ||||||
|                     </td> |  | ||||||
|                     <td>{{wrapper.activeConnection.username}}</td> |  | ||||||
|                     <td>{{wrapper.startDate}}</td> |  | ||||||
|                     <td>{{wrapper.activeConnection.remoteHost}}</td> |  | ||||||
|                     <td>{{wrapper.name}}</td> |  | ||||||
|                 </tr> |  | ||||||
|             </tbody> |  | ||||||
|         </table> |  | ||||||
|  |  | ||||||
|         <!-- Text displayed if no sessions exist --> |  | ||||||
|         <p class="placeholder" ng-hide="wrapperPage.length"> |  | ||||||
|             {{'MANAGE_SESSION.INFO_NO_SESSIONS' | translate}} |  | ||||||
|         </p> |  | ||||||
|  |  | ||||||
|         <!-- Pager for session list --> |  | ||||||
|         <guac-pager page="wrapperPage" page-size="25" |  | ||||||
|                     items="filteredWrappers | orderBy : wrapperOrder.predicate"></guac-pager> |  | ||||||
|     </div> |  | ||||||
|  |  | ||||||
| </div> |  | ||||||
| @@ -0,0 +1,363 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (C) 2015 Glyptodon LLC | ||||||
|  |  * | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A directive for managing all active Guacamole sessions. | ||||||
|  |  */ | ||||||
|  | angular.module('settings').directive('guacManageSessions', [function guacManageSessions() { | ||||||
|  |      | ||||||
|  |     return { | ||||||
|  |         // Element only | ||||||
|  |         restrict: 'E', | ||||||
|  |         replace: true, | ||||||
|  |  | ||||||
|  |         scope: { | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         templateUrl: 'app/settings/templates/manageSessions.html', | ||||||
|  |         controller: ['$scope', '$injector',  | ||||||
|  |         function manageSessionsController($scope, $injector) { | ||||||
|  |  | ||||||
|  |             // Required types | ||||||
|  |             var ActiveConnectionWrapper = $injector.get('ActiveConnectionWrapper'); | ||||||
|  |             var ConnectionGroup         = $injector.get('ConnectionGroup'); | ||||||
|  |             var SortOrder               = $injector.get('SortOrder'); | ||||||
|  |  | ||||||
|  |             // Required services | ||||||
|  |             var $filter                 = $injector.get('$filter'); | ||||||
|  |             var $translate              = $injector.get('$translate'); | ||||||
|  |             var activeConnectionService = $injector.get('activeConnectionService'); | ||||||
|  |             var authenticationService   = $injector.get('authenticationService'); | ||||||
|  |             var connectionGroupService  = $injector.get('connectionGroupService'); | ||||||
|  |             var guacNotification        = $injector.get('guacNotification'); | ||||||
|  |             var permissionService       = $injector.get('permissionService'); | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * All permissions associated with the current user, or null if the user's | ||||||
|  |              * permissions have not yet been loaded. | ||||||
|  |              * | ||||||
|  |              * @type PermissionSet | ||||||
|  |              */ | ||||||
|  |             $scope.permissions = null; | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * The ActiveConnectionWrappers of all active sessions accessible by the | ||||||
|  |              * current user, or null if the active sessions have not yet been loaded. | ||||||
|  |              * | ||||||
|  |              * @type ActiveConnectionWrapper[] | ||||||
|  |              */ | ||||||
|  |             $scope.wrappers = null; | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * SortOrder instance which maintains the sort order of the visible | ||||||
|  |              * connection wrappers. | ||||||
|  |              * | ||||||
|  |              * @type SortOrder | ||||||
|  |              */ | ||||||
|  |             $scope.wrapperOrder = new SortOrder([ | ||||||
|  |                 'activeConnection.username', | ||||||
|  |                 'startDate', | ||||||
|  |                 'activeConnection.remoteHost', | ||||||
|  |                 'name' | ||||||
|  |             ]); | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * Array of all wrapper properties that are filterable. | ||||||
|  |              * | ||||||
|  |              * @type String[] | ||||||
|  |              */ | ||||||
|  |             $scope.filteredWrapperProperties = [ | ||||||
|  |                 'activeConnection.username', | ||||||
|  |                 'startDate', | ||||||
|  |                 'activeConnection.remoteHost', | ||||||
|  |                 'name' | ||||||
|  |             ]; | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * All active connections, if known, or null if active connections have not | ||||||
|  |              * yet been loaded. | ||||||
|  |              * | ||||||
|  |              * @type ActiveConnection | ||||||
|  |              */ | ||||||
|  |             var activeConnections = null; | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * Map of all visible connections by object identifier, or null if visible | ||||||
|  |              * connections have not yet been loaded. | ||||||
|  |              * | ||||||
|  |              * @type Object.<String, Connection> | ||||||
|  |              */ | ||||||
|  |             var connections = null; | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * The date format for use for session-related dates. | ||||||
|  |              * | ||||||
|  |              * @type String | ||||||
|  |              */ | ||||||
|  |             var sessionDateFormat = null; | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * Map of all currently-selected active connection wrappers by identifier. | ||||||
|  |              *  | ||||||
|  |              * @type Object.<String, ActiveConnectionWrapper> | ||||||
|  |              */ | ||||||
|  |             var selectedWrappers = {}; | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * Adds the given connection to the internal set of visible | ||||||
|  |              * connections. | ||||||
|  |              *  | ||||||
|  |              * @param {Connection} connection | ||||||
|  |              *     The connection to add to the internal set of visible connections. | ||||||
|  |              */ | ||||||
|  |             var addConnection = function addConnection(connection) { | ||||||
|  |  | ||||||
|  |                 // Add given connection to set of visible connections | ||||||
|  |                 connections[connection.identifier] = connection; | ||||||
|  |  | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * Adds all descendant connections of the given connection group to the | ||||||
|  |              * internal set of connections. | ||||||
|  |              *  | ||||||
|  |              * @param {ConnectionGroup} connectionGroup | ||||||
|  |              *     The connection group whose descendant connections should be added to | ||||||
|  |              *     the internal set of connections. | ||||||
|  |              */ | ||||||
|  |             var addDescendantConnections = function addDescendantConnections(connectionGroup) { | ||||||
|  |  | ||||||
|  |                 // Add all child connections | ||||||
|  |                 if (connectionGroup.childConnections) | ||||||
|  |                     connectionGroup.childConnections.forEach(addConnection); | ||||||
|  |  | ||||||
|  |                 // Add all child connection groups | ||||||
|  |                 if (connectionGroup.childConnectionGroups) | ||||||
|  |                     connectionGroup.childConnectionGroups.forEach(addDescendantConnections); | ||||||
|  |  | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * Wraps all loaded active connections, storing the resulting array within | ||||||
|  |              * the scope. If required data has not yet finished loading, this function | ||||||
|  |              * has no effect. | ||||||
|  |              */ | ||||||
|  |             var wrapActiveConnections = function wrapActiveConnections() { | ||||||
|  |  | ||||||
|  |                 // Abort if not all required data is available | ||||||
|  |                 if (!activeConnections || !connections || !sessionDateFormat) | ||||||
|  |                     return; | ||||||
|  |  | ||||||
|  |                 // Wrap all active connections for sake of display | ||||||
|  |                 $scope.wrappers = []; | ||||||
|  |                 for (var identifier in activeConnections) { | ||||||
|  |  | ||||||
|  |                     var activeConnection = activeConnections[identifier]; | ||||||
|  |                     var connection = connections[activeConnection.connectionIdentifier]; | ||||||
|  |  | ||||||
|  |                     $scope.wrappers.push(new ActiveConnectionWrapper( | ||||||
|  |                         connection.name, | ||||||
|  |                         $filter('date')(activeConnection.startDate, sessionDateFormat), | ||||||
|  |                         activeConnection | ||||||
|  |                     ));  | ||||||
|  |  | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             // Query the user's permissions | ||||||
|  |             permissionService.getPermissions(authenticationService.getCurrentUserID()) | ||||||
|  |                     .success(function permissionsReceived(retrievedPermissions) { | ||||||
|  |                 $scope.permissions = retrievedPermissions; | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             // Retrieve all connections  | ||||||
|  |             connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER) | ||||||
|  |             .success(function connectionGroupReceived(retrievedRootGroup) { | ||||||
|  |  | ||||||
|  |                 // Load connections from retrieved group tree | ||||||
|  |                 connections = {}; | ||||||
|  |                 addDescendantConnections(retrievedRootGroup); | ||||||
|  |  | ||||||
|  |                 // Attempt to produce wrapped list of active connections | ||||||
|  |                 wrapActiveConnections(); | ||||||
|  |  | ||||||
|  |             }); | ||||||
|  |              | ||||||
|  |             // Query active sessions | ||||||
|  |             activeConnectionService.getActiveConnections().success(function sessionsRetrieved(retrievedActiveConnections) { | ||||||
|  |  | ||||||
|  |                 // Store received list | ||||||
|  |                 activeConnections = retrievedActiveConnections; | ||||||
|  |  | ||||||
|  |                 // Attempt to produce wrapped list of active connections | ||||||
|  |                 wrapActiveConnections(); | ||||||
|  |  | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             // Get session date format | ||||||
|  |             $translate('MANAGE_SESSION.FORMAT_STARTDATE').then(function sessionDateFormatReceived(retrievedSessionDateFormat) { | ||||||
|  |  | ||||||
|  |                 // Store received date format | ||||||
|  |                 sessionDateFormat = retrievedSessionDateFormat; | ||||||
|  |  | ||||||
|  |                 // Attempt to produce wrapped list of active connections | ||||||
|  |                 wrapActiveConnections(); | ||||||
|  |  | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * Returns whether critical data has completed being loaded. | ||||||
|  |              * | ||||||
|  |              * @returns {Boolean} | ||||||
|  |              *     true if enough data has been loaded for the user interface to be | ||||||
|  |              *     useful, false otherwise. | ||||||
|  |              */ | ||||||
|  |             $scope.isLoaded = function isLoaded() { | ||||||
|  |  | ||||||
|  |                 return $scope.wrappers          !== null | ||||||
|  |                     && $scope.sessionDateFormat !== null | ||||||
|  |                     && $scope.permissions       !== null; | ||||||
|  |  | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * An action to be provided along with the object sent to showStatus which | ||||||
|  |              * closes the currently-shown status dialog. | ||||||
|  |              */ | ||||||
|  |             var ACKNOWLEDGE_ACTION = { | ||||||
|  |                 name        : "MANAGE_SESSION.ACTION_ACKNOWLEDGE", | ||||||
|  |                 // Handle action | ||||||
|  |                 callback    : function acknowledgeCallback() { | ||||||
|  |                     guacNotification.showStatus(false); | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * An action to be provided along with the object sent to showStatus which | ||||||
|  |              * closes the currently-shown status dialog. | ||||||
|  |              */ | ||||||
|  |             var CANCEL_ACTION = { | ||||||
|  |                 name        : "MANAGE_SESSION.ACTION_CANCEL", | ||||||
|  |                 // Handle action | ||||||
|  |                 callback    : function cancelCallback() { | ||||||
|  |                     guacNotification.showStatus(false); | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |              | ||||||
|  |             /** | ||||||
|  |              * An action to be provided along with the object sent to showStatus which | ||||||
|  |              * immediately deletes the currently selected sessions. | ||||||
|  |              */ | ||||||
|  |             var DELETE_ACTION = { | ||||||
|  |                 name        : "MANAGE_SESSION.ACTION_DELETE", | ||||||
|  |                 className   : "danger", | ||||||
|  |                 // Handle action | ||||||
|  |                 callback    : function deleteCallback() { | ||||||
|  |                     deleteSessionsImmediately(); | ||||||
|  |                     guacNotification.showStatus(false); | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |              | ||||||
|  |             /** | ||||||
|  |              * Immediately deletes the selected sessions, without prompting the user for | ||||||
|  |              * confirmation. | ||||||
|  |              */ | ||||||
|  |             var deleteSessionsImmediately = function deleteSessionsImmediately() { | ||||||
|  |  | ||||||
|  |                 // Perform deletion | ||||||
|  |                 activeConnectionService.deleteActiveConnections(Object.keys(selectedWrappers)) | ||||||
|  |                 .success(function activeConnectionsDeleted() { | ||||||
|  |  | ||||||
|  |                     // Remove deleted connections from wrapper array | ||||||
|  |                     $scope.wrappers = $scope.wrappers.filter(function activeConnectionStillExists(wrapper) { | ||||||
|  |                         return !(wrapper.activeConnection.identifier in selectedWrappers); | ||||||
|  |                     }); | ||||||
|  |  | ||||||
|  |                     // Clear selection | ||||||
|  |                     selectedWrappers = {}; | ||||||
|  |  | ||||||
|  |                 }) | ||||||
|  |  | ||||||
|  |                 // Notify of any errors | ||||||
|  |                 .error(function activeConnectionDeletionFailed(error) { | ||||||
|  |                     guacNotification.showStatus({ | ||||||
|  |                         'className'  : 'error', | ||||||
|  |                         'title'      : 'MANAGE_SESSION.DIALOG_HEADER_ERROR', | ||||||
|  |                         'text'       : error.message, | ||||||
|  |                         'actions'    : [ ACKNOWLEDGE_ACTION ] | ||||||
|  |                     }); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             };  | ||||||
|  |              | ||||||
|  |             /** | ||||||
|  |              * Delete all selected sessions, prompting the user first to confirm that  | ||||||
|  |              * deletion is desired. | ||||||
|  |              */ | ||||||
|  |             $scope.deleteSessions = function deleteSessions() { | ||||||
|  |                 // Confirm deletion request | ||||||
|  |                 guacNotification.showStatus({ | ||||||
|  |                     'title'      : 'MANAGE_SESSION.DIALOG_HEADER_CONFIRM_DELETE', | ||||||
|  |                     'text'       : 'MANAGE_SESSION.TEXT_CONFIRM_DELETE', | ||||||
|  |                     'actions'    : [ DELETE_ACTION, CANCEL_ACTION] | ||||||
|  |                 }); | ||||||
|  |             }; | ||||||
|  |              | ||||||
|  |             /** | ||||||
|  |              * Returns whether the selected sessions can be deleted. | ||||||
|  |              *  | ||||||
|  |              * @returns {Boolean} | ||||||
|  |              *     true if selected sessions can be deleted, false otherwise. | ||||||
|  |              */ | ||||||
|  |             $scope.canDeleteSessions = function canDeleteSessions() { | ||||||
|  |  | ||||||
|  |                 // We can delete sessions if at least one is selected | ||||||
|  |                 for (var identifier in selectedWrappers) | ||||||
|  |                     return true; | ||||||
|  |  | ||||||
|  |                 return false; | ||||||
|  |  | ||||||
|  |             }; | ||||||
|  |              | ||||||
|  |             /** | ||||||
|  |              * Called whenever an active connection wrapper changes selected status. | ||||||
|  |              *  | ||||||
|  |              * @param {ActiveConnectionWrapper} wrapper | ||||||
|  |              *     The wrapper whose selected status has changed. | ||||||
|  |              */ | ||||||
|  |             $scope.wrapperSelectionChange = function wrapperSelectionChange(wrapper) { | ||||||
|  |  | ||||||
|  |                 // Add wrapper to map if selected | ||||||
|  |                 if (wrapper.checked) | ||||||
|  |                     selectedWrappers[wrapper.activeConnection.identifier] = wrapper; | ||||||
|  |  | ||||||
|  |                 // Otherwise, remove wrapper from map | ||||||
|  |                 else | ||||||
|  |                     delete selectedWrappers[wrapper.activeConnection.identifier]; | ||||||
|  |  | ||||||
|  |             }; | ||||||
|  |              | ||||||
|  |         }] | ||||||
|  |     }; | ||||||
|  |      | ||||||
|  | }]); | ||||||
| @@ -20,15 +20,15 @@ | |||||||
|  * THE SOFTWARE. |  * THE SOFTWARE. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| .manage table.session-list { | .settings table.session-list { | ||||||
|     width: 100%; |     width: 100%; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .manage table.session-list tr.session:hover { | .settings table.session-list tr.session:hover { | ||||||
|     background: #CDA; |     background: #CDA; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .manage table.session-list .select-session { | .settings table.session-list .select-session { | ||||||
|     min-width: 2em; |     min-width: 2em; | ||||||
|     text-align: center; |     text-align: center; | ||||||
| } | } | ||||||
| @@ -24,6 +24,17 @@ | |||||||
|     margin-bottom: 0; |     margin-bottom: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .settings table.properties th { | ||||||
|  |     text-align: left; | ||||||
|  |     font-weight: normal; | ||||||
|  |     padding-right: 1em; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .settings .action-buttons { | ||||||
|  |     text-align: center; | ||||||
|  |     margin-bottom: 1em; | ||||||
|  | } | ||||||
|  |  | ||||||
| .settings-tabs .page-list { | .settings-tabs .page-list { | ||||||
|     margin: 0; |     margin: 0; | ||||||
|     padding: 0; |     padding: 0; | ||||||
|   | |||||||
| @@ -0,0 +1,77 @@ | |||||||
|  | <div class="settings section sessions" ng-class="{loading: !isLoaded()}"> | ||||||
|  |     <!-- | ||||||
|  |     Copyright 2015 Glyptodon LLC. | ||||||
|  |  | ||||||
|  |     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. | ||||||
|  |     --> | ||||||
|  |  | ||||||
|  |     <!-- User Session management --> | ||||||
|  |     <p>{{'MANAGE_SESSION.HELP_SESSIONS' | translate}}</p> | ||||||
|  |  | ||||||
|  |     <!-- Form action buttons --> | ||||||
|  |     <div class="action-buttons"> | ||||||
|  |         <button class="delete-sessions danger" ng-disabled="!canDeleteSessions()" ng-click="deleteSessions()">{{'MANAGE_SESSION.ACTION_DELETE' | translate}}</button> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <!-- Session filter --> | ||||||
|  |     <guac-filter filtered-items="filteredWrappers" items="wrappers" | ||||||
|  |                  placeholder="'MANAGE_SESSION.FIELD_PLACEHOLDER_FILTER' | translate" | ||||||
|  |                  properties="filteredWrapperProperties"></guac-filter> | ||||||
|  |  | ||||||
|  |     <!-- List of current user sessions --> | ||||||
|  |     <table class="sorted session-list"> | ||||||
|  |         <thead> | ||||||
|  |             <tr> | ||||||
|  |                 <th class="select-session"></th> | ||||||
|  |                 <th guac-sort-order="wrapperOrder" guac-sort-property="'activeConnection.username'"> | ||||||
|  |                     {{'MANAGE_SESSION.TABLE_HEADER_SESSION_USERNAME' | translate}} | ||||||
|  |                 </th> | ||||||
|  |                 <th guac-sort-order="wrapperOrder" guac-sort-property="'startDate'"> | ||||||
|  |                     {{'MANAGE_SESSION.TABLE_HEADER_SESSION_STARTDATE' | translate}} | ||||||
|  |                 </th> | ||||||
|  |                 <th guac-sort-order="wrapperOrder" guac-sort-property="'activeConnection.remoteHost'"> | ||||||
|  |                     {{'MANAGE_SESSION.TABLE_HEADER_SESSION_REMOTEHOST' | translate}} | ||||||
|  |                 </th> | ||||||
|  |                 <th guac-sort-order="wrapperOrder" guac-sort-property="'name'"> | ||||||
|  |                     {{'MANAGE_SESSION.TABLE_HEADER_SESSION_CONNECTION_NAME' | translate}} | ||||||
|  |                 </th> | ||||||
|  |             </tr> | ||||||
|  |         </thead> | ||||||
|  |         <tbody> | ||||||
|  |             <tr ng-repeat="wrapper in wrapperPage" class="session"> | ||||||
|  |                 <td class="select-session"> | ||||||
|  |                     <input ng-change="wrapperSelectionChange(wrapper)" type="checkbox" ng-model="wrapper.checked" /> | ||||||
|  |                 </td> | ||||||
|  |                 <td>{{wrapper.activeConnection.username}}</td> | ||||||
|  |                 <td>{{wrapper.startDate}}</td> | ||||||
|  |                 <td>{{wrapper.activeConnection.remoteHost}}</td> | ||||||
|  |                 <td>{{wrapper.name}}</td> | ||||||
|  |             </tr> | ||||||
|  |         </tbody> | ||||||
|  |     </table> | ||||||
|  |  | ||||||
|  |     <!-- Text displayed if no sessions exist --> | ||||||
|  |     <p class="placeholder" ng-hide="wrapperPage.length"> | ||||||
|  |         {{'MANAGE_SESSION.INFO_NO_SESSIONS' | translate}} | ||||||
|  |     </p> | ||||||
|  |  | ||||||
|  |     <!-- Pager for session list --> | ||||||
|  |     <guac-pager page="wrapperPage" page-size="25" | ||||||
|  |                 items="filteredWrappers | orderBy : wrapperOrder.predicate"></guac-pager> | ||||||
|  | </div> | ||||||
| @@ -35,5 +35,6 @@ THE SOFTWARE. | |||||||
|     <!-- Selected tab --> |     <!-- Selected tab --> | ||||||
|     <guac-manage-users       ng-if="activeTab === 'users'"></guac-manage-users> |     <guac-manage-users       ng-if="activeTab === 'users'"></guac-manage-users> | ||||||
|     <guac-manage-connections ng-if="activeTab === 'connections'"></guac-manage-connections> |     <guac-manage-connections ng-if="activeTab === 'connections'"></guac-manage-connections> | ||||||
|  |     <guac-manage-sessions    ng-if="activeTab === 'sessions'"></guac-manage-sessions> | ||||||
|  |  | ||||||
| </div> | </div> | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ | |||||||
| /** | /** | ||||||
|  * A service for defining the ActiveConnectionWrapper class. |  * A service for defining the ActiveConnectionWrapper class. | ||||||
|  */ |  */ | ||||||
| angular.module('manage').factory('ActiveConnectionWrapper', [ | angular.module('settings').factory('ActiveConnectionWrapper', [ | ||||||
|     function defineActiveConnectionWrapper() { |     function defineActiveConnectionWrapper() { | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
		Reference in New Issue
	
	Block a user