Introduction
In the op5 Monitor user manual we describe how the widgets work in the user interface. There you can learn how to
•hide and show the widgets
•move around the widgets
•change the widgets refresh rate
•restore to default settings.
In this chapter we will take a look at how you could create your own widgets.
The op5 Monitor user interface uses Kohana as framework and some of the backend parts for the widgets are handled by Kohana. But this chapter will only describe the widget development in it self.
In this chapter we will focus on creating a small hello world widget.
The widget rules
All widgets need to follow a few rules. They need to have a correct
•File structure
•Widget class
•View file
File structure
The widget has to be placed in a folder with the exact name as the widget.
All default widgets shipped with op5 monitor is placed in:
/opt/monitor/op5/ninja/application/widgets
And the file structure below the widgets folder looks like this
But as we are developing our own widget we need to place it in:
/opt/monitor/op5/ninja/application/custom_widgets
| Everything is case sensitive here. |
Widget Class
Each widget has one main widget class. It needs to be in a PHP-file with the same name as the widget you are creating, and must be called an first character uppercased version of the same name, with a _Widget suffix.
It should have a function called index that prints the widget.
It may optionally also have an options function that specifies custom widget arguments as shown in the example
Writing the widget file on page 117If you call your widget hello_world, then hello_world.php can contain a simple echo as shown below:
{
public function index()
{
echo "Hello World!";
}
}
View file
While you could print your widget content from the index function as above, it's better to move your content to a separate view file to distinguish functions and content.
If you create the file view.php with the content: Hello World!
then you include it from your index function in the widget file by changing it to:
public function index()
{
require($this->view_path('view'));
}
In this example we will create a small hello_world widget. We will assume you have:
•ssh access to the server
•required knowledge about PHP
•knowledge about how to use an editor in a Linux environment.
We does also assume that you, before you start, log in to the op5 Monitor server.
Creating the directory structure
To create the directory structure
1 Go to the application folder:
cd /opt/monitor/op5/ninja/application
2 Create the following folders:
mkdir -p custom_widgets/hello_world
mkdir custom_widgets/hello_world/css
mkdir custom_widgets/hello_world/images
mkdir custom_widgets/hello_world/js
Writing the widget file
To write the widget file
1 Go to the widget folder:
# cd /opt/monitor/op5/ninja/application/custom_widgets/hello_world
2 Create a file, with your favorite editor, called:
hello_world.php
3 Type in the following content in the file:
<?php defined('SYSPATH') OR die('No direct access allowed.');
class Hello_world_Widget extends widget_Base {
public function options()
{
// Load default options, like refresh frequency
$options = parent::options();
// Add option for specifying a custom greeting
// (widget_name, option_name, label,
// option_type, special_options, default)
$options[] = new option('hello_world', 'greeting', 'Greeting',
'input', array(), 'World');
return $options;
}
public function index()
{
// get the widget arguments, based on the options above
$arguments = $this->get_arguments();
require($this->view_path('view'));
}
}
4 Save and exit from your editor.
Writing the view file
To write the view file
1 Go to the widget folder:
# cd /opt/monitor/op5/ninja/application/custom_widgets/hello_world
2 Create a file, with your favorite editor, called:
view.php
3 Type in the following content in the file:
<?php defined('SYSPATH') OR die('No direct access allowed.'); ?>
Hello <?php echo $arguments['greeting']; ?>!
4 Save and exit from your editor.
Multiple instances
It is possible to spawn multiple instances of a custom widget, like the ones we are shipping in op5 Monitor.
You could use this to create a gazillion of “Hello World”-widgets, or you could create a widget that has multiple datasources like the built-in widget “Unacknowledged service problems” that is described in op5 Monitor User Manual.
To add this functionality, insert the following in the top of your widget class:
protected $duplicatable = true;
Adding the widget to the widget table
Before we can see the widget on the tactical overview we need to add it to the widgets table in mysql.
To add the widget to the database
Run the following command from your shell:
# php /opt/monitor/op5/ninja/index.php cli/save_widget \
--name=hello_world --friendly_name='Hello World' --page='tac/index'
Removing a widget
If you for some reason should want to delete a added widget completely you can remove it by deleting it from the mysql database:
# mysql -uroot merlin
> DELETE FROM ninja_widgets WHERE NAME=’hello_world’
> quit
Viewing the widget
If everything is done correctly we will now be able to view the simple op5 Monitor widget.
To view the widget
Open up the Tactical overview in the op5 Monitor user interface and it will look like this:

