All Apps and Add-ons

Sideview button action to refresh page

craigkleen
Communicator

There's probably a quick way to do this with a customBehavior, but all those warnings on the docs for customBehavior make me want to ask first before trying it. I've followed instructions from:

https://answers.splunk.com/answers/68392/trigger-shell-script-from-dashboard-button.html

and I've got a nifty dashboard that will call a python script to take some action, but when I click the button, there's no refresh of the page that would indicate to the user that an action was taken. I do have the button appending to an outputcsv which records the action and displays it at the bottom of the page in another table. The page will eventually refresh to show a new entry in a table, but I'd like the button to immediately refresh the page too. This way users of the page don't start clicking >1 time.

1 Solution

sideview
SplunkTrust
SplunkTrust

The answer I gave years ago seems pretty flawed now. Here is a much better example.

If you create a csv lookup called "acknowledged_sourcetypes_example", and give it two columns "sourcetype", and "acknowledged", then you can run the following example locally.

Since it's Sideview XML view, it has no source code to read. the downside of course is you get to read a bunch of XML and learn an extremely unusual set of UI development conventions.

-- In every row of the rendered table, it presents an "acknowledge" button, for sourcetypes that do not have "acknowledged=1" set in the given lookup.
-- When that button is clicked,
a) a search is run that adds the given "acknowledged" row to that lookup, meaning on subsequent runs of the same search and subsequent renders of this same table, the button will not be shown.
b) when the button is clicked, the button dissappears immediately.
c) when the search appending the acknowledged row to the lookup completes a few moments later, a simple dynamic message is written out where the button was, here just saying "success".

<view template="dashboard.html" >
  <label>Table-Embedding Example - embedding action buttons</label>
  <module name="AccountBar" layoutPanel="appHeader" />
  <module name="AppBar" layoutPanel="appHeader" />
  <module name="SideviewUtils" layoutPanel="appHeader" />

  <module name="Messaging" layoutPanel="messaging" />

  <module name="HTML" layoutPanel="viewHeader">
    <param name="html"><![CDATA[
    <h1>Page title goes here</h1>
    ]]></param>
  </module>

  <!-- best practice for all sideview xml views to have a URLLoader module -->
  <module name="URLLoader" layoutPanel="panel_row1_col1" autoRun="True">

    <!-- our main search gets two secret fields that will not be displayed in the table.-->
    <module name="Search" layoutPanel="panel_row1_col1">
      <param name="search"><![CDATA[
index=_internal source=*metrics.log group="per_sourcetype_thruput" 
| head 1000 
| stats sum(kb) as totalKB by series 
| rename series as sourcetype
| eval actions="PLACEHOLDER"
| streamstats count as rowNumber
| lookup acknowledged_sourcetypes_example sourcetype OUTPUT acknowledged
| fillnull acknowledged value="0"
      ]]></param>

      <module name="Pager">

        <module name="Table">
          <!-- they need to be in the data for our logic, but not displayed -->
          <param name="hiddenFields">rowNumber,acknowledged</param>

          <!-- this module config is embedded in each "actions" cell in our table -->
          <module name="Switcher" group="row.fields.actions">
            <!-- basically only show the Button for rows where the ack field is "0" -->
            <param name="selectedGroup">$row.fields.acknowledged$</param>

            <module name="Button" group="0">
              <param name="label">Acknowledge</param>
              <param name="allowAutoSubmit">False</param>
              <!-- We paint this CSS class on here so we can hide the button onclick later -->
              <param name="cssClass">customButton$row.fields.rowNumber$</param>

              <!-- This is the search that then adds the new acknowledgement to the lookup -->
              <module name="Search">
                <param name="search"><![CDATA[
| inputlookup acknowledged_sourcetypes_example 
| append [
  | makeresults 
  | eval sourcetype="$row.fields.sourcetype$" 
  | eval acknowledged="1"
] 
| stats max(acknowledged) as acknowledged by sourcetype
| outputlookup acknowledged_sourcetypes_example
| eval message="Success!"
                ]]></param>

                <!-- doesn't really have to be a dynamic message but it's easy enough and might be useful -->
                <module name="HTML">
                  <param name="html"><![CDATA[
                    $results[0].message$
                  ]]></param>
                </module>
                <!-- when the push hits this module it hides the current row's button -->
                <module name="ShowHide">
                  <param name="hide">.customButton$row.fields.rowNumber$</param>
                </module>
              </module>
            </module>
          </module>
          <!-- thus endeth the table-embedding. -->

        </module>
      </module>
    </module>
  </module>

