Example: Token transform and forwarding in HTML dashboards

This example uses a converted HTML Dashboard to show different ways to change a token value by using token filters and forwarders.

[example]

This example shows the modified code (which is highlighted) for a converted HTML dashboard, and the Simple XML code for the original dashboard.

To view this dashboard:

  1. Copy the HTML code, find and replace "APP_NAME" with your app name, then save the HTML file under an existing app in $SPLUNK_HOME/etc/apps/app_name/local/data/ui/html/.
  2. Refresh the Splunk Web server by going to http://<localhost:port>/debug/refresh and clicking Refresh.
  3. In Splunk Web, view the dashboard in your app. For example, to view myexample.html in mysplunkapp, go to http://<localhost:port>/app/mysplunkapp/myexample.

To recreate the example:

  1. Save the XML file under an existing app in $SPLUNK_HOME/etc/apps/app_name/local/data/ui/views/.
  2. Refresh the Splunk Web server by going to http://<localhost:port>/debug/refresh and clicking Refresh.
  3. In Splunk Web, open the dashboard in your app. For example, to view myexample.xml in mysplunkapp, go to http://<localhost:port>/app/mysplunkapp/myexample.
  4. Click ..., then click Convert to HTML.
  5. Click Shared for Permissions, click Convert Dashboard, then click Edit HTML.
  6. Modify the HTML as shown in the example, then click Save.

tokentransform_html.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title>Token transform and forwarding</title>
    <link rel="shortcut icon" href="{{SPLUNKWEB_URL_PREFIX}}/static/img/favicon.ico" />
        <link rel="stylesheet" type="text/css" href="/en-US/static/@f4c1eb50e0f3/css/build/bootstrap.min.css" />
        <link rel="stylesheet" type="text/css" href="/en-US/static/@f4c1eb50e0f3/css/build/pages/dashboard-simple-bootstrap.min.css" />
    <link rel="stylesheet" type="text/css" media="all" href="{{SPLUNKWEB_URL_PREFIX}}/static/app/APP_NAME/dashboard.css" />
</head>
<body class="simplexml preload locale-en">
<!--
BEGIN LAYOUT
This section contains the layout for the dashboard. Splunk uses proprietary
styles in <div> tags, similar to Bootstrap's grid system.
-->
<header>
<a class="navSkip" href="#navSkip" tabindex="1">Screen reader users, click here to skip the navigation bar</a>
<div class="header splunk-header">
        <div id="placeholder-splunk-bar">
            <a href="{{SPLUNKWEB_URL_PREFIX}}/app/launcher/home" class="brand" title="splunk > listen to your data">splunk<strong>></strong></a>
        </div>
            <div id="placeholder-app-bar"></div>
</div>
<a id="navSkip"></a>
</header>
<div class="dashboard-body container-fluid main-section-body" data-role="main">
    <div class="dashboard-header clearfix">
        <h2>Token transform and forwarding</h2>
    </div>
    <div class="fieldset">
        <div class="input input-dropdown" id="input1">
        </div>
    </div>


    <div id="row1" class="dashboard-row dashboard-row1">
        <div id="panel1" class="dashboard-cell" style="width: 100%;">
            <div class="dashboard-panel clearfix">

                <div class="panel-element-row">
                    <div id="element1" class="dashboard-element table" style="width: 100%">
                        <div class="panel-head">
                            <h3>Method 1</h3>
                        </div>
                        <div class="panel-body"></div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div id="row2" class="dashboard-row dashboard-row2">
        <div id="panel2" class="dashboard-cell" style="width: 100%;">
            <div class="dashboard-panel clearfix">

                <div class="panel-element-row">
                    <div id="element2" class="dashboard-element table" style="width: 100%">
                        <div class="panel-head">
                            <h3>Method 2</h3>
                        </div>
                        <div class="panel-body"></div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<!--
END LAYOUT
-->

<script src="{{SPLUNKWEB_URL_PREFIX}}/config?autoload=1"></script>
<script src="{{SPLUNKWEB_URL_PREFIX}}/static/js/i18n.js"></script>
<script src="{{SPLUNKWEB_URL_PREFIX}}/i18ncatalog?autoload=1"></script>
<script src="{{SPLUNKWEB_URL_PREFIX}}/static/build/simplexml/index.js"></script>
<script type="text/javascript">
// <![CDATA[
// <![CDATA[

//
// LIBRARY REQUIREMENTS
//
// In the require function, we include the necessary libraries and modules for
// the HTML dashboard. Then, we pass variable names for these libraries and
// modules as function parameters, in order.
//
// When you add libraries or modules, remember to retain this mapping order
// between the library or module and its function parameter. You can do this by
// adding to the end of these lists, as shown in the commented examples below.

