---
layout: docs
title: Action Items
---

== Action Menu

{% include new_since.ad version="1.86" %}
The Action Menu is a way to create custom icons and menus for every host or
service. You may add multiple action icons or even multiple menus for each host
or service. You can then open links or trigger server actions from that menu
or icons.

image:source/action_menu.png[Action Menu]


== Adding an Action Menu
There are 3 ways to add an action menu to a host or service.

  * set the menu directly by the `_THRUK_ACTION_MENU` custom variable
  * reference an existing menu from the `action_menu_items` by a  `_THRUK_ACTION_MENU` custom variable
  * use apply rules from `action_menu_apply`

The are some examples in the link:configuration.html#action-menu-settings[configuration] documentation.

== Server Actions

Server Action can be triggered from icons or menu items by using a pseudo url
like this in your action urls. See the link:configuration.html#action-menu-settings[configuration]
section on how and where to set this link.

-----
    server://*scriptname*/argument1/argument2/...
-----

To make this work, you need to define that `scriptname` in your thruk_local.conf:

-----
  <action_menu_actions>
      scriptname   = /usr/local/bin/sample.sh $HOSTNAME$ $SERVICEDESC$
  </action_menu_actions>
-----

When somebody clicks on that link, Thruk will start the `sample.sh` script like:

-----
    /usr/local/bin/sample.sh <hostname> <servicename> argument1 argument2
-----

First it uses the complete commandline from the `thruk_local.conf` and replaces
all available macros. Then it adds the arguments from the link configuration.

== Macros

For server side scripts most macros can be used.
See link:macros.html[Macros] for a list of all available macros.

Inside the actual menu definition you can only use the following macros:

- `$HOSTNAME$`
- `$SERVICEDESC$`
- `{{ theme }}`
- `{{ remote_user }}`
- `{{ site }}`
- `{{ prefix }}`

=== Scripts
The scripts can do whatever they want. However you should consider a few things:

  * Make the script exit quickly. Thruk waits for the script to return. Long running
    tasks should be started in the background.
  * Give the user useful feedback on STDOUT
  * Exit codes should be zero if OK and greater than zero on errors.

[TIP]
.script output
=======
Script output will be displayed as user response which allows the script
to give feedback to the user. HTML is allowed and will be used in the output.
=======

=== Authorization / Security
The Script will be run whenever the user has access to the given host and service.
Its general good advice to check the `$REMOTE_USER` and/or `$REMOTE_USER_GROUPS`
environment variables before running critical scripts. Macros used in the command
line defined in your config and the REMOTE_USER variables can be trusted. Variables
supplied by the url can easily be faked and are considered unsafe.

You can use the `action_wrapper` script from the examples folder to check for valid users and groups.

-----
  <action_menu_actions>
      scriptname   = .../examples/action_wrapper -u omdadmin -g Admins,Operators ./sample.sh $HOSTNAME$ $SERVICEDESC$
  </action_menu_actions>
-----

Available standard environment variables are listed on the
link:cli.html#_environment-variables[CLI Environment] page.


== Dynamic Javascript Menus

{% include new_since.ad version="2.24" %}
Since version 2.24 you can use javascript to dynamically create menus. Basically you create a file ending on `.js` in your `action_menus` folder.
The first function will be called and expected to return a json structure with the actual menu.

Althought the first function will be called to generate the menu, the file
may contain multiple functions, for example to generate submenus.

