GUACAMOLE-334: Add support for downloading connection history search results as CSV.

This commit is contained in:
Michael Jumper
2017-06-24 12:00:12 -07:00
parent 18effb247c
commit 9902698d3a
5 changed files with 179 additions and 2 deletions

View File

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

View File

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

View File

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

View File

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

View File

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