require([
    "splunkjs/mvc",
    "splunkjs/mvc/utils",
    "splunkjs/mvc/tokenutils",
    "underscore",
    "jquery",
    "splunkjs/mvc/simplexml",
    "splunkjs/mvc/layoutview",
    "splunkjs/mvc/simplexml/dashboardview",
    "splunkjs/mvc/simplexml/dashboard/panelref",
    "splunkjs/mvc/simplexml/element/chart",
    "splunkjs/mvc/simplexml/element/event",
    "splunkjs/mvc/simplexml/element/html",
    "splunkjs/mvc/simplexml/element/list",
    "splunkjs/mvc/simplexml/element/map",
    "splunkjs/mvc/simplexml/element/single",
    "splunkjs/mvc/simplexml/element/table",
    "splunkjs/mvc/simpleform/formutils",
    "splunkjs/mvc/simplexml/eventhandler",
    "splunkjs/mvc/simplexml/searcheventhandler",
    "splunkjs/mvc/simpleform/input/dropdown",
    "splunkjs/mvc/simpleform/input/radiogroup",
    "splunkjs/mvc/simpleform/input/linklist",
    "splunkjs/mvc/simpleform/input/multiselect",
    "splunkjs/mvc/simpleform/input/checkboxgroup",
    "splunkjs/mvc/simpleform/input/text",
    "splunkjs/mvc/simpleform/input/timerange",
    "splunkjs/mvc/simpleform/input/submit",
    "splunkjs/mvc/searchmanager",
    "splunkjs/mvc/savedsearchmanager",
    "splunkjs/mvc/postprocessmanager",
    "splunkjs/mvc/simplexml/urltokenmodel",
    "splunkjs/mvc/tokenforwarder"
    ],
    function(
        mvc,
        utils,
        TokenUtils,
        _,
        $,
        DashboardController,
        LayoutView,
        Dashboard,
        PanelRef,
        ChartElement,
        EventElement,
        HtmlElement,
        ListElement,
        MapElement,
        SingleElement,
        TableElement,
        FormUtils,
        EventHandler,
        SearchEventHandler,
        DropdownInput,
        RadioGroupInput,
        LinkListInput,
        MultiSelectInput,
        CheckboxGroupInput,
        TextInput,
        TimeRangeInput,
        SubmitButton,
        SearchManager,
        SavedSearchManager,
        PostProcessManager,
        UrlTokenModel,
        TokenForwarder
        ) {

        var pageLoading = true;


        //
        // TOKENS
        //

        // Create token namespaces
        var urlTokenModel = new UrlTokenModel();
        mvc.Components.registerInstance('url', urlTokenModel);
        var defaultTokenModel = mvc.Components.getInstance('default', {create: true});
        var submittedTokenModel = mvc.Components.getInstance('submitted', {create: true});

        urlTokenModel.on('url:navigate', function() {
            defaultTokenModel.set(urlTokenModel.toJSON());
            if (!_.isEmpty(urlTokenModel.toJSON()) && !_.all(urlTokenModel.toJSON(), _.isUndefined)) {
                submitTokens();
            } else {
                submittedTokenModel.clear();
            }
        });

        // Initialize tokens
        defaultTokenModel.set(urlTokenModel.toJSON());

        function submitTokens() {
            // Copy the contents of the defaultTokenModel to the submittedTokenModel and urlTokenModel
            FormUtils.submitForm({ replaceState: pageLoading });
        }

        function setToken(name, value) {
            defaultTokenModel.set(name, value);
            submittedTokenModel.set(name, value);
        }

        function unsetToken(name) {
            defaultTokenModel.unset(name);
            submittedTokenModel.unset(name);
        }



        //
        // SEARCH MANAGERS
        //

        var search1 = new SearchManager({
            "id": "search1",
            "latest_time": "$latest$",
            "cancelOnUnload": true,
            "status_buckets": 0,
            "search": "$selectedField|makeSearchQuery1$",
            "earliest_time": "0",
            "app": utils.getCurrentApp(),
            "auto_cancel": 90,
            "preview": true,
            "runWhenTimeIsUndefined": false
        }, {tokens: true}); // changed to use the default token model

        var search2 = new SearchManager({
            "id": "search2",
            "latest_time": "$latest$",
            "cancelOnUnload": true,
            "status_buckets": 0,
            "search": "$searchQuery2$",
            "earliest_time": "0",
            "app": utils.getCurrentApp(),
            "auto_cancel": 90,
            "preview": true,
            "runWhenTimeIsUndefined": false
        }, {tokens: true}); // changed to use the default token model



        //
        // SPLUNK LAYOUT
        //

        $('header').remove();
        new LayoutView({"hideFooter": false, "hideSplunkBar": false, "hideAppBar": false, "hideChrome": false})
            .render()
            .getContainerElement()
            .appendChild($('.dashboard-body')[0]);


        //
        // DASHBOARD EDITOR
        //

        new Dashboard({
            id: 'dashboard',
            el: $('.dashboard-body'),
            showTitle: true,
            editable: true
        }, {tokens: true}).render();


        //
        // VIEWS: VISUALIZATION ELEMENTS
        //

        var element1 = new TableElement({
            "id": "element1",
            "drilldown": "row",
            "rowNumbers": "undefined",
            "wrap": "undefined",
            "managerid": "search1",
            "el": $('#element1')
        }, {tokens: true, tokenNamespace: "submitted"}).render();

        var element2 = new TableElement({
            "id": "element2",
            "drilldown": "row",
            "rowNumbers": "undefined",
            "wrap": "undefined",
            "managerid": "search2",
            "el": $('#element2')
        }, {tokens: true, tokenNamespace: "submitted"}).render();


        //
        // VIEWS: FORM INPUTS
        //

        var input1 = new DropdownInput({
            "id": "input1",
            "choices": [
                {"label": "host", "value": "host"},
                {"label": "sourcetype", "value": "sourcetype"},
                {"label": "source", "value": "source"},
                {"label": "status", "value": "status"},
                {"label": "message", "value": "message"} // Only valid when index=_internal
            ],
            "default": "host",
            "showClearButton": false,
            "selectFirstChoice": false,
            "searchWhenChanged": true,
            "value": "$selectedField$",  // Removed "form."
            "el": $('#input1')
        }, {tokens: true}).render();

        // input1.on("change", function(newValue) {
        //     FormUtils.handleValueChange(input1);
        //});


        //
        //  Add logic here:
        //

        // Method 1: Return a transformed token value
        // Create a search manager that transforms a token
        // Transform $selectedField$ and return a value. $selectedField$ is not affected.
        mvc.setFilter('makeSearchQuery1', function(selectedField) {
            var searchQuery1 = (selectedField==="message")
                ? "index=_internal | top 3 message"
                : "* | top 3 " + selectedField;
            console.log("search:", searchQuery1);
            return searchQuery1;
        });


        // Method 2: Forward one token to form a new one
        // Create a search manager that uses token forwarding
        // Form a new token $searchQuery2$ from $selectedField$
        new TokenForwarder('$selectedField$', '$searchQuery2$', function(selectedField) {
            var searchQuery2 = (selectedField === "message")
                ? "index=_internal | top 3 message"
                : "* | top 3 " + selectedField;
            console.log("search:", searchQuery2);
            return searchQuery2;
        });

        var defaultTokenModel = mvc.Components.get("default");
        defaultTokenModel.on("change:searchQuery2", function() {
            search1.startSearch();
            search2.startSearch();
        });


        // Initialize time tokens to default
        if (!defaultTokenModel.has('earliest') && !defaultTokenModel.has('latest')) {
            defaultTokenModel.set({ earliest: '0', latest: '' });
        }

        if (!_.isEmpty(urlTokenModel.toJSON())){
            submitTokens();
        }


        //
        // DASHBOARD READY
        //

        DashboardController.ready();
        pageLoading = false;

    }
);
// ]]>
</script>
</body>
</html>

tokentransform.xml

<form>
  <label>Token transform and forwarding</label>
  <fieldset submitButton="false">
    <input type="dropdown" token="selectedField">
      <choice value="host">host</choice>
      <choice value="sourcetype">sourcetype</choice>
      <choice value="source">source</choice>
      <choice value="status">status</choice>
      <choice value="message">message</choice>
      <default>host</default>
    </input>
  </fieldset>
  <row>
    <panel>
      <table>
        <title>Method 1</title>
        <search>
          <query></query>
          <earliest>0</earliest>
          <latest></latest>
        </search>
        <option name="wrap">undefined</option>
        <option name="rowNumbers">undefined</option>
        <option name="drilldown">row</option>
      </table>
    </panel>
  </row>
  <row>
    <panel>
      <table>
        <title>Method 2</title>
        <search>
          <query></query>
          <earliest>0</earliest>
          <latest></latest>
        </search>
        <option name="wrap">undefined</option>
        <option name="rowNumbers">undefined</option>
        <option name="drilldown">row</option>
      </table>
    </panel>
  </row>
</form>