diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js index 5986fdfe1..0b9ad5f2f 100644 --- a/guacamole/src/main/webapp/app/client/controllers/clientController.js +++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js @@ -34,6 +34,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams var $location = $injector.get('$location'); var guacClientManager = $injector.get('guacClientManager'); var guacNotification = $injector.get('guacNotification'); + var preferenceService = $injector.get('preferenceService'); var userPageService = $injector.get('userPageService'); /** @@ -196,7 +197,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams * * @type String */ - inputMethod : 'none', + inputMethod : preferenceService.preferences.inputMethod, /** * The current scroll state of the menu. diff --git a/guacamole/src/main/webapp/app/client/types/ClientProperties.js b/guacamole/src/main/webapp/app/client/types/ClientProperties.js index 529022cb0..e20665b19 100644 --- a/guacamole/src/main/webapp/app/client/types/ClientProperties.js +++ b/guacamole/src/main/webapp/app/client/types/ClientProperties.js @@ -23,8 +23,11 @@ /** * A service for generating new guacClient properties objects. */ -angular.module('client').factory('ClientProperties', [function defineClientProperties() { - +angular.module('client').factory('ClientProperties', ['$injector', function defineClientProperties($injector) { + + // Required services + var preferenceService = $injector.get('preferenceService'); + /** * Object used for interacting with a guacClient directive. * @@ -81,7 +84,7 @@ angular.module('client').factory('ClientProperties', [function defineClientPrope * * @type Boolean */ - this.emulateAbsoluteMouse = template.emulateAbsoluteMouse || true; + this.emulateAbsoluteMouse = template.emulateAbsoluteMouse || preferenceService.preferences.emulateAbsoluteMouse; /** * The relative Y coordinate of the scroll offset of the display within diff --git a/guacamole/src/main/webapp/app/index/styles/buttons.css b/guacamole/src/main/webapp/app/index/styles/buttons.css index fd49ea256..5990761e3 100644 --- a/guacamole/src/main/webapp/app/index/styles/buttons.css +++ b/guacamole/src/main/webapp/app/index/styles/buttons.css @@ -79,29 +79,35 @@ input[type="submit"]:disabled, button:disabled, button.danger:disabled { opacity: 0.75; } -.button.logout, .button.manage, .button.back, .button.home, .button.change-password { +.button.logout, .button.manage, .button.back, .button.home, .button.change-password, +button.logout, button.manage, button.back, button.home, button.change-password { background-repeat: no-repeat; background-size: 1em; background-position: 0.5em 0.45em; padding-left: 1.8em; } -.button.logout { +.button.logout, +button.logout { background-image: url('images/action-icons/guac-logout.png'); } -.button.manage { +.button.manage, +button.manage { background-image: url('images/action-icons/guac-config.png'); } -.button.back { +.button.back, +button.back { background-image: url('images/action-icons/guac-back.png'); } -.button.home { +.button.home, +button.home { background-image: url('images/action-icons/guac-home.png'); } -.button.change-password { +.button.change-password, +button.change-password { background-image: url('images/action-icons/guac-key.png'); } diff --git a/guacamole/src/main/webapp/app/index/styles/ui.css b/guacamole/src/main/webapp/app/index/styles/ui.css index 87c407bcb..77099e837 100644 --- a/guacamole/src/main/webapp/app/index/styles/ui.css +++ b/guacamole/src/main/webapp/app/index/styles/ui.css @@ -96,6 +96,7 @@ h2 { } +.header ~ * .header, .header ~ .header { margin-top: 1em; border-top: 1px solid rgba(0, 0, 0, 0.125); diff --git a/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js b/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js index aff6fff4a..4d3b3047e 100644 --- a/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js +++ b/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js @@ -45,30 +45,12 @@ angular.module('navigation').directive('guacUserMenu', [function guacUserMenu() templateUrl: 'app/navigation/templates/guacUserMenu.html', controller: ['$scope', '$injector', '$element', function guacUserMenuController($scope, $injector, $element) { - // Get required types - var PermissionSet = $injector.get('PermissionSet'); - // Get required services var $document = $injector.get('$document'); 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'); 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', - // Handle action - callback : function acknowledgeCallback() { - guacNotification.showStatus(false); - } - }; - /** * The outermost element of the user menu directive. * @@ -83,28 +65,6 @@ angular.module('navigation').directive('guacUserMenu', [function guacUserMenu() */ var document = $document[0]; - /** - * Whether the password edit dialog should be shown. - * - * @type Boolean - */ - $scope.showPasswordDialog = false; - - /** - * The new password for the user. - * - * @type String - */ - $scope.newPassword = null; - - /** - * The password match for the user. The update password action will - * fail if $scope.newPassword !== $scope.passwordMatch. - * - * @type String - */ - $scope.newPasswordMatch = null; - /** * Whether the contents of the user menu are currently shown. * @@ -139,81 +99,6 @@ angular.module('navigation').directive('guacUserMenu', [function guacUserMenu() $scope.menuShown = !$scope.menuShown; }; - /** - * Show the password update dialog. - */ - $scope.showPasswordUpdate = function showPasswordUpdate() { - - // Show the dialog - $scope.showPasswordDialog = true; - }; - - /** - * Close the password update dialog. - */ - $scope.closePasswordUpdate = function closePasswordUpdate() { - - // Clear the password fields and close the dialog - $scope.oldPassword = null; - $scope.newPassword = null; - $scope.newPasswordMatch = null; - $scope.showPasswordDialog = false; - }; - - /** - * Update the current user's password to the password currently set within - * the password change dialog. - */ - $scope.updatePassword = function updatePassword() { - - // Verify passwords match - if ($scope.newPasswordMatch !== $scope.newPassword) { - guacNotification.showStatus({ - className : 'error', - title : 'USER_MENU.DIALOG_HEADER_ERROR', - text : 'USER_MENU.ERROR_PASSWORD_MISMATCH', - actions : [ ACKNOWLEDGE_ACTION ] - }); - return; - } - - // Verify that the new password is not blank - if (!$scope.newPassword) { - guacNotification.showStatus({ - className : 'error', - title : 'USER_MENU.DIALOG_HEADER_ERROR', - text : 'USER_MENU.ERROR_PASSWORD_BLANK', - actions : [ ACKNOWLEDGE_ACTION ] - }); - return; - } - - // Save the user with the new password - userService.updateUserPassword($scope.username, $scope.oldPassword, $scope.newPassword) - .success(function passwordUpdated() { - - // Close the password update dialog - $scope.closePasswordUpdate(); - - // Indicate that the password has been changed - guacNotification.showStatus({ - text : 'USER_MENU.PASSWORD_CHANGED', - actions : [ ACKNOWLEDGE_ACTION ] - }); - }) - - // Notify of any errors - .error(function passwordUpdateFailed(error) { - guacNotification.showStatus({ - className : 'error', - title : 'USER_MENU.DIALOG_HEADER_ERROR', - 'text' : error.message, - actions : [ ACKNOWLEDGE_ACTION ] - }); - }); - - }; - /** * Logs out the current user, redirecting them to back to the login * screen after logout completes. @@ -234,34 +119,11 @@ angular.module('navigation').directive('guacUserMenu', [function guacUserMenu() 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/services/userPageService.js b/guacamole/src/main/webapp/app/navigation/services/userPageService.js index 2ff396d56..86453c576 100644 --- a/guacamole/src/main/webapp/app/navigation/services/userPageService.js +++ b/guacamole/src/main/webapp/app/navigation/services/userPageService.js @@ -223,6 +223,12 @@ angular.module('navigation').factory('userPageService', ['$injector', )); } + // Add link to user preferences (always accessible) + pages.push(new Page( + 'USER_MENU.ACTION_MANAGE_PREFERENCES', + '/settings/preferences' + )); + return pages; }; 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 b2a649233..d5dd37946 100644 --- a/guacamole/src/main/webapp/app/navigation/styles/user-menu.css +++ b/guacamole/src/main/webapp/app/navigation/styles/user-menu.css @@ -196,14 +196,11 @@ .user-menu .options li a[href="#/settings/users"], .user-menu .options li a[href="#/settings/connections"], -.user-menu .options li a[href="#/settings/sessions"] { +.user-menu .options li a[href="#/settings/sessions"], +.user-menu .options li a[href="#/settings/preferences"] { background-image: url('images/action-icons/guac-config-dark.png'); } -.user-menu .options li a.change-password { - background-image: url('images/action-icons/guac-key-dark.png'); -} - .user-menu .options li a.logout { background-image: url('images/action-icons/guac-logout-dark.png'); } @@ -217,45 +214,3 @@ .user-menu .options li a.danger:hover { background-color: #C54; } - -.user-menu .password-dialog { - visibility: hidden; - opacity: 0; - -webkit-transition: visibility 0.125s, opacity 0.125s; - -moz-transition: visibility 0.125s, opacity 0.125s; - -ms-transition: visibility 0.125s, opacity 0.125s; - -o-transition: visibility 0.125s, opacity 0.125s; - transition: visibility 0.125s, opacity 0.125s; - position: absolute; - background: white; - padding: 1em; - border: 1px solid rgba(0, 0, 0, 0.25); - box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.25); - margin: 1em; - right: 0; - top: 0; - z-index: 8; -} - -.user-menu .password-dialog .fields { - text-align: right; -} - -.user-menu .password-dialog .action-buttons { - text-align: center; - margin: 0; -} - -.user-menu .password-dialog.shown { - visibility: visible; - opacity: 1; - -webkit-transition: opacity 0.125s; - -moz-transition: opacity 0.125s; - -ms-transition: opacity 0.125s; - -o-transition: opacity 0.125s; - transition: opacity 0.125s; -} - -.user-menu .password-dialog .fields { - width: 100%; -} diff --git a/guacamole/src/main/webapp/app/navigation/templates/guacUserMenu.html b/guacamole/src/main/webapp/app/navigation/templates/guacUserMenu.html index ab5d1d9bf..8df34a53f 100644 --- a/guacamole/src/main/webapp/app/navigation/templates/guacUserMenu.html +++ b/guacamole/src/main/webapp/app/navigation/templates/guacUserMenu.html @@ -52,31 +52,4 @@ - -
- -
- - - - - - - - - - - - - -
{{'USER_MENU.FIELD_HEADER_PASSWORD_OLD' | translate}}
{{'USER_MENU.FIELD_HEADER_PASSWORD_NEW' | translate}}
{{'USER_MENU.FIELD_HEADER_PASSWORD_NEW_AGAIN' | translate}}
-
- - -
- - -
-
- diff --git a/guacamole/src/main/webapp/app/settings/controllers/settingsController.js b/guacamole/src/main/webapp/app/settings/controllers/settingsController.js index ce1a26aef..7608f5f9a 100644 --- a/guacamole/src/main/webapp/app/settings/controllers/settingsController.js +++ b/guacamole/src/main/webapp/app/settings/controllers/settingsController.js @@ -46,6 +46,17 @@ angular.module('manage').controller('settingsController', ['$scope', '$injector' */ $scope.activeTab = $routeParams.tab; + /** + * Returns whether the list of all available settings tabs should be shown. + * + * @returns {Boolean} + * true if the list of available settings tabs should be shown, false + * otherwise. + */ + $scope.showAvailableTabs = function showAvailableTabs() { + return !!$scope.settingsPages && $scope.settingsPages.length > 1; + }; + // Retrieve settings pages userPageService.getSettingsPages() .then(function settingsPagesRetrieved(pages) { diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js new file mode 100644 index 000000000..4a5e0e194 --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js @@ -0,0 +1,167 @@ +/* + * 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 preferences local to the current user. + */ +angular.module('settings').directive('guacSettingsPreferences', [function guacSettingsPreferences() { + + return { + // Element only + restrict: 'E', + replace: true, + + scope: { + }, + + templateUrl: 'app/settings/templates/settingsPreferences.html', + controller: ['$scope', '$injector', function settingsPreferencesController($scope, $injector) { + + // Get required types + var PermissionSet = $injector.get('PermissionSet'); + + // Required services + var authenticationService = $injector.get('authenticationService'); + var guacNotification = $injector.get('guacNotification'); + var userService = $injector.get('userService'); + var permissionService = $injector.get('permissionService'); + var preferenceService = $injector.get('preferenceService'); + + /** + * An action to be provided along with the object sent to + * showStatus which closes the currently-shown status dialog. + */ + var ACKNOWLEDGE_ACTION = { + name : 'SETTINGS_PREFERENCES.ACTION_ACKNOWLEDGE', + // Handle action + callback : function acknowledgeCallback() { + guacNotification.showStatus(false); + } + }; + + /** + * The username of the current user. + * + * @type String + */ + var username = authenticationService.getCurrentUserID(); + + /** + * All currently-set preferences, or their defaults if not yet set. + * + * @type Object. + */ + $scope.preferences = preferenceService.preferences; + + /** + * The new password for the user. + * + * @type String + */ + $scope.newPassword = null; + + /** + * The password match for the user. The update password action will + * fail if $scope.newPassword !== $scope.passwordMatch. + * + * @type String + */ + $scope.newPasswordMatch = null; + + /** + * Whether the current user can change their own password, or null + * if this is not yet known. + * + * @type Boolean + */ + $scope.canChangePassword = null; + + /** + * Update the current user's password to the password currently set within + * the password change dialog. + */ + $scope.updatePassword = function updatePassword() { + + // Verify passwords match + if ($scope.newPasswordMatch !== $scope.newPassword) { + guacNotification.showStatus({ + className : 'error', + title : 'SETTINGS_PREFERENCES.DIALOG_HEADER_ERROR', + text : 'SETTINGS_PREFERENCES.ERROR_PASSWORD_MISMATCH', + actions : [ ACKNOWLEDGE_ACTION ] + }); + return; + } + + // Verify that the new password is not blank + if (!$scope.newPassword) { + guacNotification.showStatus({ + className : 'error', + title : 'SETTINGS_PREFERENCES.DIALOG_HEADER_ERROR', + text : 'SETTINGS_PREFERENCES.ERROR_PASSWORD_BLANK', + actions : [ ACKNOWLEDGE_ACTION ] + }); + return; + } + + // Save the user with the new password + userService.updateUserPassword(username, $scope.oldPassword, $scope.newPassword) + .success(function passwordUpdated() { + + // Clear the password fields + $scope.oldPassword = null; + $scope.newPassword = null; + $scope.newPasswordMatch = null; + + // Indicate that the password has been changed + guacNotification.showStatus({ + text : 'SETTINGS_PREFERENCES.INFO_PASSWORD_CHANGED', + actions : [ ACKNOWLEDGE_ACTION ] + }); + }) + + // Notify of any errors + .error(function passwordUpdateFailed(error) { + guacNotification.showStatus({ + className : 'error', + title : 'SETTINGS_PREFERENCES.DIALOG_HEADER_ERROR', + 'text' : error.message, + actions : [ ACKNOWLEDGE_ACTION ] + }); + }); + + }; + + // Retrieve current permissions + permissionService.getPermissions(username) + .success(function permissionsRetrieved(permissions) { + + // Add action for changing password if permission is granted + $scope.canChangePassword = PermissionSet.hasUserPermission(permissions, + PermissionSet.ObjectPermissionType.UPDATE, username); + + }); + + }] + }; + +}]); diff --git a/guacamole/src/main/webapp/app/settings/services/preferenceService.js b/guacamole/src/main/webapp/app/settings/services/preferenceService.js new file mode 100644 index 000000000..3f2d79fd3 --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/services/preferenceService.js @@ -0,0 +1,117 @@ +/* + * 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 setting and retrieving browser-local preferences. Preferences + * may be any JSON-serializable type. + */ +angular.module('settings').factory('preferenceService', ['$injector', + function preferenceService($injector) { + + // Required services + var $window = $injector.get('$window'); + var $rootScope = $injector.get('$rootScope'); + + var service = {}; + + // The parameter name for getting the history from local storage + var GUAC_PREFERENCES_STORAGE_KEY = "GUAC_PREFERENCES"; + + /** + * All currently-set preferences, as name/value pairs. Each property name + * corresponds to the name of a preference. + * + * @type Object. + */ + service.preferences = { + + /** + * Whether translation of touch to mouse events should emulate an + * absolute pointer device, or a relative pointer device. + * + * @type Boolean + */ + emulateAbsoluteMouse : true, + + /** + * The default input method. This may be either "none", "osk", or + * "text". + * + * @type String + */ + inputMethod : null + + }; + + /** + * Persists the current values of all preferences, if possible. + */ + service.save = function save() { + + // Save updated preferences, ignore inability to use localStorage + try { + if (localStorage) + localStorage.setItem(GUAC_PREFERENCES_STORAGE_KEY, JSON.stringify(service.preferences)); + } + catch (ignore) {} + + }; + + // Get stored preferences, ignore inability to use localStorage + try { + + if (localStorage) { + var preferencesJSON = localStorage.getItem(GUAC_PREFERENCES_STORAGE_KEY); + if (preferencesJSON) + service.preferences = JSON.parse(preferencesJSON); + } + + } + catch (ignore) {} + + // Choose reasonable default input method based on best-guess at platform + if (service.preferences.inputMethod === null) { + + // Use text input by default if platform likely lacks physical keyboard + if (/android|ipad|iphone/i.test(navigator.userAgent)) + service.preferences.inputMethod = 'text'; + else + service.preferences.inputMethod = 'none'; + + } + + // Persist settings when window is unloaded + $window.addEventListener('unload', service.save); + + // Persist settings upon navigation + $rootScope.$on('$routeChangeSuccess', function handleNavigate() { + service.save(); + }); + + // Persist settings upon logout + $rootScope.$on('guacLogout', function handleLogout() { + service.save(); + }); + + return service; + +}]); diff --git a/guacamole/src/main/webapp/app/manage/styles/buttons.css b/guacamole/src/main/webapp/app/settings/styles/buttons.css similarity index 100% rename from guacamole/src/main/webapp/app/manage/styles/buttons.css rename to guacamole/src/main/webapp/app/settings/styles/buttons.css diff --git a/guacamole/src/main/webapp/app/settings/styles/input-method.css b/guacamole/src/main/webapp/app/settings/styles/input-method.css new file mode 100644 index 000000000..39e12e323 --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/styles/input-method.css @@ -0,0 +1,26 @@ +/* + * 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. + */ + +.preferences .input-method .caption { + margin-left: 2em; + margin-right: 2em; +} diff --git a/guacamole/src/main/webapp/app/settings/styles/mouse-mode.css b/guacamole/src/main/webapp/app/settings/styles/mouse-mode.css new file mode 100644 index 000000000..ff37b1d89 --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/styles/mouse-mode.css @@ -0,0 +1,47 @@ +/* + * 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. + */ + +.preferences .mouse-mode .choices { + text-align: center; +} + +.preferences .mouse-mode .choice { + display: inline-block; +} + +.preferences .mouse-mode .choice .figure { + display: inline-block; + vertical-align: middle; + width: 75%; + max-width: 320px; +} + +.preferences .mouse-mode .figure img { + display: block; + width: 100%; + max-width: 320px; + margin: 1em auto; +} + +.preferences .mouse-mode .caption { + text-align: left; +} diff --git a/guacamole/src/main/webapp/app/settings/styles/settings.css b/guacamole/src/main/webapp/app/settings/styles/settings.css index 961fdc5a0..67453a266 100644 --- a/guacamole/src/main/webapp/app/settings/styles/settings.css +++ b/guacamole/src/main/webapp/app/settings/styles/settings.css @@ -32,7 +32,7 @@ .settings .action-buttons { text-align: center; - margin-bottom: 1em; + margin: 1em 0; } .settings-tabs .page-list { diff --git a/guacamole/src/main/webapp/app/settings/styles/update-password.css b/guacamole/src/main/webapp/app/settings/styles/update-password.css new file mode 100644 index 000000000..3cf4d6332 --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/styles/update-password.css @@ -0,0 +1,26 @@ +/* + * 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. + */ + +.preferences .update-password .form { + padding-left: 0.5em; + border-left: 3px solid rgba(0, 0, 0, 0.125); +} diff --git a/guacamole/src/main/webapp/app/settings/templates/settings.html b/guacamole/src/main/webapp/app/settings/templates/settings.html index 59ac3d35e..3c7f26e84 100644 --- a/guacamole/src/main/webapp/app/settings/templates/settings.html +++ b/guacamole/src/main/webapp/app/settings/templates/settings.html @@ -29,12 +29,13 @@ THE SOFTWARE.
- +
+ diff --git a/guacamole/src/main/webapp/app/settings/templates/settingsPreferences.html b/guacamole/src/main/webapp/app/settings/templates/settingsPreferences.html new file mode 100644 index 000000000..d2b0a63cf --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/templates/settingsPreferences.html @@ -0,0 +1,106 @@ +
+ + + +
+

{{'SETTINGS_PREFERENCES.HELP_UPDATE_PASSWORD' | translate}}

+ + +
+ + + + + + + + + + + + + +
{{'SETTINGS_PREFERENCES.FIELD_HEADER_PASSWORD_OLD' | translate}}
{{'SETTINGS_PREFERENCES.FIELD_HEADER_PASSWORD_NEW' | translate}}
{{'SETTINGS_PREFERENCES.FIELD_HEADER_PASSWORD_NEW_AGAIN' | translate}}
+
+ + +
+ +
+
+ + +

{{'SETTINGS_PREFERENCES.SECTION_HEADER_DEFAULT_INPUT_METHOD' | translate}}

+
+

{{'SETTINGS_PREFERENCES.HELP_DEFAULT_INPUT_METHOD' | translate}}

+
+ + +
+ +

+
+ + +
+ +

+
+ + +
+ +

+
+ +
+
+ + +

{{'SETTINGS_PREFERENCES.SECTION_HEADER_DEFAULT_MOUSE_MODE' | translate}}

+
+

{{'SETTINGS_PREFERENCES.HELP_DEFAULT_MOUSE_MODE' | translate}}

+
+ + +
+ +
+ +

+
+
+ + +
+ +
+ +

+
+
+ +
+
+ +
diff --git a/guacamole/src/main/webapp/translations/en_US.json b/guacamole/src/main/webapp/translations/en_US.json index c4a00f81f..f3d8cf281 100644 --- a/guacamole/src/main/webapp/translations/en_US.json +++ b/guacamole/src/main/webapp/translations/en_US.json @@ -4,19 +4,20 @@ "ACTION_ACKNOWLEDGE" : "OK", "ACTION_CANCEL" : "Cancel", - "ACTION_CHANGE_PASSWORD" : "Change Password", "ACTION_CLONE" : "Clone", "ACTION_DELETE" : "Delete", "ACTION_DELETE_SESSIONS" : "Kill Sessions", "ACTION_LOGIN" : "Login", "ACTION_LOGOUT" : "Logout", "ACTION_MANAGE_CONNECTIONS" : "Connections", + "ACTION_MANAGE_PREFERENCES" : "Preferences", "ACTION_MANAGE_SETTINGS" : "Settings", "ACTION_MANAGE_SESSIONS" : "Active Sessions", "ACTION_MANAGE_USERS" : "Users", "ACTION_NAVIGATE_BACK" : "Back", "ACTION_NAVIGATE_HOME" : "Home", "ACTION_SAVE" : "Save", + "ACTION_UPDATE_PASSWORD" : "Update Password", "DIALOG_HEADER_ERROR" : "Error", @@ -399,6 +400,43 @@ }, + "SETTINGS_PREFERENCES" : { + + "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", + "ACTION_UPDATE_PASSWORD" : "@:APP.ACTION_UPDATE_PASSWORD", + + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", + + "ERROR_PASSWORD_BLANK" : "Your password cannot be blank.", + "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", + + "FIELD_HEADER_PASSWORD" : "Password:", + "FIELD_HEADER_PASSWORD_OLD" : "Current Password:", + "FIELD_HEADER_PASSWORD_NEW" : "New Password:", + "FIELD_HEADER_PASSWORD_NEW_AGAIN" : "Confirm New Password:", + "FIELD_HEADER_USERNAME" : "Username:", + + "HELP_DEFAULT_INPUT_METHOD" : "The default input method determines how keyboard events are received by Guacamole. Changing this setting may be necessary when using a mobile device, or when typing through an IME. This setting can be overridden on a per-connection basis within the Guacamole menu.", + "HELP_DEFAULT_MOUSE_MODE" : "The default mouse emulation mode determines how the remote mouse will behave in new connections with respect to touches. This setting can be overridden on a per-connection basis within the Guacamole menu.", + "HELP_INPUT_METHOD_NONE" : "@:CLIENT.HELP_INPUT_METHOD_NONE", + "HELP_INPUT_METHOD_OSK" : "@:CLIENT.HELP_INPUT_METHOD_OSK", + "HELP_INPUT_METHOD_TEXT" : "@:CLIENT.HELP_INPUT_METHOD_TEXT", + "HELP_MOUSE_MODE_ABSOLUTE" : "@:CLIENT.HELP_MOUSE_MODE_ABSOLUTE", + "HELP_MOUSE_MODE_RELATIVE" : "@:CLIENT.HELP_MOUSE_MODE_RELATIVE", + "HELP_UPDATE_PASSWORD" : "If you wish to change your password, enter your current password and the desired new password below, and click \"Update Password\". The change will take effect immediately.", + + "INFO_PASSWORD_CHANGED" : "Password changed.", + + "NAME_INPUT_METHOD_NONE" : "@:CLIENT.NAME_INPUT_METHOD_NONE", + "NAME_INPUT_METHOD_OSK" : "@:CLIENT.NAME_INPUT_METHOD_OSK", + "NAME_INPUT_METHOD_TEXT" : "@:CLIENT.NAME_INPUT_METHOD_TEXT", + + "SECTION_HEADER_DEFAULT_INPUT_METHOD" : "Default Input Method", + "SECTION_HEADER_DEFAULT_MOUSE_MODE" : "Default Mouse Emulation Mode" + + }, + "SETTINGS_USERS" : { "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", @@ -442,27 +480,13 @@ "USER_MENU" : { - "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", - "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", - "ACTION_CHANGE_PASSWORD" : "@:APP.ACTION_CHANGE_PASSWORD", "ACTION_LOGOUT" : "@:APP.ACTION_LOGOUT", "ACTION_MANAGE_CONNECTIONS" : "@:APP.ACTION_MANAGE_CONNECTIONS", + "ACTION_MANAGE_PREFERENCES" : "@:APP.ACTION_MANAGE_PREFERENCES", "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", - - "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", - - "ERROR_PASSWORD_BLANK" : "Your password cannot be blank.", - "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", - - "FIELD_HEADER_PASSWORD_OLD" : "Current Password:", - "FIELD_HEADER_PASSWORD_NEW" : "New Password:", - "FIELD_HEADER_PASSWORD_NEW_AGAIN" : "Confirm New Password:", - - "PASSWORD_CHANGED" : "Password changed." + "ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME" }