The example we have been working on here in this chapter is very basic and the output is not of much use. Once you have understood the basics you will probably like to create a more useful widget.
One way to get more information about how you can create a more advanced widget is to take a look at one of the widgets shipped with op5 Monitor.
The Network health widget is a good example. That one can be found here:
# /opt/monitor/op5/ninja/application/widgets/netw_health
| If you are changing any of the default widgets remember to create a copy of the widget, with a new name, and place it in: # /opt/monitor/op5/ninja/application/custom_widgets/ |
To make it easy for other users to install and start using your widget you should make a package of it. Then one can install the package in the Tactical Overview in the op5 Monitor GUI.
The package is actually a normal zip file that contains
•the widget in it self
•manifest.xml
The manifest.xml file contains basic data needed by op5 Monitor so that it knows how to install the widget.
Creating the manifest.xml
To create a manifest.xml file
1 Create an xml file that looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Manifest file for widget to be used in Ninja4Nagios -->
<widget_content>
<author>John Doe</author>
<version>1.0</version>
<Friendly_name>My cool widget</Friendly_name>
<description>A cool widget for op5 Monitor Tactical Overeview.</description>
<page>tac/index</page>
</widget_content>
2 Place it in the folder where your widget is located and make sure it is called:
manifest.xml
Creating the widget package
Now your widget should be ready and located in the filesystem of your op5 Monitor server.
In this instruction we assume that your widget is called:
my_own_widget
To create a widget package
1 Go to the folder above the my_own_widget folder where your widget is located.
2 Create the package with the zip command like this
# zip -r my_own_widget.zip my_own_widget/
Access widgets externally
It is possible to access a widget from an external site, like an intranet or network status page.
Server side setup
To configure this you need to configure a contact and a group, edit php-settings for external widgets and insert an iframe on your external web site.
Contact configuration
To set up a widget contact you first need to create a user. When that is configured create a contact with the same name as the user and specify which contact group(s) it should be a member of.
Like as a normal contact the contact groups defines which hosts are accessible.
Login as the user created for the widget.
PHP settings
After the contact is created log in to the op5 Monitor server using SSH.
# cp /opt/monitor/op5/ninja/application/config/external_widget.php /opt/monitor/op5/ninja/application/config/custom
# cd /opt/monitor/op5/ninja/application/config/custom
Edit the file external_widget.php with your favorite editor.
This file has two variables, “widget_name” specifies which widget that should be shown by default if no widget is set in the iframe. The next one is “username” and this sets the user that should be allowed to fetch the widget.
Example:
$config['widget_name'] = 'netw_health';
$config['username'] = 'jsmith';
$config['groups'] = array('');
In this example the contact jsmith will be used to view widgets and by default it will show the network health widget.
External website setup
On the external website you will need to add an iframe in which the widget is displayed.
The format of the iframe looks like this:
<iframe src="http://<SERVER_NAME>/ninja/index.php/external_widget/show_widget/<OPTIONAL WIDGET_NAME>" height="500px" frameborder=0 width="600px" scrolling='no'></iframe>
In this iframe you will need to change the <SERVER_NAME> to your op5 monitor host name and <OPTIONAL WIDGET_NAME> can either be removed and the default widget will be used or you can specify a widget name to view another widget.
The widgets names can we found in the folder /opt/monitor/op5/ninja/application/widgets. The folder names is the same as the widget name.
Widget Porting guide
In op5 Monitor 5.6 the whole widget system was redeveloped to add more functionality such as multiple instances of a widget. If you have created widgets in op5 Monitor prior to 5.6 they will not work out of the box.
Therefore we have created a porting guide to make your home brewed widgets to work in op5 Monitor 5.6 and later.
This information can also be found in your op5 Monitor installation:
# /opt/monitor/op5/ninja/applications/widgets/PORTING_GUIDE
Java script
In old-style widgets, you usually needed to create a java script file with the
following content:
$(document).ready(function() {
var my_widget = new widget('my_widget', 'widget-content');
});
If that was all your java script file contained, you can now safely remove it. If
it did more things, you may no longer initialize the widget yourself, but must
instead wait for the widget system to load your widget:
widget.register_widget_load('my_widget', function() {
var my_widget = this;
});
If your java script also kept track of your custom configuration options, that,
too, can likely be removed - see
Extra Settings on page 128
If you do custom things in your java script, you may need to adjust it, should
you want to make it possible to create multiple instances of the widget. This is
View
Old widgets that supported ajax refreshes all had to copy-paste the following:
<?php defined('SYSPATH') OR die('No direct access allowed.'); ?>
<?php if (!$ajax_call) { ?>
<div class="widget editable movable collapsable removable closeconfirm" id="widget-<?php echo $widget_id ?>">
<div class="widget-header"><span class="<?php echo $widget_id ?>_editable" id="<?php echo $widget_id ?>_title"><?php echo $title ?></span></div>
<div class="widget-editbox">
<?php echo form::open('ajax/save_widget_setting', array('id' => $widget_id.'_form', 'onsubmit' => 'return false;')); ?>
<fieldset>
<label for="<?php echo $widget_id ?>_refresh"><?php echo $this->translate->_('Refresh (sec)') ?>:</label>
<input size="3" type="text" name="<?php echo $widget_id ?>_refresh" id="<?php echo $widget_id ?>_refresh" value="<?php echo $refresh_rate ?>" />
<div id="<?php echo $widget_id ?>_slider"></div>
<!-- EXTRA CONTROLS HERE -->
</fieldset>
<?php echo form::close() ?>
</div>
<div class="widget-content">
<?php } ?>
<!-- WIDGET CONTENT HERE -->
<?php if (!$ajax_call) { ?>
</div>
</div>
<?php } ?>
With the new widget system, you should remove everything from this file that
isn't content. That is, the only thing you should keep in the view, is what you
had where it says <!-- WIDGET CONTENT HERE --> - everything else should be deleted. Any extra controls will need to be migrated to the controller - see
Extra Settings on page 128.
Controller
This is the old template for the controller, i.e. the file that had the name of
your widget. Not all widgets had all of this, but most of them had most of it:
<?php defined('SYSPATH') OR die('No direct access allowed.');
class My_widget_Widget extends widget_Core {
public function __construct()
{
parent::__construct();
# needed to figure out path to widget
$this->set_widget_name(__CLASS__, basename(__FILE__));
}
public function index($arguments=false, $master=false)
{
# required to enable us to assign the correct
# variables to the calling controller
$this->master_obj = $master;
# fetch widget view path
$view_path = $this->view_path('view');
if (is_object($arguments[0])) {
$current_status = $arguments[0];
array_shift($arguments);
} else {
$current_status = new Current_status_Model();
}
if (!$current_status->data_present()) {
$current_status->analyze_status_data();
}
$widget_id = $this->widgetname;
if (isset($arguments['refresh_interval'])) {
$refresh_rate = $arguments['refresh_interval'];
}
$title = $this->translate->_('My Widget');
if (isset($arguments['widget_title'])) {
$title = $arguments['widget_title'];
}
# let view template know if wrapping div should be hidden or not
$ajax_call = request::is_ajax() ? true : false;
/**
* Actually do stuff
*/
# fetch widget content
require_once($view_path);
if(request::is_ajax()) {
# output widget content
echo json::encode( $this->output());
} else {
$this->js = array('/js/my_widget');
$this->css = array('/css/my_widget');
# call parent helper to assign all
# variables to master controller
return $this->fetch();
}
}
}
This is the new-style equivalent:
<?php defined('SYSPATH') OR die('No direct access allowed.');
class My_widget_Widget extends widget_Base {
public function __construct($model)
{
parent::__construct($model);
/**
* Do any global initiation here
*/
}
public function index()
{
# fetch widget view path
$view_path = $this->view_path('view');
$current_status = $this->get_current_status();
$arguments = $this->get_arguments();
/**
* Actually do stuff
*/
$this->js = array('/js/my_widget');
$this->css = array('/css/my_widget');
require($view_path);
}
}
Note: The widget must inherit from widget_Base instead of widget_Core.
Note: The constructor now takes an argument, index takes none.
Note: You must not use require_once to include the view if you intend to allow multiple widget instances
Note: Still no file endings on java script and css resources.
Extra Settings
This used to be a free-form div in the view, however, it was mostly just
cut and pasted from widget to widget, so we have implemented the redundant stuff
once, so you won't have to. You now add a method to the controller, options, and
have it return an array of your extra options. This is the last example widget
again, but with two extra settings:
<?php defined('SYSPATH') OR die('No direct access allowed.');
class My_widget_Widget extends widget_Base {
public function options() {
$options = parent::options();
$options[] = new option('my_widget', // your widget name (or something else unique)
'option1', // a unique option name
$this->translate->_('My first option'), // your label
'input', // option type - input, checkbox, dropdown, etc
array('size'=>5), // extra attributes for the field
'default1'); // default value
$options[] = new option('my_widget',
'option2',
'My second option',
'input',
array('size'=>5),
'default2');
return $options;
}
public function index()
{
# fetch widget view path
$view_path = $this->view_path('view');
$current_status = $this->get_current_status();
$arguments = $this->get_arguments();
/**
* Actually do stuff
*/
require($view_path);
}
}
Note: This will automatically create java script to save any changes and refresh the page on changes. If you want to do this manually, you must call should_render_js(false) on the option object.
Note: If you want to, you can return a pure HTML string of the widget settings you want to keep track of. That way, you will get to do everything yourself.
Multiple Instances
For simple widgets, to enable multiple instances you will only have to add one
single line of code to the constructor: "protected $duplicatable = true;". This
is a simple hello world widget that can be duplicated:
<?php defined('SYSPATH') OR die('No direct access allowed.');
class My_widget_Widget extends widget_Base {
protected $duplicatable = true;
public function index()
{
print "Hello world!";
}
}
If your widget is more complicated, you will probably have to change more
things.
First, it's likely that your widget includes fields with the id attribute. Doing
so is no longer valid - you must either create a globally unique name using both
the widget's name and instance id, or you should use a class attribute instead.
Then, in your javascript, you must take care to use a combined selector to
retrieve the HTML node you want for the correct widget instance. In the past,
a common pattern in java script files was the following:
$(document).ready(function() {
var my_widget = new widget('my_widget', 'widget-content');
$('#my_widget_setting').change(function() {
my_widget.save_custom_val($(this).val(), 'my_widget_setting');
do_something();
});
});
Again, if do_something is only a widget reload, you can remove this code
completely. If you do more things, you need to take more care.
This is how the above should be written with new-style, multi-instance widgets:
widget.register_widget_load('my_widget', function() {
var my_widget = this;
$('#'+my_widget.widget_id+' .my_widget_setting').change(function() {
my_widget.save_custom_val($(this).val(), 'my_widget_setting');
do_something();
});
});
That is, you can safely search for the class within the widget instance id.