diff --git a/guacamole/src/main/webapp/app/client/clientModule.js b/guacamole/src/main/webapp/app/client/clientModule.js index bc58b7ad4..2c127d8d1 100644 --- a/guacamole/src/main/webapp/app/client/clientModule.js +++ b/guacamole/src/main/webapp/app/client/clientModule.js @@ -27,6 +27,7 @@ angular.module('client', [ 'auth', 'element', 'history', + 'navigation', 'notification', 'osk', 'rest', diff --git a/guacamole/src/main/webapp/app/home/controllers/homeController.js b/guacamole/src/main/webapp/app/home/controllers/homeController.js index a50763302..82782be95 100644 --- a/guacamole/src/main/webapp/app/home/controllers/homeController.js +++ b/guacamole/src/main/webapp/app/home/controllers/homeController.js @@ -30,9 +30,11 @@ angular.module('home').controller('homeController', ['$scope', '$injector', var ConnectionGroup = $injector.get("ConnectionGroup"); // Get required services + var $location = $injector.get("$location"); var authenticationService = $injector.get("authenticationService"); var connectionGroupService = $injector.get("connectionGroupService"); var permissionService = $injector.get("permissionService"); + var userPageService = $injector.get("userPageService"); /** * The root connection group, or null if the connection group hierarchy has @@ -67,7 +69,13 @@ angular.module('home').controller('homeController', ['$scope', '$injector', // Retrieve root group and all descendants connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER) .success(function rootGroupRetrieved(rootConnectionGroup) { + $scope.rootConnectionGroup = rootConnectionGroup; + + // Navigate to home page, if not already there + var homePage = userPageService.getHomePage(rootConnectionGroup); + $location.url(homePage.url); + }); // Retrieve current permissions diff --git a/guacamole/src/main/webapp/app/home/homeModule.js b/guacamole/src/main/webapp/app/home/homeModule.js index 97dbbcc97..6c0b9a545 100644 --- a/guacamole/src/main/webapp/app/home/homeModule.js +++ b/guacamole/src/main/webapp/app/home/homeModule.js @@ -20,4 +20,4 @@ * THE SOFTWARE. */ -angular.module('home', ['client', 'history', 'groupList', 'rest', 'userMenu']); +angular.module('home', ['client', 'groupList', 'history', 'navigation', 'rest']); diff --git a/guacamole/src/main/webapp/app/home/templates/home.html b/guacamole/src/main/webapp/app/home/templates/home.html index 7b3b5fc99..4bbfa6f05 100644 --- a/guacamole/src/main/webapp/app/home/templates/home.html +++ b/guacamole/src/main/webapp/app/home/templates/home.html @@ -27,7 +27,7 @@

{{'HOME.SECTION_HEADER_RECENT_CONNECTIONS' | translate}}

- +
diff --git a/guacamole/src/main/webapp/app/index/controllers/indexController.js b/guacamole/src/main/webapp/app/index/controllers/indexController.js index a321a3d05..75b38c1fe 100644 --- a/guacamole/src/main/webapp/app/index/controllers/indexController.js +++ b/guacamole/src/main/webapp/app/index/controllers/indexController.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Glyptodon LLC + * 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 @@ -94,14 +94,18 @@ angular.module('index').controller('indexController', ['$scope', '$injector', // Update title and CSS class upon navigation $scope.$on('$routeChangeSuccess', function(event, current, previous) { + + // If the current route is available + if (current.$$route) { - // Set title - var title = current.$$route.title; - if (title) - $scope.page.title = title; + // Set title + var title = current.$$route.title; + if (title) + $scope.page.title = title; - // Set body CSS class - $scope.page.bodyClassName = current.$$route.bodyClassName || ''; + // Set body CSS class + $scope.page.bodyClassName = current.$$route.bodyClassName || ''; + } }); diff --git a/guacamole/src/main/webapp/app/manage/manageModule.js b/guacamole/src/main/webapp/app/manage/manageModule.js index 69a7a1198..7c2a2cfc3 100644 --- a/guacamole/src/main/webapp/app/manage/manageModule.js +++ b/guacamole/src/main/webapp/app/manage/manageModule.js @@ -27,7 +27,7 @@ angular.module('manage', [ 'groupList', 'list', 'locale', + 'navigation', 'notification', - 'rest', - 'userMenu' + 'rest' ]); diff --git a/guacamole/src/main/webapp/app/userMenu/directives/guacUserMenu.js b/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js similarity index 51% rename from guacamole/src/main/webapp/app/userMenu/directives/guacUserMenu.js rename to guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js index 942644253..f48d30344 100644 --- a/guacamole/src/main/webapp/app/userMenu/directives/guacUserMenu.js +++ b/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js @@ -24,7 +24,7 @@ * A directive which provides a user-oriented menu containing options for * navigation and configuration. */ -angular.module('userMenu').directive('guacUserMenu', [function guacUserMenu() { +angular.module('navigation').directive('guacUserMenu', [function guacUserMenu() { return { restrict: 'E', @@ -37,30 +37,38 @@ angular.module('userMenu').directive('guacUserMenu', [function guacUserMenu() { * * @type PermissionSet */ - permissions : '=' + permissions : '=', + + /** + * The root of the connection group tree. + * + * @type ConnectionGroup + */ + rootGroup : '=' }, - templateUrl: 'app/userMenu/templates/guacUserMenu.html', + templateUrl: 'app/navigation/templates/guacUserMenu.html', controller: ['$scope', '$injector', '$element', function guacUserMenuController($scope, $injector, $element) { // Get required types - var ConnectionGroup = $injector.get("ConnectionGroup"); - var PermissionSet = $injector.get("PermissionSet"); + var ConnectionGroup = $injector.get('ConnectionGroup'); + var PermissionSet = $injector.get('PermissionSet'); // Get required services - var $document = $injector.get("$document"); - var $location = $injector.get("$location"); - var authenticationService = $injector.get("authenticationService"); + var $document = $injector.get('$document'); + var $location = $injector.get('$location'); + var authenticationService = $injector.get('authenticationService'); var guacNotification = $injector.get('guacNotification'); - var userService = $injector.get("userService"); + var userService = $injector.get('userService'); + var userPageService = $injector.get('userPageService'); /** * An action to be provided along with the object sent to * showStatus which closes the currently-shown status dialog. */ var ACKNOWLEDGE_ACTION = { - name : "USER_MENU.ACTION_ACKNOWLEDGE", + name : 'USER_MENU.ACTION_ACKNOWLEDGE', // Handle action callback : function acknowledgeCallback() { guacNotification.showStatus(false); @@ -81,74 +89,6 @@ angular.module('userMenu').directive('guacUserMenu', [function guacUserMenu() { */ var document = $document[0]; - /** - * Whether the option to go to the home screen is disabled. - * - * @type Boolean - */ - $scope.homeDisabled = ($location.path() === '/'); - - /** - * Whether the option to go to the user management interface is - * disabled. Note that shis is different from canManageUsers, - * which deals with whether permission to manage is granted. A user - * may have permission, yet see this option as currently disabled. - * - * @type Boolean - */ - $scope.manageUsersDisabled = - ($location.path() === '/manage/modules/users/'); - - /** - * Whether the option to go to the connection management interface - * is disabled. Note that shis is different from - * canManageConnections, which deals with whether permission to - * manage is granted. A user may have permission, yet see this - * option as currently disabled. - * - * @type Boolean - */ - $scope.manageConnectionsDisabled = - ($location.path() === '/manage/modules/connections/'); - - /** - * Whether the option to go to the session management interface is - * disabled. Note that shis is different from canManageSessions, - * which deals with whether permission to manage is granted. A user - * may have permission, yet see this option as currently disabled. - * - * @type Boolean - */ - $scope.manageSessionsDisabled = - ($location.path() === '/manage/modules/sessions/'); - - /** - * Whether the current user has sufficient permissions to use the - * user management interface. If permissions have not yet been - * loaded, this will be null. - * - * @type Boolean - */ - $scope.canManageUsers = null; - - /** - * Whether the current user has sufficient permissions to use the - * connection management interface. If permissions have not yet been - * loaded, this will be null. - * - * @type Boolean - */ - $scope.canManageConnections = null; - - /** - * Whether the current user has sufficient permissions to use the - * session management interface. If permissions have not yet been - * loaded, this will be null. - * - * @type Boolean - */ - $scope.canManageSessions = null; - /** * Whether the current user has sufficient permissions to change * his/her own password. If permissions have not yet been loaded, @@ -193,69 +133,47 @@ angular.module('userMenu').directive('guacUserMenu', [function guacUserMenu() { * @type String */ $scope.username = authenticationService.getCurrentUserID(); + + /** + * The available main pages for the current user. + * + * @type Page[] + */ + $scope.pages = null; - // Update available menu options when permissions are changed - $scope.$watch('permissions', function permissionsChanged(permissions) { - - // Permissions are unknown if no permissions are provided - if (!permissions) { + /** + * Updates the visible menu items based on the permissions and root + * group on the scope, if available. If either the permissions or + * the root group are not yet available, this function has no + * effect. + */ + var updateMenuItems = function updateMenuItems() { + + // Menu items are unknown until permissions and rootGroup are both available + if (!$scope.permissions || !$scope.rootGroup) { $scope.canChangePassword = null; - $scope.canManageGuacamole = null; + $scope.pages = []; return; } - - // Determine whether the current user can change his/her own password - $scope.canChangePassword = - PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, $scope.username) - && PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.READ, $scope.username); - - // Ignore permission to update root group - PermissionSet.removeConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, ConnectionGroup.ROOT_IDENTIFIER); + + // Retrieve the main pages from the user page service + $scope.pages = userPageService.getMainPages($scope.rootGroup, $scope.permissions); - // Ignore permission to update self - PermissionSet.removeUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, $scope.username); + // Check whether the current user can change their own password + $scope.canChangePassword = PermissionSet.hasUserPermission( + $scope.permissions, PermissionSet.ObjectPermissionType.UPDATE, + authenticationService.getCurrentUserID() + ); + }; - // Determine whether the current user needs access to the user management UI - $scope.canManageUsers = - - // System permissions - PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) - || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_USER) - - // Permission to update users - || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) - - // Permission to delete users - || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.DELETE) - - // Permission to administer users - || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER); - - // Determine whether the current user needs access to the connection management UI - $scope.canManageConnections = - - // System permissions - PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) - || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION) - || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP) - - // Permission to update connections or connection groups - || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) - || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) - - // Permission to delete connections or connection groups - || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.DELETE) - || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.DELETE) - - // Permission to administer connections or connection groups - || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER) - || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER); - - $scope.canManageSessions = - - // A user must be a system administrator to manage sessions - PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER); - + // Update available menu options when permissions are changed + $scope.$watch('permissions', function permissionsChanged() { + updateMenuItems(); + }); + + // Update available menu options when root group is changed + $scope.$watch('rootGroup', function rootGroupChanged() { + updateMenuItems(); }); /** @@ -341,33 +259,28 @@ angular.module('userMenu').directive('guacUserMenu', [function guacUserMenu() { }; /** - * Navigates to the home screen. + * Navigate to the given page. + * + * @param {Page} page + * The page to navigate to. */ - $scope.navigateHome = function navigateHome() { - $location.path('/'); + $scope.navigateToPage = function navigateToPage(page) { + $location.path(page.url); }; - + /** - * Navigates to the user management interface. + * 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.manageUsers = function manageUsers() { - $location.path('/manage/modules/users/'); + $scope.isPageDisabled = function isPageDisabled(page) { + return $location.url() === page.url; }; - - /** - * Navigates to the connection management interface. - */ - $scope.manageConnections = function manageConnections() { - $location.path('/manage/modules/connections/'); - }; - - /** - * Navigates to the user session management interface. - */ - $scope.manageSessions = function manageSessions() { - $location.path('/manage/modules/sessions/'); - }; - + /** * Logs out the current user, redirecting them to back to the login * screen after logout completes. @@ -379,14 +292,14 @@ angular.module('userMenu').directive('guacUserMenu', [function guacUserMenu() { }; // Close menu when use clicks anywhere else - document.body.addEventListener("click", function clickOutsideMenu() { + document.body.addEventListener('click', function clickOutsideMenu() { $scope.$apply(function closeMenu() { $scope.menuShown = false; }); }, false); // Prevent click within menu from triggering the outside-menu handler - element.addEventListener("click", function clickInsideMenu(e) { + element.addEventListener('click', function clickInsideMenu(e) { e.stopPropagation(); }, false); diff --git a/guacamole/src/main/webapp/app/userMenu/userMenuModule.js b/guacamole/src/main/webapp/app/navigation/navigationModule.js similarity index 84% rename from guacamole/src/main/webapp/app/userMenu/userMenuModule.js rename to guacamole/src/main/webapp/app/navigation/navigationModule.js index a7bfd8a9b..089255940 100644 --- a/guacamole/src/main/webapp/app/userMenu/userMenuModule.js +++ b/guacamole/src/main/webapp/app/navigation/navigationModule.js @@ -21,7 +21,6 @@ */ /** - * Module for displaying a user-oriented menu, containing the current username - * and options for navigation, changing the user's password, logging out, etc. + * Module for generating and implementing user navigation options. */ -angular.module('userMenu', ['notification']); +angular.module('navigation', ['notification']); diff --git a/guacamole/src/main/webapp/app/navigation/services/userPageService.js b/guacamole/src/main/webapp/app/navigation/services/userPageService.js new file mode 100644 index 000000000..8a6d37b4f --- /dev/null +++ b/guacamole/src/main/webapp/app/navigation/services/userPageService.js @@ -0,0 +1,206 @@ +/* + * 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 service for generating all the important pages a user can visit. + */ +angular.module('navigation').factory('userPageService', ['$injector', + function userPageService($injector) { + + // Get required types + var ConnectionGroup = $injector.get('ConnectionGroup'); + var PermissionSet = $injector.get('PermissionSet'); + + // Get required services + var authenticationService = $injector.get('authenticationService'); + + var service = {}; + + /** + * Construct a new Page object with the given name and url. + * @constructor + * + * @param {String} name + * The i18n key for the name of the page. + * + * @param {String} url + * The url to the page. + * + * @returns {PageDefinition} + * The newly created PageDefinition object. + */ + var Page = function Page(name, url) { + this.name = name; + this.url = url; + }; + + /** + * Returns an appropriate home page for the current user. + * + * @param {ConnectionGroup} rootGroup + * The root of the connection group tree for the current user. + * + * @returns {Page} + * The user's home page. + */ + service.getHomePage = function getHomePage(rootGroup) { + + // Get children + var connections = rootGroup.childConnections || []; + var connectionGroups = rootGroup.childConnectionGroups || []; + + // Use main connection list screen as home if multiple connections + // are available + if (connections.length + connectionGroups.length === 1) { + + var connection = connections[0]; + var connectionGroup = connectionGroups[0]; + + // Only one connection present, use as home page + if (connection) { + return new Page( + connection.name, + '/client/c/' + connection.identifier + ); + } + + // Only one connection present, use as home page + if (connectionGroup + && connectionGroup.type === ConnectionGroup.Type.BALANCING + && _.isEmpty(connectionGroup.childConnections) + && _.isEmpty(connectionGroup.childConnectionGroups)) { + return new Page( + connectionGroup.name, + '/client/g/' + connectionGroup.identifier + ); + } + + } + + // Default home page + return new Page( + 'USER_MENU.ACTION_NAVIGATE_HOME', + '/' + ); + + }; + + /** + * 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 {Array} + * An array of objects like this + */ + service.getMainPages = function getMainPages(rootGroup, permissions) { + + var pages = []; + + permissions = angular.copy(permissions); + + // Ignore permission to update root group + PermissionSet.removeConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, ConnectionGroup.ROOT_IDENTIFIER); + + // Ignore permission to update self + PermissionSet.removeUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, authenticationService.getCurrentUserID()); + + // Determine whether the current user needs access to the user management UI + var canManageUsers = + + // System permissions + PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) + || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_USER) + + // Permission to update users + || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) + + // Permission to delete users + || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.DELETE) + + // Permission to administer users + || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER); + + // Determine whether the current user needs access to the connection management UI + var canManageConnections = + + // System permissions + PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) + || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION) + || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP) + + // Permission to update connections or connection groups + || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) + || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) + + // Permission to delete connections or connection groups + || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.DELETE) + || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.DELETE) + + // Permission to administer connections or connection groups + || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER) + || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER); + + var canManageSessions = + + // A user must be a system administrator to manage sessions + PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER); + + // Add home page + pages.push(service.getHomePage(rootGroup)); + + // 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/' + )); + } + + // If user can manage connections, add link to connections management page + if (canManageConnections) { + pages.push(new Page( + 'USER_MENU.ACTION_MANAGE_CONNECTIONS', + '/manage/modules/connections/' + )); + } + + // If user can manage sessions, add link to sessions management page + if (canManageSessions) { + pages.push(new Page( + 'USER_MENU.ACTION_MANAGE_SESSIONS', + '/manage/modules/sessions/' + )); + } + + return pages; + }; + + return service; + +}]); \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/userMenu/styles/user-menu.css b/guacamole/src/main/webapp/app/navigation/styles/user-menu.css similarity index 96% rename from guacamole/src/main/webapp/app/userMenu/styles/user-menu.css rename to guacamole/src/main/webapp/app/navigation/styles/user-menu.css index 9d28a3253..9c331fbcd 100644 --- a/guacamole/src/main/webapp/app/userMenu/styles/user-menu.css +++ b/guacamole/src/main/webapp/app/navigation/styles/user-menu.css @@ -191,19 +191,19 @@ padding-left: 2.5em; } -.user-menu .options li a.home { +.user-menu .options li a[href="#/"] { background-image: url('images/action-icons/guac-home-dark.png'); } -.user-menu .options li a.manage-users { +.user-menu .options li a[href="#/manage/modules/users/"] { background-image: url('images/user-icons/guac-user.png'); } -.user-menu .options li a.manage-connections { +.user-menu .options li a[href="#/manage/modules/connections/"] { background-image: url('images/protocol-icons/guac-monitor.png'); } -.user-menu .options li a.manage-sessions { +.user-menu .options li a[href="#/manage/modules/sessions/"] { background-image: url('images/protocol-icons/guac-plug.png'); } diff --git a/guacamole/src/main/webapp/app/userMenu/templates/guacUserMenu.html b/guacamole/src/main/webapp/app/navigation/templates/guacUserMenu.html similarity index 69% rename from guacamole/src/main/webapp/app/userMenu/templates/guacUserMenu.html rename to guacamole/src/main/webapp/app/navigation/templates/guacUserMenu.html index 4f8d8bf52..3c00345ae 100644 --- a/guacamole/src/main/webapp/app/userMenu/templates/guacUserMenu.html +++ b/guacamole/src/main/webapp/app/navigation/templates/guacUserMenu.html @@ -27,39 +27,12 @@