</view>

View solution in original post

0 Karma

sideview
SplunkTrust
SplunkTrust

The answer I gave years ago seems pretty flawed now. Here is a much better example.

If you create a csv lookup called "acknowledged_sourcetypes_example", and give it two columns "sourcetype", and "acknowledged", then you can run the following example locally.

Since it's Sideview XML view, it has no source code to read. the downside of course is you get to read a bunch of XML and learn an extremely unusual set of UI development conventions.

-- In every row of the rendered table, it presents an "acknowledge" button, for sourcetypes that do not have "acknowledged=1" set in the given lookup.
-- When that button is clicked,
a) a search is run that adds the given "acknowledged" row to that lookup, meaning on subsequent runs of the same search and subsequent renders of this same table, the button will not be shown.
b) when the button is clicked, the button dissappears immediately.
c) when the search appending the acknowledged row to the lookup completes a few moments later, a simple dynamic message is written out where the button was, here just saying "success".

<view template="dashboard.html" >
  <label>Table-Embedding Example - embedding action buttons</label>
  <module name="AccountBar" layoutPanel="appHeader" />
  <module name="AppBar" layoutPanel="appHeader" />
  <module name="SideviewUtils" layoutPanel="appHeader" />

  <module name="Messaging" layoutPanel="messaging" />

  <module name="HTML" layoutPanel="viewHeader">
    <param name="html"><![CDATA[
    <h1>Page title goes here</h1>
    ]]></param>
  </module>

  <!-- best practice for all sideview xml views to have a URLLoader module -->
  <module name="URLLoader" layoutPanel="panel_row1_col1" autoRun="True">

    <!-- our main search gets two secret fields that will not be displayed in the table.-->
    <module name="Search" layoutPanel="panel_row1_col1">
      <param name="search"><![CDATA[
index=_internal source=*metrics.log group="per_sourcetype_thruput" 
| head 1000 
| stats sum(kb) as totalKB by series 
| rename series as sourcetype
| eval actions="PLACEHOLDER"
| streamstats count as rowNumber
| lookup acknowledged_sourcetypes_example sourcetype OUTPUT acknowledged
| fillnull acknowledged value="0"
      ]]></param>

      <module name="Pager">

        <module name="Table">
          <!-- they need to be in the data for our logic, but not displayed -->
          <param name="hiddenFields">rowNumber,acknowledged</param>

          <!-- this module config is embedded in each "actions" cell in our table -->
          <module name="Switcher" group="row.fields.actions">
            <!-- basically only show the Button for rows where the ack field is "0" -->
            <param name="selectedGroup">$row.fields.acknowledged$</param>

            <module name="Button" group="0">
              <param name="label">Acknowledge</param>
              <param name="allowAutoSubmit">False</param>
              <!-- We paint this CSS class on here so we can hide the button onclick later -->
              <param name="cssClass">customButton$row.fields.rowNumber$</param>

              <!-- This is the search that then adds the new acknowledgement to the lookup -->
              <module name="Search">
                <param name="search"><![CDATA[
| inputlookup acknowledged_sourcetypes_example 
| append [
  | makeresults 
  | eval sourcetype="$row.fields.sourcetype$" 
  | eval acknowledged="1"
] 
| stats max(acknowledged) as acknowledged by sourcetype
| outputlookup acknowledged_sourcetypes_example
| eval message="Success!"
                ]]></param>

                <!-- doesn't really have to be a dynamic message but it's easy enough and might be useful -->
                <module name="HTML">
                  <param name="html"><![CDATA[
                    $results[0].message$
                  ]]></param>
                </module>
                <!-- when the push hits this module it hides the current row's button -->
                <module name="ShowHide">
                  <param name="hide">.customButton$row.fields.rowNumber$</param>
                </module>
              </module>
            </module>
          </module>
          <!-- thus endeth the table-embedding. -->

        </module>
      </module>
    </module>
  </module>

</view>

View solution in original post

0 Karma

scannon4
SplunkTrust
SplunkTrust

Thanks this is what helped me get my dashboard exactly the way I needed it. Your help has been greatly appreciated.

0 Karma

scannon4
SplunkTrust
SplunkTrust

I know this is an old post but I need to do the same thing here. After my users click a button that is in each row, I need for the table to refresh to show the updated values that clicking the button changes.

I tried Gate but that does not seem to work for me. I am totally confused with the application.js stuff and how to use it and call it in the right place. Any assistance would be greatly appreciated.

