From 2928472549e5664247ff892d9155ddca8c9365b5 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 11 Sep 2017 18:27:21 -0700 Subject: [PATCH 1/5] GUACAMOLE-394: Add user history REST endpoint. --- .../rest/history/APIActivityRecord.java | 131 ++++++++++++++++++ .../rest/history/APIConnectionRecord.java | 89 +----------- ...rtPredicate.java => APISortPredicate.java} | 14 +- .../rest/history/HistoryResource.java | 60 +++++++- 4 files changed, 198 insertions(+), 96 deletions(-) create mode 100644 guacamole/src/main/java/org/apache/guacamole/rest/history/APIActivityRecord.java rename guacamole/src/main/java/org/apache/guacamole/rest/history/{APIConnectionRecordSortPredicate.java => APISortPredicate.java} (92%) diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/history/APIActivityRecord.java b/guacamole/src/main/java/org/apache/guacamole/rest/history/APIActivityRecord.java new file mode 100644 index 000000000..c1a0149d6 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/rest/history/APIActivityRecord.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.rest.history; + +import java.util.Date; +import org.apache.guacamole.net.auth.ActivityRecord; + +/** + * A activity record which may be exposed through the REST endpoints. + */ +public class APIActivityRecord { + + /** + * The date and time the activity began. + */ + private final Date startDate; + + /** + * The date and time the activity ended, or null if the activity is + * still in progress or if the end time is unknown. + */ + private final Date endDate; + + /** + * The hostname or IP address of the remote host that performed the + * activity associated with this record, if known. + */ + private final String remoteHost; + + /** + * The name of the user who performed or is performing the activity + * associated with this record. + */ + private final String username; + + /** + * Whether the activity is still in progress. + */ + private final boolean active; + + /** + * Creates a new APIActivityRecord, copying the data from the given activity + * record. + * + * @param record + * The record to copy data from. + */ + public APIActivityRecord(ActivityRecord record) { + this.startDate = record.getStartDate(); + this.endDate = record.getEndDate(); + this.remoteHost = record.getRemoteHost(); + this.username = record.getUsername(); + this.active = record.isActive(); + } + + /** + * Returns the date and time the activity began. + * + * @return + * The date and time the activity began. + */ + public Date getStartDate() { + return startDate; + } + + /** + * Returns the date and time the activity ended, if applicable. + * + * @return + * The date and time the activity ended, or null if the activity is + * still in progress or if the end time is unknown. + */ + public Date getEndDate() { + return endDate; + } + + /** + * Returns the hostname or IP address of the remote host that performed the + * activity associated with this record, if known. + * + * @return + * The hostname or IP address of the remote host that performed the + * activity associated with this record, or null if the remote host is + * unknown. + */ + public String getRemoteHost() { + return remoteHost; + } + + /** + * Returns the name of the user who performed or is performing the activity + * associated with this record. + * + * @return + * The name of the user who performed or is performing the activity + * associated with this record. + */ + public String getUsername() { + return username; + } + + /** + * Returns whether the activity associated with this record is still in + * progress. + * + * @return + * true if the activity associated with this record is still in + * progress, false otherwise. + */ + public boolean isActive() { + return active; + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecord.java b/guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecord.java index 97c99a04d..9d17fbf83 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecord.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecord.java @@ -19,13 +19,12 @@ package org.apache.guacamole.rest.history; -import java.util.Date; import org.apache.guacamole.net.auth.ConnectionRecord; /** * A connection record which may be exposed through the REST endpoints. */ -public class APIConnectionRecord { +public class APIConnectionRecord extends APIActivityRecord { /** * The identifier of the connection associated with this record. @@ -47,32 +46,6 @@ public class APIConnectionRecord { */ private final String sharingProfileName; - /** - * The date and time the connection began. - */ - private final Date startDate; - - /** - * The date and time the connection ended, or null if the connection is - * still running or if the end time is unknown. - */ - private final Date endDate; - - /** - * The host from which the connection originated, if known. - */ - private final String remoteHost; - - /** - * The name of the user who used or is using the connection. - */ - private final String username; - - /** - * Whether the connection is currently active. - */ - private final boolean active; - /** * Creates a new APIConnectionRecord, copying the data from the given * record. @@ -81,15 +54,11 @@ public class APIConnectionRecord { * The record to copy data from. */ public APIConnectionRecord(ConnectionRecord record) { + super(record); this.connectionIdentifier = record.getConnectionIdentifier(); this.connectionName = record.getConnectionName(); this.sharingProfileIdentifier = record.getSharingProfileIdentifier(); this.sharingProfileName = record.getSharingProfileName(); - this.startDate = record.getStartDate(); - this.endDate = record.getEndDate(); - this.remoteHost = record.getRemoteHost(); - this.username = record.getUsername(); - this.active = record.isActive(); } /** @@ -139,58 +108,4 @@ public class APIConnectionRecord { return sharingProfileName; } - /** - * Returns the date and time the connection began. - * - * @return - * The date and time the connection began. - */ - public Date getStartDate() { - return startDate; - } - - /** - * Returns the date and time the connection ended, if applicable. - * - * @return - * The date and time the connection ended, or null if the connection is - * still running or if the end time is unknown. - */ - public Date getEndDate() { - return endDate; - } - - /** - * Returns the remote host from which this connection originated. - * - * @return - * The remote host from which this connection originated. - */ - public String getRemoteHost() { - return remoteHost; - } - - /** - * Returns the name of the user who used or is using the connection at the - * times given by this connection record. - * - * @return - * The name of the user who used or is using the associated connection. - */ - public String getUsername() { - return username; - } - - /** - * Returns whether the connection associated with this record is still - * active. - * - * @return - * true if the connection associated with this record is still active, - * false otherwise. - */ - public boolean isActive() { - return active; - } - } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecordSortPredicate.java b/guacamole/src/main/java/org/apache/guacamole/rest/history/APISortPredicate.java similarity index 92% rename from guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecordSortPredicate.java rename to guacamole/src/main/java/org/apache/guacamole/rest/history/APISortPredicate.java index d2281d037..c45ae9a81 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecordSortPredicate.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/history/APISortPredicate.java @@ -25,10 +25,10 @@ import org.apache.guacamole.net.auth.ActivityRecordSet; import org.apache.guacamole.rest.APIException; /** - * A sort predicate which species the property to use when sorting connection + * A sort predicate which species the property to use when sorting activity * records, along with the sort order. */ -public class APIConnectionRecordSortPredicate { +public class APISortPredicate { /** * The prefix which will be included before the name of a sortable property @@ -43,8 +43,8 @@ public class APIConnectionRecordSortPredicate { public enum SortableProperty { /** - * The date that the connection associated with the connection record - * began (connected). + * The date that the activity associated with the activity record + * began. */ startDate(ActivityRecordSet.SortableProperty.START_DATE); @@ -70,7 +70,7 @@ public class APIConnectionRecordSortPredicate { } /** - * The property to use when sorting ConnectionRecords. + * The property to use when sorting ActivityRecords. */ private ActivityRecordSet.SortableProperty property; @@ -93,7 +93,7 @@ public class APIConnectionRecordSortPredicate { * @throws APIException * If the provided sort predicate string is invalid. */ - public APIConnectionRecordSortPredicate(String value) + public APISortPredicate(String value) throws APIException { // Parse whether sort order is descending @@ -124,7 +124,7 @@ public class APIConnectionRecordSortPredicate { * @return * The ActivityRecordSet.SortableProperty which refers to the same * property as the string originally provided when this - * APIConnectionRecordSortPredicate was created. + * APISortPredicate was created. */ public ActivityRecordSet.SortableProperty getProperty() { return property; diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/history/HistoryResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/history/HistoryResource.java index 53a8cdba8..559ebd565 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/history/HistoryResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/history/HistoryResource.java @@ -28,6 +28,7 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.auth.ActivityRecord; import org.apache.guacamole.net.auth.ActivityRecordSet; import org.apache.guacamole.net.auth.ConnectionRecord; import org.apache.guacamole.net.auth.UserContext; @@ -88,7 +89,7 @@ public class HistoryResource { @Path("connections") public List getConnectionHistory( @QueryParam("contains") List requiredContents, - @QueryParam("order") List sortPredicates) + @QueryParam("order") List sortPredicates) throws GuacamoleException { // Retrieve overall connection history @@ -101,7 +102,7 @@ public class HistoryResource { } // Sort according to specified ordering - for (APIConnectionRecordSortPredicate predicate : sortPredicates) + for (APISortPredicate predicate : sortPredicates) history = history.sort(predicate.getProperty(), predicate.isDescending()); // Limit to maximum result size @@ -117,4 +118,59 @@ public class HistoryResource { } + /** + * Retrieves the login history for all users, restricted by optional filter + * parameters. + * + * @param requiredContents + * The set of strings that each must occur somewhere within the + * returned user records, whether within the associated username or any + * associated date. If non-empty, any user record not matching each of + * the strings within the collection will be excluded from the results. + * + * @param sortPredicates + * A list of predicates to apply while sorting the resulting user + * records, describing the properties involved and the sort order for + * those properties. + * + * @return + * A list of user records, describing the start and end times of user + * sessions. + * + * @throws GuacamoleException + * If an error occurs while retrieving the user history. + */ + @GET + @Path("users") + public List getUserHistory( + @QueryParam("contains") List requiredContents, + @QueryParam("order") List sortPredicates) + throws GuacamoleException { + + // Retrieve overall user history + ActivityRecordSet history = userContext.getUserHistory(); + + // Restrict to records which contain the specified strings + for (String required : requiredContents) { + if (!required.isEmpty()) + history = history.contains(required); + } + + // Sort according to specified ordering + for (APISortPredicate predicate : sortPredicates) + history = history.sort(predicate.getProperty(), predicate.isDescending()); + + // Limit to maximum result size + history = history.limit(MAXIMUM_HISTORY_SIZE); + + // Convert record set to collection of API user records + List apiRecords = new ArrayList(); + for (ActivityRecord record : history.asCollection()) + apiRecords.add(new APIActivityRecord(record)); + + // Return the converted history + return apiRecords; + + } + } From b8ce9c96e712c7751eb7b2dc606c3f39c3945133 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 11 Sep 2017 18:39:32 -0700 Subject: [PATCH 2/5] GUACAMOLE-394: Add history endpoint at user level (analogous to Connection). --- .../guacamole/rest/user/UserResource.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/user/UserResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/user/UserResource.java index 3f142ff08..75a49dbbf 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/user/UserResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/user/UserResource.java @@ -21,8 +21,11 @@ package org.apache.guacamole.rest.user; import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; +import java.util.ArrayList; +import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; +import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.Produces; @@ -30,6 +33,7 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleSecurityException; +import org.apache.guacamole.net.auth.ActivityRecord; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.User; @@ -38,6 +42,7 @@ import org.apache.guacamole.net.auth.UserContext; import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException; import org.apache.guacamole.rest.directory.DirectoryObjectResource; import org.apache.guacamole.rest.directory.DirectoryObjectTranslator; +import org.apache.guacamole.rest.history.APIActivityRecord; import org.apache.guacamole.rest.permission.PermissionSetResource; /** @@ -93,6 +98,31 @@ public class UserResource this.user = user; } + /** + * Retrieves the login (session) history of a single user. + * + * @return + * A list of activity records, describing the start and end times of + * this user's sessions. + * + * @throws GuacamoleException + * If an error occurs while retrieving the user history. + */ + @GET + @Path("history") + public List getUserHistory() + throws GuacamoleException { + + // Retrieve the requested user's history + List apiRecords = new ArrayList(); + for (ActivityRecord record : user.getHistory()) + apiRecords.add(new APIActivityRecord(record)); + + // Return the converted history + return apiRecords; + + } + @Override public void updateObject(APIUser modifiedObject) throws GuacamoleException { From 67fc77a1c7a2efb64e74d400fec8d2013c41ab56 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 11 Sep 2017 19:03:27 -0700 Subject: [PATCH 3/5] GUACAMOLE-394: Expose "last active" time for connections and users via REST API. --- .../rest/connection/APIConnection.java | 33 ++++++++++++++++++- .../apache/guacamole/rest/user/APIUser.java | 31 +++++++++++++++++ .../main/webapp/app/rest/types/Connection.js | 9 +++++ .../src/main/webapp/app/rest/types/User.js | 9 +++++ 4 files changed, 81 insertions(+), 1 deletion(-) diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/connection/APIConnection.java b/guacamole/src/main/java/org/apache/guacamole/rest/connection/APIConnection.java index 8e6a385f4..e402e67a9 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/connection/APIConnection.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/connection/APIConnection.java @@ -20,6 +20,7 @@ package org.apache.guacamole.rest.connection; import java.util.Collection; +import java.util.Date; import java.util.Map; import org.codehaus.jackson.annotate.JsonIgnoreProperties; import org.codehaus.jackson.map.annotate.JsonSerialize; @@ -75,7 +76,13 @@ public class APIConnection { * The count of currently active connections using this connection. */ private int activeConnections; - + + /** + * The date and time that this connection was last used, or null if this + * connection has never been used or this information is unavailable. + */ + private Date lastActive; + /** * Create an empty APIConnection. */ @@ -97,6 +104,7 @@ public class APIConnection { this.identifier = connection.getIdentifier(); this.parentIdentifier = connection.getParentIdentifier(); this.activeConnections = connection.getActiveConnections(); + this.lastActive = connection.getLastActive(); // Set protocol from configuration GuacamoleConfiguration configuration = connection.getConfiguration(); @@ -257,4 +265,27 @@ public class APIConnection { this.sharingProfiles = sharingProfiles; } + /** + * Returns the date and time that this connection was last used, or null if + * this connection has never been used or this information is unavailable. + * + * @return + * The date and time that this connection was last used, or null if this + * connection has never been used or this information is unavailable. + */ + public Date getLastActive() { + return lastActive; + } + + /** + * Sets the date and time that this connection was last used. + * + * @param lastActive + * The date and time that this connection was last used, or null if this + * connection has never been used or this information is unavailable. + */ + public void setLastActive(Date lastActive) { + this.lastActive = lastActive; + } + } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/user/APIUser.java b/guacamole/src/main/java/org/apache/guacamole/rest/user/APIUser.java index 96e223040..e71b5d278 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/user/APIUser.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/user/APIUser.java @@ -19,6 +19,7 @@ package org.apache.guacamole.rest.user; +import java.util.Date; import java.util.Map; import org.codehaus.jackson.annotate.JsonIgnoreProperties; import org.codehaus.jackson.map.annotate.JsonSerialize; @@ -46,6 +47,12 @@ public class APIUser { */ private Map attributes; + /** + * The date and time that this user was last logged in, or null if this user + * has never logged in or this information is unavailable. + */ + private Date lastActive; + /** * Construct a new empty APIUser. */ @@ -60,6 +67,7 @@ public class APIUser { // Set user information this.username = user.getIdentifier(); this.password = user.getPassword(); + this.lastActive = user.getLastActive(); // Associate any attributes this.attributes = user.getAttributes(); @@ -122,4 +130,27 @@ public class APIUser { this.attributes = attributes; } + /** + * Returns the date and time that this user was last logged in, or null if + * this user has never logged in or this information is unavailable. + * + * @return + * The date and time that this user was last logged in, or null if this + * user has never logged in or this information is unavailable. + */ + public Date getLastActive() { + return lastActive; + } + + /** + * Sets the date and time that this user was last logged in. + * + * @param lastActive + * The date and time that this user was last logged in, or null if this + * user has never logged in or this information is unavailable. + */ + public void setLastActive(Date lastActive) { + this.lastActive = lastActive; + } + } diff --git a/guacamole/src/main/webapp/app/rest/types/Connection.js b/guacamole/src/main/webapp/app/rest/types/Connection.js index b4639b23a..52da6b7e2 100644 --- a/guacamole/src/main/webapp/app/rest/types/Connection.js +++ b/guacamole/src/main/webapp/app/rest/types/Connection.js @@ -104,6 +104,15 @@ angular.module('rest').factory('Connection', [function defineConnection() { */ this.sharingProfiles = template.sharingProfiles; + /** + * The time that this connection was last used, in seconds since + * 1970-01-01 00:00:00 UTC. If this information is unknown or + * unavailable, this will be null. + * + * @type Number + */ + this.lastActive = template.lastActive; + }; return Connection; diff --git a/guacamole/src/main/webapp/app/rest/types/User.js b/guacamole/src/main/webapp/app/rest/types/User.js index 9edd1f258..d0a96cc3e 100644 --- a/guacamole/src/main/webapp/app/rest/types/User.js +++ b/guacamole/src/main/webapp/app/rest/types/User.js @@ -53,6 +53,15 @@ angular.module('rest').factory('User', [function defineUser() { */ this.password = template.password; + /** + * The time that this user was last logged in, in seconds since + * 1970-01-01 00:00:00 UTC. If this information is unknown or + * unavailable, this will be null. + * + * @type Number + */ + this.lastActive = template.lastActive; + /** * Arbitrary name/value pairs which further describe this user. The * semantics and validity of these attributes are dictated by the From 138ea40dc947f8ff32f82737e7c2d74adcc07718 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 18 Sep 2017 12:36:35 -0700 Subject: [PATCH 4/5] GUACAMOLE-394: List users in a table displaying last active time. --- .../settings/directives/guacSettingsUsers.js | 33 ++++++++++++++- .../webapp/app/settings/styles/user-list.css | 42 +++++++++++++++++++ .../app/settings/templates/settingsUsers.html | 35 +++++++++++----- .../src/main/webapp/translations/de.json | 6 ++- .../src/main/webapp/translations/en.json | 7 +++- .../src/main/webapp/translations/fr.json | 6 ++- .../src/main/webapp/translations/it.json | 6 ++- .../src/main/webapp/translations/nl.json | 6 ++- .../src/main/webapp/translations/no.json | 6 ++- .../src/main/webapp/translations/ru.json | 6 ++- 10 files changed, 134 insertions(+), 19 deletions(-) create mode 100644 guacamole/src/main/webapp/app/settings/styles/user-list.css diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js index cc134fa94..1ac9fad9c 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js @@ -36,9 +36,11 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings // Required types var ManageableUser = $injector.get('ManageableUser'); var PermissionSet = $injector.get('PermissionSet'); + var SortOrder = $injector.get('SortOrder'); // Required services var $location = $injector.get('$location'); + var $translate = $injector.get('$translate'); var authenticationService = $injector.get('authenticationService'); var dataSourceService = $injector.get('dataSourceService'); var guacNotification = $injector.get('guacNotification'); @@ -98,9 +100,37 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings * @type String[] */ $scope.filteredUserProperties = [ + 'user.lastActive', 'user.username' ]; + /** + * The date format for use for the last active date. + * + * @type String + */ + $scope.dateFormat = null; + + /** + * SortOrder instance which stores the sort order of the listed + * users. + * + * @type SortOrder + */ + $scope.order = new SortOrder([ + 'user.username', + '-user.lastActive' + ]); + + // Get session date format + $translate('SETTINGS_USERS.FORMAT_DATE') + .then(function dateFormatReceived(retrievedDateFormat) { + + // Store received date format + $scope.dateFormat = retrievedDateFormat; + + }); + /** * Returns whether critical data has completed being loaded. * @@ -110,7 +140,8 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings */ $scope.isLoaded = function isLoaded() { - return $scope.manageableUsers !== null + return $scope.dateFormat !== null + && $scope.manageableUsers !== null && $scope.permissions !== null; }; diff --git a/guacamole/src/main/webapp/app/settings/styles/user-list.css b/guacamole/src/main/webapp/app/settings/styles/user-list.css new file mode 100644 index 000000000..6c0edd59f --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/styles/user-list.css @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +.settings.users table.user-list { + width: 100%; +} + +.settings.users table.user-list th.last-active, +.settings.users table.user-list td.last-active { + white-space: nowrap; + width: 0; +} + +.settings.users table.user-list th.username, +.settings.users table.user-list td.username { + width: 100%; +} + +.settings.users table.user-list tr.user td.username a[href] { + display: block; + padding: .5em 1em; +} + +.settings.users table.user-list tr.user td.username { + padding: 0; +} diff --git a/guacamole/src/main/webapp/app/settings/templates/settingsUsers.html b/guacamole/src/main/webapp/app/settings/templates/settingsUsers.html index 41dac6c4b..67f6760eb 100644 --- a/guacamole/src/main/webapp/app/settings/templates/settingsUsers.html +++ b/guacamole/src/main/webapp/app/settings/templates/settingsUsers.html @@ -21,19 +21,32 @@ - + + + + + + + + + + + + + +
+ {{'SETTINGS_USERS.TABLE_HEADER_USERNAME' | translate}} + + {{'SETTINGS_USERS.TABLE_HEADER_LAST_ACTIVE' | translate}} +
+ +
+ {{manageableUser.user.username}} +
+
{{manageableUser.user.lastActive | date : dateFormat}}
+ items="filteredManageableUsers | orderBy : order.predicate"> \ No newline at end of file diff --git a/guacamole/src/main/webapp/translations/de.json b/guacamole/src/main/webapp/translations/de.json index 940340281..90953e5c7 100644 --- a/guacamole/src/main/webapp/translations/de.json +++ b/guacamole/src/main/webapp/translations/de.json @@ -598,9 +598,13 @@ "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "FORMAT_DATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", + "HELP_USERS" : "Klicke oder Tippe auf einen Benutzer um diesen zu verwalten. Abhänig von Ihrer Zugriffsebene können Benutzer hinzugefügt, gelöscht bzw. dessen Passwort geändert werden.", - "SECTION_HEADER_USERS" : "Benutzer" + "SECTION_HEADER_USERS" : "Benutzer", + + "TABLE_HEADER_USERNAME" : "Benutzername" }, diff --git a/guacamole/src/main/webapp/translations/en.json b/guacamole/src/main/webapp/translations/en.json index 2b4fb5d32..a64f64ca4 100644 --- a/guacamole/src/main/webapp/translations/en.json +++ b/guacamole/src/main/webapp/translations/en.json @@ -675,9 +675,14 @@ "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "FORMAT_DATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", + "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" + "SECTION_HEADER_USERS" : "Users", + + "TABLE_HEADER_LAST_ACTIVE" : "Last active", + "TABLE_HEADER_USERNAME" : "Username" }, diff --git a/guacamole/src/main/webapp/translations/fr.json b/guacamole/src/main/webapp/translations/fr.json index d59c38406..8ebbd36cf 100644 --- a/guacamole/src/main/webapp/translations/fr.json +++ b/guacamole/src/main/webapp/translations/fr.json @@ -601,9 +601,13 @@ "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "FORMAT_DATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", + "HELP_USERS" : "Cliquer ou appuyer sur un utilisateur en dessous pour le gérer. Selon vos permissions, les utilisateurs peuvent être ajoutés, supprimés, leur mot de passe changé.", - "SECTION_HEADER_USERS" : "Utilisateur" + "SECTION_HEADER_USERS" : "Utilisateur", + + "TABLE_HEADER_USERNAME" : "Identifiant" }, diff --git a/guacamole/src/main/webapp/translations/it.json b/guacamole/src/main/webapp/translations/it.json index e84e67f48..5b3f64178 100644 --- a/guacamole/src/main/webapp/translations/it.json +++ b/guacamole/src/main/webapp/translations/it.json @@ -543,9 +543,13 @@ "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "FORMAT_DATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", + "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" : "Utenti" + "SECTION_HEADER_USERS" : "Utenti", + + "TABLE_HEADER_USERNAME" : "Username" }, diff --git a/guacamole/src/main/webapp/translations/nl.json b/guacamole/src/main/webapp/translations/nl.json index 7cc867b74..69a789ac6 100644 --- a/guacamole/src/main/webapp/translations/nl.json +++ b/guacamole/src/main/webapp/translations/nl.json @@ -631,9 +631,13 @@ "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "FORMAT_DATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", + "HELP_USERS" : "Klik of tik op een van de onderstaande gebruikers om die te beheren. Afhankelijk van uw toegangsniveau kunnen gebruikers worden toegevoegd, verwijderd en hun wachtwoorden gewijzigd.", - "SECTION_HEADER_USERS" : "Gebruikers" + "SECTION_HEADER_USERS" : "Gebruikers", + + "TABLE_HEADER_USERNAME" : "Gebruikersnaam" }, diff --git a/guacamole/src/main/webapp/translations/no.json b/guacamole/src/main/webapp/translations/no.json index 8b20b5d84..30ea8719d 100644 --- a/guacamole/src/main/webapp/translations/no.json +++ b/guacamole/src/main/webapp/translations/no.json @@ -612,9 +612,13 @@ "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "FORMAT_DATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", + "HELP_USERS" : "Klikk på en bruker under for å administrere den brukeren. Avhengig av din tilgang kan brukere legges til, slettes og passordet kan endres.", - "SECTION_HEADER_USERS" : "Brukere" + "SECTION_HEADER_USERS" : "Brukere", + + "TABLE_HEADER_USERNAME" : "Brukernavn" }, diff --git a/guacamole/src/main/webapp/translations/ru.json b/guacamole/src/main/webapp/translations/ru.json index 0e47f60d2..4f045257e 100644 --- a/guacamole/src/main/webapp/translations/ru.json +++ b/guacamole/src/main/webapp/translations/ru.json @@ -524,9 +524,13 @@ "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "FORMAT_DATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", + "HELP_USERS" : "Нажмите на пользователя, чтобы управлять им. В зависимости от прав доступа возможно добавление и удаление пользователей, а также изменение паролей.", - "SECTION_HEADER_USERS" : "Пользователи" + "SECTION_HEADER_USERS" : "Пользователи", + + "TABLE_HEADER_USERNAME" : "Имя пользователя" }, From dba3f52d2e50fc7f1ca11a457172c52d0054217e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 4 Jan 2018 23:25:09 -0800 Subject: [PATCH 5/5] GUACAMOLE-394: Timestamps within JSON from the REST API are in milliseconds, not seconds. --- guacamole/src/main/webapp/app/rest/types/Connection.js | 2 +- guacamole/src/main/webapp/app/rest/types/User.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/guacamole/src/main/webapp/app/rest/types/Connection.js b/guacamole/src/main/webapp/app/rest/types/Connection.js index 52da6b7e2..76ece9d58 100644 --- a/guacamole/src/main/webapp/app/rest/types/Connection.js +++ b/guacamole/src/main/webapp/app/rest/types/Connection.js @@ -105,7 +105,7 @@ angular.module('rest').factory('Connection', [function defineConnection() { this.sharingProfiles = template.sharingProfiles; /** - * The time that this connection was last used, in seconds since + * The time that this connection was last used, in milliseconds since * 1970-01-01 00:00:00 UTC. If this information is unknown or * unavailable, this will be null. * diff --git a/guacamole/src/main/webapp/app/rest/types/User.js b/guacamole/src/main/webapp/app/rest/types/User.js index d0a96cc3e..f796147d6 100644 --- a/guacamole/src/main/webapp/app/rest/types/User.js +++ b/guacamole/src/main/webapp/app/rest/types/User.js @@ -54,7 +54,7 @@ angular.module('rest').factory('User', [function defineUser() { this.password = template.password; /** - * The time that this user was last logged in, in seconds since + * The time that this user was last logged in, in milliseconds since * 1970-01-01 00:00:00 UTC. If this information is unknown or * unavailable, this will be null. *