The first function in the file must return an array of (link:configuration.html#action_menu_items[action_menu_items]). The menu
itself can be a function again to create more dynamic menus.

[WARNING]
=======
All defined functions are running in the same namespace, so make sure your functions use uniq names over all your menus. Best practice is to use the filename as prefix.
=======

[TIP]
.user data
=======
{% include new_since.ad version="3.02" %}

The current user with its roles and groups is available in the `thruk_user` variable and can be used to make your menu depend on user attributes.
ex.:

  {
    "name":"thrukadmin",
    "groups":["example"],
    "roles":["authorized_for_admin","authorized_for_all_host_commands",,...],
    "can_submit_commands":true,
    "readonly":false
  }
=======

A very simple example:

.action_menus/js_basic_menu.js
.....
{% raw %}
function js_basic_menu(data) {
    return({
      "icon":"../themes/{{theme}}/images/dropdown.png",
      "title": "javascript menu",
      "menu":[{
          "icon":"uil-link",
          "label":"test menu item",
          "action":"http://..."
        }]
    });
}
{% endraw %}
.....


A more advanced example with dynamic submenus. Menu callbacks may return thenables (in jQuery context). This makes it possible to do ajax requests and use the result to create submenus. This example creates a submenu listing all hosts.

.action_menus/js_adv_menu.js
.....
{% raw %}
function js_adv_menu(cfg) {
    return({
      "icon":"../themes/{{theme}}/images/dropdown.png",
      "title": "javascript advanced menu",
      "menu":[
        {
          "icon":"uil-link",
          "label":"test menu item",
          "action":"http://..."
        },
        {
          "icon":"fa-folder",
          "label":"list all hosts",
          "menu": js_adv_menu_submenu
        }
      ]
    });
}

function js_adv_menu_submenu(cfg) {
  return(jQuery.get("../r/hosts?columns=name").then(function(data, textStatus, jqXHR) {
    var result = [];
    jQuery(data).each(function(i, r) {
      result.push({ label: r.name });
    });
    return(result);
  }));
}
{% endraw %}
.....


== Custom Quick Actions

{% include new_since.ad version="3.24" %}

Custom server actions can be added to the quick action menu on host and service detail pages.
There is a detailed example in the link:custom-quick-actions.html[quick actions] documentation.


== Examples

=== Panorama Dashboard

This is a small example script which uses perl to change the color of a shaped
icon.

.icon settings

image:source/switch_color.png[Dashboard Icon]

.thruk_local.conf
-----
  <action_menu_actions>
    switch_color    = .../switch_color.pl $DASHBOARD_ID$ $DASHBOARD_ICON$
  </action_menu_actions>
-----

[TIP]
.switch_color.pl
=======
just changes the color of the icon.
=======

++++++++++++++++++++++++
{% highlight perl %}
#!/usr/bin/perl

use warnings;
use strict;
use Thruk::Utils;

my $file  = 'var/panorama/'.$ARGV[0].'.tab';
my $data  = Thruk::Utils::read_data_file($file);
my $color = $data->{$ARGV[1]}->{'xdata'}->{'appearance'}->{'shapecolor_ok'};
if($color eq '#CA1414') {
    $color = '#199C0F';
} else {
    $color = '#CA1414';
}
$data->{$ARGV[1]}->{'xdata'}->{'appearance'}->{'shapecolor_ok'} = $color;
Thruk::Utils::write_data_file($file, $data);

print "switched color to $color\n";
{% endhighlight %}
++++++++++++++++++++++++


=== Confirmation Dialog

{% include new_since.ad version="2.14-2" %}
Using arbitrary attributes, you can easily add a confirmation dialog to your
server actions.

.action_menus/confirm_restart.json
.....
{% raw %}
{
  "icon":"uil-redo",
  "label":"service restart",
  "action":"server://restart_service",
  "onclick": "return(confirm('Really restart service?'));"
}
{% endraw %}
.....


=== Hide Menu Item for Readonly Users

{% include new_since.ad version="3.02" %}
With the hidden attribute items can be made invisible.
server actions.
In this example the item is made hidden if the user has the readonly role.

.action_menus/hidden_items.json
.....
{% raw %}
{
  "icon":"uil-redo",
  "label":"service restart",
  "action":"server://restart_service",
  "onclick": "return(confirm('Really restart service?'));",
  "hidden": thruk_user.readonly
}
{% endraw %}
.....


=== Custom Form Elements
Combining the raw html menu item with javascript menus can create any arbitrary
menu.

.action_menus/js_form_menu.js
.....
{% raw %}
function js_menu(d) {
  return({
    "icon":"../themes/{{theme}}/images/dropdown.png",
    "title": "form menu",
    "menu":[
      {
        "icon":"uil-document-info",
        "html":"<div class='flexrow gap-1 flex-nowrap'>"
              +"<b>test</b>"
              +"<input type='text'>"
              +"<button onclick='test_form_handler(this)'>ok</button>"
              +"</div>"
      }
    ]
  });
}

function test_form_handler(btn) {
  // prevent clicking twice
  // and give user some response by adding a loading gif to the button
  jQuery(btn).attr('disabled', true).html("<div class='spinner'>");

  // send user input as server action
  var input = jQuery(btn).prev().val();
  jQuery.post("status.cgi?serveraction=1",{
    link:    'server://NameOfServerAction/'+encodeURIComponent(input),
    host:    action_menu_options.host,
    service: action_menu_options.service,
    backend: action_menu_options.backend,
    token:   CSRFtoken
  }, function(response) {
    // show repsonse
    thruk_message(response.rc, response.msg);

    // close menu again
    action_menu_close();
  });
}
{% endraw %}
.....



=== Sending Commands
This example fetches some host details from the rest api and shows a dynamic
host menu based on some attributes. It also sends some commands.

.action_menus/js_cmd_menu.js
.....
{% raw %}
function js_menu(d) {
  return({
    "icon":"../themes/{{theme}}/images/dropdown.png",
    "title": "confirm menu",
    "menu": js_menu_items
  });
}

function js_menu_items(d) {
  // fetch host attributes
  return(
    jQuery.get("../r/sites/"+d.backend+"/hosts/"+d.host)
    .then(function(data, textStatus, jqXHR) {
    var result = [];
    host_data = data[0];
    if(!host_data) {
      return({ 'label': 'host not found?'});
    }

    result.push({ label: d.host+":", disabled: true });

    // add submenu with all parents
    if(host_data.parents.length > 0) {
      parents_menu = [];
      jQuery(host_data.parents).each(function(i, r) {
        parents_menu.push({ label: r, action: "extinfo.cgi?type=1&host="+r });
      });
      var parents = {
        'label': 'parents',
        'menu': parents_menu
      };
      result.push(parents);
    }

    // add submenu with all child hosts
    if(host_data.childs.length > 0) {
      childs_menu = [];
      jQuery(host_data.childs).each(function(i, r) {
        childs_menu.push({ label: r, action: "extinfo.cgi?type=1&host="+r });
      });
      var childs = {
        'label': 'childs',
        'menu': childs_menu
      };
      result.push(childs);
    }

    // add active checks switch
    result.push({
      'label': "active checks",
      'menu': [{
        'label': 'disable active checks',
        'disabled': !host_data.active_checks_enabled,
        'action': function() {
          jQuery.post("../r/sites/"+d.backend+"/hosts/"+d.host+'/cmd/disable_host_check')
                .then(function(data, textStatus, jqXHR) {
                  thruk_message(0, "active checks disabled");
                });
        }
      }, {
        'label':   'enable active checks',
        'disabled': host_data.active_checks_enabled,
        'action': function() {
          jQuery.post("../r/sites/"+d.backend+"/hosts/"+d.host+'/cmd/enable_host_check')
                .then(function(data, textStatus, jqXHR) {
                  thruk_message(0, "active checks enabled");
                });
        }
      }]
    });

    return(result);
  }));
}
{% endraw %}
.....
