Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3.7 New feature: export dynamic tables in CSV format #348

Merged
merged 44 commits into from
Nov 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
ff880d6
Added new service for CSV requests
manuasir Nov 13, 2018
e17e5c8
Including third party file-saver library
manuasir Nov 13, 2018
8ee13ec
CSV request service
manuasir Nov 13, 2018
8b5cce1
Added download csv method to agents controller
manuasir Nov 13, 2018
06173ba
Added scope method, also injecting dependencies
manuasir Nov 13, 2018
f880b3b
Added new service for CSV requests
manuasir Nov 13, 2018
d09b1d5
Including third party file-saver library
manuasir Nov 13, 2018
1af8675
CSV request service
manuasir Nov 13, 2018
f2a4ffa
Added download csv method to agents controller
manuasir Nov 13, 2018
f32f217
Added scope method, also injecting dependencies
manuasir Nov 13, 2018
5e05786
Merge branch '3.7-export-csv' of https://github.com/wazuh/wazuh-splun…
manuasir Nov 13, 2018
97a7780
Added new service for CSV requests
manuasir Nov 13, 2018
580a225
Including third party file-saver library
manuasir Nov 13, 2018
ba9a174
CSV request service
manuasir Nov 13, 2018
a18698b
Added download csv method to agents controller
manuasir Nov 13, 2018
75474b8
Added scope method, also injecting dependencies
manuasir Nov 13, 2018
18b89d7
csv request service back
manuasir Nov 13, 2018
c5ec59f
CSV request service
manuasir Nov 13, 2018
1ef296c
Fixed conflicts
manuasir Nov 13, 2018
b47a399
Fixed agent class
manuasir Nov 13, 2018
f7b2532
Added csv backend route
manuasir Nov 13, 2018
a225184
Added filesaver browser module and loading it at start
manuasir Nov 13, 2018
9f6a65f
Communicating service with new backend route
manuasir Nov 13, 2018
1905cfd
Added method to agent class
manuasir Nov 13, 2018
38f9b3a
Resolving conflicts
manuasir Nov 13, 2018
f94b681
Attached class method to scope
manuasir Nov 13, 2018
2e5557f
Implementing backend route for exporting csv
manuasir Nov 13, 2018
a0c0adc
Added csv exporter to backend module
manuasir Nov 14, 2018
c10f63b
Debugging csv output
manuasir Nov 14, 2018
6484678
Merge branch 'dev-refactor' into 3.7-export-csv
manuasir Nov 14, 2018
7c7edcf
Extracting fields with same Kibana delimiters
manuasir Nov 14, 2018
ecf6032
Paginating results in backend
manuasir Nov 14, 2018
a50600c
Adjusting pagination to csv route
manuasir Nov 14, 2018
daa70e2
Managing filters
manuasir Nov 14, 2018
8d68fa4
Exporting csv with filters
manuasir Nov 14, 2018
69d2c99
Downloading csv in agent dashboard
manuasir Nov 14, 2018
d65c177
Improved csv method
manuasir Nov 14, 2018
2f4f721
Sending a parsed object
manuasir Nov 14, 2018
ce76ba9
Deleted console output
manuasir Nov 14, 2018
5c16f51
Improvements after testing
manuasir Nov 15, 2018
b1c066e
Supporting arrays
manuasir Nov 15, 2018
17c621a
Handling empty results
manuasir Nov 15, 2018
187a6e5
Tested feature, this closes #311
manuasir Nov 15, 2018
984a486
Merge branch '3.7' into 3.7-export-csv
Nov 15, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions SplunkAppForWazuh/appserver/controllers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import json
import requests
import re
import csv
import cStringIO
import splunk.appserver.mrsparkle.controllers as controllers
import splunk.appserver.mrsparkle.lib.util as util
from splunk.appserver.mrsparkle.lib.util import make_splunkhome_path
Expand All @@ -34,6 +36,32 @@ def __init__(self):
except Exception as e:
self.logger.error("Error in API module constructor: %s" % (e))

