Merge pull request #150 from glyptodon/preferences

GUAC-1053: Add preferences tab to settings screen
This commit is contained in:
James Muehlner
2015-04-19 22:49:59 -07:00
19 changed files with 573 additions and 241 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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');
}

View File

@@ -96,6 +96,7 @@ h2 {
}
.header ~ * .header,
.header ~ .header {
margin-top: 1em;
border-top: 1px solid rgba(0, 0, 0, 0.125);

View File

@@ -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() {

View File

@@ -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;
};

View File

@@ -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%;
}

View File

@@ -52,31 +52,4 @@
</div>
</div>
<!-- Password dialog -->
<div class="password-dialog" ng-class="{shown: showPasswordDialog}">
<!-- Password editor -->
<div class="section">
<table class="fields">
<tr>
<th>{{'USER_MENU.FIELD_HEADER_PASSWORD_OLD' | translate}}</th>
<td><input ng-model="oldPassword" type="password" /></td>
</tr>
<tr>
<th>{{'USER_MENU.FIELD_HEADER_PASSWORD_NEW' | translate}}</th>
<td><input ng-model="newPassword" type="password" /></td>
</tr>
<tr>
<th>{{'USER_MENU.FIELD_HEADER_PASSWORD_NEW_AGAIN' | translate}}</th>
<td><input ng-model="newPasswordMatch" type="password" /></td>
</tr>
</table>
</div>
<!-- Form action buttons -->
<div class="action-buttons">
<button ng-click="updatePassword()">{{'USER_MENU.ACTION_SAVE' | translate}}</button>
<button ng-click="closePasswordUpdate()">{{'USER_MENU.ACTION_CANCEL' | translate}}</button>
</div>
</div>
</div>

View File

@@ -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) {

View File

@@ -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.<String, 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);
});
}]
};
}]);

View File

@@ -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.<String, 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;
}]);

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -32,7 +32,7 @@
.settings .action-buttons {
text-align: center;
margin-bottom: 1em;
margin: 1em 0;
}
.settings-tabs .page-list {

View File

@@ -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);
}

View File

@@ -29,12 +29,13 @@ THE SOFTWARE.
<!-- Available tabs -->
<div class="settings-tabs">
<guac-page-list pages="settingsPages"></guac-page-list>
<guac-page-list pages="settingsPages" ng-show="showAvailableTabs()"></guac-page-list>
</div>
<!-- Selected tab -->
<guac-settings-users ng-if="activeTab === 'users'"></guac-settings-users>
<guac-settings-connections ng-if="activeTab === 'connections'"></guac-settings-connections>
<guac-settings-sessions ng-if="activeTab === 'sessions'"></guac-settings-sessions>
<guac-settings-preferences ng-if="activeTab === 'preferences'"></guac-settings-preferences>
</div>

View File

@@ -0,0 +1,106 @@
<div class="preferences">
<!--
Copyright 2015 Glyptodon LLC.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<!-- Password update -->
<div class="settings section update-password" ng-show="canChangePassword">
<p>{{'SETTINGS_PREFERENCES.HELP_UPDATE_PASSWORD' | translate}}</p>
<!-- Password editor -->
<div class="form">
<table class="fields">
<tr>
<th>{{'SETTINGS_PREFERENCES.FIELD_HEADER_PASSWORD_OLD' | translate}}</th>
<td><input ng-model="oldPassword" type="password" /></td>
</tr>
<tr>
<th>{{'SETTINGS_PREFERENCES.FIELD_HEADER_PASSWORD_NEW' | translate}}</th>
<td><input ng-model="newPassword" type="password" /></td>
</tr>
<tr>
<th>{{'SETTINGS_PREFERENCES.FIELD_HEADER_PASSWORD_NEW_AGAIN' | translate}}</th>
<td><input ng-model="newPasswordMatch" type="password" /></td>
</tr>
</table>
</div>
<!-- Form action buttons -->
<div class="action-buttons">
<button class="change-password" ng-click="updatePassword()">{{'SETTINGS_PREFERENCES.ACTION_UPDATE_PASSWORD' | translate}}</button>
</div>
</div>
<!-- Input method -->
<h2 class="header">{{'SETTINGS_PREFERENCES.SECTION_HEADER_DEFAULT_INPUT_METHOD' | translate}}</h2>
<div class="settings section input-method">
<p>{{'SETTINGS_PREFERENCES.HELP_DEFAULT_INPUT_METHOD' | translate}}</p>
<div class="choices">
<!-- No IME -->
<div class="choice">
<label><input id="ime-none" name="input-method" ng-model="preferences.inputMethod" type="radio" value="none"/> {{'SETTINGS_PREFERENCES.NAME_INPUT_METHOD_NONE' | translate}}</label>
<p class="caption"><label for="ime-none">{{'SETTINGS_PREFERENCES.HELP_INPUT_METHOD_NONE' | translate}}</label></p>
</div>
<!-- Text input -->
<div class="choice">
<label><input id="ime-text" name="input-method" ng-model="preferences.inputMethod" type="radio" value="text"/> {{'SETTINGS_PREFERENCES.NAME_INPUT_METHOD_TEXT' | translate}}</label>
<p class="caption"><label for="ime-text">{{'SETTINGS_PREFERENCES.HELP_INPUT_METHOD_TEXT' | translate}} </label></p>
</div>
<!-- Guac OSK -->
<div class="choice">
<label><input id="ime-osk" name="input-method" ng-model="preferences.inputMethod" type="radio" value="osk"/> {{'SETTINGS_PREFERENCES.NAME_INPUT_METHOD_OSK' | translate}}</label>
<p class="caption"><label for="ime-osk">{{'SETTINGS_PREFERENCES.HELP_INPUT_METHOD_OSK' | translate}}</label></p>
</div>
</div>
</div>
<!-- Mouse mode -->
<h2 class="header">{{'SETTINGS_PREFERENCES.SECTION_HEADER_DEFAULT_MOUSE_MODE' | translate}}</h2>
<div class="settings section mouse-mode">
<p>{{'SETTINGS_PREFERENCES.HELP_DEFAULT_MOUSE_MODE' | translate}}</p>
<div class="choices">
<!-- Touchscreen -->
<div class="choice">
<input name="mouse-mode" ng-model="preferences.emulateAbsoluteMouse" type="radio" ng-value="true" checked="checked" id="absolute"/>
<div class="figure">
<label for="absolute"><img src="images/settings/touchscreen.png" alt="{{'SETTINGS_PREFERENCES.NAME_MOUSE_MODE_ABSOLUTE' | translate}}"/></label>
<p class="caption"><label for="absolute">{{'SETTINGS_PREFERENCES.HELP_MOUSE_MODE_ABSOLUTE' | translate}}</label></p>
</div>
</div>
<!-- Touchpad -->
<div class="choice">
<input name="mouse-mode" ng-model="preferences.emulateAbsoluteMouse" type="radio" ng-value="false" id="relative"/>
<div class="figure">
<label for="relative"><img src="images/settings/touchpad.png" alt="{{'SETTINGS_PREFERENCES.NAME_MOUSE_MODE_RELATIVE' | translate}}"/></label>
<p class="caption"><label for="relative">{{'SETTINGS_PREFERENCES.HELP_MOUSE_MODE_RELATIVE' | translate}}</label></p>
</div>
</div>
</div>
</div>
</div>

View File

@@ -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"
}