diff --git a/guacamole/src/main/webapp/app/form/directives/form.js b/guacamole/src/main/webapp/app/form/directives/form.js index 8623e6f57..f22b9952b 100644 --- a/guacamole/src/main/webapp/app/form/directives/form.js +++ b/guacamole/src/main/webapp/app/form/directives/form.js @@ -104,6 +104,10 @@ angular.module('form').directive('guacForm', [function form() { */ $scope.getSectionHeader = function getSectionHeader(form) { + // If no form, or no name, then no header + if (!form || !form.name) + return ''; + return translationStringService.canonicalize($scope.namespace || 'MISSING_NAMESPACE') + '.SECTION_HEADER_' + translationStringService.canonicalize(form.name); @@ -128,6 +132,10 @@ angular.module('form').directive('guacForm', [function form() { */ $scope.getFieldHeader = function getFieldHeader(field) { + // If no field, or no name, then no header + if (!field || !field.name) + return ''; + return translationStringService.canonicalize($scope.namespace || 'MISSING_NAMESPACE') + '.FIELD_HEADER_' + translationStringService.canonicalize(field.name); diff --git a/guacamole/src/main/webapp/app/form/directives/formField.js b/guacamole/src/main/webapp/app/form/directives/formField.js index bf9b35434..60b440267 100644 --- a/guacamole/src/main/webapp/app/form/directives/formField.js +++ b/guacamole/src/main/webapp/app/form/directives/formField.js @@ -131,8 +131,8 @@ angular.module('form').directive('guacFormField', [function formField() { */ $scope.getFieldOption = function getFieldOption(value) { - // Don't bother if the model is not yet defined - if (!$scope.field) + // If no field, or no value, then no corresponding translation string + if (!$scope.field || !$scope.field.name || !value) return ''; return translationStringService.canonicalize($scope.namespace || 'MISSING_NAMESPACE') diff --git a/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js b/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js index 837f4e632..3d51f4b52 100644 --- a/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js +++ b/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js @@ -125,30 +125,12 @@ angular.module('index').config(['$routeProvider', '$locationProvider', resolve : { routeToUserHomePage: routeToUserHomePage } }) - // Connection management screen - .when('/manage/modules/connections/', { + // Management screen + .when('/settings/:tab', { title : 'APP.NAME', - bodyClassName : 'manage', - templateUrl : 'app/manage/templates/manageConnections.html', - controller : 'manageConnectionsController', - resolve : { updateCurrentToken: updateCurrentToken } - }) - - // User management screen - .when('/manage/modules/users/', { - title : 'APP.NAME', - bodyClassName : 'manage', - templateUrl : 'app/manage/templates/manageUsers.html', - controller : 'manageUsersController', - resolve : { updateCurrentToken: updateCurrentToken } - }) - - // Session management screen - .when('/manage/modules/sessions/', { - title : 'APP.NAME', - bodyClassName : 'manage', - templateUrl : 'app/manage/templates/manageSessions.html', - controller : 'manageSessionsController', + bodyClassName : 'settings', + templateUrl : 'app/settings/templates/settings.html', + controller : 'settingsController', resolve : { updateCurrentToken: updateCurrentToken } }) diff --git a/guacamole/src/main/webapp/app/index/indexModule.js b/guacamole/src/main/webapp/app/index/indexModule.js index a1d927330..99fb457fa 100644 --- a/guacamole/src/main/webapp/app/index/indexModule.js +++ b/guacamole/src/main/webapp/app/index/indexModule.js @@ -34,5 +34,6 @@ angular.module('index', [ 'ngTouch', 'notification', 'pascalprecht.translate', - 'rest' + 'rest', + 'settings' ]); diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js index 08700c893..989875a69 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js @@ -317,7 +317,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i * Cancels all pending edits, returning to the management page. */ $scope.cancel = function cancel() { - $location.path('/manage/modules/connections/'); + $location.path('/settings/connections'); }; /** @@ -339,7 +339,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i // Save the connection connectionService.saveConnection($scope.connection) .success(function savedConnection() { - $location.path('/manage/modules/connections/'); + $location.path('/settings/connections'); }) // Notify of any errors @@ -389,7 +389,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i // Delete the connection connectionService.deleteConnection($scope.connection) .success(function deletedConnection() { - $location.path('/manage/modules/connections/'); + $location.path('/settings/connections'); }) // Notify of any errors diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionsController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionsController.js deleted file mode 100644 index 76978ec10..000000000 --- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionsController.js +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2014 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 connection and connection group administration page. - */ -angular.module('manage').controller('manageConnectionsController', ['$scope', '$injector', - function manageConnectionsController($scope, $injector) { - - // Required types - var ConnectionGroup = $injector.get('ConnectionGroup'); - var PermissionSet = $injector.get('PermissionSet'); - - // Required services - var $location = $injector.get('$location'); - var authenticationService = $injector.get('authenticationService'); - var connectionGroupService = $injector.get('connectionGroupService'); - var guacNotification = $injector.get('guacNotification'); - var permissionService = $injector.get('permissionService'); - - // Identifier of the current user - var currentUserID = authenticationService.getCurrentUserID(); - - /** - * An action to be provided along with the object sent to showStatus which - * closes the currently-shown status dialog. - */ - var ACKNOWLEDGE_ACTION = { - name : "MANAGE_CONNECTION.ACTION_ACKNOWLEDGE", - // Handle action - callback : function acknowledgeCallback() { - guacNotification.showStatus(false); - } - }; - - /** - * The root connection group of the connection group hierarchy. - * - * @type ConnectionGroup - */ - $scope.rootGroup = null; - - /** - * Whether the current user can manage connections. If the current - * permissions have not yet been loaded, this will be null. - * - * @type Boolean - */ - $scope.canManageConnections = null; - - /** - * Whether the current user can create new connections. If the current - * permissions have not yet been loaded, this will be null. - * - * @type Boolean - */ - $scope.canCreateConnections = null; - - /** - * Whether the current user can create new connection groups. If the - * current permissions have not yet been loaded, this will be null. - * - * @type Boolean - */ - $scope.canCreateConnectionGroups = null; - - /** - * All permissions associated with the current user, or null if the user's - * permissions have not yet been loaded. - * - * @type PermissionSet - */ - $scope.permissions = null; - - /** - * 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.rootGroup !== null - && $scope.permissions !== null - && $scope.canManageConnections !== null - && $scope.canCreateConnections !== null - && $scope.canCreateConnectionGroups !== null; - - }; - - // Retrieve current permissions - permissionService.getPermissions(currentUserID) - .success(function permissionsRetrieved(permissions) { - - $scope.permissions = permissions; - - // Ignore permission to update root group - PermissionSet.removeConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, ConnectionGroup.ROOT_IDENTIFIER); - - // Determine whether the current user can create new users - $scope.canCreateConnections = - PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) - || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION); - - // Determine whether the current user can create new users - $scope.canCreateConnectionGroups = - PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) - || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP); - - // Determine whether the current user can manage other connections or groups - $scope.canManageConnections = - - // Permission to manage connections - $scope.canCreateConnections - || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) - || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.DELETE) - - // Permission to manage groups - || $scope.canCreateConnectionGroups - || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) - || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.DELETE); - - // Return to home if there's nothing to do here - if (!$scope.canManageConnections) - $location.path('/'); - - }); - - // Retrieve all connections for which we have UPDATE or DELETE permission - connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, - [PermissionSet.ObjectPermissionType.UPDATE, PermissionSet.ObjectPermissionType.DELETE]) - .success(function connectionGroupReceived(rootGroup) { - $scope.rootGroup = rootGroup; - }); - -}]); diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageSessionsController.js b/guacamole/src/main/webapp/app/manage/controllers/manageSessionsController.js deleted file mode 100644 index f89f19a32..000000000 --- a/guacamole/src/main/webapp/app/manage/controllers/manageSessionsController.js +++ /dev/null @@ -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. - */ - 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. - */ - 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]; - - }; - -}]); diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index 1a43744be..d45491dc9 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -472,7 +472,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto * Cancels all pending edits, returning to the management page. */ $scope.cancel = function cancel() { - $location.path('/manage/modules/users/'); + $location.path('/settings/users'); }; /** @@ -498,7 +498,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto // Upon success, save any changed permissions permissionService.patchPermissions($scope.user.username, permissionsAdded, permissionsRemoved) .success(function patchedUserPermissions() { - $location.path('/manage/modules/users/'); + $location.path('/settings/users'); }) // Notify of any errors @@ -560,7 +560,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto // Delete the user userService.deleteUser($scope.user) .success(function deletedUser() { - $location.path('/manage/modules/users/'); + $location.path('/settings/users'); }) // Notify of any errors diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUsersController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUsersController.js deleted file mode 100644 index d50b5d5b5..000000000 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUsersController.js +++ /dev/null @@ -1,179 +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 administration page. - */ -angular.module('manage').controller('manageUsersController', ['$scope', '$injector', - function manageUsersController($scope, $injector) { - - // Required types - var PermissionSet = $injector.get('PermissionSet'); - var User = $injector.get('User'); - - // Required services - var $location = $injector.get('$location'); - var authenticationService = $injector.get('authenticationService'); - var guacNotification = $injector.get('guacNotification'); - var permissionService = $injector.get('permissionService'); - var userService = $injector.get('userService'); - - // Identifier of the current user - var currentUserID = authenticationService.getCurrentUserID(); - - /** - * An action to be provided along with the object sent to showStatus which - * closes the currently-shown status dialog. - */ - var ACKNOWLEDGE_ACTION = { - name : "MANAGE_USER.ACTION_ACKNOWLEDGE", - // Handle action - callback : function acknowledgeCallback() { - guacNotification.showStatus(false); - } - }; - - /** - * All visible users. - * - * @type User[] - */ - $scope.users = null; - - /** - * Whether the current user can manage users. If the current permissions - * have not yet been loaded, this will be null. - * - * @type Boolean - */ - $scope.canManageUsers = null; - - /** - * Whether the current user can create new users. If the current - * permissions have not yet been loaded, this will be null. - * - * @type Boolean - */ - $scope.canCreateUsers = null; - - /** - * The name of the new user to create, if any, when user creation is - * requested via newUser(). - * - * @type String - */ - $scope.newUsername = ""; - - /** - * All permissions associated with the current user, or null if the user's - * permissions have not yet been loaded. - * - * @type PermissionSet - */ - $scope.permissions = null; - - /** - * 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.users !== null - && $scope.permissions !== null - && $scope.canManageUsers !== null - && $scope.canCreateUsers !== null; - - }; - - // Retrieve current permissions - permissionService.getPermissions(currentUserID) - .success(function permissionsRetrieved(permissions) { - - $scope.permissions = permissions; - - // Determine whether the current user can create new users - $scope.canCreateUsers = - PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) - || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_USER); - - // Determine whether the current user can manage other users - $scope.canManageUsers = - $scope.canCreateUsers - || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) - || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.DELETE); - - // Return to home if there's nothing to do here - if (!$scope.canManageUsers) - $location.path('/'); - - }); - - // Retrieve all users for whom we have UPDATE or DELETE permission - userService.getUsers([PermissionSet.ObjectPermissionType.UPDATE, - PermissionSet.ObjectPermissionType.DELETE]) - .success(function usersReceived(users) { - - // Display only other users, not self - $scope.users = users.filter(function isNotSelf(user) { - return user.username !== currentUserID; - }); - - }); - - /** - * Creates a new user having the username specified in the user creation - * interface. - */ - $scope.newUser = function newUser() { - - // Create user skeleton - var user = new User({ - username: $scope.newUsername || '' - }); - - // Create specified user - userService.createUser(user) - - // Add user to visible list upon success - .success(function userCreated() { - $scope.users.push(user); - }) - - // Notify of any errors - .error(function userCreationFailed(error) { - guacNotification.showStatus({ - 'className' : 'error', - 'title' : 'MANAGE_USER.DIALOG_HEADER_ERROR', - 'text' : error.message, - 'actions' : [ ACKNOWLEDGE_ACTION ] - }); - }); - - // Reset username - $scope.newUsername = ""; - - }; - -}]); diff --git a/guacamole/src/main/webapp/app/manage/templates/manageConnections.html b/guacamole/src/main/webapp/app/manage/templates/manageConnections.html deleted file mode 100644 index 84773c8e7..000000000 --- a/guacamole/src/main/webapp/app/manage/templates/manageConnections.html +++ /dev/null @@ -1,57 +0,0 @@ - - -
- -
-

{{'MANAGE_CONNECTION.SECTION_HEADER_CONNECTIONS' | translate}}

- -
- - -
-

{{'MANAGE_CONNECTION.HELP_CONNECTIONS' | translate}}

- - - - - -
- -
-
- -
\ No newline at end of file diff --git a/guacamole/src/main/webapp/app/manage/templates/manageSessions.html b/guacamole/src/main/webapp/app/manage/templates/manageSessions.html deleted file mode 100644 index 2b39290e1..000000000 --- a/guacamole/src/main/webapp/app/manage/templates/manageSessions.html +++ /dev/null @@ -1,86 +0,0 @@ - - -
- -
-

{{'MANAGE_SESSION.SECTION_HEADER_SESSIONS' | translate}}

- -
- - -
-

{{'MANAGE_SESSION.HELP_SESSIONS' | translate}}

- - -
- -
- - - - - - - - - - - - - - - - - - - - - - - - -
- {{'MANAGE_SESSION.TABLE_HEADER_SESSION_USERNAME' | translate}} - - {{'MANAGE_SESSION.TABLE_HEADER_SESSION_STARTDATE' | translate}} - - {{'MANAGE_SESSION.TABLE_HEADER_SESSION_REMOTEHOST' | translate}} - - {{'MANAGE_SESSION.TABLE_HEADER_SESSION_CONNECTION_NAME' | translate}} -
- - {{wrapper.activeConnection.username}}{{wrapper.startDate}}{{wrapper.activeConnection.remoteHost}}{{wrapper.name}}
- - -

- {{'MANAGE_SESSION.INFO_NO_SESSIONS' | translate}} -

- - - -
- -
\ No newline at end of file diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUsers.html b/guacamole/src/main/webapp/app/manage/templates/manageUsers.html deleted file mode 100644 index e1231a262..000000000 --- a/guacamole/src/main/webapp/app/manage/templates/manageUsers.html +++ /dev/null @@ -1,56 +0,0 @@ - - -
- -
-

{{'MANAGE_USER.SECTION_HEADER_USERS' | translate}}

- -
- - -
-

{{'MANAGE_USER.HELP_USERS' | translate}}

- - -
- - -
- - - - - - -
- -
\ No newline at end of file diff --git a/guacamole/src/main/webapp/app/navigation/directives/guacPageList.js b/guacamole/src/main/webapp/app/navigation/directives/guacPageList.js new file mode 100644 index 000000000..bd5f909c5 --- /dev/null +++ b/guacamole/src/main/webapp/app/navigation/directives/guacPageList.js @@ -0,0 +1,74 @@ +/* + * 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 which provides a list of links to specific pages. + */ +angular.module('navigation').directive('guacPageList', [function guacPageList() { + + return { + restrict: 'E', + replace: true, + scope: { + + /** + * The array of pages to display. + * + * @type Page[] + */ + pages : '=' + + }, + + templateUrl: 'app/navigation/templates/guacPageList.html', + controller: ['$scope', '$injector', function guacPageListController($scope, $injector) { + + // Get required services + var $location = $injector.get('$location'); + + /** + * Navigate to the given page. + * + * @param {Page} page + * The page to navigate to. + */ + $scope.navigateToPage = function navigateToPage(page) { + $location.path(page.url); + }; + + /** + * Tests whether the given page is the page currently being viewed. + * + * @param {Page} page + * The page to test. + * + * @returns {Boolean} + * true if the given page is the current page, false otherwise. + */ + $scope.isCurrentPage = function isCurrentPage(page) { + return $location.url() === page.url; + }; + + }] // end controller + + }; +}]); diff --git a/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js b/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js index 1868cda06..aff6fff4a 100644 --- a/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js +++ b/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js @@ -214,29 +214,6 @@ angular.module('navigation').directive('guacUserMenu', [function guacUserMenu() }; - /** - * Navigate to the given page. - * - * @param {Page} page - * The page to navigate to. - */ - $scope.navigateToPage = function navigateToPage(page) { - $location.path(page.url); - }; - - /** - * Tests whether the given page should be disabled. - * - * @param {Page} page - * The page to test. - * - * @returns {Boolean} - * true if the given page should be disabled, false otherwise. - */ - $scope.isPageDisabled = function isPageDisabled(page) { - return $location.url() === page.url; - }; - /** * Logs out the current user, redirecting them to back to the login * screen after logout completes. diff --git a/guacamole/src/main/webapp/app/navigation/services/userPageService.js b/guacamole/src/main/webapp/app/navigation/services/userPageService.js index 792c55045..2ff396d56 100644 --- a/guacamole/src/main/webapp/app/navigation/services/userPageService.js +++ b/guacamole/src/main/webapp/app/navigation/services/userPageService.js @@ -137,20 +137,16 @@ angular.module('navigation').factory('userPageService', ['$injector', }; /** - * Returns all the main pages that the current user can visit. This can - * include the home page, manage pages, etc. In the case that there are no - * applicable pages of this sort, it may return a client page. + * Returns all settings pages that the current user can visit. This can + * include any of the various manage pages. * - * @param {ConnectionGroup} rootGroup - * The root of the connection group tree for the current user. - * * @param {PermissionSet} permissions * The permissions for the current user. * * @returns {Page[]} - * An array of all main pages that the current user can visit. + * An array of all settings pages that the current user can visit. */ - var generateMainPages = function generateMainPages(rootGroup, permissions) { + var generateSettingsPages = function generateSettingsPages(permissions) { var pages = []; @@ -202,18 +198,20 @@ angular.module('navigation').factory('userPageService', ['$injector', // A user must be a system administrator to manage sessions PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER); - - // Only include the home page in the list of main pages if the user - // can navigate elsewhere. - var homePage = generateHomePage(rootGroup); - if (homePage === SYSTEM_HOME_PAGE) - pages.push(homePage); + + // If user can manage sessions, add link to sessions management page + if (canManageSessions) { + pages.push(new Page( + 'USER_MENU.ACTION_MANAGE_SESSIONS', + '/settings/sessions' + )); + } // If user can manage users, add link to user management page if (canManageUsers) { pages.push(new Page( 'USER_MENU.ACTION_MANAGE_USERS', - '/manage/modules/users/' + '/settings/users' )); } @@ -221,15 +219,69 @@ angular.module('navigation').factory('userPageService', ['$injector', if (canManageConnections) { pages.push(new Page( 'USER_MENU.ACTION_MANAGE_CONNECTIONS', - '/manage/modules/connections/' + '/settings/connections' )); } + + return pages; + }; + + /** + * Returns a promise which resolves to an array of all settings pages that + * the current user can visit. This can include any of the various manage + * pages. + * + * @returns {Promise.} + * A promise which resolves to an array of all settings pages that the + * current user can visit. + */ + service.getSettingsPages = function getSettingsPages() { + + var deferred = $q.defer(); + + // Retrieve current permissions, resolving main pages if possible + // Resolve promise using settings pages derived from permissions + permissionService.getPermissions(authenticationService.getCurrentUserID()) + .success(function permissionsRetrieved(permissions) { + deferred.resolve(generateSettingsPages(permissions)); + }); - // If user can manage sessions, add link to sessions management page - if (canManageSessions) { + return deferred.promise; + + }; + + /** + * Returns all the main pages that the current user can visit. This can + * include the home page, manage pages, etc. In the case that there are no + * applicable pages of this sort, it may return a client page. + * + * @param {ConnectionGroup} rootGroup + * The root of the connection group tree for the current user. + * + * @param {PermissionSet} permissions + * The permissions for the current user. + * + * @returns {Page[]} + * An array of all main pages that the current user can visit. + */ + var generateMainPages = function generateMainPages(rootGroup, permissions) { + + var pages = []; + + // Get home page and settings pages + var homePage = generateHomePage(rootGroup); + var settingsPages = generateSettingsPages(permissions); + + // Only include the home page in the list of main pages if the user + // can navigate elsewhere. + if (homePage === SYSTEM_HOME_PAGE || settingsPages.length) + pages.push(homePage); + + // Add generic link to the first-available settings page + if (settingsPages.length) { pages.push(new Page( - 'USER_MENU.ACTION_MANAGE_SESSIONS', - '/manage/modules/sessions/' + 'USER_MENU.ACTION_MANAGE_SETTINGS', + settingsPages[0].url )); } diff --git a/guacamole/src/main/webapp/app/navigation/styles/user-menu.css b/guacamole/src/main/webapp/app/navigation/styles/user-menu.css index ba54e150d..b2a649233 100644 --- a/guacamole/src/main/webapp/app/navigation/styles/user-menu.css +++ b/guacamole/src/main/webapp/app/navigation/styles/user-menu.css @@ -139,8 +139,6 @@ top: 100%; right: 0; left: -1px; - margin: 0; - padding: 0; background: #EEE; box-shadow: 0 2px 2px rgba(0, 0, 0, 0.125); @@ -151,6 +149,11 @@ } +.user-menu .options ul { + margin: 0; + padding: 0; +} + .user-menu .user-menu-dropdown.open .options { visibility: visible; } @@ -161,50 +164,40 @@ } .user-menu .options li a { + display: block; cursor: pointer; color: black; text-decoration: none; padding: 0.75em; + + background-repeat: no-repeat; + background-size: 1em; + background-position: 0.75em center; + padding-left: 2.5em; + background-image: url('images/protocol-icons/guac-monitor.png'); + } .user-menu .options li a:hover { background-color: #CDA; } -.user-menu .options li a.disabled, -.user-menu .options li a.disabled:hover { +.user-menu .options li a.current, +.user-menu .options li a.current:hover { background-color: transparent; cursor: default; opacity: 0.25; } -.user-menu .options li a.home, -.user-menu .options li a.manage-users, -.user-menu .options li a.manage-connections, -.user-menu .options li a.manage-sessions, -.user-menu .options li a.change-password, -.user-menu .options li a.logout { - background-repeat: no-repeat; - background-size: 1em; - background-position: 0.75em center; - padding-left: 2.5em; -} - .user-menu .options li a[href="#/"] { background-image: url('images/action-icons/guac-home-dark.png'); } -.user-menu .options li a[href="#/manage/modules/users/"] { - background-image: url('images/user-icons/guac-user.png'); -} - -.user-menu .options li a[href="#/manage/modules/connections/"] { - background-image: url('images/protocol-icons/guac-monitor.png'); -} - -.user-menu .options li a[href="#/manage/modules/sessions/"] { - background-image: url('images/protocol-icons/guac-plug.png'); +.user-menu .options li a[href="#/settings/users"], +.user-menu .options li a[href="#/settings/connections"], +.user-menu .options li a[href="#/settings/sessions"] { + background-image: url('images/action-icons/guac-config-dark.png'); } .user-menu .options li a.change-password { diff --git a/guacamole/src/main/webapp/app/navigation/templates/guacPageList.html b/guacamole/src/main/webapp/app/navigation/templates/guacPageList.html new file mode 100644 index 000000000..d303c8efc --- /dev/null +++ b/guacamole/src/main/webapp/app/navigation/templates/guacPageList.html @@ -0,0 +1,32 @@ + diff --git a/guacamole/src/main/webapp/app/navigation/templates/guacUserMenu.html b/guacamole/src/main/webapp/app/navigation/templates/guacUserMenu.html index 564d95173..ab5d1d9bf 100644 --- a/guacamole/src/main/webapp/app/navigation/templates/guacUserMenu.html +++ b/guacamole/src/main/webapp/app/navigation/templates/guacUserMenu.html @@ -26,31 +26,30 @@ - + diff --git a/guacamole/src/main/webapp/app/settings/controllers/settingsController.js b/guacamole/src/main/webapp/app/settings/controllers/settingsController.js new file mode 100644 index 000000000..ce1a26aef --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/controllers/settingsController.js @@ -0,0 +1,55 @@ +/* + * 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 general settings page. + */ +angular.module('manage').controller('settingsController', ['$scope', '$injector', + function settingsController($scope, $injector) { + + // Required services + var $routeParams = $injector.get('$routeParams'); + var userPageService = $injector.get('userPageService'); + + /** + * The array of settings pages available to the current user, or null if + * not yet known. + * + * @type Page[] + */ + $scope.settingsPages = null; + + /** + * The currently-selected settings tab. This may be 'users', 'connections', + * or 'sessions'. + * + * @type String + */ + $scope.activeTab = $routeParams.tab; + + // Retrieve settings pages + userPageService.getSettingsPages() + .then(function settingsPagesRetrieved(pages) { + $scope.settingsPages = pages; + }); + +}]); diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js new file mode 100644 index 000000000..a900aed11 --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2014 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 connections and connection groups in the system. + */ +angular.module('settings').directive('guacSettingsConnections', [function guacSettingsConnections() { + + return { + // Element only + restrict: 'E', + replace: true, + + scope: { + }, + + templateUrl: 'app/settings/templates/settingsConnections.html', + controller: ['$scope', '$injector', function settingsConnectionsController($scope, $injector) { + + // Required types + var ConnectionGroup = $injector.get('ConnectionGroup'); + var PermissionSet = $injector.get('PermissionSet'); + + // Required services + var $location = $injector.get('$location'); + var authenticationService = $injector.get('authenticationService'); + var connectionGroupService = $injector.get('connectionGroupService'); + var guacNotification = $injector.get('guacNotification'); + var permissionService = $injector.get('permissionService'); + + // Identifier of the current user + var currentUserID = authenticationService.getCurrentUserID(); + + /** + * An action to be provided along with the object sent to + * showStatus which closes the currently-shown status dialog. + */ + var ACKNOWLEDGE_ACTION = { + name : "SETTINGS_CONNECTIONS.ACTION_ACKNOWLEDGE", + // Handle action + callback : function acknowledgeCallback() { + guacNotification.showStatus(false); + } + }; + + /** + * The root connection group of the connection group hierarchy. + * + * @type ConnectionGroup + */ + $scope.rootGroup = null; + + /** + * Whether the current user can manage connections. If the current + * permissions have not yet been loaded, this will be null. + * + * @type Boolean + */ + $scope.canManageConnections = null; + + /** + * Whether the current user can create new connections. If the + * current permissions have not yet been loaded, this will be null. + * + * @type Boolean + */ + $scope.canCreateConnections = null; + + /** + * Whether the current user can create new connection groups. If + * the current permissions have not yet been loaded, this will be + * null. + * + * @type Boolean + */ + $scope.canCreateConnectionGroups = null; + + /** + * All permissions associated with the current user, or null if the + * user's permissions have not yet been loaded. + * + * @type PermissionSet + */ + $scope.permissions = null; + + /** + * 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.rootGroup !== null + && $scope.permissions !== null + && $scope.canManageConnections !== null + && $scope.canCreateConnections !== null + && $scope.canCreateConnectionGroups !== null; + + }; + + // Retrieve current permissions + permissionService.getPermissions(currentUserID) + .success(function permissionsRetrieved(permissions) { + + $scope.permissions = permissions; + + // Ignore permission to update root group + PermissionSet.removeConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, ConnectionGroup.ROOT_IDENTIFIER); + + // Determine whether the current user can create new users + $scope.canCreateConnections = + PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) + || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION); + + // Determine whether the current user can create new users + $scope.canCreateConnectionGroups = + PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) + || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP); + + // Determine whether the current user can manage other connections or groups + $scope.canManageConnections = + + // Permission to manage connections + $scope.canCreateConnections + || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) + || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.DELETE) + + // Permission to manage groups + || $scope.canCreateConnectionGroups + || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) + || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.DELETE); + + // Return to home if there's nothing to do here + if (!$scope.canManageConnections) + $location.path('/'); + + }); + + // Retrieve all connections for which we have UPDATE or DELETE permission + connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, + [PermissionSet.ObjectPermissionType.UPDATE, PermissionSet.ObjectPermissionType.DELETE]) + .success(function connectionGroupReceived(rootGroup) { + $scope.rootGroup = rootGroup; + }); + + }] + }; + +}]); diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js new file mode 100644 index 000000000..e478fbd69 --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js @@ -0,0 +1,367 @@ +/* + * 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('guacSettingsSessions', [function guacSettingsSessions() { + + return { + // Element only + restrict: 'E', + replace: true, + + scope: { + }, + + templateUrl: 'app/settings/templates/settingsSessions.html', + controller: ['$scope', '$injector', function settingsSessionsController($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. + */ + 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. + */ + 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('SETTINGS_SESSIONS.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 : "SETTINGS_SESSIONS.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 : "SETTINGS_SESSIONS.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 : "SETTINGS_SESSIONS.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' : 'SETTINGS_SESSIONS.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' : 'SETTINGS_SESSIONS.DIALOG_HEADER_CONFIRM_DELETE', + 'text' : 'SETTINGS_SESSIONS.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]; + + }; + + }] + }; + +}]); diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js new file mode 100644 index 000000000..20c877b46 --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js @@ -0,0 +1,192 @@ +/* + * 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 users in the system. + */ +angular.module('settings').directive('guacSettingsUsers', [function guacSettingsUsers() { + + return { + // Element only + restrict: 'E', + replace: true, + + scope: { + }, + + templateUrl: 'app/settings/templates/settingsUsers.html', + controller: ['$scope', '$injector', function settingsUsersController($scope, $injector) { + + // Required types + var PermissionSet = $injector.get('PermissionSet'); + var User = $injector.get('User'); + + // Required services + var $location = $injector.get('$location'); + var authenticationService = $injector.get('authenticationService'); + var guacNotification = $injector.get('guacNotification'); + var permissionService = $injector.get('permissionService'); + var userService = $injector.get('userService'); + + // Identifier of the current user + var currentUserID = authenticationService.getCurrentUserID(); + + /** + * An action to be provided along with the object sent to + * showStatus which closes the currently-shown status dialog. + */ + var ACKNOWLEDGE_ACTION = { + name : "SETTINGS_USERS.ACTION_ACKNOWLEDGE", + // Handle action + callback : function acknowledgeCallback() { + guacNotification.showStatus(false); + } + }; + + /** + * All visible users. + * + * @type User[] + */ + $scope.users = null; + + /** + * Whether the current user can manage users. If the current + * permissions have not yet been loaded, this will be null. + * + * @type Boolean + */ + $scope.canManageUsers = null; + + /** + * Whether the current user can create new users. If the current + * permissions have not yet been loaded, this will be null. + * + * @type Boolean + */ + $scope.canCreateUsers = null; + + /** + * The name of the new user to create, if any, when user creation + * is requested via newUser(). + * + * @type String + */ + $scope.newUsername = ""; + + /** + * All permissions associated with the current user, or null if the + * user's permissions have not yet been loaded. + * + * @type PermissionSet + */ + $scope.permissions = null; + + /** + * 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.users !== null + && $scope.permissions !== null + && $scope.canManageUsers !== null + && $scope.canCreateUsers !== null; + + }; + + // Retrieve current permissions + permissionService.getPermissions(currentUserID) + .success(function permissionsRetrieved(permissions) { + + $scope.permissions = permissions; + + // Determine whether the current user can create new users + $scope.canCreateUsers = + PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) + || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_USER); + + // Determine whether the current user can manage other users + $scope.canManageUsers = + $scope.canCreateUsers + || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) + || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.DELETE); + + // Return to home if there's nothing to do here + if (!$scope.canManageUsers) + $location.path('/'); + + }); + + // Retrieve all users for whom we have UPDATE or DELETE permission + userService.getUsers([PermissionSet.ObjectPermissionType.UPDATE, + PermissionSet.ObjectPermissionType.DELETE]) + .success(function usersReceived(users) { + + // Display only other users, not self + $scope.users = users.filter(function isNotSelf(user) { + return user.username !== currentUserID; + }); + + }); + + /** + * Creates a new user having the username specified in the user + * creation interface. + */ + $scope.newUser = function newUser() { + + // Create user skeleton + var user = new User({ + username: $scope.newUsername || '' + }); + + // Create specified user + userService.createUser(user) + + // Add user to visible list upon success + .success(function userCreated() { + $scope.users.push(user); + }) + + // Notify of any errors + .error(function userCreationFailed(error) { + guacNotification.showStatus({ + 'className' : 'error', + 'title' : 'SETTINGS_USERS.DIALOG_HEADER_ERROR', + 'text' : error.message, + 'actions' : [ ACKNOWLEDGE_ACTION ] + }); + }); + + // Reset username + $scope.newUsername = ""; + + }; + + }] + }; + +}]); diff --git a/guacamole/src/main/webapp/app/settings/settingsModule.js b/guacamole/src/main/webapp/app/settings/settingsModule.js new file mode 100644 index 000000000..b3d8c0665 --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/settingsModule.js @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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 module for manipulation of general settings. This is distinct from the + * "manage" module, which deals only with administrator-level system management. + */ +angular.module('settings', [ + 'groupList', + 'list', + 'navigation', + 'notification', + 'rest' +]); diff --git a/guacamole/src/main/webapp/app/manage/styles/sessions.css b/guacamole/src/main/webapp/app/settings/styles/sessions.css similarity index 90% rename from guacamole/src/main/webapp/app/manage/styles/sessions.css rename to guacamole/src/main/webapp/app/settings/styles/sessions.css index ad8a7307e..e13a38293 100644 --- a/guacamole/src/main/webapp/app/manage/styles/sessions.css +++ b/guacamole/src/main/webapp/app/settings/styles/sessions.css @@ -20,15 +20,15 @@ * THE SOFTWARE. */ -.manage table.session-list { +.settings table.session-list { width: 100%; } -.manage table.session-list tr.session:hover { +.settings table.session-list tr.session:hover { background: #CDA; } -.manage table.session-list .select-session { +.settings table.session-list .select-session { min-width: 2em; text-align: center; } diff --git a/guacamole/src/main/webapp/app/settings/styles/settings.css b/guacamole/src/main/webapp/app/settings/styles/settings.css new file mode 100644 index 000000000..961fdc5a0 --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/styles/settings.css @@ -0,0 +1,69 @@ +/* + * 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. + */ + +.settings .header { + 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 { + margin: 0; + padding: 0; + background: rgba(0, 0, 0, 0.0125); + border-bottom: 1px solid rgba(0, 0, 0, 0.05); +} + +.settings-tabs .page-list li { + display: inline-block; + list-style: none; +} + +.settings-tabs .page-list li a[href] { + display: block; + color: black; + text-decoration: none; + padding: 0.75em 1em; +} + +.settings-tabs .page-list li a[href]:visited { + color: black; +} + +.settings-tabs .page-list li a[href]:hover { + background-color: #CDA; +} + +.settings-tabs .page-list li a[href].current, +.settings-tabs .page-list li a[href].current:hover { + background: rgba(0,0,0,0.3); + cursor: default; +} diff --git a/guacamole/src/main/webapp/app/manage/templates/connection.html b/guacamole/src/main/webapp/app/settings/templates/connection.html similarity index 93% rename from guacamole/src/main/webapp/app/manage/templates/connection.html rename to guacamole/src/main/webapp/app/settings/templates/connection.html index b96e3dfa7..97960a2dd 100644 --- a/guacamole/src/main/webapp/app/manage/templates/connection.html +++ b/guacamole/src/main/webapp/app/settings/templates/connection.html @@ -33,7 +33,7 @@ - {{'MANAGE_CONNECTION.INFO_ACTIVE_USER_COUNT' | translate:'{USERS: item.getActiveConnections()}'}} + {{'SETTINGS_CONNECTIONS.INFO_ACTIVE_USER_COUNT' | translate:'{USERS: item.getActiveConnections()}'}} diff --git a/guacamole/src/main/webapp/app/manage/templates/connectionGroup.html b/guacamole/src/main/webapp/app/settings/templates/connectionGroup.html similarity index 100% rename from guacamole/src/main/webapp/app/manage/templates/connectionGroup.html rename to guacamole/src/main/webapp/app/settings/templates/connectionGroup.html diff --git a/guacamole/src/main/webapp/app/settings/templates/settings.html b/guacamole/src/main/webapp/app/settings/templates/settings.html new file mode 100644 index 000000000..59ac3d35e --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/templates/settings.html @@ -0,0 +1,40 @@ + + +
+ +
+

{{'SETTINGS.SECTION_HEADER_SETTINGS' | translate}}

+ +
+ + +
+ +
+ + + + + + +
diff --git a/guacamole/src/main/webapp/app/settings/templates/settingsConnections.html b/guacamole/src/main/webapp/app/settings/templates/settingsConnections.html new file mode 100644 index 000000000..38860b259 --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/templates/settingsConnections.html @@ -0,0 +1,48 @@ +
+ + + +

{{'SETTINGS_CONNECTIONS.HELP_CONNECTIONS' | translate}}

+ + + + + +
+ +
+
diff --git a/guacamole/src/main/webapp/app/settings/templates/settingsSessions.html b/guacamole/src/main/webapp/app/settings/templates/settingsSessions.html new file mode 100644 index 000000000..405c9d332 --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/templates/settingsSessions.html @@ -0,0 +1,77 @@ +
+ + + +

{{'SETTINGS_SESSIONS.HELP_SESSIONS' | translate}}

+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ {{'SETTINGS_SESSIONS.TABLE_HEADER_SESSION_USERNAME' | translate}} + + {{'SETTINGS_SESSIONS.TABLE_HEADER_SESSION_STARTDATE' | translate}} + + {{'SETTINGS_SESSIONS.TABLE_HEADER_SESSION_REMOTEHOST' | translate}} + + {{'SETTINGS_SESSIONS.TABLE_HEADER_SESSION_CONNECTION_NAME' | translate}} +
+ + {{wrapper.activeConnection.username}}{{wrapper.startDate}}{{wrapper.activeConnection.remoteHost}}{{wrapper.name}}
+ + +

+ {{'SETTINGS_SESSIONS.INFO_NO_SESSIONS' | translate}} +

+ + + +
diff --git a/guacamole/src/main/webapp/app/settings/templates/settingsUsers.html b/guacamole/src/main/webapp/app/settings/templates/settingsUsers.html new file mode 100644 index 000000000..79290cb07 --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/templates/settingsUsers.html @@ -0,0 +1,48 @@ +
+ + + +

{{'SETTINGS_USERS.HELP_USERS' | translate}}

+ + +
+ + +
+ + + + + + + +
\ No newline at end of file diff --git a/guacamole/src/main/webapp/app/manage/types/ActiveConnectionWrapper.js b/guacamole/src/main/webapp/app/settings/types/ActiveConnectionWrapper.js similarity index 97% rename from guacamole/src/main/webapp/app/manage/types/ActiveConnectionWrapper.js rename to guacamole/src/main/webapp/app/settings/types/ActiveConnectionWrapper.js index cb2c43e3d..85ffe02e7 100644 --- a/guacamole/src/main/webapp/app/manage/types/ActiveConnectionWrapper.js +++ b/guacamole/src/main/webapp/app/settings/types/ActiveConnectionWrapper.js @@ -23,7 +23,7 @@ /** * A service for defining the ActiveConnectionWrapper class. */ -angular.module('manage').factory('ActiveConnectionWrapper', [ +angular.module('settings').factory('ActiveConnectionWrapper', [ function defineActiveConnectionWrapper() { /** diff --git a/guacamole/src/main/webapp/translations/en_US.json b/guacamole/src/main/webapp/translations/en_US.json index c3d07bee5..c4a00f81f 100644 --- a/guacamole/src/main/webapp/translations/en_US.json +++ b/guacamole/src/main/webapp/translations/en_US.json @@ -10,7 +10,8 @@ "ACTION_DELETE_SESSIONS" : "Kill Sessions", "ACTION_LOGIN" : "Login", "ACTION_LOGOUT" : "Logout", - "ACTION_MANAGE_CONNECTIONS" : "Manage Connections", + "ACTION_MANAGE_CONNECTIONS" : "Connections", + "ACTION_MANAGE_SETTINGS" : "Settings", "ACTION_MANAGE_SESSIONS" : "Active Sessions", "ACTION_MANAGE_USERS" : "Users", "ACTION_NAVIGATE_BACK" : "Back", @@ -154,8 +155,6 @@ "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", "ACTION_CLONE" : "@:APP.ACTION_CLONE", "ACTION_DELETE" : "@:APP.ACTION_DELETE", - "ACTION_NEW_CONNECTION" : "New Connection", - "ACTION_NEW_CONNECTION_GROUP" : "New Group", "ACTION_SAVE" : "@:APP.ACTION_SAVE", "DIALOG_HEADER_CONFIRM_DELETE" : "Delete Connection", @@ -167,14 +166,10 @@ "FORMAT_HISTORY_START" : "@:APP.FORMAT_DATE_TIME_PRECISE", - "HELP_CONNECTIONS" : "Click or tap on a connection below to manage that connection. Depending on your access level, connections can be added and deleted, and their properties (protocol, hostname, port, etc.) can be changed.", - - "INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT", "INFO_CONNECTION_DURATION_UNKNOWN" : "--", "INFO_CONNECTION_ACTIVE_NOW" : "Active Now", "INFO_CONNECTION_NOT_USED" : "This connection has not yet been used.", - "SECTION_HEADER_CONNECTIONS" : "Manage Connections", "SECTION_HEADER_EDIT_CONNECTION" : "Edit Connection", "SECTION_HEADER_HISTORY" : "Usage History", "SECTION_HEADER_PARAMETERS" : "Parameters", @@ -216,7 +211,6 @@ "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", "ACTION_DELETE" : "@:APP.ACTION_DELETE", - "ACTION_NEW_USER" : "New User", "ACTION_SAVE" : "@:APP.ACTION_SAVE", "DIALOG_HEADER_CONFIRM_DELETE" : "Delete User", @@ -233,45 +227,14 @@ "FIELD_HEADER_PASSWORD_AGAIN" : "@:APP.FIELD_HEADER_PASSWORD_AGAIN", "FIELD_HEADER_USERNAME" : "Username:", - "HELP_USERS" : "Click or tap on a user below to manage that user. Depending on your access level, users can be added and deleted, and their passwords can be changed.", - "SECTION_HEADER_CONNECTIONS" : "Connections", "SECTION_HEADER_EDIT_USER" : "Edit User", "SECTION_HEADER_PERMISSIONS" : "Permissions", - "SECTION_HEADER_USERS" : "Users", "TEXT_CONFIRM_DELETE" : "Users cannot be restored after they have been deleted. Are you sure you want to delete this user?" }, - "MANAGE_SESSION" : { - - "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", - "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", - "ACTION_DELETE" : "Kill Sessions", - - "DIALOG_HEADER_CONFIRM_DELETE" : "Kill Sessions", - "DIALOG_HEADER_ERROR" : "Error", - - "FIELD_PLACEHOLDER_FILTER" : "Filter", - - "FORMAT_STARTDATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", - - "HELP_SESSIONS" : "All currently-active Guacamole sessions are listed here. If you wish to kill one or more sessions, check the box next to those sessions and click \"Kill Sessions\". Killing a session will immediately disconnect the user from the associated connection.", - - "INFO_NO_SESSIONS" : "No active sessions", - - "SECTION_HEADER_SESSIONS" : "Active Sessions", - - "TABLE_HEADER_SESSION_USERNAME" : "Username", - "TABLE_HEADER_SESSION_STARTDATE" : "Active since", - "TABLE_HEADER_SESSION_REMOTEHOST" : "Remote host", - "TABLE_HEADER_SESSION_CONNECTION_NAME" : "Connection name", - - "TEXT_CONFIRM_DELETE" : "Are you sure you want to kill all selected sessions? The users using these sessions will be immediately disconnected." - - }, - "PROTOCOL_RDP" : { "FIELD_HEADER_COLOR_DEPTH" : "Color depth:", @@ -414,6 +377,69 @@ }, + "SETTINGS" : { + + "SECTION_HEADER_SETTINGS" : "Settings" + + }, + + "SETTINGS_CONNECTIONS" : { + + "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_NEW_CONNECTION" : "New Connection", + "ACTION_NEW_CONNECTION_GROUP" : "New Group", + + "DIALOG_HEADER_ERROR" : "Error", + + "HELP_CONNECTIONS" : "Click or tap on a connection below to manage that connection. Depending on your access level, connections can be added and deleted, and their properties (protocol, hostname, port, etc.) can be changed.", + + "INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT", + + "SECTION_HEADER_CONNECTIONS" : "Connections" + + }, + + "SETTINGS_USERS" : { + + "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_NEW_USER" : "New User", + + "DIALOG_HEADER_ERROR" : "Error", + + "HELP_USERS" : "Click or tap on a user below to manage that user. Depending on your access level, users can be added and deleted, and their passwords can be changed.", + + "SECTION_HEADER_USERS" : "Users" + + }, + + "SETTINGS_SESSIONS" : { + + "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", + "ACTION_DELETE" : "Kill Sessions", + + "DIALOG_HEADER_CONFIRM_DELETE" : "Kill Sessions", + "DIALOG_HEADER_ERROR" : "Error", + + "FIELD_PLACEHOLDER_FILTER" : "Filter", + + "FORMAT_STARTDATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", + + "HELP_SESSIONS" : "All currently-active Guacamole sessions are listed here. If you wish to kill one or more sessions, check the box next to those sessions and click \"Kill Sessions\". Killing a session will immediately disconnect the user from the associated connection.", + + "INFO_NO_SESSIONS" : "No active sessions", + + "SECTION_HEADER_SESSIONS" : "Active Sessions", + + "TABLE_HEADER_SESSION_USERNAME" : "Username", + "TABLE_HEADER_SESSION_STARTDATE" : "Active since", + "TABLE_HEADER_SESSION_REMOTEHOST" : "Remote host", + "TABLE_HEADER_SESSION_CONNECTION_NAME" : "Connection name", + + "TEXT_CONFIRM_DELETE" : "Are you sure you want to kill all selected sessions? The users using these sessions will be immediately disconnected." + + }, + "USER_MENU" : { "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", @@ -422,6 +448,7 @@ "ACTION_LOGOUT" : "@:APP.ACTION_LOGOUT", "ACTION_MANAGE_CONNECTIONS" : "@:APP.ACTION_MANAGE_CONNECTIONS", "ACTION_MANAGE_SESSIONS" : "@:APP.ACTION_MANAGE_SESSIONS", + "ACTION_MANAGE_SETTINGS" : "@:APP.ACTION_MANAGE_SETTINGS", "ACTION_MANAGE_USERS" : "@:APP.ACTION_MANAGE_USERS", "ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME", "ACTION_SAVE" : "@:APP.ACTION_SAVE",