0 Karma

sideview
SplunkTrust
SplunkTrust

Let me give you two ways to do it.

1) CustomBehavior!
The most broadly useful technique to do this is to create another customBehavior further upstream in the view, whose job is to leave a hook to itself, for later that you'll use to push downstream and re-dispatch the main search(es).

So higher up in the page, typically just after the main Button module if there is one, you would have

<module name="CustomBehavior">
  <param name="customBehavior">hookForReloadingPage</param>
  ... make sure everything you need to redispatch and refresh is downstream from this module...

Here's the definition. We dont even really define any behavior, we just leave a global reference to the module object itself for later. (Yes globals are evil and window._reloader is not going to win you any js awards, but you'll be fine. )

Sideview.utils.declareCustomBehavior("hookForReloadingPage", function(module) {
    if (window._reloader) {
        console.error("hookForReloadingPage - There can be only one.");
        return false;
    }
    window._reloader = module;
});

By the time any searches are dispatched on the page, that window._reloader will have been defined long ago, so whenever you need to reload the main searches to show changes, just call from the JS:

window._reloader.pushContextToChildren()

Of course, the big question becomes, exactly when is it safe to call pushContextToChildren? The UI framework is better than you might think about cancelling and aborting searches once they seem to be superceded by subsequent interaction, and if you dont think about this, it's not hard to reload the page before or even at the exact time you're dispatching your "updating" search, setting up a race.

My advice is if you have an existing CustomBehavior that is running when the button is clicked to do something, implement this inside that existing customBehavior:

var PCTCReference = module.pushContextToChildren.bind(module);
module.pushContextToChildren = function() {
        PCTCReference();
        window._reloader.pushContextToChildren();
    }

It looks a little insane, and it looks like a race, but the "updating" search will definitely get dispatched here before the main search reload is triggered. And I believe that "updating" search will be able to run to completion unperturbed while the upstream "main" search(es) redispatch.


2) the Gate module

There is another way, and that's to use the Gate module. If you weren't already using a customBehavior in your view, I'd say go with the Gate.

In this scenario, this Gate module takes the place of our reloader customBehavior

<module name="Gate">
  <param name="id">reloader</param>
  ... everything you need to reload/redispatch is downstream from this Gate...

And then further downstream your button and search etc would look like this:

<module name="Button">
  <param name="label">update</param>
  <module name="Search"> | inputlookup | EvalStatementsToUpdateThings | outputlookup</param>

     <!-- customBehavior is sometimes useful to just sneak in with only requiresDispatch
            so as to force a dispatch at a particular point wihtout an unwanted visible module 
            like JobProgressIndicator being there.  -->
    <module name="CustomBehavior">
      <param name="requiresDispatch">True</param>

       <module name="Gate">
         <param name="to">reloader</param>
       </module>
    </module>

End result is the same. On button click the search runs, once it's dispatched and running the context push moves down to the Gate module, which reaches up to the top of the page and starts a push from up there. Terrifying circular push ftw!

craigkleen
Communicator

I'm thinking the Gate looks like the easier way. But, it looks like I'm still missing something. Here's what my view looks like:

  <label>Blacklist Clearing Dashboard</label>
  <module name="AccountBar" layoutPanel="appHeader"/>
  <module name="AppBar" layoutPanel="appHeader"/>
  <module name="SideviewUtils" layoutPanel="appHeader"/>
  <module name="Message" layoutPanel="messaging">
    <param name="filter">*</param>
    <param name="clearOnJobDispatch">False</param>
    <param name="maxSize">2</param>
  </module>
  <module name="HTML" layoutPanel="viewHeader">
    <param name="html">&lt;h1&gt;Blacklist Remove Dashboard&lt;/h1&gt;</param>
  </module>
  <module name="Search" layoutPanel="panel_row1_col1" autoRun="True">
    <param name="search">forstuff | table some things | eval actions="PLACEHOLDER"</param>
    <param name="earliest">-34m</param>
    <module name="HTML">
      <param name="html">&lt;H2&gt;Recent Blacklist Adds</param>
    </module>
    <module name="Gate">
      <param name="id">reloader</param>
      <module name="Pager">
        <module name="Table">
          <module name="Button" group="row.fields.actions">
            <param name="groupLabel">row.fields.actions</param>
            <param name="allowAutoSubmit">False</param>
            <param name="label">Remove from Blacklist</param>
            <module name="Search">
              <param name="search">search forstuff | table some things | outputcsv a.csv</param>
              <module name="CustomBehavior">
                <param name="requiresDispatch">True</param>
                <module name="Gate">
                  <param name="to">reloader</param>
                </module>
              </module>
            </module>
          </module>
        </module>
      </module>
    </module>
  </module>
  <module name="Search" layoutPanel="panel_row2_col1" autoRun="True">
    <param name="search">| inputcsv a.csv events=true | table some things</param>
    <module name="HTML">
      <param name="html">&lt;H2&gt;Recent Blacklist Clears&lt;/H2&gt;</param>
    </module>
    <module name="Pager">
      <module name="Table"/>
    </module>
  </module>

