diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js
index 7faa9c758..5986fdfe1 100644
--- a/guacamole/src/main/webapp/app/client/controllers/clientController.js
+++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js
@@ -544,6 +544,19 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
};
+ /**
+ * Action which immediately disconnects the currently-connected client, if
+ * any.
+ */
+ var DISCONNECT_MENU_ACTION = {
+ name : 'CLIENT.ACTION_DISCONNECT',
+ className : 'danger disconnect',
+ callback : $scope.disconnect
+ };
+
+ // Set client-specific menu actions
+ $scope.clientMenuActions = [ DISCONNECT_MENU_ACTION ];
+
// Clean up when view destroyed
$scope.$on('$destroy', function clientViewDestroyed() {
diff --git a/guacamole/src/main/webapp/app/client/styles/client.css b/guacamole/src/main/webapp/app/client/styles/client.css
index db405fff1..8dde10db0 100644
--- a/guacamole/src/main/webapp/app/client/styles/client.css
+++ b/guacamole/src/main/webapp/app/client/styles/client.css
@@ -117,4 +117,12 @@ body.client {
width: auto;
height: auto;
-}
\ No newline at end of file
+}
+
+.client .user-menu .options li a.disconnect {
+ background-repeat: no-repeat;
+ background-size: 1em;
+ background-position: 0.75em center;
+ padding-left: 2.5em;
+ background-image: url('images/x.png');
+}
diff --git a/guacamole/src/main/webapp/app/client/styles/menu.css b/guacamole/src/main/webapp/app/client/styles/menu.css
index 3895d6324..c35949f01 100644
--- a/guacamole/src/main/webapp/app/client/styles/menu.css
+++ b/guacamole/src/main/webapp/app/client/styles/menu.css
@@ -81,37 +81,6 @@
}
-.menu-content .header button.close {
-
- margin: 0 0.75em;
- padding: 0;
- width: 1.5em;
- height: 1.5em;
- min-width: 0;
-
- box-shadow: none;
- -moz-border-radius: 1.5em;
- -webkit-border-radius: 1.5em;
- -khtml-border-radius: 1.5em;
- border-radius: 1.5em;
-
- -moz-background-size: 0.75em;
- -webkit-background-size: 0.75em;
- -khtml-background-size: 0.75em;
- background-size: 0.75em;
-
- background-image: url('images/x-shadow.png');
- background-repeat: no-repeat;
- background-position: center;
-
- vertical-align: middle;
- -ms-flex-align-self: center;
- -moz-align-self: center;
- -webkit-align-self: center;
- align-self: center;
-
-}
-
.menu-body {
-ms-flex: 1 1 auto;
diff --git a/guacamole/src/main/webapp/app/client/templates/client.html b/guacamole/src/main/webapp/app/client/templates/client.html
index 8cb709c36..01e0fa547 100644
--- a/guacamole/src/main/webapp/app/client/templates/client.html
+++ b/guacamole/src/main/webapp/app/client/templates/client.html
@@ -59,8 +59,7 @@
diff --git a/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js b/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js
index c9c09f7d2..1868cda06 100644
--- a/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js
+++ b/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js
@@ -30,6 +30,16 @@ angular.module('navigation').directive('guacUserMenu', [function guacUserMenu()
restrict: 'E',
replace: true,
scope: {
+
+ /**
+ * Optional array of actions which are specific to this particular
+ * location, as these actions may not be appropriate for other
+ * locations which contain the user menu.
+ *
+ * @type MenuAction[]
+ */
+ localActions : '='
+
},
templateUrl: 'app/navigation/templates/guacUserMenu.html',
@@ -73,15 +83,6 @@ angular.module('navigation').directive('guacUserMenu', [function guacUserMenu()
*/
var document = $document[0];
- /**
- * Whether the current user has sufficient permissions to change
- * his/her own password. If permissions have not yet been loaded,
- * this will be null.
- *
- * @type Boolean
- */
- $scope.canChangePassword = null;
-
/**
* Whether the password edit dialog should be shown.
*
@@ -125,18 +126,6 @@ angular.module('navigation').directive('guacUserMenu', [function guacUserMenu()
*/
$scope.pages = null;
- // Retrieve current permissions
- permissionService.getPermissions(authenticationService.getCurrentUserID())
- .success(function permissionsRetrieved(permissions) {
-
- // Check whether the current user can change their own password
- $scope.canChangePassword = PermissionSet.hasUserPermission(
- permissions, PermissionSet.ObjectPermissionType.UPDATE,
- authenticationService.getCurrentUserID()
- );
-
- });
-
// Retrieve the main pages from the user page service
userPageService.getMainPages()
.then(function retrievedMainPages(pages) {
@@ -258,6 +247,44 @@ angular.module('navigation').directive('guacUserMenu', [function guacUserMenu()
});
};
+ /**
+ * Action which logs out the current user, redirecting them to back
+ * to the login screen after logout completes.
+ */
+ var LOGOUT_ACTION = {
+ name : 'USER_MENU.ACTION_LOGOUT',
+ className : 'logout',
+ callback : $scope.logout
+ };
+
+ /**
+ * Action which shows the password update dialog.
+ */
+ var CHANGE_PASSWORD_ACTION = {
+ name : 'USER_MENU.ACTION_CHANGE_PASSWORD',
+ className : 'change-password',
+ callback : $scope.showPasswordUpdate
+ };
+
+ /**
+ * All available actions for the current user.
+ */
+ $scope.actions = [ LOGOUT_ACTION ];
+
+ // Retrieve current permissions
+ permissionService.getPermissions(authenticationService.getCurrentUserID())
+ .success(function permissionsRetrieved(permissions) {
+
+ // Add action for changing password if permission is granted
+ if (PermissionSet.hasUserPermission(permissions,
+ PermissionSet.ObjectPermissionType.UPDATE,
+ authenticationService.getCurrentUserID()))
+ $scope.actions.unshift(CHANGE_PASSWORD_ACTION);
+
+
+ });
+
+
// Close menu when use clicks anywhere else
document.body.addEventListener('click', function clickOutsideMenu() {
$scope.$apply(function closeMenu() {
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 9c331fbcd..ba54e150d 100644
--- a/guacamole/src/main/webapp/app/navigation/styles/user-menu.css
+++ b/guacamole/src/main/webapp/app/navigation/styles/user-menu.css
@@ -215,6 +215,16 @@
background-image: url('images/action-icons/guac-logout-dark.png');
}
+.user-menu .options li a.danger {
+ color: white;
+ font-weight: bold;
+ background-color: #A43;
+}
+
+.user-menu .options li a.danger:hover {
+ background-color: #C54;
+}
+
.user-menu .password-dialog {
visibility: hidden;
opacity: 0;
diff --git a/guacamole/src/main/webapp/app/navigation/templates/guacUserMenu.html b/guacamole/src/main/webapp/app/navigation/templates/guacUserMenu.html
index 3c00345ae..564d95173 100644
--- a/guacamole/src/main/webapp/app/navigation/templates/guacUserMenu.html
+++ b/guacamole/src/main/webapp/app/navigation/templates/guacUserMenu.html
@@ -28,6 +28,13 @@
+
+ -
+
+ {{action.name | translate}}
+
+
+
-
-
- -
-
- {{'USER_MENU.ACTION_CHANGE_PASSWORD' | translate}}
-
-
-
-
- -
-
- {{'USER_MENU.ACTION_LOGOUT' | translate}}
+
+
-
+
+ {{action.name | translate}}
diff --git a/guacamole/src/main/webapp/app/navigation/types/MenuAction.js b/guacamole/src/main/webapp/app/navigation/types/MenuAction.js
new file mode 100644
index 000000000..eb8c7b72b
--- /dev/null
+++ b/guacamole/src/main/webapp/app/navigation/types/MenuAction.js
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+/**
+ * Provides the MenuAction class definition.
+ */
+angular.module('navigation').factory('MenuAction', [function defineMenuAction() {
+
+ /**
+ * Creates a new MenuAction, which pairs an arbitrary callback with
+ * an action name. The name of this action will ultimately be presented to
+ * the user when the user when this action's associated menu is open.
+ *
+ * @constructor
+ * @param {String} name
+ * The name of this action.
+ *
+ * @param {Function} callback
+ * The callback to call when the user elects to perform this action.
+ *
+ * @param {String} className
+ * The CSS class to associate with this action, if any.
+ */
+ var MenuAction = function MenuAction(name, callback, className) {
+
+ /**
+ * Reference to this MenuAction.
+ *
+ * @type MenuAction
+ */
+ var action = this;
+
+ /**
+ * The CSS class associated with this action.
+ *
+ * @type String
+ */
+ this.className = className;
+
+ /**
+ * The name of this action.
+ *
+ * @type String
+ */
+ this.name = name;
+
+ /**
+ * The callback to call when this action is performed.
+ *
+ * @type Function
+ */
+ this.callback = callback;
+
+ };
+
+ return MenuAction;
+
+}]);
diff --git a/guacamole/src/main/webapp/images/x-shadow.png b/guacamole/src/main/webapp/images/x-shadow.png
deleted file mode 100644
index a274e6155..000000000
Binary files a/guacamole/src/main/webapp/images/x-shadow.png and /dev/null differ
diff --git a/guacamole/src/main/webapp/images/x.png b/guacamole/src/main/webapp/images/x.png
new file mode 100644
index 000000000..294b93994
Binary files /dev/null and b/guacamole/src/main/webapp/images/x.png differ