def format(self,arr):
try:
if isinstance(arr,list):
for item in arr:
if isinstance(item,dict):
for key, value in item.iteritems():
if isinstance(value,dict):
self.logger.info('This is a dict '+str(value))

item[key] = json.dumps(value)
elif isinstance(value,list):
i = 0
while i < len(value):
value[i] = str(value[i])
i += 1
else:
item[key] = str(value)
elif isinstance(item,list):
for each in item:
each = str(each)
else:
item = str(item)
return arr
except Exception as e:
raise e

# /custom/SplunkAppForWazuh/api/node
# This will perform an HTTP request to Wazuh API
# It will return the full API response with including its error codes
Expand Down Expand Up @@ -61,3 +89,73 @@ def request(self, **kwargs):
self.logger.error("Error making API request: %s" % (e))
return json.dumps({'error': str(e)})
return result


@expose_page(must_login=False, methods=['POST'])
def csv(self, **kwargs):
try:
if 'id' not in kwargs or 'path' not in kwargs:
raise Exception("Invalid arguments or missing params.")
filters = {}
filters['limit'] = 1000
filters['offset'] = 0

if 'filters' in kwargs:
self.logger.info('Filters route: %s ' % (kwargs['filters']))
parsed_filters = json.loads(kwargs['filters'])
filters.update(parsed_filters)
the_id = kwargs['id']
api = self.db.get(the_id)
opt_username = api[0]["userapi"]
opt_password = api[0]["passapi"]
opt_base_url = api[0]["url"]
opt_base_port = api[0]["portapi"]
opt_endpoint = kwargs['path']
del kwargs['id']
del kwargs['path']
url = opt_base_url + ":" + opt_base_port
auth = requests.auth.HTTPBasicAuth(opt_username, opt_password)
verify = False
# init csv writer
output_file = cStringIO.StringIO()
# get total items and keys
request = self.session.get(url + opt_endpoint, params=filters, auth=auth, verify=verify).json()
self.logger.info('Data items: %s ' % (request['data']['items']))
self.logger.info('Items length: %s ' % (len(request['data']['items'])))
if 'items' in request['data'] and len(request['data']['items']) > 0:
final_obj = request["data"]["items"]
if isinstance(final_obj,list):
keys = final_obj[0].keys()
self.format(keys)
final_obj_dict = self.format(final_obj)
total_items = request["data"]["totalItems"]
# initializes CSV buffer
if total_items > 0:
dict_writer = csv.DictWriter(output_file, delimiter=',',fieldnames=keys,extrasaction='ignore',lineterminator='\n',quotechar='"')
# write CSV header
dict_writer.writeheader()
dict_writer.writerows(final_obj_dict)

offset = 0
# get the rest of results
while offset <= total_items:
offset+=filters['limit']
filters['offset']=offset
req = self.session.get(url + opt_endpoint, params=filters, auth=auth, verify=verify).json()
paginated_result = req['data']['items']
format_paginated_results = self.format(paginated_result)
dict_writer.writerows(format_paginated_results)

csv_result = output_file.getvalue()
self.logger.info('CSV generated successfully.')
else:
csv_result = '[]'
else:
self.logger.info('Else')
csv_result = '[]'
output_file.close()

