Merge pull request #431 from wazuh/3.2-csv-export

CSV exporting
This commit is contained in:
Javier Castro 2018-05-07 11:42:04 +02:00 committed by GitHub
commit d780082803
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 245 additions and 19 deletions

View File

@ -31,6 +31,7 @@
"ansicolors": "^0.3.2",
"install": "^0.10.1",
"js-yaml": "3.10.0",
"json2csv": "^4.1.2",
"lodash": "3.10.1",
"needle": "^2.0.1",
"node-cron": "^1.1.2",

View File

@ -56,6 +56,7 @@ import 'plugins/wazuh/services/data-handler.js';
import 'plugins/wazuh/services/app-state.js';
import 'plugins/wazuh/services/api-tester.js';
import 'plugins/wazuh/services/pattern-handler.js';
import 'plugins/wazuh/services/csv-request.js';
// Set up routes and views
import 'plugins/wazuh/services/routes.js';

View File

@ -10,10 +10,11 @@
* Find more information about this on the LICENSE file.
*/
import * as modules from 'ui/modules'
import CsvGenerator from './csv-generator'
const app = modules.get('app/wazuh', []);
app.controller('agentsPreviewController', function ($scope, $rootScope, $routeParams, genericReq, apiReq, appState, Agents, $location, errorHandler) {
app.controller('agentsPreviewController', function ($scope, $rootScope, $routeParams, genericReq, apiReq, appState, Agents, $location, errorHandler, csvReq) {
$scope.loading = true;
$scope.agents = Agents;
$scope.status = 'all';
@ -80,6 +81,18 @@ app.controller('agentsPreviewController', function ($scope, $rootScope, $routePa
}
}
$scope.downloadCsv = async () => {
try {
const currentApi = JSON.parse(appState.getCurrentAPI()).id;
const output = await csvReq.fetch('/agents', currentApi, $scope.agents ? $scope.agents.filters : null);
const csvGenerator = new CsvGenerator(output.csv, 'agents.csv');
csvGenerator.download(true);
} catch (error) {
errorHandler.handle(error,'Download CSV');
if(!$rootScope.$$phase) $rootScope.$digest();
}
}
const load = async () => {
try{
const data = await Promise.all([

View File

@ -0,0 +1,34 @@
/*
* Wazuh app - CSV file generator
* Copyright (C) 2018 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
export default class CsvGenerator {
constructor (dataArray, fileName) {
this.dataArray = dataArray;
this.fileName = fileName;
}
getLinkElement (linkText) {
return this.linkElement = this.linkElement || $('<a>' + (linkText || '') + '</a>', {
href: 'data:attachment/csv;base64,' + encodeURI(btoa(this.dataArray)),
target: '_blank',
download: this.fileName
});
}
download (removeAfterDownload) {
this.getLinkElement().css('display', 'none').appendTo('body');
this.getLinkElement()[0].click();
if (removeAfterDownload) {
this.getLinkElement().remove();
}
}
}

View File

@ -11,12 +11,13 @@
*/
import beautifier from 'plugins/wazuh/utils/json-beautifier';
import * as modules from 'ui/modules'
import CsvGenerator from './csv-generator'
const app = modules.get('app/wazuh', []);
// Groups preview controller
app.controller('groupsPreviewController',
function ($scope, $rootScope, $location, apiReq, Groups, GroupFiles, GroupAgents, errorHandler) {
function ($scope, $rootScope, $location, apiReq, Groups, GroupFiles, GroupAgents, errorHandler, csvReq, appState) {
const reloadWatcher = $rootScope.$watch('groupsIsReloaded',() => {
delete $rootScope.groupsIsReloaded;
$scope.lookingGroup = false;
@ -31,6 +32,19 @@ function ($scope, $rootScope, $location, apiReq, Groups, GroupFiles, GroupAgents
$scope.groupAgents = GroupAgents;
$scope.groupFiles = GroupFiles;
$scope.downloadCsv = async dataProvider => {
try {
const path = $scope[dataProvider] ? $scope[dataProvider].path : null;
const currentApi = JSON.parse(appState.getCurrentAPI()).id;
const output = await csvReq.fetch(path, currentApi, $scope[dataProvider] ? $scope[dataProvider].filters : null);
const csvGenerator = new CsvGenerator(output.csv, 'groups.csv');
csvGenerator.download(true);
} catch (error) {
errorHandler.handle(error,'Download CSV');
if(!$rootScope.$$phase) $rootScope.$digest();
}
}
// Store a boolean variable to check if come from agents
const fromAgents = ('comeFrom' in $rootScope) && ('globalAgent' in $rootScope) && $rootScope.comeFrom === 'agents';

View File

@ -10,11 +10,12 @@
* Find more information about this on the LICENSE file.
*/
import * as modules from 'ui/modules'
import CsvGenerator from './csv-generator'
const app = modules.get('app/wazuh', []);
// Logs controller
app.controller('managerLogController', function ($scope, $rootScope, Logs, apiReq, errorHandler) {
app.controller('managerLogController', function ($scope, $rootScope, Logs, apiReq, errorHandler, csvReq, appState) {
$scope.searchTerm = '';
$scope.loading = true;
$scope.logs = Logs;
@ -48,8 +49,20 @@ app.controller('managerLogController', function ($scope, $rootScope, Logs, apiRe
if(!$scope.$$phase) $scope.$digest();
}
$scope.downloadCsv = async () => {
try {
const currentApi = JSON.parse(appState.getCurrentAPI()).id;
const output = await csvReq.fetch('/manager/logs', currentApi, $scope.logs ? $scope.logs.filters : null);
const csvGenerator = new CsvGenerator(output.csv, 'logs.csv');
csvGenerator.download(true);
} catch (error) {
errorHandler.handle(error,'Download CSV');
if(!$rootScope.$$phase) $rootScope.$digest();
}
}
const initialize = async () => {
try{
try{
await $scope.logs.nextPage();
const data = await apiReq.request('GET', '/manager/logs/summary', {});
$scope.summary = data.data.data;

View File

@ -10,10 +10,11 @@
* Find more information about this on the LICENSE file.
*/
import * as modules from 'ui/modules'
import CsvGenerator from './csv-generator'
const app = modules.get('app/wazuh', []);
app.controller('rulesController', function ($scope, $rootScope, Rules, RulesRelated, RulesAutoComplete, errorHandler, genericReq, appState) {
app.controller('rulesController', function ($scope, $rootScope, Rules, RulesRelated, RulesAutoComplete, errorHandler, genericReq, appState, csvReq) {
$scope.setRulesTab = tab => $rootScope.globalsubmenuNavItem2 = tab;
@ -66,6 +67,18 @@ app.controller('rulesController', function ($scope, $rootScope, Rules, RulesRela
}
};
$scope.downloadCsv = async () => {
try {
const currentApi = JSON.parse(appState.getCurrentAPI()).id;
const output = await csvReq.fetch('/rules', currentApi, $scope.rules ? $scope.rules.filters : null);
const csvGenerator = new CsvGenerator(output.csv, 'rules.csv');
csvGenerator.download(true);
} catch (error) {
errorHandler.handle(error,'Download CSV');
if(!$rootScope.$$phase) $rootScope.$digest();
}
}
/**
* This function takes back to the list but adding a group filter
*/
@ -169,7 +182,7 @@ app.controller('rulesController', function ($scope, $rootScope, Rules, RulesRela
});
});
app.controller('decodersController', function ($scope, $rootScope, $sce, Decoders, DecodersRelated, DecodersAutoComplete, errorHandler, genericReq, appState) {
app.controller('decodersController', function ($scope, $rootScope, $sce, Decoders, DecodersRelated, DecodersAutoComplete, errorHandler, genericReq, appState, csvReq) {
$scope.setRulesTab = tab => $rootScope.globalsubmenuNavItem2 = tab;
//Initialization
@ -257,6 +270,18 @@ app.controller('decodersController', function ($scope, $rootScope, $sce, Decoder
}
}
$scope.downloadCsv = async () => {
try {
const currentApi = JSON.parse(appState.getCurrentAPI()).id;
const output = await csvReq.fetch('/decoders', currentApi, $scope.decoders ? $scope.decoders.filters : null);
const csvGenerator = new CsvGenerator(output.csv, 'decoders.csv');
csvGenerator.download(true);
} catch (error) {
errorHandler.handle(error,'Download CSV');
if(!$rootScope.$$phase) $rootScope.$digest();
}
}
/**
* This function changes to the decoder detail view
*/

View File

@ -0,0 +1,27 @@
/*
* Wazuh app - API request service
* Copyright (C) 2018 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import * as modules from 'ui/modules'
const app = modules.get('app/wazuh', []);
app.service('csvReq', function (genericReq) {
return {
fetch: async (path, id, filters = null) => {
try {
const output = await genericReq.request('POST','/api/wazuh-api/csv',{ path, id, filters });
return output.data;
} catch (error) {
return Promise.reject(error);
}
}
}
});

View File

@ -20,6 +20,7 @@ app.service('errorHandler', function ( Notifier, appState, $location) {
if(error.data && error.data.errorData && error.data.errorData.message) return error.data.errorData.message;
if(error.errorData && error.errorData.message) return error.errorData.message;
if(error.data && typeof error.data === 'string') return error.data;
if(error.data && error.data.error && typeof error.data.error === 'string') return error.data.error;
if(error.data && error.data.message && typeof error.data.message === 'string') return error.data.message;
if(error.data && error.data.message && error.data.message.msg && typeof error.data.message.msg === 'string') return error.data.message.msg;
if(error.data && error.data.data && typeof error.data.data === 'string') return error.data.data;
@ -119,7 +120,7 @@ app.service('errorHandler', function ( Notifier, appState, $location) {
if(error.extraMessage) text = error.extraMessage;
text = location ? location + '. ' + text : text;
if(!silent){
if(isWarning) notify.warning(text);
if(isWarning || (text && typeof text === 'string' && text.toLowerCase().includes('no results'))) notify.warning(text);
else notify.error(text);
}
if(goSettings) $location.path('/settings');

View File

@ -109,4 +109,8 @@
class="no-lateral-padding"
full="'agent'">
</wz-table>
<div layout="row" class="wz-margin-top-10">
<span flex></span>
<a class="small" id="btnDownload" ng-click="downloadCsv()">Formatted <i aria-hidden="true" class="fa fa-download"></i></a>
</div>
</div>

View File

@ -52,9 +52,12 @@
{name:'MD5 agent.conf'}
]">
</wz-table-header>
<wz-table ng-if="!lookingGroup" layout="row" flex func="loadGroup(index)" data="groups" keys="[{col:'name',size:20},{col:'count',size:15},'merged_sum']"
ng-if="!lookingGroup"></wz-table>
<wz-table ng-if="!lookingGroup" layout="row" flex func="loadGroup(index)" data="groups" keys="[{col:'name',size:20},{col:'count',size:15},'merged_sum']"></wz-table>
<div layout="row" class="wz-margin-top-10" ng-if="!lookingGroup">
<span flex></span>
<a class="small" id="btnDownload" ng-click="downloadCsv('groups')">Formatted <i aria-hidden="true" class="fa fa-download"></i></a>
</div>
<wz-table-header layout="row" data="groupAgents" keys="[
{name:'ID',sortValue:'id',size:10},
@ -66,19 +69,28 @@
]" ng-show="lookingGroup && groupsSelectedTab==='agents'">
</wz-table-header>
<wz-table layout="column" flex full="'agent'" func="showAgent(agent)" data="groupAgents" keys="[{col:'id',size:10},'name','ip','os.platform','os.version','version']" ng-if="lookingGroup && groupsSelectedTab==='agents'">
</wz-table>
<div layout="row" class="wz-margin-top-10" ng-if="lookingGroup && groupsSelectedTab==='agents'">
<span flex></span>
<a class="small" id="btnDownload" ng-click="downloadCsv('groupAgents')">Formatted <i aria-hidden="true" class="fa fa-download"></i></a>
</div>
<wz-table-header layout="row" data="groupFiles" keys="[
{name:'File',sortValue:'filename',size:30},
{name:'MD5 checksum'}
]" ng-show="lookingGroup && groupsSelectedTab==='files' && !file">
</wz-table-header>
<wz-table layout="column" flex full="'agent'" func="showAgent(agent)" data="groupAgents" keys="[{col:'id',size:10},'name','ip','os.platform','os.version','version']"
ng-if="lookingGroup && groupsSelectedTab==='agents'">
</wz-table>
<wz-table layout="column" flex func="showFile(index)" data="groupFiles" keys="[{col:'filename',size:30},'hash']" ng-if="lookingGroup && groupsSelectedTab==='files' && !file">
</wz-table>
<div layout="row" class="wz-margin-top-10" ng-if="lookingGroup && groupsSelectedTab==='files' && !file">
<span flex></span>
<a class="small" id="btnDownload" ng-click="downloadCsv('groupFiles')">Formatted <i aria-hidden="true" class="fa fa-download"></i></a>
</div>
<div flex layout="column" class="md-padding" ng-if="lookingGroup && groupsSelectedTab==='files' && file">
<div flex layout="column">
<div layout="row" class="wz-padding-bottom-14">

View File

@ -18,6 +18,7 @@
<option value="warning">Warning</option>
</select>
</div>
<span flex></span>
<md-button ng-if="!realtime" class="wazuh-button md-raised md-primary manager-log-buttonplay" ng-click="playRealtime()" aria-label="Play realtime button">
<i class="fa fa-play fa-fw" aria-hidden="true"></i>
@ -83,5 +84,9 @@
nopointer="true"
noheight="true">
</wz-table>
<div layout="row" class="wz-margin-top-10">
<span flex></span>
<a class="small" id="btnDownload" ng-click="downloadCsv()">Formatted <i aria-hidden="true" class="fa fa-download"></i></a>
</div>
</div>
</div>

View File

@ -83,4 +83,8 @@
isdecoders="true"
class="no-lateral-padding">
</wz-table>
<div layout="row" class="wz-margin-top-10">
<span flex></span>
<a class="small" id="btnDownload" ng-click="downloadCsv()">Formatted <i aria-hidden="true" class="fa fa-download"></i></a>
</div>
</div>

View File

@ -103,4 +103,8 @@
isdecoders="false"
class="no-lateral-padding">
</wz-table>
<div layout="row" class="wz-margin-top-10">
<span flex></span>
<a class="small" id="btnDownload" ng-click="downloadCsv()">Formatted <i aria-hidden="true" class="fa fa-download"></i></a>
</div>
</div>

View File

@ -21,7 +21,7 @@ import ElasticWrapper from '../lib/elastic-wrapper'
import getPath from '../../util/get-path'
import packageInfo from '../../package.json'
import monitoring from '../monitoring'
import { Parser } from 'json2csv';
const blueWazuh = colors.blue('wazuh');
export default class WazuhApi {
@ -497,4 +497,69 @@ export default class WazuhApi {
return reply(this.genericErrorBuilder(500,6,error.message || error)).code(500)
}
}
/**
* Get full data on CSV format from a list Wazuh API endpoint
* @param {*} req
* @param {*} res
*/
async csv(req,res) {
try{
if(!req.payload || !req.payload.path) throw new Error('Field path is required')
if(!req.payload.id) throw new Error('Field id is required')
const filters = req.payload && req.payload.filters && Array.isArray(req.payload.filters) ?
req.payload.filters :
[];
const config = await this.wzWrapper.getWazuhConfigurationById(req.payload.id)
let path = req.payload.path;
if(path && typeof path === 'string'){
path = path[0] === '/' ? path.substr(1) : path
}
if(!path) throw new Error('An error occurred parsing path field')
const params = { limit: 99999 };
if(filters.length) {
for(const filter of filters) {
if(!filter.name || !filter.value) continue;
params[filter.name] = filter.value;
}
}
const output = await needle('get', `${config.url}:${config.port}/${path}`, params, {
username : config.user,
password : config.password,
rejectUnauthorized: !config.insecure
})
if(output && output.body && output.body.data && output.body.data.totalItems) {
const fields = Object.keys(output.body.data.items[0]);
const data = output.body.data.items;
const json2csvParser = new Parser({ fields });
const csv = json2csvParser.parse(data);
return res({ csv });
} else if (output && output.body && output.body.data && !output.body.data.totalItems) {
throw new Error('No results')
} else {
throw new Error('An error occurred fetching data from the Wazuh API')
}
} catch (error) {
return res({ error: error.message || error }).code(500)
}
}
}

View File

@ -33,12 +33,15 @@ export default (server, options) => {
// Write in debug log
server.route({ method: 'POST', path: '/api/wazuh/errlog', handler: (req,res) => ctrl.postErrorLog(req,res) });
// COMMENT HERE
// Force fetch data to be inserted on wazuh-monitoring indices
server.route({ method: 'GET', path: '/api/wazuh-api/fetchAgents', handler: (req,res) => ctrl.fetchAgents(req,res) });
// COMMENT HERE
// Returns the config.yml file parsed
server.route({ method: 'GET', path: '/api/wazuh-api/configuration', handler: (req,res) => ctrl.getConfigurationFile(req,res) });
// COMMENT HERE
server.route({ method: 'POST',path: '/api/wazuh-api/wlogin', handler: (req,res) => ctrl.login(req,res) });
// Experimental feature to simulate a login system
server.route({ method: 'POST', path: '/api/wazuh-api/wlogin', handler: (req,res) => ctrl.login(req,res) });
// Returns data from the Wazuh API on CSV readable format
server.route({ method: 'POST', path: '/api/wazuh-api/csv', handler: (req,res) => ctrl.csv(req,res)})
};