Example: Modified HTML dashboard

This example shows a modified version of this HTML dashboard: the layout is changed, the Search button is no longer needed (the search runs automatically in response to user selections), and checkboxes replace radio buttons (allowing for multiple source types to be selected). Compare this to the code for the Django template and the JavaScript and HTML version, which produce the same dashboard.

[example]

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title>HTML Dashboard | Splunk</title>
    <link rel="shortcut icon" href="{{SPLUNKWEB_URL_PREFIX}}/static/img/favicon.ico" />
    <link rel="stylesheet" type="text/css" href="{{SPLUNKWEB_URL_PREFIX}}/static/css/bootstrap.min.css" />
    <link rel="stylesheet" type="text/css" media="all" href="{{SPLUNKWEB_URL_PREFIX}}/static/css/pages/dashboard-simple-bootstrap.min.css" />
    <!--[if IE 7]><link rel="stylesheet" href="{{SPLUNKWEB_URL_PREFIX}}/static/css/sprites-ie7.css" /><![endif]-->
</head>
<body class="simplexml preload">

<!-- 
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>HTML Dashboard</h2>
        <p class="description">An HTML dashboard converted from a Simple XML dashboard, then modified: the layout has changed, checkboxes instead of radio buttons allow for multiple sourcetypes, and the Search button is no longer needed</p>
    </div>

    <!-- Replace the fieldset with one row with three cells -->

    <div class="dashboard-row dashboard-row1">
        <div class="dashboard-cell" style="width: 25%;">
            <div class="dashboard-panel clearfix">
                <div class="dashboard-element">
                    <div class="panel-head">
                        <h3>Number of results</h3>
                    </div>
                    <div class="panel-body" id="field1">
                    </div>
                </div>
            </div>
        </div>
        <div class="dashboard-cell" style="width: 25%;">
            <div class="dashboard-panel clearfix">
                <div class="dashboard-element">
                    <div class="panel-head">
                        <h3>Source types</h3>
                    </div>
                    <!-- <div class="panel-body" id="field2"> -->
                    <div class="panel-body" id="view_checkboxgroup">
                    </div>
                </div>
            </div>
        </div>
        <div class="dashboard-cell" style="width: 50%;">
            <div class="dashboard-panel clearfix">
                <div class="dashboard-element">
                    <div class="panel-head">
                        <h3>Count by sourcetype</h3>
                    </div>
                    <div class="panel-body" id="element2">
                    </div>
                </div>
            </div>
        </div>
    </div>


    <!-- The result table is now in row 2 -->
    <div class="dashboard-row dashboard-row2">
        <div class="dashboard-cell" style="width: 100%;">
            <div class="dashboard-panel clearfix">                
                <div class="panel-element-row">
                    <div class="dashboard-element table" id="element1" style="width: 100%">
                        <div class="panel-head">
                            <h3>index=_internal $sourcetype$ | head $headcount$</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}}/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/dashboard",
    "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/input/dropdown",
    "splunkjs/mvc/simpleform/input/radiogroup",
    "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/checkboxgroupview",
    ],
    function(
        mvc,
        utils,
        TokenUtils,
        _,
        $,
        DashboardController,
        LayoutView,
        Dashboard,
        ChartElement,
        EventElement,
        HtmlElement,
        ListElement,
        MapElement,
        SingleElement,
        TableElement,
        DropdownInput,
        RadioGroupInput,
        TextInput,
        TimeRangeInput,
        SubmitButton,
        SearchManager,
        SavedSearchManager,
        PostProcessManager,
        UrlTokenModel,
        CheckboxGroupView
        ) {



        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());

        var defaultUpdate = {};

        var submitTokens = function() {
            submitTokensSoon(pageLoading);
        };

        var submitTokensSoon = _.debounce(function(replaceState) {
            submittedTokenModel.set(defaultTokenModel.toJSON());
            urlTokenModel.saveOnlyWithPrefix('form\\.', defaultTokenModel.toJSON(), {
                replaceState: replaceState
            });
        });


        //
        // SEARCH MANAGERS
        //

        // To allow for multiple sourcetype selections, modify the search
        // query by removing "sourcetype=" (the sourcetype is no 
        // longer a single value), and determine the value in a change event.
        // Also, change the token model to "default" (by removing "submitted"), 
        // which eliminates the need for a Submit button to rerun the search. 
        var search1 = new SearchManager({
            "id": "search1",
            "cancelOnUnload": true,
            "status_buckets": 0,
            "latest_time": "$latest$",
            "search": "index=_internal $sourcetype$ | head $headcount$",
            "earliest_time": "$earliest$",
            "app": utils.getCurrentApp(),
            "auto_cancel": 90,
            "preview": true
        }, {tokens: true}); 

        var search2 = new SearchManager({
            "id": "search2",
            "cancelOnUnload": true,
            "status_buckets": 0,
            "latest_time": "$latest$",
            "search": "index=_internal | stats count by sourcetype",
            "earliest_time": "$earliest$",
            "app": utils.getCurrentApp(),
            "auto_cancel": 90,
            "preview": true
        }, {tokens: true, tokenNamespace: "submitted"});



        //
        // 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')
        }, {tokens: true}).render();


        //
        // VIEWS: VISUALIZATION ELEMENTS
        //

        var element1 = new TableElement({
            "id": "element1",
            "showPager": true,
            "managerid": "search1",
            "el": $('#element1')
        }, {tokens: true}).render();
        
        var element2 = new ChartElement({
            "id": "element2",
            "charting.chart": "pie",
            "resizable": true,
            "managerid": "search2",
            "drilldown": "none", // Disables drilldown behavior
            "el": $('#element2')
        }, {tokens: true}).render();
        


        //
        // VIEWS: FORM INPUTS
        //

        var field1 = new DropdownInput({
            "id": "field1",
            "choices": [
                {"label": "5", "value": "5"},
                {"label": "10", "value": "10"},
                {"label": "15", "value": "15"}
            ],
            "default": "5",
            "value": "$form.headcount$",
            "showClearButton": false, // Hides the "x" button
            "el": $('#field1')
        }, {tokens: true}).render();

        field1.on("change", function(value, input, options) {
            if (!field1.hasValue()) {
                defaultUpdate['field1'] = true;
                this.val(field1.settings.get("default"));
                return;
            }
            
            var newValue = field1.val() || field1.settings.get("default");
            var newComputedValue = newValue;

            // Update computed value
            defaultTokenModel.set("headcount", newComputedValue);
        });
        defaultUpdate['field1'] = true;
        field1.trigger("change", field1.val(), field1);

        var field2 = new RadioGroupInput({
            "id": "field2",
            "choices": [
                {"label": "All", "value": "*"}
            ],
            "valueField": "sourcetype",
            "labelField": "sourcetype",
            "default": "*",
            "value": "$form.sourcetype$",
            "managerid": "search3",
            "el": $('#field2')
        }, {tokens: true}).render();

        field2.on("change", function(value, input, options) {
            if (!field2.hasValue()) {
                defaultUpdate['field2'] = true;
                this.val(field2.settings.get("default"));
                return;
            }
            
            var newValue = field2.val() || field2.settings.get("default");
            var newComputedValue = newValue;

            // Update computed value
            defaultTokenModel.set("sourcetype", newComputedValue);
        });
        defaultUpdate['field2'] = true;
        field2.trigger("change", field2.val(), field2);

        // Populating search for field 'field2'
        var search3 = new SearchManager({
            "id": "search3",
            "cancelOnUnload": true,
            "status_buckets": 0,
            "earliest_time": "$earliest$",
            "search": "index=_internal | head 1000 | top sourcetype",
            "latest_time": "$latest$",
            "app": utils.getCurrentApp(),
            "auto_cancel": 90,
            "preview": true
        }, {tokens: true});




        //
        // CUSTOM JAVASCRIPT
        //

        // Add a checkbox view to allow for multiple sourcetypes
        view_checkboxgroup = new CheckboxGroupView({
            id: "view_checkboxgroup",
            "default": "splunkd",
            "labelField": "sourcetype",
            "valueField": "sourcetype",
            "managerid": "search3",
            el: $("#view_checkboxgroup")
        }).render();

        // Use a change event to calculate the new value of $sourcetype$
        // When a checkbox is clicked, form a sourcetype string from the selected choices
        // and manually update the $sourcetype$ token
        view_checkboxgroup.on("change", function() {
            // Form the sourcetype string
            var selectedsourcetypes="sourcetype=" + view_checkboxgroup.val().join(" OR sourcetype=");

            // If no boxes checked, set the sourcetype to nothing so that the search is properly formed
            if (selectedsourcetypes=="sourcetype=")
            {
                selectedsourcetypes="sourcetype=''";
            }
            
            // Set the value for the $sourcetype$ token
            defaultTokenModel.set("sourcetype", selectedsourcetypes);
        });

        // If the checkbox isn't set to anything...
        if (view_checkboxgroup.settings.get("value") !== undefined) {
            view_checkboxgroup.trigger("change", view_checkboxgroup.val(), view_checkboxgroup, {noSubmit: true});
        }

        //
        // END CUSTOM JAVASCRIPT
        //



        // 
        // SUBMIT FORM DATA
        //

        var submit = new SubmitButton({
            id: 'submit',
            el: $('#search_btn')
        }, {tokens: true}).render();

        submit.on("submit", function() {
            submitTokens();
        });

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


        //
        // DASHBOARD READY
        //

        DashboardController.ready();
        pageLoading = false;

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