Did the Gate bits go in the right place? They don't seem to have, because the page sort of locks up when I click the button.

0 Karma

sideview
SplunkTrust
SplunkTrust

Hm. The dispatching module right there is the Pager, and the Gate being above it, this should be the right place. Curious what you mean by the page freezing. Are there any JS errors showing up in the error console?

Any search syntax errors not coming back in the UI for any reason?

And if nothing leaps out there, what does happen if you move the Gate just upstream from the Search module?

0 Karma

craigkleen
Communicator

Yeah, I'm probably in over my head on this one. Error console? There's no error that shows up anywhere, but the top Search, which returns more than one Page of data, will let me interact with the the Pager module, but the data doesn't change until it's manually refreshed. It looks "frozen". The Button action still works.

I tried moving the first Gate upstream of the first Search, and that caused the panel to not draw.

0 Karma

sideview
SplunkTrust
SplunkTrust

Are you remembering to shift-reload or explicitly clear cache? Splunk is notoriously grabby as far as cacheing static files including application.js.

Also where the error console is depends on your browser. In Chrome it's generally Tools > Developer Tools > Console. IN Firefox try " > Developer > Web Console".

0 Karma

craigkleen
Communicator

Started Chrome fresh and deleted cache before connecting to my server. Error console is giving me:
Gate module with a 'to' param (Gate_1_7_0_0) is ignoring a call to hideDescendants
ncaught RangeError: Maximum call stack size exceeded(anonymous function) @ modules-211d52e….min.js:178(anonymous function) @ modules-211d52e….min.js:270Splunk.Module.$.klass.withEachChild @ modules-211d52e….min.js:265Splunk.Module.$.klass.withEachDescendant @ modules-211d52e….min.js:269(anonymous function) @ modules-211d52e….min.js:271Splunk.Module.$.klass.withEachChild @ modules-211d52e….min.js:265Splunk.Module.$.klass.withEachDescendant @ modules-211d52e….min.js:269(anonymous function) @ modules-211d52e….min.js:271Splunk.Module.$.klass.withEachChild @ modules-211d52e….min.js:265Splunk.Module.$.klass.withEachDescendant @ modules-211d52e….min.js:269
6modules-211d52e….min.js:442 XHR clear for takeoff for module Pager_0_2_0

The Gate module is back in the original place, just after the first Search.

0 Karma

sideview
SplunkTrust
SplunkTrust

Oh goodness. yes. Button will block the push, but nothing blocks the withEachDescendant call. Ow. OK use the customBehavior approach!

0 Karma

craigkleen
Communicator

Oh boy! Gate looked so much easier. I'm still a bit confused by CustomBehavior. I've yet to find much of a tutorial. I sort of get that I need to put some part into a file, as it can't all be done in the Sideview Editor (or can it?). I'll go research some more.

0 Karma

craigkleen
Communicator

So, the code to do a refresh was scaring me. Not being a javascript programmer, and not able to see exactly what it was, I decided instead to go with an alert() after reading the post at https://answers.splunk.com/answers/99676/how-is-customebehavior-module-working-model-is.html

Since this is all in my search app, I created an application.js in etc/apps/search/appserver/static with something simple:
if (typeof(Sideview)!="undefined") {

    Sideview.utils.declareCustomBehavior("alertBlacklist", function(module) {
        module.onContextChange = function() {
            alert("Request submitted.  Please refresh page to update recent clears table.");
        }
    });
}

Then I called this in the Search right after the Button.

This should be enough for what I was doing. It takes a little time for my back-end python script to run and "unblacklist" a WiFi client, so an instant refresh wouldn't show anything anyway.

Many thanks Nick for the help on this one. I'm loving the Sideview Editor, and am looking forward to making a lot more dashboards with it!

0 Karma
Did you miss .conf21 Virtual?

Good news! The event's keynotes and many of its breakout sessions are now available online, and still totally FREE!