except Exception as e:
self.logger.error("Error in CSV generation!: %s" % (e))
return json.dumps({"error":str(e)})
return csv_result
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ define(['../module'], function (module) {
.state('agents', {
templateUrl: BASE_URL + '/static/app/SplunkAppForWazuh/js/controllers/agents/agents/agents.html',
controller: 'agentsCtrl',
onEnter: ($navigationService) => { $navigationService.storeRoute('agents') },
resolve: {
agentData: ['$requestService','$state', async ($requestService, $state) => {
try{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,10 @@

<div ng-if="!loading" layout="row" class="wz-margin-10">
<wazuh-table flex path="'/agents'" keys="['id',{value:'name',size:2},'ip','status','group','os.name','os.version','version']" allow-click="true" rows-per-page="17"></wazuh-table>
</div>
<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>
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
define([
'../../module',
'../../../services/visualizations/search/search-handler',
'FileSaver'
],function (
app,
SearchHandler
SearchHandler,
FileSaver
){

'use strict'
Expand All @@ -32,17 +34,19 @@ define([
* @param {Object} $requestService
* @param {Object} agentData
*/
constructor($urlTokenModel, $scope, $currentDataService, $state, $notificationService, $requestService, agentData){

constructor($urlTokenModel, $scope, $currentDataService, $state, $notificationService, $requestService, $csvRequestService,$tableFilterService, agentData){
this.scope = $scope
this.submittedTokenModel = $urlTokenModel.getSubmittedTokenModel()
this.submittedTokenModel.set('activeAgentToken', '-')

this.api = $currentDataService.getApi()
this.apiReq = $requestService.apiReq
this.state = $state
this.toast = $notificationService.showSimpleToast
this.currentClusterInfo = $currentDataService.getClusterInfo()
this.filters = $currentDataService.getSerializedFilters()

this.csvReq = $csvRequestService
this.wzTableFilter = $tableFilterService
const parsedResult = agentData.map(item => item && item.data && item.data.data ? item.data.data : false)

const [
Expand Down Expand Up @@ -82,13 +86,34 @@ define([
this.scope.version = 'all'
this.scope.node_name = 'all'
this.scope.versionModel = 'all'

this.scope.downloadCsv = () => this.downloadCsv()
this.scope.$on('$destroy', () => {
this.topAgent.destroy()
})

}

/**
* Exports the table in CSV format
*/
async downloadCsv() {
try {
this.toast('Your download should begin automatically...')
const currentApi = this.api.id
const output = await this.csvReq.fetch(
'/agents',
currentApi,
this.wzTableFilter.get()
)
const blob = new Blob([output], { type: 'text/csv' }) // eslint-disable-line
saveAs(blob, 'agents.csv')
return
} catch (error) {
console.error('error ',error)
this.toast('Error downloading CSV')
}
return
}

/**
* Searches by a term
* @param {String} term
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,12 @@ <h1 class="md-headline wz-headline">
</wazuh-table>
</div>
<!-- End related decoders section -->

<div layout="row" class="wz-margin-top-10">
<span flex></span>
<a class="small" id="btnDownload" ng-click="downloadCsv('/decoders','decoders.csv')">Formatted
<i aria-hidden="true" class="fa fa-download"></i>
</a>
</div>
</div>
<!-- End rest of decoder information -->
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@
allow-click="true" rows-per-page="14">
</wazuh-table>
</div>

<div layout="row" class="wz-margin-top-10">
<span flex></span>
<a class="small" id="btnDownload" ng-click="downloadCsv('/decoders','decoders.csv')">Formatted
<i aria-hidden="true" class="fa fa-download"></i>
</a>
</div>
</div>

</div>
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ define(['../../module', '../rules/ruleset'], function (controllers, Ruleset) {
'use strict'

class Decoders extends Ruleset {
constructor($scope, $sce, $notificationService) {
super($scope, $sce, $notificationService, 'decoders')
constructor($scope, $sce, $notificationService, $currentDataService,$tableFilterService,$csvRequestService) {
super($scope, $sce, $notificationService, 'decoders',$currentDataService,$tableFilterService,$csvRequestService)
this.scope.typeFilter = 'all'
}

Expand All @@ -13,7 +13,8 @@ define(['../../module', '../rules/ruleset'], function (controllers, Ruleset) {
*/
$onInit() {
// Reloading event listener
this.scope.$broadcast('wazuhSearch', { term:'', removeFilters: true });
this.scope.$broadcast('wazuhSearch', { term:'', removeFilters: true })
this.scope.downloadCsv = (path,name) => this.downloadCsv(path,name)
this.scope.$on('decodersIsReloaded', () => {
this.scope.viewingDetail = false
if (!this.scope.$$phase) this.scope.$digest()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ define(['../../module','../rules/ruleset'], function (controllers, Ruleset) {
'use strict'

class DecodersId extends Ruleset {
constructor($scope, $sce, $notificationService, $state, currentDecoder) {
super($scope,$sce,$notificationService,'decoders')
constructor($scope, $sce, $notificationService, $state, currentDecoder,$currentDataService,$tableFilterService,$csvRequestService) {
super($scope,$sce,$notificationService,'decoders',$currentDataService,$tableFilterService,$csvRequestService)
this.state = $state
try {
this.filters = JSON.parse(window.localStorage.decoders) || []
Expand All @@ -14,6 +14,7 @@ define(['../../module','../rules/ruleset'], function (controllers, Ruleset) {
}

$onInit(){
this.scope.downloadCsv = (path,name) => this.downloadCsv(path,name)
this.scope.addDetailFilter = (name,value) => this.addDetailFilter(name,value)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,28 +81,43 @@

<!-- Groups table -->
<div layout="row" ng-if="!lookingGroup" class="md-padding">
<wazuh-table flex extra-limit="true" path="'/agents/groups'" keys="['name',{value:'count',size:1},{value:'mergedSum',size:3}]"
<wazuh-table ng-if="!lookingGroup" flex extra-limit="true" path="'/agents/groups'" keys="['name',{value:'count',size:1},{value:'mergedSum',size:3}]"
allow-click="true" rows-per-page="14">
</wazuh-table>
</div>
<!-- End groups table -->

<div ng-if="!lookingGroup" layout="row" class="wz-margin-top-10">
<span flex></span>
<a class="small" id="btnDownload" ng-click="downloadCsv('/agents/groups','groups.csv')">Formatted
<i aria-hidden="true" class="fa fa-download"></i>
</a>
</div>
<!-- Group agents table -->
<div layout="row" ng-if="lookingGroup && groupsSelectedTab==='agents' && currentGroup" class="md-padding">
<wazuh-table flex path="'/agents/groups/' + currentGroup.name" keys="['id','name','ip','status','os.name','os.version','version']"
allow-click="true" rows-per-page="14">
</wazuh-table>
</div>
<!-- End Group agents table -->

<div ng-if="lookingGroup && groupsSelectedTab==='agents' && currentGroup" layout="row" class="wz-margin-top-10">
<span flex></span>
<a class="small" id="btnDownload" ng-click="downloadCsv('/agents/groups/' + currentGroup.name,'agentsgroups.csv')">Formatted
<i aria-hidden="true" class="fa fa-download"></i>
</a>
</div>
<!-- Group files table -->
<div layout="row" ng-if="lookingGroup && groupsSelectedTab==='files' && !fileViewer && currentGroup" class="md-padding">
<wazuh-table extra-limit="true" flex path="'/agents/groups/' + currentGroup.name + '/files'" keys="[{value:'filename',size:2},{value:'hash',size:6}]"
allow-click="true" rows-per-page="10">
</wazuh-table>
</div>
<!-- End Group files table -->

<div ng-if="lookingGroup && groupsSelectedTab==='files' && !fileViewer && currentGroup" layout="row" class="wz-margin-top-10">
<span flex></span>
<a class="small" id="btnDownload" ng-click="downloadCsv('/agents/groups/' + currentGroup.name + '/files','groupfiles.csv')">Formatted
<i aria-hidden="true" class="fa fa-download"></i>
</a>
</div>
<!-- File JSON viewer section -->
<div flex layout="column" class="md-padding" ng-if="lookingGroup && groupsSelectedTab==='files' && file">
<div flex layout="column">
Expand Down
Loading