Splunk/table-expandable-rows-time-picker

From aldeid
Jump to navigation Jump to search
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