HowTo - Developing modules for icinga-web
- HowTo - Developing modules for icinga-web
- What you will learn here
- About the module system
- Preparing the module
- Implementing the logic
- Deploying the module
- Module guidelines
- Way to go
Extensibility is one of the main aspects of the icinga-web interface. When you miss a feature in the icinga-web or have special requirements you should be able to quickly implement it by yourself (or let someone else do it).
Therefore we provided two extension mechanisms called cronks and modules. Cronks help you customize views in the web portal while modules are (in most cases) standalone applications that are implemented in the icinga-web ui.
What you will learn here
The focus of this article lays on the module creation and deployment process. After completing this How-To, you will be able to:
- Create and access a new module in icinga-web
- Use the icinga-web Authentication-system (tbd)
- Implement the Icinga-API
- Access a database
- Pack and Deploy the module to a self-running installer (tbd)
Note: This How-To only scratches the surface of what is possible and should provide an overview of how to start when writing a module for Icinga-Web.
What you really need for this HowTo:
- A running instance of the newest icinga-web version (git clone git://git.icinga.org/icinga-web)
- Average PHP5/HTML knowledge (Object and Classes)
- A running database (I'd recommend mysql, other dbms are not tested with this HowTo yet)
What you should, but needn't know:
- Basic knowledge about Agavi (http://www.agavi.org). They have a really good tutorial on their site which explains (almost) every feature used in this How-To
- Basic knowledge about Doctrine (http://www.doctrine-project.org). Again, doctrine specific functions will be explained
About the module system
Icinga-web is based on Agavi, a powerful MVC framework, which is expanded by our AppKit framework that adds several little helpers and features.
While you need no in-depth knowledge about Agavi at this point, you should know that Agavi itself uses modules for splitting applications in logical units - and we will use this feature to build our modules.
You might feel a little bit lost when seeing the source code tree for the first time. You'll soon see that there's no reason for that.
The source code tree
When looking at the icinga-web source, we (basically) see the following structure:
Luckily, we firstly can ignore all folders except the 'app' folder, which contains
So, at this time we only need the %icinga-web%/app/modules and the %icinga-web%/app/config folder.
Preparing the module
Summarizing the features
As mentioned before, at this time we only want users to send messages to other users and receive messages. As a little feature, we'll add a Host/Service selector for the Subject field.
Creating an Agavi-module
To create an Agavi module, type
Now you should see a Messenger folder in 'app/modules/'.
Making it accessible - creating routes
Agavi uses a front-controller that tells your program which module and action to call for which url. To be able to access your module, you first have to define routes for it.
This is done at app/config/routes.xml, where we add the following lines:
Let's assume icinga-web is accessed via localhost/icinga-web. This xml-snippet tells the front controller to use the Messenger module for all URLs that start with /localhost/icinga-web/messenger.
The .overview route tells Agavi to execute the ListMessages action when nothing else is provided, the .writeMessage to open the WriteMessage class. When you know open /localhost/icinga-web/messenger, you should see a blank page, perhaps only the navigation bar (as we don't have content yet).
>(Note: This is very basic. For further information about routing, visit http://www.agavi.org)
Before we can see anything, we must create the tables for our messages first.
- A message id
- The id of the sender
- The id of the receiver
- The message_subject
- The message_content
So, in your icinga-web db (mysql), insert the following command:
Creating Doctrine models
Icinga-web doesn't use raw SQL to access databases, instead it uses Doctrine as an ORM-Layer, which makes life much easier. But before we can access the table, we have to generate a doctrine model.
Fortunately, this is rather easy. Go to your icinga-web folder and type:
Enter your db settings and save the folder to a temporary folder. Now enter (assuming tmp is your temporary folder):
To make life a little bit easier, we have one little change in (%icinga)/app/modules/appkit/database/models/generated/BaseMsgMessenger.php.
Alter the setUp function and rename the relationship-aliases 'user_from' and 'user_to'. That's much easier to read than 'NsmUser' and 'NsmUser_2'. Also add the foreign key relation if it's not generated. See:
Afterwards, you can remove the temporary folder.
Implementing the logic
Read and Write via Agavi models
Now we can start putting it all together.
First of all, we don't want every action to implement their own code for reading from the database. For this reason, we create a Model that does this for us.
Again, this is done with the Agavi tool under bin/agavi:
Additionally, we create another model representing the messages:
Those two models are now at app/modules/Messenger/models/.
The code for the models is straightforward. If encounter any problems, look at the Agavi guide at http://www.agavi.com.
Apparently, at this time everyone can open our messenger, no matter if he's logged in or not.
To force users to login, just add the following to lib/actions/IcingaMessengerBaseAction.class.php
That's it, now only users who are logged in are allowed to open the messenger.
Creating the message-list
The data retrieval is done at views/ListMessagesSuccessView.class.php:
(You could move this part to the action, too. Actually, we mostly put write operations to the action, but that's up to you)
The html returned to the user is at template/ListMessagesSuccess.php. We only iterate over the model array and print it.
That's it, the message list is complete (although ugly, but we'll come to the CSS-part later)
Creating the message-writer
Now we come to writing a message. The subject of a message should be a select box with the hosts, the receiver should also be selectable via dropdown.
Therefore, we have to fetch the host list via the icinga-api and the user list via doctrine.
The template for this class looks like this:
Adding a message
When hitting submit, you'll land on a empty 'new message' page again. That's because we haven't added the insert logic.
First of all, we have to write validators for the parameters. this is done in validate/WriteMessage.xml:
The file should look like this:
Now we can add the the executeWrite method (which is called when sending data via POST) to actions/WriteMessageAction.php:
Our feature set is finished now. Unfortunately, it doesn't look very nice yet. That's why I added some lines to %icinga-web%/pub/styles/icinga.css;
The /MODULEMessenger/ markers are very important as they are used by the snippet extractor - we'll come to that in the [HowTo__Developing_modules_for_icinga-web#files|manifest>Files\] description.
The result should look somewhat like this:
Adding a menu point
At this time, we don't have a "Messaging" entry in our navigation bar - time to change that!
The navigation bar is event driven - it sends an 'ready' event when it's available and all available MenuExtender classes add their menupoints by themselves.
Create a new file MessengerMenuExtender.class.php at %icinga-web%/app/modules/Web/lib/menu
Now we have to tell the autoloader that the menupoint exists. Insert the following line to %icinga-web%/app/modules/Web/config/autoloader.xml:
The last step is to add the MenuExtender to the global web config at app/AppKit/config/module.xml. Add "MessengerMenuExtender" to the entry "menu_extender".
The final file should look like this:
Now clear the cache by removing the contents of app/cache/config and watch the result in your browser. You should now see the new menupoint.
Deploying the module
The module install/deploy system
You now have a working instance of your module in your developement system. At this point, in order to install this module to another system, you must copy all files to the target destination, create all config entries, create the tables, etc...
If you want to publish a more complex module, the install process wouldn't be too user friendly.
Fortunately there is an easier way for this, allowing you to exporting a self-installable package directly from your icinga-web instance containing the module. Once set up, deploying your module (or patched versions of your module) is done in no time.
Writing a manifest file
Before you can deploy your module, you need to export it first. Unfortunately, the module system needs to know which files are needed, which config entries to use, which tables have to be set up and so on.
This is done via a manifest file, which must be located at the root of your module folder (icinga-web/app/modules/ myModule /manifest.xml.
Manifest.xml basically consists of 5 parts : Meta, Dependencies, Files, Config and Database.
The meta part of the manifest contains information like the module name, the version, description, etc.
The Module name should be the name of your agavi module
Except the module name, this informations are optional.
Possible nodes are :
- Name : The name of the module (it's best to choose the name of the agavi module for that)
- Version: An arbitary version number
- Description: Short description about the module's function
- Author: The author of the module
- Company: The company, if any
Sets the minimum required version of icinga, php and (if any) php extensions.
*Icinga-Web: Settings affecting icinga-web required
>* Version: The minimum icinga-web version required
*PHP: Settings affecting PHP required
>* Version: The minimum PHP version required required
>* Extensions: List of PHP extensions
>>* Extension: The name of a PHP extension, as listed in [http://de3.php.net/manual/en/function.get-loaded-extensions.php]
Describes all file based copy/paste operation as well as the SnippetExtractor, a little tool that simply exports the text between marked areas of a file and inserts them on installation.
*Paths: Contains all path directives required
>* Icinga: The root path to icinga required
>* AppKit: The path to the appkit directory, relative to PATH_Icinga required
>* Module: The path to the module directory, relative to PATH_Icinga required
>* Doctrine: The path to doctrine, relative to Path_Icinga required
>* Api: The path to the api (i.e. the symlink), relative to PATH_Icinga required
>* PATH-NAME : Any path that should be accessible via the %PATH_PATH_NAME% tag
*Additional: All files and folders that are not in the Module directory but should also be installed required
>* File: A single file to copy
>* Folder A complete folder to copy (allows * and ** for subdirectories and recursive subdirectories):
>>* (PARAM) to: If the file should not be installed to the same folder you can define a target here.
*Excludes: Files and folders that should not be installed (like the agavi cache) required
>* File: A filename to exclude (allows * and ** for subdirectories and recursive subdirectories)
>* Folder A folder to exclude (allows * and ** for subdirectories and recursive subdirectories):
*Extractor: Instructions for the snippet extractor required
>* Extract: A file containing snippets to extract (and, of course, the snippet will be injected in this file on install)
>>* (PARAM) mark: The name of the marker (Module_mark_)
This is the most complex part of the module installer and deployer. Here you can define xml config-settings that should be set on install.
This can be done via hardcoded settings for settings that won't change (like the autoloader prefixes or the menu extender).
Anyhow, some settings will be often altered. There's a good chance that you might add or alter a route when adding a feature. For this settings, you can define a config-node to be dynamically exported from the config file during deployement.
Path directives are defined via xpath.
*Entry: a config file to set settings (or export settings from)
>* (PARAM) file: The filename of the xml
>* (PARAM) namespacePrefix: All parameters except file will be handled as namespace registrations. You only need this when entering fix values (fromConfig = false).
(Note): If fromConfig is false you must define a default namespace
>* Nodes: List of nodes to copy
>>* (PARAM) base: XPath of the base node. All path parameters in the node elements will be relative to this setting
>>* (PARAM) fromConfig: If true, all nodes inherited by 'base' will be exported. Excludes "Node"
>>* Node: A node to set a specific value to
>>>* (PARAM) path: The xpath describing the target. NOTE: This must be a complete path without any // entries! required
>>>* (PARAM) nodeName: If set, the entry will have a node with this name wrapped around
The last part of the manifest is the database section. Here you can define which doctrine-models should be exported.
On installation, doctrine will setup the required tables for every model you export. Additionally, you can define sql routines to set default values.
Note: If you define sql routines for tables that are not part of the installation you should provide a database cleanscript to remove these values on module deletion.
*Doctrine: Doctrine specific settings
>*Models: Models to export
>**Prefix: Model prefixes to exprt
>**File: Model file to export
>**Folder: Model folder to export
*SQLRoutines: Defines SQL files to parse on installation
>*File: A SQL file to parse on installation
>*Folder: A Folder containing sql files
The complete manifest.xml
The complete manifest file could look like this
Save this file under /icinga-web/app/modules/Messenger/manifest.xml
>Note: This looks like a lot of stuff - but basically most of this settings can be used for 90% of your modules. In fact, all relevant features can be found here.
Creating a self installable module
You can now finally deploy the module. For that, go to the icinga-web module folder 'icinga-web/modules'.
And answer the questions (If you don't have phing installed use the binary shipped with agavi - this one can be found at _icinga-web/bin/phing).
After the script is finished, you'll find a folder called "Messenger" and a Messenger_module.tar (which is exactly the same, but packed). That's it - no more work to do.
Installing the module
To install the module, go to the Messenger module folder and type
<code> phing install-module</code>.
Just answer the questions - the module should be installed now.
>Note: If the table installation failed you should drop the msg tables first (as there will be foreign key errors).
If anything goes wrong - rollback
If anything goes wrong during installation, first type
And remove the messenger module. After that call
To restore the original state of any overwritten file.
Additional install routines
By adding a additional.xml file you can append phing routines after the installation, e.g. when you want to add permissions, run post-installation tests etc.
When designing and creating a module, you should regard some guidelines:
- Use global config only when needed. The only settings that should be inserted to icinga.xml are the autoloader prefix and the menuExtender. All settings that only affect the module should be
put into the module.xml.
- Be careful with global overwrites. Don't overwrite any global settings as they cannot be restored on deletion.
- Use the translation manager: To make the examples easier to read, no translation support was added. This shouldn't be done. If you are in JS context, use the _() function to add multi-language support.
In PHP $tm->_() is used for this.
- Test the module with a blank icinga-web before making it public. Perhaps you forgot an icon folder or a crucial config setting in your manifest. You won't see this in your module enviroment, as they exist there.
- Don't modify globally used base classes. If you have added a missing feature to a library or improved a class to be more versatile, we're happy to know and will add them to icinga-web if it is sensible.
- Try to use what's there. We use ExtJS for client-side scripting, which is one of the most powerful JS libraries around. Only use different libraries when there's no other way.
- Never add restricting foreign key relations to tables like NsmUser or NsmRole (generally speaking, all Nsm classes). You won't be able to delete the user afterwards in the admin panel if there are related entries!
Way to go
Congratulations! You finished.
Although this was a lot of stuff, it's only the beginning. For the caring, there are some things that could be added to this module in order to make it more like a 'real' module.
- Create Delete and Edit functions
- Add multicast/broadcast messages
- Play around with agavi output types - it's really easy to make an output accessible via various formats (e.g. JSON, XML, RSS...)
- Write your own Module to make icinga-web the jack of all trades you always dreamed of.
You should now have a good overview over the main features icinga-web offers to module-developers. No matter if you need a static HTML site, displaying some information in a special manner or a complete, full-featured RIA with database support and so on.