Repository: incubator-guacamole-client Updated Branches: refs/heads/master ef9ba688c -> b26a664d6
GUACAMOLE-334: Add support for downloading connection history search results as CSV. Project: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/commit/9902698d Tree: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/tree/9902698d Diff: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/diff/9902698d Branch: refs/heads/master Commit: 9902698d3a93c0a61d884afac14a93358c7b3293 Parents: 18effb2 Author: Michael Jumper <[email protected]> Authored: Sat Jun 24 12:00:12 2017 -0700 Committer: Michael Jumper <[email protected]> Committed: Mon Jun 26 13:13:44 2017 -0700 ---------------------------------------------------------------------- .../directives/guacSettingsConnectionHistory.js | 49 +++++++++ .../webapp/app/settings/services/csvService.js | 106 +++++++++++++++++++ .../main/webapp/app/settings/styles/history.css | 19 +++- .../templates/settingsConnectionHistory.html | 1 + guacamole/src/main/webapp/translations/en.json | 6 +- 5 files changed, 179 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/9902698d/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnectionHistory.js ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnectionHistory.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnectionHistory.js index 9f5c80d..216641d 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnectionHistory.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnectionHistory.js @@ -39,8 +39,10 @@ angular.module('settings').directive('guacSettingsConnectionHistory', [function var SortOrder = $injector.get('SortOrder'); // Get required services + var $filter = $injector.get('$filter'); var $routeParams = $injector.get('$routeParams'); var $translate = $injector.get('$translate'); + var csvService = $injector.get('csvService'); var historyService = $injector.get('historyService'); /** @@ -178,6 +180,53 @@ angular.module('settings').directive('guacSettingsConnectionHistory', [function }; + /** + * Initiates a download of a CSV version of the displayed history + * search results. + */ + $scope.downloadCSV = function downloadCSV() { + + // Translate CSV header + $translate([ + 'SETTINGS_CONNECTION_HISTORY.TABLE_HEADER_SESSION_USERNAME', + 'SETTINGS_CONNECTION_HISTORY.TABLE_HEADER_SESSION_STARTDATE', + 'SETTINGS_CONNECTION_HISTORY.TABLE_HEADER_SESSION_DURATION', + 'SETTINGS_CONNECTION_HISTORY.TABLE_HEADER_SESSION_CONNECTION_NAME', + 'SETTINGS_CONNECTION_HISTORY.FILENAME_HISTORY_CSV' + ]).then(function headerTranslated(translations) { + + // Initialize records with translated header row + var records = [[ + translations['SETTINGS_CONNECTION_HISTORY.TABLE_HEADER_SESSION_USERNAME'], + translations['SETTINGS_CONNECTION_HISTORY.TABLE_HEADER_SESSION_STARTDATE'], + translations['SETTINGS_CONNECTION_HISTORY.TABLE_HEADER_SESSION_DURATION'], + translations['SETTINGS_CONNECTION_HISTORY.TABLE_HEADER_SESSION_CONNECTION_NAME'] + ]]; + + // Add rows for all history entries, using the same sort + // order as the displayed table + angular.forEach( + $filter('orderBy')( + $scope.historyEntryWrappers, + $scope.order.predicate + ), + function pushRecord(historyEntryWrapper) { + records.push([ + historyEntryWrapper.username, + $filter('date')(historyEntryWrapper.startDate, $scope.dateFormat), + historyEntryWrapper.duration / 1000, + historyEntryWrapper.connectionName + ]); + } + ); + + // Save the result + saveAs(csvService.toBlob(records), translations['SETTINGS_CONNECTION_HISTORY.FILENAME_HISTORY_CSV']); + + }); + + }; + // Initialize search results $scope.search(); http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/9902698d/guacamole/src/main/webapp/app/settings/services/csvService.js ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/settings/services/csvService.js b/guacamole/src/main/webapp/app/settings/services/csvService.js new file mode 100644 index 0000000..e68cf15 --- /dev/null +++ b/guacamole/src/main/webapp/app/settings/services/csvService.js @@ -0,0 +1,106 @@ +/* + * 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. + */ + +/** + * A service for generating downloadable CSV links given arbitrary data. + */ +angular.module('settings').factory('csvService', [function csvService() { + + var service = {}; + + /** + * Encodes an arbitrary value for inclusion in a CSV file as an individual + * field. With the exception of null and undefined (which are both + * interpreted as equivalent to an empty string), all values are coerced to + * a string and, if non-numeric, included within double quotes. If the + * value itself includes double quotes, those quotes will be properly + * escaped. + * + * @param {*} field + * The arbitrary value to encode. + * + * @return {String} + * The provided value, coerced to a string and properly escaped for + * CSV. + */ + var encodeField = function encodeField(field) { + + // Coerce field to string + if (field === null || field === undefined) + field = ''; + else + field = '' + field; + + // Do not quote numeric fields + if (/^[0-9.]*$/.test(field)) + return field; + + // Enclose all other fields in quotes, escaping any quotes therein + return '"' + field.replace(/"/g, '""') + '"'; + + }; + + /** + * Encodes each of the provided values for inclusion in a CSV file as + * fields within the same record (in the manner specified by + * encodeField()), separated by commas. + * + * @param {*[]} fields + * An array of arbitrary values which make up the record. + * + * @return {String} + * A CSV record containing the each value in the given array. + */ + var encodeRecord = function encodeRecord(fields) { + return fields.map(encodeField).join(','); + }; + + /** + * Encodes an entire array of records as properly-formatted CSV, where each + * entry in the provided array is an array of arbitrary fields. + * + * @param {Array.<*[]>} records + * An array of all records making up the desired CSV. + * + * @return {String} + * An entire CSV containing each provided record, separated by CR+LF + * line terminators. + */ + var encodeCSV = function encodeCSV(records) { + return records.map(encodeRecord).join('\r\n'); + }; + + /** + * Creates a new Blob containing properly-formatted CSV generated from the + * given array of records, where each entry in the provided array is an + * array of arbitrary fields. + * + * @param {Array.<*[]>} records + * An array of all records making up the desired CSV. + * + * @returns {Blob} + * A new Blob containing each provided record in CSV format. + */ + service.toBlob = function toBlob(records) { + return new Blob([ encodeCSV(records) ], { type : 'text/csv' }); + }; + + return service; + +}]); http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/9902698d/guacamole/src/main/webapp/app/settings/styles/history.css ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/settings/styles/history.css b/guacamole/src/main/webapp/app/settings/styles/history.css index 174903e..7b05e1e 100644 --- a/guacamole/src/main/webapp/app/settings/styles/history.css +++ b/guacamole/src/main/webapp/app/settings/styles/history.css @@ -46,9 +46,26 @@ } -.settings.connectionHistory .filter .search-button { +.settings.connectionHistory .filter .search-string { + -ms-flex: 1 1 auto; + -moz-box-flex: 1; + -webkit-box-flex: 1; + -webkit-flex: 1 1 auto; + flex: 1 1 auto; +} + +.settings.connectionHistory .filter .search-button, +.settings.connectionHistory .filter button { + + -ms-flex: 0 0 auto; + -moz-box-flex: 0; + -webkit-box-flex: 0; + -webkit-flex: 0 0 auto; + flex: 0 0 auto; + margin-top: 0; margin-bottom: 0; + } .settings.connectionHistory .history-list { http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/9902698d/guacamole/src/main/webapp/app/settings/templates/settingsConnectionHistory.html ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/settings/templates/settingsConnectionHistory.html b/guacamole/src/main/webapp/app/settings/templates/settingsConnectionHistory.html index d0de134..2963ba1 100644 --- a/guacamole/src/main/webapp/app/settings/templates/settingsConnectionHistory.html +++ b/guacamole/src/main/webapp/app/settings/templates/settingsConnectionHistory.html @@ -7,6 +7,7 @@ <form class="filter" ng-submit="search()"> <input class="search-string" type="text" placeholder="{{'SETTINGS_CONNECTION_HISTORY.FIELD_PLACEHOLDER_FILTER' | translate}}" ng-model="searchString" /> <input class="search-button" type="submit" value="{{'SETTINGS_CONNECTION_HISTORY.ACTION_SEARCH' | translate}}" /> + <button type="button" ng-click="downloadCSV()">{{'SETTINGS_CONNECTION_HISTORY.ACTION_DOWNLOAD' | translate}}</button> </form> <!-- Search results --> http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/9902698d/guacamole/src/main/webapp/translations/en.json ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/translations/en.json b/guacamole/src/main/webapp/translations/en.json index 4995f54..86090cd 100644 --- a/guacamole/src/main/webapp/translations/en.json +++ b/guacamole/src/main/webapp/translations/en.json @@ -13,6 +13,7 @@ "ACTION_CONTINUE" : "Continue", "ACTION_DELETE" : "Delete", "ACTION_DELETE_SESSIONS" : "Kill Sessions", + "ACTION_DOWNLOAD" : "Download", "ACTION_LOGIN" : "Login", "ACTION_LOGOUT" : "Logout", "ACTION_MANAGE_CONNECTIONS" : "Connections", @@ -575,10 +576,13 @@ "SETTINGS_CONNECTION_HISTORY" : { - "ACTION_SEARCH" : "@:APP.ACTION_SEARCH", + "ACTION_DOWNLOAD" : "@:APP.ACTION_DOWNLOAD", + "ACTION_SEARCH" : "@:APP.ACTION_SEARCH", "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "FILENAME_HISTORY_CSV" : "history.csv", + "FORMAT_DATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", "HELP_CONNECTION_HISTORY" : "History records for past connections are listed here and can be sorted by clicking the column headers. To search for specific records, enter a filter string and click \"Search\". Only records which match the provided filter string will be listed.",
