Splunk/table-expandable-rows-time-picker
You are here: | Table with exandable rows showing events associated + time picker
|
Description
In this tutorial, I'm going to show you an advanced dashboard using Javascript. It's a table that shows alerts and categories from Suricata along with counters (number of alerts, number of distinct sources and destinations) and a sparkline. Each row can be expanded to show the events that correspond to the alert selected. And finally, the entire table (alerts and associated events) can be time filtered thanks to a time picker input.
Here is the final result:
Form
XML code
Below is the code for the form:
<form script="expand_alerts.js">
<label>SIEM</label>
<fieldset submitButton="false">
<input type="time" token="TimeRangePicker" searchWhenChanged="true">
<label>TimeRange</label>
<default>
<earliest>@d</earliest>
<latest>now</latest>
</default>
</input>
</fieldset>
<row>
<panel>
<table id="expand_with_events">
<title>Alerts</title>
<search>
<query>source="*suricata*" | stats distinct_count(src_ip) AS src_ip distinct_count(dest_ip) AS dest_ip sparkline count by alert.signature_id alert.signature alert.category | sort -count | table alert.signature_id alert.signature alert.category count src_ip dest_ip sparkline</query>
<earliest>$TimeRangePicker.earliest$</earliest>
<latest>$TimeRangePicker.latest$</latest>
</search>
<option name="wrap">true</option>
<option name="rowNumbers">false</option>
<option name="drilldown">cell</option>
<option name="dataOverlayMode">none</option>
<option name="count">10</option>
</table>
</panel>
</row>
</form>
Search
The search is defined as follows:
source="*suricata*" | stats distinct_count(src_ip) AS src_ip distinct_count(dest_ip) AS dest_ip sparkline count by alert.signature_id alert.signature alert.category | sort -count | table alert.signature_id alert.signature alert.category count src_ip dest_ip sparkline
It selects all entries from the suricata json log file, and aggregates them (stats distinct_count) to get unique values of alerts and categories. It also computes for each group of alarms the number of distinct source and destination IP addresses and adds a sparkline. The whole resulting list is then sorted by the number of alerts in the descending order (sort -count), and fields are reorganized in a more appropriate order (table ....
You will notice that the table has the ID expand_with_events that will be used later in the javascript.
Time Picker input
On top of the form, a time picker input has been added to be able to filter alerts. Whenever it is changed (searchWhenChanged), it will automatically refresh the table (set to true):
<input type="time" token="TimeRangePicker" searchWhenChanged="true">
<label>TimeRange</label>
<default>
<earliest>@d</earliest>
<latest>now</latest>
</default>
</input>
Notice that a token (TimeRangePicker) should be defined in order to reference the object in the javascript.
Javascript
An external javascript (expand_alerts.js) is called as follows:
<form script="expand_alerts.js">
...
</form>
The javascript will be in charge of expanding rows and displaying corresponding events.
Javascript
Code
The following code should be put in $SPLUNK_HOME/etc/apps/APP_NAME/appserver/static/expand_alerts.js.
require([
'splunkjs/mvc/tableview',
'splunkjs/mvc/eventsviewerview',
'splunkjs/mvc/searchmanager',
'splunkjs/mvc',
'underscore',
'splunkjs/mvc/simplexml/ready!'
],function(
TableView,
EventsViewer,
SearchManager,
mvc,
_
){
var EventSearchBasedRowExpansionRenderer = TableView.BaseRowExpansionRenderer.extend({
initialize: function(args) {
// initialize will run once, so we will set up a search and events to be reused.
this._searchManager = new SearchManager({
id: 'details-search-manager',
preview: false
});
this._eventsViewer = new EventsViewer({
managerid: 'details-search-manager'
});
},
canRender: function(rowData) {
// Since more than one row expansion renderer can be registered we let each decide if
// they can handle that data
// Here we will always handle it.
return true;
},
render: function($container, rowData) {
// rowData contains information about the row that is expanded. We can see the cells, fields, and values
// We will find the sourcetype cell to use its value
var alertSignatureIdCell = _(rowData.cells).find(function (cell) {
return cell.field === 'alert.signature_id';
});
//update the search with the sourcetype that we are interested in
this._searchManager.set({
earliest_time: "$form.TimeRangePicker.earliest$",
latest_time: "$form.TimeRangePicker.latest$",
search: 'source="*suricata*" event_type="alert" alert.signature_id=' + alertSignatureIdCell.value
}, {tokens: true});
// $container is the jquery object where we can put out content.
// In this case we will render our chart and add it to the $container
$container.append(this._eventsViewer.render().el);
}
});
var tableElement = mvc.Components.getInstance("expand_with_events");
tableElement.getVisualization(function(tableView) {
// Add custom cell renderer, the table will re-render automatically.
tableView.addRowExpansionRenderer(new EventSearchBasedRowExpansionRenderer());
});
});
SplunkJS Stack components
This code uses the SplunkJS Stack. Following components are used:
- TableView: will be used to modify the table containing results from the previous search
- EventsViewer: will be used to display events related to the selected alert from the table
- SearchManager: essential component used to perform the search of events related to the selected alert
To use these components, your code should start as follows:
require([
'splunkjs/mvc/tableview',
'splunkjs/mvc/eventsviewerview',
'splunkjs/mvc/searchmanager',
'splunkjs/mvc',
'underscore',
'splunkjs/mvc/simplexml/ready!'
],function(
TableView,
EventsViewer,
SearchManager,
mvc,
_
){
...
}
});
initialize function
The SearchManager and EventsViewer objects are initialized as follows:
initialize: function(args) {
// initialize will run once, so we will set up a search and events to be reused.
this._searchManager = new SearchManager({
id: 'details-search-manager',
preview: false
});
this._eventsViewer = new EventsViewer({
managerid: 'details-search-manager'
});
},
render function
The selected alert is saved to the alertSignatureIdCell variable as follows:
// rowData contains information about the row that is expanded. We can see the cells, fields, and values
// We will find the sourcetype cell to use its value
var alertSignatureIdCell = _(rowData.cells).find(function (cell) {
return cell.field === 'alert.signature_id';
});
Then, this variable is passed to the SearchManager to perform a search of events filtered by the signature_id. Also notice that the values from the timepicker input (value from its token) are sent to the search:
//update the search with the sourcetype that we are interested in
this._searchManager.set({
earliest_time: "$form.TimeRangePicker.earliest$",
latest_time: "$form.TimeRangePicker.latest$",
search: 'source="*suricata*" event_type="alert" alert.signature_id=' + alertSignatureIdCell.value
}, {tokens: true});
// $container is the jquery object where we can put out content.
// In this case we will render our chart and add it to the $container
$container.append(this._eventsViewer.render().el);
}
Table update
Then, the ID of the table (expand_with_events) is used to create a tableElement variable and the events are sent to the expanded row:
var tableElement = mvc.Components.getInstance("expand_with_events");
tableElement.getVisualization(function(tableView) {
// Add custom cell renderer, the table will re-render automatically.
tableView.addRowExpansionRenderer(new EventSearchBasedRowExpansionRenderer());
});
Drilldown
Now, supposed that you would like to display 2 more tables to display unique sources and destinations based on a row when clicked.
Just modify your code as follows:
<form script="expand_alerts.js">
<label>SIEM Dashboard Alerts</label>
<fieldset submitButton="false" autoRun="true">
<input type="time" token="TimeRangePicker" searchWhenChanged="true">
<label>TimeRange</label>
<default>
<earliest>@d</earliest>
<latest>now</latest>
</default>
</input>
</fieldset>
<row>
<panel>
<table id="expand_with_events">
<title>Alerts</title>
<search>
<query>source="*suricata*" | stats distinct_count(src_ip) AS src_ip distinct_count(dest_ip) AS dest_ip sparkline count by alert.signature_id alert.signature alert.category | sort -count | table alert.signature_id alert.signature alert.category count src_ip dest_ip sparkline</query>
<earliest>$TimeRangePicker.earliest$</earliest>
<latest>$TimeRangePicker.latest$</latest>
</search>
<drilldown>
<set token="signatureid">$click.value$</set>
</drilldown>
<option name="wrap">true</option>
<option name="rowNumbers">false</option>
<option name="drilldown">row</option>
<option name="dataOverlayMode">none</option>
<option name="count">30</option>
</table>
</panel>
</row>
<row>
<panel>
<table>
<title>Top 20 sources</title>
<search>
<query>source="*suricata*" alert.signature_id=$signatureid$ | top 20 src_ip showperc=f showcount=f | sort src_ip</query>
<earliest>$TimeRangePicker.earliest$</earliest>
<latest>$TimeRangePicker.latest$</latest>
</search>
<option name="wrap">true</option>
<option name="rowNumbers">false</option>
<option name="dataOverlayMode">none</option>
<option name="drilldown">cell</option>
<option name="count">10</option>
</table>
</panel>
<panel>
<table>
<title>Top 20 destinations</title>
<search>
<query>source="*suricata*" alert.signature_id=$signatureid$ | top 20 dest_ip showperc=f showcount=f | sort dest_ip</query>
<earliest>$TimeRangePicker.earliest$</earliest>
<latest>$TimeRangePicker.latest$</latest>
</search>
<option name="wrap">true</option>
<option name="rowNumbers">false</option>
<option name="dataOverlayMode">none</option>
<option name="drilldown">cell</option>
<option name="count">10</option>
</table>
</panel>
</row>
</form>
We capture the "click" action from the first table with a token (signatureid) that is then used in the search for the 2 other tables.
Comments
Keywords: splunk table expand rows timepicker splunkjs