diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordSearchTerm.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordSearchTerm.java index e2e62b32e..a03d478f9 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordSearchTerm.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordSearchTerm.java @@ -215,7 +215,7 @@ public class ConnectionRecordSearchTerm { startCalendar.clear(); startCalendar.set( Integer.parseInt(year), - parseInt(month, 0), + parseInt(month, 1) - 1, parseInt(day, 1) ); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml index ad118923f..f6b6698a9 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml @@ -40,19 +40,20 @@ @@ -61,7 +62,6 @@ INSERT INTO guacamole_connection_history ( connection_id, - connection_name, user_id, start_date, end_date @@ -108,8 +108,8 @@ OR ( - (start_date BETWEEN #{term.startDate,jdbcType=DATE} AND #{term.endDate,jdbcType=DATE}) - AND (end_date BETWEEN #{term.startDate,jdbcType=DATE} AND #{term.endDate,jdbcType=DATE}) + (start_date BETWEEN #{term.startDate,jdbcType=TIMESTAMP} AND #{term.endDate,jdbcType=TIMESTAMP}) + AND (end_date BETWEEN #{term.startDate,jdbcType=TIMESTAMP} AND #{term.endDate,jdbcType=TIMESTAMP}) ) @@ -184,8 +184,8 @@ OR ( - (start_date BETWEEN #{term.startDate,jdbcType=DATE} AND #{term.endDate,jdbcType=DATE}) - AND (end_date BETWEEN #{term.startDate,jdbcType=DATE} AND #{term.endDate,jdbcType=DATE}) + (start_date BETWEEN #{term.startDate,jdbcType=TIMESTAMP} AND #{term.endDate,jdbcType=TIMESTAMP}) + AND (end_date BETWEEN #{term.startDate,jdbcType=TIMESTAMP} AND #{term.endDate,jdbcType=TIMESTAMP}) ) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml index 1fa7490a5..b3a23eadc 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionRecordMapper.xml @@ -40,19 +40,20 @@ @@ -61,7 +62,6 @@ INSERT INTO guacamole_connection_history ( connection_id, - connection_name, user_id, start_date, end_date @@ -108,8 +108,8 @@ OR ( - (start_date BETWEEN #{term.startDate,jdbcType=DATE} AND #{term.endDate,jdbcType=DATE}) - AND (end_date BETWEEN #{term.startDate,jdbcType=DATE} AND #{term.endDate,jdbcType=DATE}) + (start_date BETWEEN #{term.startDate,jdbcType=TIMESTAMP} AND #{term.endDate,jdbcType=TIMESTAMP}) + AND (end_date BETWEEN #{term.startDate,jdbcType=TIMESTAMP} AND #{term.endDate,jdbcType=TIMESTAMP}) ) @@ -184,8 +184,8 @@ OR ( - (start_date BETWEEN #{term.startDate,jdbcType=DATE} AND #{term.endDate,jdbcType=DATE}) - AND (end_date BETWEEN #{term.startDate,jdbcType=DATE} AND #{term.endDate,jdbcType=DATE}) + (start_date BETWEEN #{term.startDate,jdbcType=TIMESTAMP} AND #{term.endDate,jdbcType=TIMESTAMP}) + AND (end_date BETWEEN #{term.startDate,jdbcType=TIMESTAMP} AND #{term.endDate,jdbcType=TIMESTAMP}) ) diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/APIConnectionRecordSortPredicate.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/APIConnectionRecordSortPredicate.java index 5c4ebbb94..17872d77d 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/APIConnectionRecordSortPredicate.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/APIConnectionRecordSortPredicate.java @@ -50,7 +50,7 @@ public class APIConnectionRecordSortPredicate { * The name (not identifier) of the connection associated with the * connection record. */ - connection(ConnectionRecordSet.SortableProperty.CONNECTION_NAME), + connectionName(ConnectionRecordSet.SortableProperty.CONNECTION_NAME), /** * The username (identifier) of the user associated with the connection diff --git a/guacamole/src/main/webapp/app/navigation/services/userPageService.js b/guacamole/src/main/webapp/app/navigation/services/userPageService.js index 114ee2efb..9001aa1e0 100644 --- a/guacamole/src/main/webapp/app/navigation/services/userPageService.js +++ b/guacamole/src/main/webapp/app/navigation/services/userPageService.js @@ -173,6 +173,7 @@ angular.module('navigation').factory('userPageService', ['$injector', var canManageUsers = []; var canManageConnections = []; + var canViewConnectionRecords = []; var canManageSessions = []; // Inspect the contents of each provided permission set @@ -234,12 +235,13 @@ angular.module('navigation').factory('userPageService', ['$injector', ) canManageConnections.push(dataSource); - // Determine whether the current user needs access to the session management UI + // Determine whether the current user needs access to the session management UI or view connection history if ( // A user must be a system administrator to manage sessions PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) ) canManageSessions.push(dataSource); + canViewConnectionRecords.push(dataSource); }); @@ -250,7 +252,18 @@ angular.module('navigation').factory('userPageService', ['$injector', url : '/settings/sessions' })); } - + + // If user can manage connections, add links for connection management pages + angular.forEach(canViewConnectionRecords, function addConnectionHistoryLink(dataSource) { + pages.push(new PageDefinition({ + name : [ + 'USER_MENU.ACTION_VIEW_HISTORY', + translationStringService.canonicalize('DATA_SOURCE_' + dataSource) + '.NAME' + ], + url : '/settings/' + encodeURIComponent(dataSource) + '/history' + })); + }); + // If user can manage users, add link to user management page if (canManageUsers.length) { pages.push(new PageDefinition({ diff --git a/guacamole/src/main/webapp/app/rest/types/ConnectionHistoryEntry.js b/guacamole/src/main/webapp/app/rest/types/ConnectionHistoryEntry.js index 1ffb78e37..7e892538c 100644 --- a/guacamole/src/main/webapp/app/rest/types/ConnectionHistoryEntry.js +++ b/guacamole/src/main/webapp/app/rest/types/ConnectionHistoryEntry.js @@ -48,6 +48,13 @@ angular.module('rest').factory('ConnectionHistoryEntry', [function defineConnect */ this.connectionIdentifier = template.connectionIdentifier; + /** + * The name of the connection associated with this history entry. + * + * @type String + */ + this.connectionName = template.connectionName; + /** * The time that usage began, in seconds since 1970-01-01 00:00:00 UTC. * @@ -107,7 +114,7 @@ angular.module('rest').factory('ConnectionHistoryEntry', [function defineConnect * The name of the connection associated with the history entry (not * the connection identifier). */ - CONNECTION_NAME : 'connection', + CONNECTION_NAME : 'connectionName', /** * The username of the user associated with the history entry (the user diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnectionHistory.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnectionHistory.js new file mode 100644 index 000000000..7de5587e1 --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnectionHistory.js @@ -0,0 +1,142 @@ +/* + * 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 viewing connection history records. + */ +angular.module('settings').directive('guacSettingsConnectionHistory', [function guacSettingsConnectionHistory() { + + return { + // Element only + restrict: 'E', + replace: true, + + scope: { + }, + + templateUrl: 'app/settings/templates/settingsConnectionHistory.html', + controller: ['$scope', '$injector', function settingsConnectionHistoryController($scope, $injector) { + + // Get required types + var SortOrder = $injector.get('SortOrder'); + + // Get required services + var $routeParams = $injector.get('$routeParams'); + var $translate = $injector.get('$translate'); + var historyService = $injector.get('historyService'); + + /** + * The identifier of the currently-selected data source. + * + * @type String + */ + $scope.dataSource = $routeParams.dataSource; + + /** + * The identifier of the currently-selected data source. + * + * @type String + */ + $scope.historyRecords = null; + + /** + * The search terms to use when filtering the history records. + * + * @type String + */ + $scope.searchString = ''; + + /** + * The date format for use for start/end dates. + * + * @type String + */ + $scope.dateFormat = null; + + /** + * SortOrder instance which stores the sort order of the history + * records. + * + * @type SortOrder + */ + $scope.order = new SortOrder([ + 'username', + 'startDate', + 'endDate', + 'connectionName' + ]); + + // Get session date format + $translate('SETTINGS_CONNECTION_HISTORY.FORMAT_DATE') + .then(function dateFormatReceived(retrievedDateFormat) { + + // Store received date format + $scope.dateFormat = retrievedDateFormat; + + }); + + /** + * Returns true if the connection history records have been loaded, + * indicating that information needed to render the page is fully + * loaded. + * + * @returns {Boolean} + * true if the history records have been loaded, false + * otherwise. + * + */ + $scope.isLoaded = function isLoaded() { + return $scope.historyRecords !== null + && $scope.dateFormat !== null; + }; + + /** + * Query the API for the connection record history, filtered by + * searchString, and ordered by order. + */ + $scope.search = function search() { + + // Clear current results + $scope.historyRecords = null; + + // Fetch history records + historyService.getConnectionHistory( + $scope.dataSource, + $scope.searchString.split(/\s+/), + $scope.order.predicate + ) + .success(function historyRetrieved(historyRecords) { + + // Store retrieved permissions + $scope.historyRecords = historyRecords; + + }); + + }; + + // Initialize search results + $scope.search(); + + }] + }; + +}]); diff --git a/guacamole/src/main/webapp/app/settings/styles/history.css b/guacamole/src/main/webapp/app/settings/styles/history.css new file mode 100644 index 000000000..92e553baa --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/styles/history.css @@ -0,0 +1,59 @@ +/* + * 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. + */ + +.settings.connectionHistory .filter { + + /* IE10 */ + display: -ms-flexbox; + -ms-flex-align: stretch; + -ms-flex-direction: row; + + /* Ancient Mozilla */ + display: -moz-box; + -moz-box-align: stretch; + -moz-box-orient: horizontal; + + /* Ancient WebKit */ + display: -webkit-box; + -webkit-box-align: stretch; + -webkit-box-orient: horizontal; + + /* Old WebKit */ + display: -webkit-flex; + -webkit-align-items: stretch; + -webkit-flex-direction: row; + + /* W3C */ + display: flex; + align-items: stretch; + flex-direction: row; + +} + +.settings.connectionHistory .filter .search-button { + margin-top: 0; + margin-bottom: 0; +} + +.settings.connectionHistory .history-list { + width: 100%; +} \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/settings/templates/settings.html b/guacamole/src/main/webapp/app/settings/templates/settings.html index e138e6f68..9880ee29c 100644 --- a/guacamole/src/main/webapp/app/settings/templates/settings.html +++ b/guacamole/src/main/webapp/app/settings/templates/settings.html @@ -33,9 +33,10 @@ THE SOFTWARE. - - - - + + + + + diff --git a/guacamole/src/main/webapp/app/settings/templates/settingsConnectionHistory.html b/guacamole/src/main/webapp/app/settings/templates/settingsConnectionHistory.html new file mode 100644 index 000000000..a27a2d06b --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/templates/settingsConnectionHistory.html @@ -0,0 +1,74 @@ +
+ + + +

{{'SETTINGS_CONNECTION_HISTORY.HELP_CONNECTION_HISTORY' | translate}}

+ + +
+ + +
+ + +
+ + + + + + + + + + + + + + + + + + + +
+ {{'SETTINGS_CONNECTION_HISTORY.TABLE_HEADER_SESSION_USERNAME' | translate}} + + {{'SETTINGS_CONNECTION_HISTORY.TABLE_HEADER_SESSION_STARTDATE' | translate}} + + {{'SETTINGS_CONNECTION_HISTORY.TABLE_HEADER_SESSION_ENDDATE' | translate}} + + {{'SETTINGS_CONNECTION_HISTORY.TABLE_HEADER_SESSION_CONNECTION_NAME' | translate}} +
{{historyRecord.username}}{{historyRecord.startDate | date : dateFormat}}{{historyRecord.endDate | date : dateFormat}}{{historyRecord.connectionName}}
+ + +

+ {{'SETTINGS_CONNECTION_HISTORY.INFO_NO_HISTORY' | translate}} +

+ + + +
+ +
diff --git a/guacamole/src/main/webapp/translations/en.json b/guacamole/src/main/webapp/translations/en.json index 918e75efd..b58c209de 100644 --- a/guacamole/src/main/webapp/translations/en.json +++ b/guacamole/src/main/webapp/translations/en.json @@ -20,7 +20,9 @@ "ACTION_NAVIGATE_BACK" : "Back", "ACTION_NAVIGATE_HOME" : "Home", "ACTION_SAVE" : "Save", + "ACTION_SEARCH" : "Search", "ACTION_UPDATE_PASSWORD" : "Update Password", + "ACTION_VIEW_HISTORY" : "History", "DIALOG_HEADER_ERROR" : "Error", @@ -473,6 +475,23 @@ }, + "SETTINGS_CONNECTION_HISTORY" : { + + "ACTION_SEARCH" : "@:APP.ACTION_SEARCH", + + "FORMAT_DATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", + + "HELP_CONNECTION_HISTORY" : "All connection history records are listed here. If you wish to filter the connection records, you can enter a query.", + + "INFO_NO_HISTORY" : "No matching connection history", + + "TABLE_HEADER_SESSION_CONNECTION_NAME" : "Connection name", + "TABLE_HEADER_SESSION_ENDDATE" : "End Time", + "TABLE_HEADER_SESSION_STARTDATE" : "Start Time", + "TABLE_HEADER_SESSION_USERNAME" : "Username" + + }, + "SETTINGS_CONNECTIONS" : { "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", @@ -561,10 +580,10 @@ "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", + "TABLE_HEADER_SESSION_REMOTEHOST" : "Remote host", + "TABLE_HEADER_SESSION_STARTDATE" : "Active since", + "TABLE_HEADER_SESSION_USERNAME" : "Username", "TEXT_CONFIRM_DELETE" : "Are you sure you want to kill all selected sessions? The users using these sessions will be immediately disconnected." @@ -578,7 +597,8 @@ "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_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME", + "ACTION_VIEW_HISTORY" : "@:APP.ACTION_VIEW_HISTORY" }