mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
Merge pull request #149 from glyptodon/settings-page
GUAC-1053: Move manage sections into tabbed settings page
This commit is contained in:
@@ -104,6 +104,10 @@ angular.module('form').directive('guacForm', [function form() {
|
||||
*/
|
||||
$scope.getSectionHeader = function getSectionHeader(form) {
|
||||
|
||||
// If no form, or no name, then no header
|
||||
if (!form || !form.name)
|
||||
return '';
|
||||
|
||||
return translationStringService.canonicalize($scope.namespace || 'MISSING_NAMESPACE')
|
||||
+ '.SECTION_HEADER_' + translationStringService.canonicalize(form.name);
|
||||
|
||||
@@ -128,6 +132,10 @@ angular.module('form').directive('guacForm', [function form() {
|
||||
*/
|
||||
$scope.getFieldHeader = function getFieldHeader(field) {
|
||||
|
||||
// If no field, or no name, then no header
|
||||
if (!field || !field.name)
|
||||
return '';
|
||||
|
||||
return translationStringService.canonicalize($scope.namespace || 'MISSING_NAMESPACE')
|
||||
+ '.FIELD_HEADER_' + translationStringService.canonicalize(field.name);
|
||||
|
||||
|
@@ -131,8 +131,8 @@ angular.module('form').directive('guacFormField', [function formField() {
|
||||
*/
|
||||
$scope.getFieldOption = function getFieldOption(value) {
|
||||
|
||||
// Don't bother if the model is not yet defined
|
||||
if (!$scope.field)
|
||||
// If no field, or no value, then no corresponding translation string
|
||||
if (!$scope.field || !$scope.field.name || !value)
|
||||
return '';
|
||||
|
||||
return translationStringService.canonicalize($scope.namespace || 'MISSING_NAMESPACE')
|
||||
|
@@ -125,30 +125,12 @@ angular.module('index').config(['$routeProvider', '$locationProvider',
|
||||
resolve : { routeToUserHomePage: routeToUserHomePage }
|
||||
})
|
||||
|
||||
// Connection management screen
|
||||
.when('/manage/modules/connections/', {
|
||||
// Management screen
|
||||
.when('/settings/:tab', {
|
||||
title : 'APP.NAME',
|
||||
bodyClassName : 'manage',
|
||||
templateUrl : 'app/manage/templates/manageConnections.html',
|
||||
controller : 'manageConnectionsController',
|
||||
resolve : { updateCurrentToken: updateCurrentToken }
|
||||
})
|
||||
|
||||
// User management screen
|
||||
.when('/manage/modules/users/', {
|
||||
title : 'APP.NAME',
|
||||
bodyClassName : 'manage',
|
||||
templateUrl : 'app/manage/templates/manageUsers.html',
|
||||
controller : 'manageUsersController',
|
||||
resolve : { updateCurrentToken: updateCurrentToken }
|
||||
})
|
||||
|
||||
// Session management screen
|
||||
.when('/manage/modules/sessions/', {
|
||||
title : 'APP.NAME',
|
||||
bodyClassName : 'manage',
|
||||
templateUrl : 'app/manage/templates/manageSessions.html',
|
||||
controller : 'manageSessionsController',
|
||||
bodyClassName : 'settings',
|
||||
templateUrl : 'app/settings/templates/settings.html',
|
||||
controller : 'settingsController',
|
||||
resolve : { updateCurrentToken: updateCurrentToken }
|
||||
})
|
||||
|
||||
|
@@ -34,5 +34,6 @@ angular.module('index', [
|
||||
'ngTouch',
|
||||
'notification',
|
||||
'pascalprecht.translate',
|
||||
'rest'
|
||||
'rest',
|
||||
'settings'
|
||||
]);
|
||||
|
@@ -317,7 +317,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
|
||||
* Cancels all pending edits, returning to the management page.
|
||||
*/
|
||||
$scope.cancel = function cancel() {
|
||||
$location.path('/manage/modules/connections/');
|
||||
$location.path('/settings/connections');
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -339,7 +339,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
|
||||
// Save the connection
|
||||
connectionService.saveConnection($scope.connection)
|
||||
.success(function savedConnection() {
|
||||
$location.path('/manage/modules/connections/');
|
||||
$location.path('/settings/connections');
|
||||
})
|
||||
|
||||
// Notify of any errors
|
||||
@@ -389,7 +389,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
|
||||
// Delete the connection
|
||||
connectionService.deleteConnection($scope.connection)
|
||||
.success(function deletedConnection() {
|
||||
$location.path('/manage/modules/connections/');
|
||||
$location.path('/settings/connections');
|
||||
})
|
||||
|
||||
// Notify of any errors
|
||||
|
@@ -1,156 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The controller for the connection and connection group administration page.
|
||||
*/
|
||||
angular.module('manage').controller('manageConnectionsController', ['$scope', '$injector',
|
||||
function manageConnectionsController($scope, $injector) {
|
||||
|
||||
// Required types
|
||||
var ConnectionGroup = $injector.get('ConnectionGroup');
|
||||
var PermissionSet = $injector.get('PermissionSet');
|
||||
|
||||
// Required services
|
||||
var $location = $injector.get('$location');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var connectionGroupService = $injector.get('connectionGroupService');
|
||||
var guacNotification = $injector.get('guacNotification');
|
||||
var permissionService = $injector.get('permissionService');
|
||||
|
||||
// Identifier of the current user
|
||||
var currentUserID = authenticationService.getCurrentUserID();
|
||||
|
||||
/**
|
||||
* An action to be provided along with the object sent to showStatus which
|
||||
* closes the currently-shown status dialog.
|
||||
*/
|
||||
var ACKNOWLEDGE_ACTION = {
|
||||
name : "MANAGE_CONNECTION.ACTION_ACKNOWLEDGE",
|
||||
// Handle action
|
||||
callback : function acknowledgeCallback() {
|
||||
guacNotification.showStatus(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The root connection group of the connection group hierarchy.
|
||||
*
|
||||
* @type ConnectionGroup
|
||||
*/
|
||||
$scope.rootGroup = null;
|
||||
|
||||
/**
|
||||
* Whether the current user can manage connections. If the current
|
||||
* permissions have not yet been loaded, this will be null.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
$scope.canManageConnections = null;
|
||||
|
||||
/**
|
||||
* Whether the current user can create new connections. If the current
|
||||
* permissions have not yet been loaded, this will be null.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
$scope.canCreateConnections = null;
|
||||
|
||||
/**
|
||||
* Whether the current user can create new connection groups. If the
|
||||
* current permissions have not yet been loaded, this will be null.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
$scope.canCreateConnectionGroups = null;
|
||||
|
||||
/**
|
||||
* All permissions associated with the current user, or null if the user's
|
||||
* permissions have not yet been loaded.
|
||||
*
|
||||
* @type PermissionSet
|
||||
*/
|
||||
$scope.permissions = null;
|
||||
|
||||
/**
|
||||
* Returns whether critical data has completed being loaded.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if enough data has been loaded for the user interface to be
|
||||
* useful, false otherwise.
|
||||
*/
|
||||
$scope.isLoaded = function isLoaded() {
|
||||
|
||||
return $scope.rootGroup !== null
|
||||
&& $scope.permissions !== null
|
||||
&& $scope.canManageConnections !== null
|
||||
&& $scope.canCreateConnections !== null
|
||||
&& $scope.canCreateConnectionGroups !== null;
|
||||
|
||||
};
|
||||
|
||||
// Retrieve current permissions
|
||||
permissionService.getPermissions(currentUserID)
|
||||
.success(function permissionsRetrieved(permissions) {
|
||||
|
||||
$scope.permissions = permissions;
|
||||
|
||||
// Ignore permission to update root group
|
||||
PermissionSet.removeConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, ConnectionGroup.ROOT_IDENTIFIER);
|
||||
|
||||
// Determine whether the current user can create new users
|
||||
$scope.canCreateConnections =
|
||||
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION);
|
||||
|
||||
// Determine whether the current user can create new users
|
||||
$scope.canCreateConnectionGroups =
|
||||
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP);
|
||||
|
||||
// Determine whether the current user can manage other connections or groups
|
||||
$scope.canManageConnections =
|
||||
|
||||
// Permission to manage connections
|
||||
$scope.canCreateConnections
|
||||
|| PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)
|
||||
|| PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.DELETE)
|
||||
|
||||
// Permission to manage groups
|
||||
|| $scope.canCreateConnectionGroups
|
||||
|| PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)
|
||||
|| PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.DELETE);
|
||||
|
||||
// Return to home if there's nothing to do here
|
||||
if (!$scope.canManageConnections)
|
||||
$location.path('/');
|
||||
|
||||
});
|
||||
|
||||
// Retrieve all connections for which we have UPDATE or DELETE permission
|
||||
connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER,
|
||||
[PermissionSet.ObjectPermissionType.UPDATE, PermissionSet.ObjectPermissionType.DELETE])
|
||||
.success(function connectionGroupReceived(rootGroup) {
|
||||
$scope.rootGroup = rootGroup;
|
||||
});
|
||||
|
||||
}]);
|
@@ -1,349 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The controller for the user session administration page.
|
||||
*/
|
||||
angular.module('manage').controller('manageSessionsController', ['$scope', '$injector',
|
||||
function manageSessionsController($scope, $injector) {
|
||||
|
||||
// Required types
|
||||
var ActiveConnectionWrapper = $injector.get('ActiveConnectionWrapper');
|
||||
var ConnectionGroup = $injector.get('ConnectionGroup');
|
||||
var SortOrder = $injector.get('SortOrder');
|
||||
|
||||
// Required services
|
||||
var $filter = $injector.get('$filter');
|
||||
var $translate = $injector.get('$translate');
|
||||
var activeConnectionService = $injector.get('activeConnectionService');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var connectionGroupService = $injector.get('connectionGroupService');
|
||||
var guacNotification = $injector.get('guacNotification');
|
||||
var permissionService = $injector.get('permissionService');
|
||||
|
||||
/**
|
||||
* All permissions associated with the current user, or null if the user's
|
||||
* permissions have not yet been loaded.
|
||||
*
|
||||
* @type PermissionSet
|
||||
*/
|
||||
$scope.permissions = null;
|
||||
|
||||
/**
|
||||
* The ActiveConnectionWrappers of all active sessions accessible by the
|
||||
* current user, or null if the active sessions have not yet been loaded.
|
||||
*
|
||||
* @type ActiveConnectionWrapper[]
|
||||
*/
|
||||
$scope.wrappers = null;
|
||||
|
||||
/**
|
||||
* SortOrder instance which maintains the sort order of the visible
|
||||
* connection wrappers.
|
||||
*
|
||||
* @type SortOrder
|
||||
*/
|
||||
$scope.wrapperOrder = new SortOrder([
|
||||
'activeConnection.username',
|
||||
'startDate',
|
||||
'activeConnection.remoteHost',
|
||||
'name'
|
||||
]);
|
||||
|
||||
/**
|
||||
* Array of all wrapper properties that are filterable.
|
||||
*
|
||||
* @type String[]
|
||||
*/
|
||||
$scope.filteredWrapperProperties = [
|
||||
'activeConnection.username',
|
||||
'startDate',
|
||||
'activeConnection.remoteHost',
|
||||
'name'
|
||||
];
|
||||
|
||||
/**
|
||||
* All active connections, if known, or null if active connections have not
|
||||
* yet been loaded.
|
||||
*
|
||||
* @type ActiveConnection
|
||||
*/
|
||||
var activeConnections = null;
|
||||
|
||||
/**
|
||||
* Map of all visible connections by object identifier, or null if visible
|
||||
* connections have not yet been loaded.
|
||||
*
|
||||
* @type Object.<String, Connection>
|
||||
*/
|
||||
var connections = null;
|
||||
|
||||
/**
|
||||
* The date format for use for session-related dates.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
var sessionDateFormat = null;
|
||||
|
||||
/**
|
||||
* Map of all currently-selected active connection wrappers by identifier.
|
||||
*
|
||||
* @type Object.<String, ActiveConnectionWrapper>
|
||||
*/
|
||||
var selectedWrappers = {};
|
||||
|
||||
/**
|
||||
* Adds the given connection to the internal set of visible
|
||||
* connections.
|
||||
*
|
||||
* @param {Connection} connection
|
||||
* The connection to add to the internal set of visible connections.
|
||||
*/
|
||||
var addConnection = function addConnection(connection) {
|
||||
|
||||
// Add given connection to set of visible connections
|
||||
connections[connection.identifier] = connection;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds all descendant connections of the given connection group to the
|
||||
* internal set of connections.
|
||||
*
|
||||
* @param {ConnectionGroup} connectionGroup
|
||||
* The connection group whose descendant connections should be added to
|
||||
* the internal set of connections.
|
||||
*/
|
||||
var addDescendantConnections = function addDescendantConnections(connectionGroup) {
|
||||
|
||||
// Add all child connections
|
||||
if (connectionGroup.childConnections)
|
||||
connectionGroup.childConnections.forEach(addConnection);
|
||||
|
||||
// Add all child connection groups
|
||||
if (connectionGroup.childConnectionGroups)
|
||||
connectionGroup.childConnectionGroups.forEach(addDescendantConnections);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps all loaded active connections, storing the resulting array within
|
||||
* the scope. If required data has not yet finished loading, this function
|
||||
* has no effect.
|
||||
*/
|
||||
var wrapActiveConnections = function wrapActiveConnections() {
|
||||
|
||||
// Abort if not all required data is available
|
||||
if (!activeConnections || !connections || !sessionDateFormat)
|
||||
return;
|
||||
|
||||
// Wrap all active connections for sake of display
|
||||
$scope.wrappers = [];
|
||||
for (var identifier in activeConnections) {
|
||||
|
||||
var activeConnection = activeConnections[identifier];
|
||||
var connection = connections[activeConnection.connectionIdentifier];
|
||||
|
||||
$scope.wrappers.push(new ActiveConnectionWrapper(
|
||||
connection.name,
|
||||
$filter('date')(activeConnection.startDate, sessionDateFormat),
|
||||
activeConnection
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Query the user's permissions
|
||||
permissionService.getPermissions(authenticationService.getCurrentUserID())
|
||||
.success(function permissionsReceived(retrievedPermissions) {
|
||||
$scope.permissions = retrievedPermissions;
|
||||
});
|
||||
|
||||
// Retrieve all connections
|
||||
connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER)
|
||||
.success(function connectionGroupReceived(retrievedRootGroup) {
|
||||
|
||||
// Load connections from retrieved group tree
|
||||
connections = {};
|
||||
addDescendantConnections(retrievedRootGroup);
|
||||
|
||||
// Attempt to produce wrapped list of active connections
|
||||
wrapActiveConnections();
|
||||
|
||||
});
|
||||
|
||||
// Query active sessions
|
||||
activeConnectionService.getActiveConnections().success(function sessionsRetrieved(retrievedActiveConnections) {
|
||||
|
||||
// Store received list
|
||||
activeConnections = retrievedActiveConnections;
|
||||
|
||||
// Attempt to produce wrapped list of active connections
|
||||
wrapActiveConnections();
|
||||
|
||||
});
|
||||
|
||||
// Get session date format
|
||||
$translate('MANAGE_SESSION.FORMAT_STARTDATE').then(function sessionDateFormatReceived(retrievedSessionDateFormat) {
|
||||
|
||||
// Store received date format
|
||||
sessionDateFormat = retrievedSessionDateFormat;
|
||||
|
||||
// Attempt to produce wrapped list of active connections
|
||||
wrapActiveConnections();
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns whether critical data has completed being loaded.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if enough data has been loaded for the user interface to be
|
||||
* useful, false otherwise.
|
||||
*/
|
||||
$scope.isLoaded = function isLoaded() {
|
||||
|
||||
return $scope.wrappers !== null
|
||||
&& $scope.sessionDateFormat !== null
|
||||
&& $scope.permissions !== null;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* An action to be provided along with the object sent to showStatus which
|
||||
* closes the currently-shown status dialog.
|
||||
*/
|
||||
var ACKNOWLEDGE_ACTION = {
|
||||
name : "MANAGE_SESSION.ACTION_ACKNOWLEDGE",
|
||||
// Handle action
|
||||
callback : function acknowledgeCallback() {
|
||||
guacNotification.showStatus(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An action to be provided along with the object sent to showStatus which
|
||||
* closes the currently-shown status dialog.
|
||||
*/
|
||||
var CANCEL_ACTION = {
|
||||
name : "MANAGE_SESSION.ACTION_CANCEL",
|
||||
// Handle action
|
||||
callback : function cancelCallback() {
|
||||
guacNotification.showStatus(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An action to be provided along with the object sent to showStatus which
|
||||
* immediately deletes the currently selected sessions.
|
||||
*/
|
||||
var DELETE_ACTION = {
|
||||
name : "MANAGE_SESSION.ACTION_DELETE",
|
||||
className : "danger",
|
||||
// Handle action
|
||||
callback : function deleteCallback() {
|
||||
deleteSessionsImmediately();
|
||||
guacNotification.showStatus(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Immediately deletes the selected sessions, without prompting the user for
|
||||
* confirmation.
|
||||
*/
|
||||
var deleteSessionsImmediately = function deleteSessionsImmediately() {
|
||||
|
||||
// Perform deletion
|
||||
activeConnectionService.deleteActiveConnections(Object.keys(selectedWrappers))
|
||||
.success(function activeConnectionsDeleted() {
|
||||
|
||||
// Remove deleted connections from wrapper array
|
||||
$scope.wrappers = $scope.wrappers.filter(function activeConnectionStillExists(wrapper) {
|
||||
return !(wrapper.activeConnection.identifier in selectedWrappers);
|
||||
});
|
||||
|
||||
// Clear selection
|
||||
selectedWrappers = {};
|
||||
|
||||
})
|
||||
|
||||
// Notify of any errors
|
||||
.error(function activeConnectionDeletionFailed(error) {
|
||||
guacNotification.showStatus({
|
||||
'className' : 'error',
|
||||
'title' : 'MANAGE_SESSION.DIALOG_HEADER_ERROR',
|
||||
'text' : error.message,
|
||||
'actions' : [ ACKNOWLEDGE_ACTION ]
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete all selected sessions, prompting the user first to confirm that
|
||||
* deletion is desired.
|
||||
*/
|
||||
$scope.deleteSessions = function deleteSessions() {
|
||||
// Confirm deletion request
|
||||
guacNotification.showStatus({
|
||||
'title' : 'MANAGE_SESSION.DIALOG_HEADER_CONFIRM_DELETE',
|
||||
'text' : 'MANAGE_SESSION.TEXT_CONFIRM_DELETE',
|
||||
'actions' : [ DELETE_ACTION, CANCEL_ACTION]
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the selected sessions can be deleted.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if selected sessions can be deleted, false otherwise.
|
||||
*/
|
||||
$scope.canDeleteSessions = function canDeleteSessions() {
|
||||
|
||||
// We can delete sessions if at least one is selected
|
||||
for (var identifier in selectedWrappers)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Called whenever an active connection wrapper changes selected status.
|
||||
*
|
||||
* @param {ActiveConnectionWrapper} wrapper
|
||||
* The wrapper whose selected status has changed.
|
||||
*/
|
||||
$scope.wrapperSelectionChange = function wrapperSelectionChange(wrapper) {
|
||||
|
||||
// Add wrapper to map if selected
|
||||
if (wrapper.checked)
|
||||
selectedWrappers[wrapper.activeConnection.identifier] = wrapper;
|
||||
|
||||
// Otherwise, remove wrapper from map
|
||||
else
|
||||
delete selectedWrappers[wrapper.activeConnection.identifier];
|
||||
|
||||
};
|
||||
|
||||
}]);
|
@@ -472,7 +472,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
|
||||
* Cancels all pending edits, returning to the management page.
|
||||
*/
|
||||
$scope.cancel = function cancel() {
|
||||
$location.path('/manage/modules/users/');
|
||||
$location.path('/settings/users');
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -498,7 +498,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
|
||||
// Upon success, save any changed permissions
|
||||
permissionService.patchPermissions($scope.user.username, permissionsAdded, permissionsRemoved)
|
||||
.success(function patchedUserPermissions() {
|
||||
$location.path('/manage/modules/users/');
|
||||
$location.path('/settings/users');
|
||||
})
|
||||
|
||||
// Notify of any errors
|
||||
@@ -560,7 +560,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
|
||||
// Delete the user
|
||||
userService.deleteUser($scope.user)
|
||||
.success(function deletedUser() {
|
||||
$location.path('/manage/modules/users/');
|
||||
$location.path('/settings/users');
|
||||
})
|
||||
|
||||
// Notify of any errors
|
||||
|
@@ -1,179 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The controller for the user administration page.
|
||||
*/
|
||||
angular.module('manage').controller('manageUsersController', ['$scope', '$injector',
|
||||
function manageUsersController($scope, $injector) {
|
||||
|
||||
// Required types
|
||||
var PermissionSet = $injector.get('PermissionSet');
|
||||
var User = $injector.get('User');
|
||||
|
||||
// Required services
|
||||
var $location = $injector.get('$location');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var guacNotification = $injector.get('guacNotification');
|
||||
var permissionService = $injector.get('permissionService');
|
||||
var userService = $injector.get('userService');
|
||||
|
||||
// Identifier of the current user
|
||||
var currentUserID = authenticationService.getCurrentUserID();
|
||||
|
||||
/**
|
||||
* An action to be provided along with the object sent to showStatus which
|
||||
* closes the currently-shown status dialog.
|
||||
*/
|
||||
var ACKNOWLEDGE_ACTION = {
|
||||
name : "MANAGE_USER.ACTION_ACKNOWLEDGE",
|
||||
// Handle action
|
||||
callback : function acknowledgeCallback() {
|
||||
guacNotification.showStatus(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* All visible users.
|
||||
*
|
||||
* @type User[]
|
||||
*/
|
||||
$scope.users = null;
|
||||
|
||||
/**
|
||||
* Whether the current user can manage users. If the current permissions
|
||||
* have not yet been loaded, this will be null.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
$scope.canManageUsers = null;
|
||||
|
||||
/**
|
||||
* Whether the current user can create new users. If the current
|
||||
* permissions have not yet been loaded, this will be null.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
$scope.canCreateUsers = null;
|
||||
|
||||
/**
|
||||
* The name of the new user to create, if any, when user creation is
|
||||
* requested via newUser().
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
$scope.newUsername = "";
|
||||
|
||||
/**
|
||||
* All permissions associated with the current user, or null if the user's
|
||||
* permissions have not yet been loaded.
|
||||
*
|
||||
* @type PermissionSet
|
||||
*/
|
||||
$scope.permissions = null;
|
||||
|
||||
/**
|
||||
* Returns whether critical data has completed being loaded.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if enough data has been loaded for the user interface to be
|
||||
* useful, false otherwise.
|
||||
*/
|
||||
$scope.isLoaded = function isLoaded() {
|
||||
|
||||
return $scope.users !== null
|
||||
&& $scope.permissions !== null
|
||||
&& $scope.canManageUsers !== null
|
||||
&& $scope.canCreateUsers !== null;
|
||||
|
||||
};
|
||||
|
||||
// Retrieve current permissions
|
||||
permissionService.getPermissions(currentUserID)
|
||||
.success(function permissionsRetrieved(permissions) {
|
||||
|
||||
$scope.permissions = permissions;
|
||||
|
||||
// Determine whether the current user can create new users
|
||||
$scope.canCreateUsers =
|
||||
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_USER);
|
||||
|
||||
// Determine whether the current user can manage other users
|
||||
$scope.canManageUsers =
|
||||
$scope.canCreateUsers
|
||||
|| PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)
|
||||
|| PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.DELETE);
|
||||
|
||||
// Return to home if there's nothing to do here
|
||||
if (!$scope.canManageUsers)
|
||||
$location.path('/');
|
||||
|
||||
});
|
||||
|
||||
// Retrieve all users for whom we have UPDATE or DELETE permission
|
||||
userService.getUsers([PermissionSet.ObjectPermissionType.UPDATE,
|
||||
PermissionSet.ObjectPermissionType.DELETE])
|
||||
.success(function usersReceived(users) {
|
||||
|
||||
// Display only other users, not self
|
||||
$scope.users = users.filter(function isNotSelf(user) {
|
||||
return user.username !== currentUserID;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a new user having the username specified in the user creation
|
||||
* interface.
|
||||
*/
|
||||
$scope.newUser = function newUser() {
|
||||
|
||||
// Create user skeleton
|
||||
var user = new User({
|
||||
username: $scope.newUsername || ''
|
||||
});
|
||||
|
||||
// Create specified user
|
||||
userService.createUser(user)
|
||||
|
||||
// Add user to visible list upon success
|
||||
.success(function userCreated() {
|
||||
$scope.users.push(user);
|
||||
})
|
||||
|
||||
// Notify of any errors
|
||||
.error(function userCreationFailed(error) {
|
||||
guacNotification.showStatus({
|
||||
'className' : 'error',
|
||||
'title' : 'MANAGE_USER.DIALOG_HEADER_ERROR',
|
||||
'text' : error.message,
|
||||
'actions' : [ ACKNOWLEDGE_ACTION ]
|
||||
});
|
||||
});
|
||||
|
||||
// Reset username
|
||||
$scope.newUsername = "";
|
||||
|
||||
};
|
||||
|
||||
}]);
|
@@ -1,57 +0,0 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<div class="view" ng-class="{loading: !isLoaded()}">
|
||||
|
||||
<div class="header">
|
||||
<h2>{{'MANAGE_CONNECTION.SECTION_HEADER_CONNECTIONS' | translate}}</h2>
|
||||
<guac-user-menu></guac-user-menu>
|
||||
</div>
|
||||
|
||||
<!-- Connection management -->
|
||||
<div class="settings section connections">
|
||||
<p>{{'MANAGE_CONNECTION.HELP_CONNECTIONS' | translate}}</p>
|
||||
|
||||
<!-- Form action buttons -->
|
||||
<div class="action-buttons">
|
||||
|
||||
<a class="add-connection button"
|
||||
ng-show="canCreateConnections"
|
||||
href="#/manage/connections/">{{'MANAGE_CONNECTION.ACTION_NEW_CONNECTION' | translate}}</a>
|
||||
|
||||
<a class="add-connection-group button"
|
||||
ng-show="canCreateConnectionGroups"
|
||||
href="#/manage/connectionGroups/">{{'MANAGE_CONNECTION.ACTION_NEW_CONNECTION_GROUP' | translate}}</a>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- List of accessible connections and groups -->
|
||||
<div class="connection-list">
|
||||
<guac-group-list
|
||||
page-size="25"
|
||||
connection-group="rootGroup"
|
||||
connection-template="'app/manage/templates/connection.html'"
|
||||
connection-group-template="'app/manage/templates/connectionGroup.html'"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
@@ -1,86 +0,0 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<div class="view" ng-class="{loading: !isLoaded()}">
|
||||
|
||||
<div class="header">
|
||||
<h2>{{'MANAGE_SESSION.SECTION_HEADER_SESSIONS' | translate}}</h2>
|
||||
<guac-user-menu></guac-user-menu>
|
||||
</div>
|
||||
|
||||
<!-- User Session management -->
|
||||
<div class="settings section sessions">
|
||||
<p>{{'MANAGE_SESSION.HELP_SESSIONS' | translate}}</p>
|
||||
|
||||
<!-- Form action buttons -->
|
||||
<div class="action-buttons">
|
||||
<button class="delete-sessions danger" ng-disabled="!canDeleteSessions()" ng-click="deleteSessions()">{{'MANAGE_SESSION.ACTION_DELETE' | translate}}</button>
|
||||
</div>
|
||||
|
||||
<!-- Session filter -->
|
||||
<guac-filter filtered-items="filteredWrappers" items="wrappers"
|
||||
placeholder="'MANAGE_SESSION.FIELD_PLACEHOLDER_FILTER' | translate"
|
||||
properties="filteredWrapperProperties"></guac-filter>
|
||||
|
||||
<!-- List of current user sessions -->
|
||||
<table class="sorted session-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="select-session"></th>
|
||||
<th guac-sort-order="wrapperOrder" guac-sort-property="'activeConnection.username'">
|
||||
{{'MANAGE_SESSION.TABLE_HEADER_SESSION_USERNAME' | translate}}
|
||||
</th>
|
||||
<th guac-sort-order="wrapperOrder" guac-sort-property="'startDate'">
|
||||
{{'MANAGE_SESSION.TABLE_HEADER_SESSION_STARTDATE' | translate}}
|
||||
</th>
|
||||
<th guac-sort-order="wrapperOrder" guac-sort-property="'activeConnection.remoteHost'">
|
||||
{{'MANAGE_SESSION.TABLE_HEADER_SESSION_REMOTEHOST' | translate}}
|
||||
</th>
|
||||
<th guac-sort-order="wrapperOrder" guac-sort-property="'name'">
|
||||
{{'MANAGE_SESSION.TABLE_HEADER_SESSION_CONNECTION_NAME' | translate}}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="wrapper in wrapperPage" class="session">
|
||||
<td class="select-session">
|
||||
<input ng-change="wrapperSelectionChange(wrapper)" type="checkbox" ng-model="wrapper.checked" />
|
||||
</td>
|
||||
<td>{{wrapper.activeConnection.username}}</td>
|
||||
<td>{{wrapper.startDate}}</td>
|
||||
<td>{{wrapper.activeConnection.remoteHost}}</td>
|
||||
<td>{{wrapper.name}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Text displayed if no sessions exist -->
|
||||
<p class="placeholder" ng-hide="wrapperPage.length">
|
||||
{{'MANAGE_SESSION.INFO_NO_SESSIONS' | translate}}
|
||||
</p>
|
||||
|
||||
<!-- Pager for session list -->
|
||||
<guac-pager page="wrapperPage" page-size="25"
|
||||
items="filteredWrappers | orderBy : wrapperOrder.predicate"></guac-pager>
|
||||
</div>
|
||||
|
||||
</div>
|
@@ -1,56 +0,0 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<div class="view" ng-class="{loading: !isLoaded()}">
|
||||
|
||||
<div class="header">
|
||||
<h2>{{'MANAGE_USER.SECTION_HEADER_USERS' | translate}}</h2>
|
||||
<guac-user-menu></guac-user-menu>
|
||||
</div>
|
||||
|
||||
<!-- User management -->
|
||||
<div class="settings section users">
|
||||
<p>{{'MANAGE_USER.HELP_USERS' | translate}}</p>
|
||||
|
||||
<!-- Form action buttons -->
|
||||
<div class="action-buttons" ng-show="canCreateUsers">
|
||||
<input type="text" ng-model="newUsername" class="name username" autocorrect="off" autocapitalize="off"/>
|
||||
<button class="add-user" ng-click="newUser()">{{'MANAGE_USER.ACTION_NEW_USER' | translate}}</button>
|
||||
</div>
|
||||
|
||||
<!-- List of users this user has access to -->
|
||||
<div class="user-list">
|
||||
<div ng-repeat="user in userPage" class="user list-item">
|
||||
<a ng-href="#/manage/users/{{user.username}}">
|
||||
<div class="caption">
|
||||
<div class="icon user"></div>
|
||||
<span class="name">{{user.username}}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pager controls for user list -->
|
||||
<guac-pager page="userPage" page-size="25" items="users | orderBy : 'username'"></guac-pager>
|
||||
</div>
|
||||
|
||||
</div>
|
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A directive which provides a list of links to specific pages.
|
||||
*/
|
||||
angular.module('navigation').directive('guacPageList', [function guacPageList() {
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
scope: {
|
||||
|
||||
/**
|
||||
* The array of pages to display.
|
||||
*
|
||||
* @type Page[]
|
||||
*/
|
||||
pages : '='
|
||||
|
||||
},
|
||||
|
||||
templateUrl: 'app/navigation/templates/guacPageList.html',
|
||||
controller: ['$scope', '$injector', function guacPageListController($scope, $injector) {
|
||||
|
||||
// Get required services
|
||||
var $location = $injector.get('$location');
|
||||
|
||||
/**
|
||||
* Navigate to the given page.
|
||||
*
|
||||
* @param {Page} page
|
||||
* The page to navigate to.
|
||||
*/
|
||||
$scope.navigateToPage = function navigateToPage(page) {
|
||||
$location.path(page.url);
|
||||
};
|
||||
|
||||
/**
|
||||
* Tests whether the given page is the page currently being viewed.
|
||||
*
|
||||
* @param {Page} page
|
||||
* The page to test.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the given page is the current page, false otherwise.
|
||||
*/
|
||||
$scope.isCurrentPage = function isCurrentPage(page) {
|
||||
return $location.url() === page.url;
|
||||
};
|
||||
|
||||
}] // end controller
|
||||
|
||||
};
|
||||
}]);
|
@@ -214,29 +214,6 @@ angular.module('navigation').directive('guacUserMenu', [function guacUserMenu()
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to the given page.
|
||||
*
|
||||
* @param {Page} page
|
||||
* The page to navigate to.
|
||||
*/
|
||||
$scope.navigateToPage = function navigateToPage(page) {
|
||||
$location.path(page.url);
|
||||
};
|
||||
|
||||
/**
|
||||
* Tests whether the given page should be disabled.
|
||||
*
|
||||
* @param {Page} page
|
||||
* The page to test.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the given page should be disabled, false otherwise.
|
||||
*/
|
||||
$scope.isPageDisabled = function isPageDisabled(page) {
|
||||
return $location.url() === page.url;
|
||||
};
|
||||
|
||||
/**
|
||||
* Logs out the current user, redirecting them to back to the login
|
||||
* screen after logout completes.
|
||||
|
@@ -137,20 +137,16 @@ angular.module('navigation').factory('userPageService', ['$injector',
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns all the main pages that the current user can visit. This can
|
||||
* include the home page, manage pages, etc. In the case that there are no
|
||||
* applicable pages of this sort, it may return a client page.
|
||||
* Returns all settings pages that the current user can visit. This can
|
||||
* include any of the various manage pages.
|
||||
*
|
||||
* @param {ConnectionGroup} rootGroup
|
||||
* The root of the connection group tree for the current user.
|
||||
*
|
||||
* @param {PermissionSet} permissions
|
||||
* The permissions for the current user.
|
||||
*
|
||||
* @returns {Page[]}
|
||||
* An array of all main pages that the current user can visit.
|
||||
* An array of all settings pages that the current user can visit.
|
||||
*/
|
||||
var generateMainPages = function generateMainPages(rootGroup, permissions) {
|
||||
var generateSettingsPages = function generateSettingsPages(permissions) {
|
||||
|
||||
var pages = [];
|
||||
|
||||
@@ -202,18 +198,20 @@ angular.module('navigation').factory('userPageService', ['$injector',
|
||||
|
||||
// A user must be a system administrator to manage sessions
|
||||
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER);
|
||||
|
||||
// Only include the home page in the list of main pages if the user
|
||||
// can navigate elsewhere.
|
||||
var homePage = generateHomePage(rootGroup);
|
||||
if (homePage === SYSTEM_HOME_PAGE)
|
||||
pages.push(homePage);
|
||||
|
||||
// If user can manage sessions, add link to sessions management page
|
||||
if (canManageSessions) {
|
||||
pages.push(new Page(
|
||||
'USER_MENU.ACTION_MANAGE_SESSIONS',
|
||||
'/settings/sessions'
|
||||
));
|
||||
}
|
||||
|
||||
// If user can manage users, add link to user management page
|
||||
if (canManageUsers) {
|
||||
pages.push(new Page(
|
||||
'USER_MENU.ACTION_MANAGE_USERS',
|
||||
'/manage/modules/users/'
|
||||
'/settings/users'
|
||||
));
|
||||
}
|
||||
|
||||
@@ -221,15 +219,69 @@ angular.module('navigation').factory('userPageService', ['$injector',
|
||||
if (canManageConnections) {
|
||||
pages.push(new Page(
|
||||
'USER_MENU.ACTION_MANAGE_CONNECTIONS',
|
||||
'/manage/modules/connections/'
|
||||
'/settings/connections'
|
||||
));
|
||||
}
|
||||
|
||||
return pages;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a promise which resolves to an array of all settings pages that
|
||||
* the current user can visit. This can include any of the various manage
|
||||
* pages.
|
||||
*
|
||||
* @returns {Promise.<Page[]>}
|
||||
* A promise which resolves to an array of all settings pages that the
|
||||
* current user can visit.
|
||||
*/
|
||||
service.getSettingsPages = function getSettingsPages() {
|
||||
|
||||
var deferred = $q.defer();
|
||||
|
||||
// Retrieve current permissions, resolving main pages if possible
|
||||
// Resolve promise using settings pages derived from permissions
|
||||
permissionService.getPermissions(authenticationService.getCurrentUserID())
|
||||
.success(function permissionsRetrieved(permissions) {
|
||||
deferred.resolve(generateSettingsPages(permissions));
|
||||
});
|
||||
|
||||
// If user can manage sessions, add link to sessions management page
|
||||
if (canManageSessions) {
|
||||
return deferred.promise;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns all the main pages that the current user can visit. This can
|
||||
* include the home page, manage pages, etc. In the case that there are no
|
||||
* applicable pages of this sort, it may return a client page.
|
||||
*
|
||||
* @param {ConnectionGroup} rootGroup
|
||||
* The root of the connection group tree for the current user.
|
||||
*
|
||||
* @param {PermissionSet} permissions
|
||||
* The permissions for the current user.
|
||||
*
|
||||
* @returns {Page[]}
|
||||
* An array of all main pages that the current user can visit.
|
||||
*/
|
||||
var generateMainPages = function generateMainPages(rootGroup, permissions) {
|
||||
|
||||
var pages = [];
|
||||
|
||||
// Get home page and settings pages
|
||||
var homePage = generateHomePage(rootGroup);
|
||||
var settingsPages = generateSettingsPages(permissions);
|
||||
|
||||
// Only include the home page in the list of main pages if the user
|
||||
// can navigate elsewhere.
|
||||
if (homePage === SYSTEM_HOME_PAGE || settingsPages.length)
|
||||
pages.push(homePage);
|
||||
|
||||
// Add generic link to the first-available settings page
|
||||
if (settingsPages.length) {
|
||||
pages.push(new Page(
|
||||
'USER_MENU.ACTION_MANAGE_SESSIONS',
|
||||
'/manage/modules/sessions/'
|
||||
'USER_MENU.ACTION_MANAGE_SETTINGS',
|
||||
settingsPages[0].url
|
||||
));
|
||||
}
|
||||
|
||||
|
@@ -139,8 +139,6 @@
|
||||
top: 100%;
|
||||
right: 0;
|
||||
left: -1px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
background: #EEE;
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.125);
|
||||
@@ -151,6 +149,11 @@
|
||||
|
||||
}
|
||||
|
||||
.user-menu .options ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.user-menu .user-menu-dropdown.open .options {
|
||||
visibility: visible;
|
||||
}
|
||||
@@ -161,50 +164,40 @@
|
||||
}
|
||||
|
||||
.user-menu .options li a {
|
||||
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
padding: 0.75em;
|
||||
|
||||
background-repeat: no-repeat;
|
||||
background-size: 1em;
|
||||
background-position: 0.75em center;
|
||||
padding-left: 2.5em;
|
||||
background-image: url('images/protocol-icons/guac-monitor.png');
|
||||
|
||||
}
|
||||
|
||||
.user-menu .options li a:hover {
|
||||
background-color: #CDA;
|
||||
}
|
||||
|
||||
.user-menu .options li a.disabled,
|
||||
.user-menu .options li a.disabled:hover {
|
||||
.user-menu .options li a.current,
|
||||
.user-menu .options li a.current:hover {
|
||||
background-color: transparent;
|
||||
cursor: default;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.user-menu .options li a.home,
|
||||
.user-menu .options li a.manage-users,
|
||||
.user-menu .options li a.manage-connections,
|
||||
.user-menu .options li a.manage-sessions,
|
||||
.user-menu .options li a.change-password,
|
||||
.user-menu .options li a.logout {
|
||||
background-repeat: no-repeat;
|
||||
background-size: 1em;
|
||||
background-position: 0.75em center;
|
||||
padding-left: 2.5em;
|
||||
}
|
||||
|
||||
.user-menu .options li a[href="#/"] {
|
||||
background-image: url('images/action-icons/guac-home-dark.png');
|
||||
}
|
||||
|
||||
.user-menu .options li a[href="#/manage/modules/users/"] {
|
||||
background-image: url('images/user-icons/guac-user.png');
|
||||
}
|
||||
|
||||
.user-menu .options li a[href="#/manage/modules/connections/"] {
|
||||
background-image: url('images/protocol-icons/guac-monitor.png');
|
||||
}
|
||||
|
||||
.user-menu .options li a[href="#/manage/modules/sessions/"] {
|
||||
background-image: url('images/protocol-icons/guac-plug.png');
|
||||
.user-menu .options li a[href="#/settings/users"],
|
||||
.user-menu .options li a[href="#/settings/connections"],
|
||||
.user-menu .options li a[href="#/settings/sessions"] {
|
||||
background-image: url('images/action-icons/guac-config-dark.png');
|
||||
}
|
||||
|
||||
.user-menu .options li a.change-password {
|
||||
|
@@ -0,0 +1,32 @@
|
||||
<ul class="page-list">
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<!-- Navigation links -->
|
||||
<li ng-repeat="page in pages">
|
||||
<a class="home" ng-click="navigateToPage(page)"
|
||||
ng-class="{current: isCurrentPage(page)}" href="#{{page.url}}">
|
||||
{{page.name | translate}}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
@@ -26,31 +26,30 @@
|
||||
<div class="menu-indicator"></div>
|
||||
|
||||
<!-- Menu options -->
|
||||
<ul class="options">
|
||||
<div class="options">
|
||||
|
||||
<!-- Local actions -->
|
||||
<li ng-repeat="action in localActions">
|
||||
<a ng-class="action.className" ng-click="action.callback()">
|
||||
{{action.name | translate}}
|
||||
</a>
|
||||
</li>
|
||||
<ul class="action-list">
|
||||
<li ng-repeat="action in localActions">
|
||||
<a ng-class="action.className" ng-click="action.callback()">
|
||||
{{action.name | translate}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Navigation links -->
|
||||
<li ng-repeat="page in pages">
|
||||
<a class="home" ng-click="navigateToPage(page)"
|
||||
ng-class="{disabled: isPageDisabled(page)}" href="#{{page.url}}">
|
||||
{{page.name | translate}}
|
||||
</a>
|
||||
</li>
|
||||
<guac-page-list pages="pages"></guac-page-list>
|
||||
|
||||
<!-- Actions -->
|
||||
<li ng-repeat="action in actions">
|
||||
<a ng-class="action.className" ng-click="action.callback()">
|
||||
{{action.name | translate}}
|
||||
</a>
|
||||
</li>
|
||||
<ul class="action-list">
|
||||
<li ng-repeat="action in actions">
|
||||
<a ng-class="action.className" ng-click="action.callback()">
|
||||
{{action.name | translate}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password dialog -->
|
||||
|
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The controller for the general settings page.
|
||||
*/
|
||||
angular.module('manage').controller('settingsController', ['$scope', '$injector',
|
||||
function settingsController($scope, $injector) {
|
||||
|
||||
// Required services
|
||||
var $routeParams = $injector.get('$routeParams');
|
||||
var userPageService = $injector.get('userPageService');
|
||||
|
||||
/**
|
||||
* The array of settings pages available to the current user, or null if
|
||||
* not yet known.
|
||||
*
|
||||
* @type Page[]
|
||||
*/
|
||||
$scope.settingsPages = null;
|
||||
|
||||
/**
|
||||
* The currently-selected settings tab. This may be 'users', 'connections',
|
||||
* or 'sessions'.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
$scope.activeTab = $routeParams.tab;
|
||||
|
||||
// Retrieve settings pages
|
||||
userPageService.getSettingsPages()
|
||||
.then(function settingsPagesRetrieved(pages) {
|
||||
$scope.settingsPages = pages;
|
||||
});
|
||||
|
||||
}]);
|
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A directive for managing all connections and connection groups in the system.
|
||||
*/
|
||||
angular.module('settings').directive('guacSettingsConnections', [function guacSettingsConnections() {
|
||||
|
||||
return {
|
||||
// Element only
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
|
||||
scope: {
|
||||
},
|
||||
|
||||
templateUrl: 'app/settings/templates/settingsConnections.html',
|
||||
controller: ['$scope', '$injector', function settingsConnectionsController($scope, $injector) {
|
||||
|
||||
// Required types
|
||||
var ConnectionGroup = $injector.get('ConnectionGroup');
|
||||
var PermissionSet = $injector.get('PermissionSet');
|
||||
|
||||
// Required services
|
||||
var $location = $injector.get('$location');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var connectionGroupService = $injector.get('connectionGroupService');
|
||||
var guacNotification = $injector.get('guacNotification');
|
||||
var permissionService = $injector.get('permissionService');
|
||||
|
||||
// Identifier of the current user
|
||||
var currentUserID = authenticationService.getCurrentUserID();
|
||||
|
||||
/**
|
||||
* An action to be provided along with the object sent to
|
||||
* showStatus which closes the currently-shown status dialog.
|
||||
*/
|
||||
var ACKNOWLEDGE_ACTION = {
|
||||
name : "SETTINGS_CONNECTIONS.ACTION_ACKNOWLEDGE",
|
||||
// Handle action
|
||||
callback : function acknowledgeCallback() {
|
||||
guacNotification.showStatus(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The root connection group of the connection group hierarchy.
|
||||
*
|
||||
* @type ConnectionGroup
|
||||
*/
|
||||
$scope.rootGroup = null;
|
||||
|
||||
/**
|
||||
* Whether the current user can manage connections. If the current
|
||||
* permissions have not yet been loaded, this will be null.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
$scope.canManageConnections = null;
|
||||
|
||||
/**
|
||||
* Whether the current user can create new connections. If the
|
||||
* current permissions have not yet been loaded, this will be null.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
$scope.canCreateConnections = null;
|
||||
|
||||
/**
|
||||
* Whether the current user can create new connection groups. If
|
||||
* the current permissions have not yet been loaded, this will be
|
||||
* null.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
$scope.canCreateConnectionGroups = null;
|
||||
|
||||
/**
|
||||
* All permissions associated with the current user, or null if the
|
||||
* user's permissions have not yet been loaded.
|
||||
*
|
||||
* @type PermissionSet
|
||||
*/
|
||||
$scope.permissions = null;
|
||||
|
||||
/**
|
||||
* Returns whether critical data has completed being loaded.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if enough data has been loaded for the user interface
|
||||
* to be useful, false otherwise.
|
||||
*/
|
||||
$scope.isLoaded = function isLoaded() {
|
||||
|
||||
return $scope.rootGroup !== null
|
||||
&& $scope.permissions !== null
|
||||
&& $scope.canManageConnections !== null
|
||||
&& $scope.canCreateConnections !== null
|
||||
&& $scope.canCreateConnectionGroups !== null;
|
||||
|
||||
};
|
||||
|
||||
// Retrieve current permissions
|
||||
permissionService.getPermissions(currentUserID)
|
||||
.success(function permissionsRetrieved(permissions) {
|
||||
|
||||
$scope.permissions = permissions;
|
||||
|
||||
// Ignore permission to update root group
|
||||
PermissionSet.removeConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, ConnectionGroup.ROOT_IDENTIFIER);
|
||||
|
||||
// Determine whether the current user can create new users
|
||||
$scope.canCreateConnections =
|
||||
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION);
|
||||
|
||||
// Determine whether the current user can create new users
|
||||
$scope.canCreateConnectionGroups =
|
||||
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP);
|
||||
|
||||
// Determine whether the current user can manage other connections or groups
|
||||
$scope.canManageConnections =
|
||||
|
||||
// Permission to manage connections
|
||||
$scope.canCreateConnections
|
||||
|| PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)
|
||||
|| PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.DELETE)
|
||||
|
||||
// Permission to manage groups
|
||||
|| $scope.canCreateConnectionGroups
|
||||
|| PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)
|
||||
|| PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.DELETE);
|
||||
|
||||
// Return to home if there's nothing to do here
|
||||
if (!$scope.canManageConnections)
|
||||
$location.path('/');
|
||||
|
||||
});
|
||||
|
||||
// Retrieve all connections for which we have UPDATE or DELETE permission
|
||||
connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER,
|
||||
[PermissionSet.ObjectPermissionType.UPDATE, PermissionSet.ObjectPermissionType.DELETE])
|
||||
.success(function connectionGroupReceived(rootGroup) {
|
||||
$scope.rootGroup = rootGroup;
|
||||
});
|
||||
|
||||
}]
|
||||
};
|
||||
|
||||
}]);
|
@@ -0,0 +1,367 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A directive for managing all active Guacamole sessions.
|
||||
*/
|
||||
angular.module('settings').directive('guacSettingsSessions', [function guacSettingsSessions() {
|
||||
|
||||
return {
|
||||
// Element only
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
|
||||
scope: {
|
||||
},
|
||||
|
||||
templateUrl: 'app/settings/templates/settingsSessions.html',
|
||||
controller: ['$scope', '$injector', function settingsSessionsController($scope, $injector) {
|
||||
|
||||
// Required types
|
||||
var ActiveConnectionWrapper = $injector.get('ActiveConnectionWrapper');
|
||||
var ConnectionGroup = $injector.get('ConnectionGroup');
|
||||
var SortOrder = $injector.get('SortOrder');
|
||||
|
||||
// Required services
|
||||
var $filter = $injector.get('$filter');
|
||||
var $translate = $injector.get('$translate');
|
||||
var activeConnectionService = $injector.get('activeConnectionService');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var connectionGroupService = $injector.get('connectionGroupService');
|
||||
var guacNotification = $injector.get('guacNotification');
|
||||
var permissionService = $injector.get('permissionService');
|
||||
|
||||
/**
|
||||
* All permissions associated with the current user, or null if the
|
||||
* user's permissions have not yet been loaded.
|
||||
*
|
||||
* @type PermissionSet
|
||||
*/
|
||||
$scope.permissions = null;
|
||||
|
||||
/**
|
||||
* The ActiveConnectionWrappers of all active sessions accessible
|
||||
* by the current user, or null if the active sessions have not yet
|
||||
* been loaded.
|
||||
*
|
||||
* @type ActiveConnectionWrapper[]
|
||||
*/
|
||||
$scope.wrappers = null;
|
||||
|
||||
/**
|
||||
* SortOrder instance which maintains the sort order of the visible
|
||||
* connection wrappers.
|
||||
*
|
||||
* @type SortOrder
|
||||
*/
|
||||
$scope.wrapperOrder = new SortOrder([
|
||||
'activeConnection.username',
|
||||
'startDate',
|
||||
'activeConnection.remoteHost',
|
||||
'name'
|
||||
]);
|
||||
|
||||
/**
|
||||
* Array of all wrapper properties that are filterable.
|
||||
*
|
||||
* @type String[]
|
||||
*/
|
||||
$scope.filteredWrapperProperties = [
|
||||
'activeConnection.username',
|
||||
'startDate',
|
||||
'activeConnection.remoteHost',
|
||||
'name'
|
||||
];
|
||||
|
||||
/**
|
||||
* All active connections, if known, or null if active connections
|
||||
* have not yet been loaded.
|
||||
*
|
||||
* @type ActiveConnection
|
||||
*/
|
||||
var activeConnections = null;
|
||||
|
||||
/**
|
||||
* Map of all visible connections by object identifier, or null if
|
||||
* visible connections have not yet been loaded.
|
||||
*
|
||||
* @type Object.<String, Connection>
|
||||
*/
|
||||
var connections = null;
|
||||
|
||||
/**
|
||||
* The date format for use for session-related dates.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
var sessionDateFormat = null;
|
||||
|
||||
/**
|
||||
* Map of all currently-selected active connection wrappers by
|
||||
* identifier.
|
||||
*
|
||||
* @type Object.<String, ActiveConnectionWrapper>
|
||||
*/
|
||||
var selectedWrappers = {};
|
||||
|
||||
/**
|
||||
* Adds the given connection to the internal set of visible
|
||||
* connections.
|
||||
*
|
||||
* @param {Connection} connection
|
||||
* The connection to add to the internal set of visible
|
||||
* connections.
|
||||
*/
|
||||
var addConnection = function addConnection(connection) {
|
||||
|
||||
// Add given connection to set of visible connections
|
||||
connections[connection.identifier] = connection;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds all descendant connections of the given connection group to
|
||||
* the internal set of connections.
|
||||
*
|
||||
* @param {ConnectionGroup} connectionGroup
|
||||
* The connection group whose descendant connections should be
|
||||
* added to the internal set of connections.
|
||||
*/
|
||||
var addDescendantConnections = function addDescendantConnections(connectionGroup) {
|
||||
|
||||
// Add all child connections
|
||||
if (connectionGroup.childConnections)
|
||||
connectionGroup.childConnections.forEach(addConnection);
|
||||
|
||||
// Add all child connection groups
|
||||
if (connectionGroup.childConnectionGroups)
|
||||
connectionGroup.childConnectionGroups.forEach(addDescendantConnections);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps all loaded active connections, storing the resulting array
|
||||
* within the scope. If required data has not yet finished loading,
|
||||
* this function has no effect.
|
||||
*/
|
||||
var wrapActiveConnections = function wrapActiveConnections() {
|
||||
|
||||
// Abort if not all required data is available
|
||||
if (!activeConnections || !connections || !sessionDateFormat)
|
||||
return;
|
||||
|
||||
// Wrap all active connections for sake of display
|
||||
$scope.wrappers = [];
|
||||
for (var identifier in activeConnections) {
|
||||
|
||||
var activeConnection = activeConnections[identifier];
|
||||
var connection = connections[activeConnection.connectionIdentifier];
|
||||
|
||||
$scope.wrappers.push(new ActiveConnectionWrapper(
|
||||
connection.name,
|
||||
$filter('date')(activeConnection.startDate, sessionDateFormat),
|
||||
activeConnection
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Query the user's permissions
|
||||
permissionService.getPermissions(authenticationService.getCurrentUserID())
|
||||
.success(function permissionsReceived(retrievedPermissions) {
|
||||
$scope.permissions = retrievedPermissions;
|
||||
});
|
||||
|
||||
// Retrieve all connections
|
||||
connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER)
|
||||
.success(function connectionGroupReceived(retrievedRootGroup) {
|
||||
|
||||
// Load connections from retrieved group tree
|
||||
connections = {};
|
||||
addDescendantConnections(retrievedRootGroup);
|
||||
|
||||
// Attempt to produce wrapped list of active connections
|
||||
wrapActiveConnections();
|
||||
|
||||
});
|
||||
|
||||
// Query active sessions
|
||||
activeConnectionService.getActiveConnections().success(function sessionsRetrieved(retrievedActiveConnections) {
|
||||
|
||||
// Store received list
|
||||
activeConnections = retrievedActiveConnections;
|
||||
|
||||
// Attempt to produce wrapped list of active connections
|
||||
wrapActiveConnections();
|
||||
|
||||
});
|
||||
|
||||
// Get session date format
|
||||
$translate('SETTINGS_SESSIONS.FORMAT_STARTDATE').then(function sessionDateFormatReceived(retrievedSessionDateFormat) {
|
||||
|
||||
// Store received date format
|
||||
sessionDateFormat = retrievedSessionDateFormat;
|
||||
|
||||
// Attempt to produce wrapped list of active connections
|
||||
wrapActiveConnections();
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns whether critical data has completed being loaded.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if enough data has been loaded for the user interface
|
||||
* to be useful, false otherwise.
|
||||
*/
|
||||
$scope.isLoaded = function isLoaded() {
|
||||
|
||||
return $scope.wrappers !== null
|
||||
&& $scope.sessionDateFormat !== null
|
||||
&& $scope.permissions !== null;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* An action to be provided along with the object sent to
|
||||
* showStatus which closes the currently-shown status dialog.
|
||||
*/
|
||||
var ACKNOWLEDGE_ACTION = {
|
||||
name : "SETTINGS_SESSIONS.ACTION_ACKNOWLEDGE",
|
||||
// Handle action
|
||||
callback : function acknowledgeCallback() {
|
||||
guacNotification.showStatus(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An action to be provided along with the object sent to
|
||||
* showStatus which closes the currently-shown status dialog.
|
||||
*/
|
||||
var CANCEL_ACTION = {
|
||||
name : "SETTINGS_SESSIONS.ACTION_CANCEL",
|
||||
// Handle action
|
||||
callback : function cancelCallback() {
|
||||
guacNotification.showStatus(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An action to be provided along with the object sent to
|
||||
* showStatus which immediately deletes the currently selected
|
||||
* sessions.
|
||||
*/
|
||||
var DELETE_ACTION = {
|
||||
name : "SETTINGS_SESSIONS.ACTION_DELETE",
|
||||
className : "danger",
|
||||
// Handle action
|
||||
callback : function deleteCallback() {
|
||||
deleteSessionsImmediately();
|
||||
guacNotification.showStatus(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Immediately deletes the selected sessions, without prompting the
|
||||
* user for confirmation.
|
||||
*/
|
||||
var deleteSessionsImmediately = function deleteSessionsImmediately() {
|
||||
|
||||
// Perform deletion
|
||||
activeConnectionService.deleteActiveConnections(Object.keys(selectedWrappers))
|
||||
.success(function activeConnectionsDeleted() {
|
||||
|
||||
// Remove deleted connections from wrapper array
|
||||
$scope.wrappers = $scope.wrappers.filter(function activeConnectionStillExists(wrapper) {
|
||||
return !(wrapper.activeConnection.identifier in selectedWrappers);
|
||||
});
|
||||
|
||||
// Clear selection
|
||||
selectedWrappers = {};
|
||||
|
||||
})
|
||||
|
||||
// Notify of any errors
|
||||
.error(function activeConnectionDeletionFailed(error) {
|
||||
guacNotification.showStatus({
|
||||
'className' : 'error',
|
||||
'title' : 'SETTINGS_SESSIONS.DIALOG_HEADER_ERROR',
|
||||
'text' : error.message,
|
||||
'actions' : [ ACKNOWLEDGE_ACTION ]
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete all selected sessions, prompting the user first to
|
||||
* confirm that deletion is desired.
|
||||
*/
|
||||
$scope.deleteSessions = function deleteSessions() {
|
||||
// Confirm deletion request
|
||||
guacNotification.showStatus({
|
||||
'title' : 'SETTINGS_SESSIONS.DIALOG_HEADER_CONFIRM_DELETE',
|
||||
'text' : 'SETTINGS_SESSIONS.TEXT_CONFIRM_DELETE',
|
||||
'actions' : [ DELETE_ACTION, CANCEL_ACTION]
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the selected sessions can be deleted.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if selected sessions can be deleted, false otherwise.
|
||||
*/
|
||||
$scope.canDeleteSessions = function canDeleteSessions() {
|
||||
|
||||
// We can delete sessions if at least one is selected
|
||||
for (var identifier in selectedWrappers)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Called whenever an active connection wrapper changes selected
|
||||
* status.
|
||||
*
|
||||
* @param {ActiveConnectionWrapper} wrapper
|
||||
* The wrapper whose selected status has changed.
|
||||
*/
|
||||
$scope.wrapperSelectionChange = function wrapperSelectionChange(wrapper) {
|
||||
|
||||
// Add wrapper to map if selected
|
||||
if (wrapper.checked)
|
||||
selectedWrappers[wrapper.activeConnection.identifier] = wrapper;
|
||||
|
||||
// Otherwise, remove wrapper from map
|
||||
else
|
||||
delete selectedWrappers[wrapper.activeConnection.identifier];
|
||||
|
||||
};
|
||||
|
||||
}]
|
||||
};
|
||||
|
||||
}]);
|
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A directive for managing all users in the system.
|
||||
*/
|
||||
angular.module('settings').directive('guacSettingsUsers', [function guacSettingsUsers() {
|
||||
|
||||
return {
|
||||
// Element only
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
|
||||
scope: {
|
||||
},
|
||||
|
||||
templateUrl: 'app/settings/templates/settingsUsers.html',
|
||||
controller: ['$scope', '$injector', function settingsUsersController($scope, $injector) {
|
||||
|
||||
// Required types
|
||||
var PermissionSet = $injector.get('PermissionSet');
|
||||
var User = $injector.get('User');
|
||||
|
||||
// Required services
|
||||
var $location = $injector.get('$location');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var guacNotification = $injector.get('guacNotification');
|
||||
var permissionService = $injector.get('permissionService');
|
||||
var userService = $injector.get('userService');
|
||||
|
||||
// Identifier of the current user
|
||||
var currentUserID = authenticationService.getCurrentUserID();
|
||||
|
||||
/**
|
||||
* An action to be provided along with the object sent to
|
||||
* showStatus which closes the currently-shown status dialog.
|
||||
*/
|
||||
var ACKNOWLEDGE_ACTION = {
|
||||
name : "SETTINGS_USERS.ACTION_ACKNOWLEDGE",
|
||||
// Handle action
|
||||
callback : function acknowledgeCallback() {
|
||||
guacNotification.showStatus(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* All visible users.
|
||||
*
|
||||
* @type User[]
|
||||
*/
|
||||
$scope.users = null;
|
||||
|
||||
/**
|
||||
* Whether the current user can manage users. If the current
|
||||
* permissions have not yet been loaded, this will be null.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
$scope.canManageUsers = null;
|
||||
|
||||
/**
|
||||
* Whether the current user can create new users. If the current
|
||||
* permissions have not yet been loaded, this will be null.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
$scope.canCreateUsers = null;
|
||||
|
||||
/**
|
||||
* The name of the new user to create, if any, when user creation
|
||||
* is requested via newUser().
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
$scope.newUsername = "";
|
||||
|
||||
/**
|
||||
* All permissions associated with the current user, or null if the
|
||||
* user's permissions have not yet been loaded.
|
||||
*
|
||||
* @type PermissionSet
|
||||
*/
|
||||
$scope.permissions = null;
|
||||
|
||||
/**
|
||||
* Returns whether critical data has completed being loaded.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if enough data has been loaded for the user interface
|
||||
* to be useful, false otherwise.
|
||||
*/
|
||||
$scope.isLoaded = function isLoaded() {
|
||||
|
||||
return $scope.users !== null
|
||||
&& $scope.permissions !== null
|
||||
&& $scope.canManageUsers !== null
|
||||
&& $scope.canCreateUsers !== null;
|
||||
|
||||
};
|
||||
|
||||
// Retrieve current permissions
|
||||
permissionService.getPermissions(currentUserID)
|
||||
.success(function permissionsRetrieved(permissions) {
|
||||
|
||||
$scope.permissions = permissions;
|
||||
|
||||
// Determine whether the current user can create new users
|
||||
$scope.canCreateUsers =
|
||||
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_USER);
|
||||
|
||||
// Determine whether the current user can manage other users
|
||||
$scope.canManageUsers =
|
||||
$scope.canCreateUsers
|
||||
|| PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)
|
||||
|| PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.DELETE);
|
||||
|
||||
// Return to home if there's nothing to do here
|
||||
if (!$scope.canManageUsers)
|
||||
$location.path('/');
|
||||
|
||||
});
|
||||
|
||||
// Retrieve all users for whom we have UPDATE or DELETE permission
|
||||
userService.getUsers([PermissionSet.ObjectPermissionType.UPDATE,
|
||||
PermissionSet.ObjectPermissionType.DELETE])
|
||||
.success(function usersReceived(users) {
|
||||
|
||||
// Display only other users, not self
|
||||
$scope.users = users.filter(function isNotSelf(user) {
|
||||
return user.username !== currentUserID;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a new user having the username specified in the user
|
||||
* creation interface.
|
||||
*/
|
||||
$scope.newUser = function newUser() {
|
||||
|
||||
// Create user skeleton
|
||||
var user = new User({
|
||||
username: $scope.newUsername || ''
|
||||
});
|
||||
|
||||
// Create specified user
|
||||
userService.createUser(user)
|
||||
|
||||
// Add user to visible list upon success
|
||||
.success(function userCreated() {
|
||||
$scope.users.push(user);
|
||||
})
|
||||
|
||||
// Notify of any errors
|
||||
.error(function userCreationFailed(error) {
|
||||
guacNotification.showStatus({
|
||||
'className' : 'error',
|
||||
'title' : 'SETTINGS_USERS.DIALOG_HEADER_ERROR',
|
||||
'text' : error.message,
|
||||
'actions' : [ ACKNOWLEDGE_ACTION ]
|
||||
});
|
||||
});
|
||||
|
||||
// Reset username
|
||||
$scope.newUsername = "";
|
||||
|
||||
};
|
||||
|
||||
}]
|
||||
};
|
||||
|
||||
}]);
|
33
guacamole/src/main/webapp/app/settings/settingsModule.js
Normal file
33
guacamole/src/main/webapp/app/settings/settingsModule.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The module for manipulation of general settings. This is distinct from the
|
||||
* "manage" module, which deals only with administrator-level system management.
|
||||
*/
|
||||
angular.module('settings', [
|
||||
'groupList',
|
||||
'list',
|
||||
'navigation',
|
||||
'notification',
|
||||
'rest'
|
||||
]);
|
@@ -20,15 +20,15 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
.manage table.session-list {
|
||||
.settings table.session-list {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.manage table.session-list tr.session:hover {
|
||||
.settings table.session-list tr.session:hover {
|
||||
background: #CDA;
|
||||
}
|
||||
|
||||
.manage table.session-list .select-session {
|
||||
.settings table.session-list .select-session {
|
||||
min-width: 2em;
|
||||
text-align: center;
|
||||
}
|
69
guacamole/src/main/webapp/app/settings/styles/settings.css
Normal file
69
guacamole/src/main/webapp/app/settings/styles/settings.css
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
.settings .header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.settings table.properties th {
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.settings .action-buttons {
|
||||
text-align: center;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.settings-tabs .page-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: rgba(0, 0, 0, 0.0125);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.settings-tabs .page-list li {
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.settings-tabs .page-list li a[href] {
|
||||
display: block;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
padding: 0.75em 1em;
|
||||
}
|
||||
|
||||
.settings-tabs .page-list li a[href]:visited {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.settings-tabs .page-list li a[href]:hover {
|
||||
background-color: #CDA;
|
||||
}
|
||||
|
||||
.settings-tabs .page-list li a[href].current,
|
||||
.settings-tabs .page-list li a[href].current:hover {
|
||||
background: rgba(0,0,0,0.3);
|
||||
cursor: default;
|
||||
}
|
@@ -33,7 +33,7 @@
|
||||
|
||||
<!-- Active user count -->
|
||||
<span class="activeUserCount" ng-show="item.getActiveConnections()">
|
||||
{{'MANAGE_CONNECTION.INFO_ACTIVE_USER_COUNT' | translate:'{USERS: item.getActiveConnections()}'}}
|
||||
{{'SETTINGS_CONNECTIONS.INFO_ACTIVE_USER_COUNT' | translate:'{USERS: item.getActiveConnections()}'}}
|
||||
</span>
|
||||
|
||||
</div>
|
@@ -0,0 +1,40 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<div class="view">
|
||||
|
||||
<div class="header">
|
||||
<h2>{{'SETTINGS.SECTION_HEADER_SETTINGS' | translate}}</h2>
|
||||
<guac-user-menu></guac-user-menu>
|
||||
</div>
|
||||
|
||||
<!-- Available tabs -->
|
||||
<div class="settings-tabs">
|
||||
<guac-page-list pages="settingsPages"></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>
|
||||
|
||||
</div>
|
@@ -0,0 +1,48 @@
|
||||
<div class="settings section connections" ng-class="{loading: !isLoaded()}">
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<!-- Connection management -->
|
||||
<p>{{'SETTINGS_CONNECTIONS.HELP_CONNECTIONS' | translate}}</p>
|
||||
|
||||
<!-- Form action buttons -->
|
||||
<div class="action-buttons">
|
||||
|
||||
<a class="add-connection button"
|
||||
ng-show="canCreateConnections"
|
||||
href="#/manage/connections/">{{'SETTINGS_CONNECTIONS.ACTION_NEW_CONNECTION' | translate}}</a>
|
||||
|
||||
<a class="add-connection-group button"
|
||||
ng-show="canCreateConnectionGroups"
|
||||
href="#/manage/connectionGroups/">{{'SETTINGS_CONNECTIONS.ACTION_NEW_CONNECTION_GROUP' | translate}}</a>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- List of accessible connections and groups -->
|
||||
<div class="connection-list">
|
||||
<guac-group-list
|
||||
page-size="25"
|
||||
connection-group="rootGroup"
|
||||
connection-template="'app/settings/templates/connection.html'"
|
||||
connection-group-template="'app/settings/templates/connectionGroup.html'"/>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,77 @@
|
||||
<div class="settings section sessions" ng-class="{loading: !isLoaded()}">
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<!-- User Session management -->
|
||||
<p>{{'SETTINGS_SESSIONS.HELP_SESSIONS' | translate}}</p>
|
||||
|
||||
<!-- Form action buttons -->
|
||||
<div class="action-buttons">
|
||||
<button class="delete-sessions danger" ng-disabled="!canDeleteSessions()" ng-click="deleteSessions()">{{'SETTINGS_SESSIONS.ACTION_DELETE' | translate}}</button>
|
||||
</div>
|
||||
|
||||
<!-- Session filter -->
|
||||
<guac-filter filtered-items="filteredWrappers" items="wrappers"
|
||||
placeholder="'SETTINGS_SESSIONS.FIELD_PLACEHOLDER_FILTER' | translate"
|
||||
properties="filteredWrapperProperties"></guac-filter>
|
||||
|
||||
<!-- List of current user sessions -->
|
||||
<table class="sorted session-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="select-session"></th>
|
||||
<th guac-sort-order="wrapperOrder" guac-sort-property="'activeConnection.username'">
|
||||
{{'SETTINGS_SESSIONS.TABLE_HEADER_SESSION_USERNAME' | translate}}
|
||||
</th>
|
||||
<th guac-sort-order="wrapperOrder" guac-sort-property="'startDate'">
|
||||
{{'SETTINGS_SESSIONS.TABLE_HEADER_SESSION_STARTDATE' | translate}}
|
||||
</th>
|
||||
<th guac-sort-order="wrapperOrder" guac-sort-property="'activeConnection.remoteHost'">
|
||||
{{'SETTINGS_SESSIONS.TABLE_HEADER_SESSION_REMOTEHOST' | translate}}
|
||||
</th>
|
||||
<th guac-sort-order="wrapperOrder" guac-sort-property="'name'">
|
||||
{{'SETTINGS_SESSIONS.TABLE_HEADER_SESSION_CONNECTION_NAME' | translate}}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="wrapper in wrapperPage" class="session">
|
||||
<td class="select-session">
|
||||
<input ng-change="wrapperSelectionChange(wrapper)" type="checkbox" ng-model="wrapper.checked" />
|
||||
</td>
|
||||
<td>{{wrapper.activeConnection.username}}</td>
|
||||
<td>{{wrapper.startDate}}</td>
|
||||
<td>{{wrapper.activeConnection.remoteHost}}</td>
|
||||
<td>{{wrapper.name}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Text displayed if no sessions exist -->
|
||||
<p class="placeholder" ng-hide="wrapperPage.length">
|
||||
{{'SETTINGS_SESSIONS.INFO_NO_SESSIONS' | translate}}
|
||||
</p>
|
||||
|
||||
<!-- Pager for session list -->
|
||||
<guac-pager page="wrapperPage" page-size="25"
|
||||
items="filteredWrappers | orderBy : wrapperOrder.predicate"></guac-pager>
|
||||
</div>
|
@@ -0,0 +1,48 @@
|
||||
<div class="settings section users" ng-class="{loading: !isLoaded()}">
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<!-- User management -->
|
||||
<p>{{'SETTINGS_USERS.HELP_USERS' | translate}}</p>
|
||||
|
||||
<!-- Form action buttons -->
|
||||
<div class="action-buttons" ng-show="canCreateUsers">
|
||||
<input type="text" ng-model="newUsername" class="name username" autocorrect="off" autocapitalize="off"/>
|
||||
<button class="add-user" ng-click="newUser()">{{'SETTINGS_USERS.ACTION_NEW_USER' | translate}}</button>
|
||||
</div>
|
||||
|
||||
<!-- List of users this user has access to -->
|
||||
<div class="user-list">
|
||||
<div ng-repeat="user in userPage" class="user list-item">
|
||||
<a ng-href="#/manage/users/{{user.username}}">
|
||||
<div class="caption">
|
||||
<div class="icon user"></div>
|
||||
<span class="name">{{user.username}}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pager controls for user list -->
|
||||
<guac-pager page="userPage" page-size="25" items="users | orderBy : 'username'"></guac-pager>
|
||||
|
||||
</div>
|
@@ -23,7 +23,7 @@
|
||||
/**
|
||||
* A service for defining the ActiveConnectionWrapper class.
|
||||
*/
|
||||
angular.module('manage').factory('ActiveConnectionWrapper', [
|
||||
angular.module('settings').factory('ActiveConnectionWrapper', [
|
||||
function defineActiveConnectionWrapper() {
|
||||
|
||||
/**
|
@@ -10,7 +10,8 @@
|
||||
"ACTION_DELETE_SESSIONS" : "Kill Sessions",
|
||||
"ACTION_LOGIN" : "Login",
|
||||
"ACTION_LOGOUT" : "Logout",
|
||||
"ACTION_MANAGE_CONNECTIONS" : "Manage Connections",
|
||||
"ACTION_MANAGE_CONNECTIONS" : "Connections",
|
||||
"ACTION_MANAGE_SETTINGS" : "Settings",
|
||||
"ACTION_MANAGE_SESSIONS" : "Active Sessions",
|
||||
"ACTION_MANAGE_USERS" : "Users",
|
||||
"ACTION_NAVIGATE_BACK" : "Back",
|
||||
@@ -154,8 +155,6 @@
|
||||
"ACTION_CANCEL" : "@:APP.ACTION_CANCEL",
|
||||
"ACTION_CLONE" : "@:APP.ACTION_CLONE",
|
||||
"ACTION_DELETE" : "@:APP.ACTION_DELETE",
|
||||
"ACTION_NEW_CONNECTION" : "New Connection",
|
||||
"ACTION_NEW_CONNECTION_GROUP" : "New Group",
|
||||
"ACTION_SAVE" : "@:APP.ACTION_SAVE",
|
||||
|
||||
"DIALOG_HEADER_CONFIRM_DELETE" : "Delete Connection",
|
||||
@@ -167,14 +166,10 @@
|
||||
|
||||
"FORMAT_HISTORY_START" : "@:APP.FORMAT_DATE_TIME_PRECISE",
|
||||
|
||||
"HELP_CONNECTIONS" : "Click or tap on a connection below to manage that connection. Depending on your access level, connections can be added and deleted, and their properties (protocol, hostname, port, etc.) can be changed.",
|
||||
|
||||
"INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT",
|
||||
"INFO_CONNECTION_DURATION_UNKNOWN" : "--",
|
||||
"INFO_CONNECTION_ACTIVE_NOW" : "Active Now",
|
||||
"INFO_CONNECTION_NOT_USED" : "This connection has not yet been used.",
|
||||
|
||||
"SECTION_HEADER_CONNECTIONS" : "Manage Connections",
|
||||
"SECTION_HEADER_EDIT_CONNECTION" : "Edit Connection",
|
||||
"SECTION_HEADER_HISTORY" : "Usage History",
|
||||
"SECTION_HEADER_PARAMETERS" : "Parameters",
|
||||
@@ -216,7 +211,6 @@
|
||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
||||
"ACTION_CANCEL" : "@:APP.ACTION_CANCEL",
|
||||
"ACTION_DELETE" : "@:APP.ACTION_DELETE",
|
||||
"ACTION_NEW_USER" : "New User",
|
||||
"ACTION_SAVE" : "@:APP.ACTION_SAVE",
|
||||
|
||||
"DIALOG_HEADER_CONFIRM_DELETE" : "Delete User",
|
||||
@@ -233,45 +227,14 @@
|
||||
"FIELD_HEADER_PASSWORD_AGAIN" : "@:APP.FIELD_HEADER_PASSWORD_AGAIN",
|
||||
"FIELD_HEADER_USERNAME" : "Username:",
|
||||
|
||||
"HELP_USERS" : "Click or tap on a user below to manage that user. Depending on your access level, users can be added and deleted, and their passwords can be changed.",
|
||||
|
||||
"SECTION_HEADER_CONNECTIONS" : "Connections",
|
||||
"SECTION_HEADER_EDIT_USER" : "Edit User",
|
||||
"SECTION_HEADER_PERMISSIONS" : "Permissions",
|
||||
"SECTION_HEADER_USERS" : "Users",
|
||||
|
||||
"TEXT_CONFIRM_DELETE" : "Users cannot be restored after they have been deleted. Are you sure you want to delete this user?"
|
||||
|
||||
},
|
||||
|
||||
"MANAGE_SESSION" : {
|
||||
|
||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
||||
"ACTION_CANCEL" : "@:APP.ACTION_CANCEL",
|
||||
"ACTION_DELETE" : "Kill Sessions",
|
||||
|
||||
"DIALOG_HEADER_CONFIRM_DELETE" : "Kill Sessions",
|
||||
"DIALOG_HEADER_ERROR" : "Error",
|
||||
|
||||
"FIELD_PLACEHOLDER_FILTER" : "Filter",
|
||||
|
||||
"FORMAT_STARTDATE" : "@:APP.FORMAT_DATE_TIME_PRECISE",
|
||||
|
||||
"HELP_SESSIONS" : "All currently-active Guacamole sessions are listed here. If you wish to kill one or more sessions, check the box next to those sessions and click \"Kill Sessions\". Killing a session will immediately disconnect the user from the associated connection.",
|
||||
|
||||
"INFO_NO_SESSIONS" : "No active sessions",
|
||||
|
||||
"SECTION_HEADER_SESSIONS" : "Active Sessions",
|
||||
|
||||
"TABLE_HEADER_SESSION_USERNAME" : "Username",
|
||||
"TABLE_HEADER_SESSION_STARTDATE" : "Active since",
|
||||
"TABLE_HEADER_SESSION_REMOTEHOST" : "Remote host",
|
||||
"TABLE_HEADER_SESSION_CONNECTION_NAME" : "Connection name",
|
||||
|
||||
"TEXT_CONFIRM_DELETE" : "Are you sure you want to kill all selected sessions? The users using these sessions will be immediately disconnected."
|
||||
|
||||
},
|
||||
|
||||
"PROTOCOL_RDP" : {
|
||||
|
||||
"FIELD_HEADER_COLOR_DEPTH" : "Color depth:",
|
||||
@@ -414,6 +377,69 @@
|
||||
|
||||
},
|
||||
|
||||
"SETTINGS" : {
|
||||
|
||||
"SECTION_HEADER_SETTINGS" : "Settings"
|
||||
|
||||
},
|
||||
|
||||
"SETTINGS_CONNECTIONS" : {
|
||||
|
||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
||||
"ACTION_NEW_CONNECTION" : "New Connection",
|
||||
"ACTION_NEW_CONNECTION_GROUP" : "New Group",
|
||||
|
||||
"DIALOG_HEADER_ERROR" : "Error",
|
||||
|
||||
"HELP_CONNECTIONS" : "Click or tap on a connection below to manage that connection. Depending on your access level, connections can be added and deleted, and their properties (protocol, hostname, port, etc.) can be changed.",
|
||||
|
||||
"INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT",
|
||||
|
||||
"SECTION_HEADER_CONNECTIONS" : "Connections"
|
||||
|
||||
},
|
||||
|
||||
"SETTINGS_USERS" : {
|
||||
|
||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
||||
"ACTION_NEW_USER" : "New User",
|
||||
|
||||
"DIALOG_HEADER_ERROR" : "Error",
|
||||
|
||||
"HELP_USERS" : "Click or tap on a user below to manage that user. Depending on your access level, users can be added and deleted, and their passwords can be changed.",
|
||||
|
||||
"SECTION_HEADER_USERS" : "Users"
|
||||
|
||||
},
|
||||
|
||||
"SETTINGS_SESSIONS" : {
|
||||
|
||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
||||
"ACTION_CANCEL" : "@:APP.ACTION_CANCEL",
|
||||
"ACTION_DELETE" : "Kill Sessions",
|
||||
|
||||
"DIALOG_HEADER_CONFIRM_DELETE" : "Kill Sessions",
|
||||
"DIALOG_HEADER_ERROR" : "Error",
|
||||
|
||||
"FIELD_PLACEHOLDER_FILTER" : "Filter",
|
||||
|
||||
"FORMAT_STARTDATE" : "@:APP.FORMAT_DATE_TIME_PRECISE",
|
||||
|
||||
"HELP_SESSIONS" : "All currently-active Guacamole sessions are listed here. If you wish to kill one or more sessions, check the box next to those sessions and click \"Kill Sessions\". Killing a session will immediately disconnect the user from the associated connection.",
|
||||
|
||||
"INFO_NO_SESSIONS" : "No active sessions",
|
||||
|
||||
"SECTION_HEADER_SESSIONS" : "Active Sessions",
|
||||
|
||||
"TABLE_HEADER_SESSION_USERNAME" : "Username",
|
||||
"TABLE_HEADER_SESSION_STARTDATE" : "Active since",
|
||||
"TABLE_HEADER_SESSION_REMOTEHOST" : "Remote host",
|
||||
"TABLE_HEADER_SESSION_CONNECTION_NAME" : "Connection name",
|
||||
|
||||
"TEXT_CONFIRM_DELETE" : "Are you sure you want to kill all selected sessions? The users using these sessions will be immediately disconnected."
|
||||
|
||||
},
|
||||
|
||||
"USER_MENU" : {
|
||||
|
||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
||||
@@ -422,6 +448,7 @@
|
||||
"ACTION_LOGOUT" : "@:APP.ACTION_LOGOUT",
|
||||
"ACTION_MANAGE_CONNECTIONS" : "@:APP.ACTION_MANAGE_CONNECTIONS",
|
||||
"ACTION_MANAGE_SESSIONS" : "@:APP.ACTION_MANAGE_SESSIONS",
|
||||
"ACTION_MANAGE_SETTINGS" : "@:APP.ACTION_MANAGE_SETTINGS",
|
||||
"ACTION_MANAGE_USERS" : "@:APP.ACTION_MANAGE_USERS",
|
||||
"ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME",
|
||||
"ACTION_SAVE" : "@:APP.ACTION_SAVE",
|
||||
|
Reference in New Issue
Block a user