Page tree
Skip to end of metadata
Go to start of metadata

This documentation is related to openITCOCKPIT 4



Best practices for adding new openITCOCKPIT features recommend to develop your own module for openITCOCKPIT, which should encapsulate your custom code.

Developing your own modules ensures that your system can still be updated and maintained by our experts.


Getting started

The backend of openITCOCKPIT is written in PHP using the framework  CakePHP 4.

The frontend consists of an AngularJS , jQuery and Bootstrap 4 technology stack.


As IDE we recommend JetBrains PhpStorm and Mozilla Firefox as browser.


The example code is available on GitHub: https://github.com/it-novum/openITCOCKPIT-ExampleModule

Requirements

This document will only cover how to develop modules for openITCOCKPIT. 

It assumes that you are familiar with CakePHP and AngularJS.

Working directory

The working directory of openITCOCKPIT is: /opt/openitc/frontend.

Make sure you're inside of this directory before you start.

We also recommend to use git to keep your changes tracked.

Enable Debug mode

Important

Please be aware, that enabling the debug mode could lead to leakage of sensitive information. 

By default, openITCOCKPIT will run in product mode. To get error messages and to make use of uncompressed / not minified JavaScript files, you need to enable the debug mode.

To do so open up the file /etc/nginx/openitc/master.conf and set OITC_DEBUG to from 0 to 1.

/etc/nginx/openitc/master.conf
fastcgi_param OITC_DEBUG 1;


To apply the changes you need to execute:

openitcockpit-update --no-system-files

Important

Again, please be aware, that enabling the debug mode could lead to leakage of sensitive information.

Disable Debug mode

To disable the debug mode in openITCOCKPIT, you simply need to execute:

openitcockpit-update


Bake your Module

Before you start, make sure you have switched into the /opt/openitc/frontend directory and created a new git branch for your development.

openITCOCKPIT has its own CLI tool called oitc which among other things can be also used to create a skeleton of a new module. This is called the "bake" command as it is based on the cakephp bake command.


To create a new Module run the following command:

oitc bake plugin ExampleModule

Notice

It is important to emphasize that the name of your module ends with "Module". Like ExampleModule, AutoreportsModule, MkModule and so on.


The system will ask for the path of the plugin. In this example.

Plugin Directory: /opt/openitc/frontend/plugins/ExampleModule

Please confirm with "y".


The system will also ask if you want to overwrite the file.

/opt/openitc/frontend/composer.json.

Do not overwrite the file /opt/openitc/frontend/composer.json and skip this by entering "n".

 CLI output - Click here to expand...

root @ /opt/openitc/frontend - [ExampleModule] # oitc bake plugin ExampleModule
Plugin Name: ExampleModule
Plugin Directory: /opt/openitc/frontend/plugins/ExampleModule
-------------------------------------------------------------------------------
Look okay? (y/n/q)
[y] > y
Generating .gitignore file...

Creating file /opt/openitc/frontend/plugins/ExampleModule/.gitignore
Wrote `/opt/openitc/frontend/plugins/ExampleModule/.gitignore`
Generating README.md file...

Creating file /opt/openitc/frontend/plugins/ExampleModule/README.md
Wrote `/opt/openitc/frontend/plugins/ExampleModule/README.md`
Generating composer.json file...

Creating file /opt/openitc/frontend/plugins/ExampleModule/composer.json
Wrote `/opt/openitc/frontend/plugins/ExampleModule/composer.json`
Generating phpunit.xml.dist file...

Creating file /opt/openitc/frontend/plugins/ExampleModule/phpunit.xml.dist
Wrote `/opt/openitc/frontend/plugins/ExampleModule/phpunit.xml.dist`
Generating src/Controller/AppController.php file...

Creating file /opt/openitc/frontend/plugins/ExampleModule/src/Controller/AppController.php
Wrote `/opt/openitc/frontend/plugins/ExampleModule/src/Controller/AppController.php`
Generating src/Plugin.php file...

Creating file /opt/openitc/frontend/plugins/ExampleModule/src/Plugin.php
Wrote `/opt/openitc/frontend/plugins/ExampleModule/src/Plugin.php`
Generating tests/bootstrap.php file...

Creating file /opt/openitc/frontend/plugins/ExampleModule/tests/bootstrap.php
Wrote `/opt/openitc/frontend/plugins/ExampleModule/tests/bootstrap.php`
Generating webroot/.gitkeep file...

Creating file /opt/openitc/frontend/plugins/ExampleModule/webroot/.gitkeep
Wrote `/opt/openitc/frontend/plugins/ExampleModule/webroot/.gitkeep`
Modifying composer autoloader

File `/opt/openitc/frontend/composer.json` exists
Do you want to overwrite? (y/n/a/q)
[n] > n
Skip `/opt/openitc/frontend/composer.json`

Generating autoload files
Generated autoload files


/opt/openitc/frontend/src/Application.php modified
-------------------------------------------------------------------------------
Created: ExampleModule in /opt/openitc/frontend/plugins/ExampleModule


root @ /opt/openitc/frontend - [ExampleModule] #

Fix file permissions

Whenever you use the oitc generated files it is recommended to set the file permissions for the web server user to www-data.

openITCOCKPIT provides an own command to fix file permissions.

oitc rights


Cleanup src/Application.php

Edit the file /opt/openitc/frontend/src/Application.php and remove the following line:

$this->addPlugin('ExampleModule');

openITCOCKPIT will load Modules automatically. No manually action or code is required.


Change routing

By default CakePHP use a hyphen (-) as CamelCase separator in the URL. Due to the legacy of openITCOCKPIT it is required to change the separator to an underscore (_)

To do so, open the file /opt/openitc/frontend/plugins/ExampleModule/src/Plugin.php and search for the following code.

    public function routes(RouteBuilder $routes): void
    {
        $routes->plugin(
            'ExampleModule',
            ['path' => '/example-module'],
            function (RouteBuilder $builder) {
                // Add custom routes here

                $builder->fallbacks();
            }
        );
        parent::routes($routes);
    }

Now change the value of the path from example-module to example_module.

['path' => '/example_module'],


Hello World - Backend

The structure of your new module should look like in this example:



A good starting point would be to create the first page of your module and print the message "Hello World".

Creating a new Controller

CakePHP controllers contain all your business logic and provide API endpoints to the frontend.


In this Example we will create the "TestController".

Create the file /opt/openitc/frontend/plugins/ExampleModule/src/Controller/TestController.php with the following content:

/opt/openitc/frontend/plugins/ExampleModule/src/Controller/TestController.php
<?php
declare(strict_types=1);

namespace ExampleModule\Controller;


class TestController extends AppController {
    
}

Creating a new Action

Actions contains the logic of your API endpoint.

We will new create action called "index" to print the message "Hello World":

<?php
declare(strict_types=1);

namespace ExampleModule\Controller;


class TestController extends AppController {

    public function index() {
        if (!$this->isApiRequest()) {
            // The requested URL was: /example_module/test/index.html
            // The controller only sends the HTML template to the client browser / AngularJS

            // Pass the variable "message" with the content "Hello World (HTML)" to the view for .html requests
            $this->set('message', 'Hello World (HTML)');
            return;
        }

        // This get executed for API requests
        //  The requested URL was: /example_module/test/index.json

        // Pass the variable "message" with the content "Hello World" to the view
        $this->set('message', 'Hello World');

        // Add the variable "message" to .json output
        $this->viewBuilder()->setOption('serialize', ['message']);
    }

}


Create the actions View

The View gets rendered to your browser. openITCOCKPIT supports two types of views.

The HTML view gets loaded by AngularJS and contains the static HTML structure with placeholders for information of the current action.

The JSON view contains the data for the placeholders of the current action.

Creating .html View

Create the file /opt/openitc/frontend/plugins/ExampleModule/templates/Test/index.php with the following content:

/opt/openitc/frontend/plugins/ExampleModule/templates/Test/index.php
<?php
/**
 * @var \App\View\AppView $this
 * @var string $message
 */
?>

<div class="row">
    <div class="col-xl-12">
        <div id="panel-1" class="panel">
            <div class="panel-hdr">
                <h2>
                    <?php echo __('Example Module'); ?>
                    <span class="fw-300"><i><?php echo __('Hello World'); ?></i></span>
                </h2>
            </div>
            <div class="panel-container show">
                <div class="panel-content">

                    <!-- Output "Hello World (HTML)" that was set by the controller -->
                    <?= h($message); ?>

                </div>
            </div>
        </div>
    </div>
</div>

Notice

AngularJS will only load the template once and cache it.

Do not use <?php foreach($data as $record): ?> to output your data.


Creating .json View

The JSON view gets automatically generated by the CakePHP framework.

You could learn more about this in the CakePHP documentation.


Grant permissions for "Administrator" user role

By default the system denies access to all API actions. openITCOCKPIT grants all permissions to the "Administrator" by executing:

openitcockpit-update --no-system-files

If your current user is not part of the "Administrator" group,  you need to navigate to "Manage User Roles" and grant the permission manually.

You can read more about user permissions in the permissions section of this article.


Query the Action

You should now be able to query the .html and .json actions.

Use your web browser to navigate to /example_module/test/index.html to get the HTML action 

It is normal that no CSS and Javascript gets loaded at this point.


Via /example_module/test/index.json you can reach the JSON action.

Hello World - Frontend

New Menu Entry

By default, the menu of openITCOCKPIT is separated into four categories(MenuHeadline) : Overview, Monitoring, Administration and System Configuration.

You can either create links in these categories or define your own categories.

All menu records of your module gets defined in the file /opt/openitc/frontend/plugins/ExampleModule/src/Lib/Menu.php.

Menu entry in existing headline:


PHP Code


ResultPhpStorm
<?php

namespace ExampleModule\Lib;


use itnovum\openITCOCKPIT\Core\Menu\MenuCategory;
use itnovum\openITCOCKPIT\Core\Menu\MenuHeadline;
use itnovum\openITCOCKPIT\Core\Menu\MenuInterface;
use itnovum\openITCOCKPIT\Core\Menu\MenuLink;

class Menu implements MenuInterface {

    /**
     * @return array
     */
    public function getHeadlines() {
        $Overview = new MenuHeadline(\itnovum\openITCOCKPIT\Core\Menu\Menu::MENU_OVERVIEW);
        $Overview
            //Create a new Sub-Category of the Overview Headline
            ->addCategory((new MenuCategory(
                'ExampleModule',
                __('Example Module'),
                1000,
                'fas fa-burn'
            ))
                //Add new Link to Sub-Category
                ->addLink(new MenuLink(
                    __('Hello world'),
                    'TestIndex', //Name of the NG-State
                    'Test', //Name of the PHP Controller
                    'index', //Name of the PHP action
                    'ExampleModule', //Name of the Module
                    'fas fa-code', //Menu Icon
                    [],
                    1
                ))
            );

        return [$Overview];
    }

}



Afterwards execute this command to update the display of the edited views.

openitcockpit-update --no-system-files

Menu entry in new headline

If required, you can also define your own new headlines.


PHP Code


Result

PhpStorm

<?php

namespace ExampleModule\Lib;


use itnovum\openITCOCKPIT\Core\Menu\MenuCategory;
use itnovum\openITCOCKPIT\Core\Menu\MenuHeadline;
use itnovum\openITCOCKPIT\Core\Menu\MenuInterface;
use itnovum\openITCOCKPIT\Core\Menu\MenuLink;

class Menu implements MenuInterface {

    /**
     * @return array
     */
    public function getHeadlines() {
        $ExampleModuleHeadline = new MenuHeadline(
            'ExampleModuleHeadline',
            __('Example Module')
        );
        $ExampleModuleHeadline
            //Create a new Sub-Category of the Overview Headline
            ->addCategory((new MenuCategory(
                'ExampleModule',
                __('Example Module'),
                1000,
                'fas fa-burn'
            ))
                //Add new Link to Sub-Category
                ->addLink(new MenuLink(
                    __('Hello world'),
                    'TestIndex', //Name of the NG-State
                    'Test', //Name of the PHP Controller
                    'index', //Name of the PHP action
                    'ExampleModule', //Name of the Module
                    'fas fa-code', //Menu Icon
                    [],
                    1
                ))
            );

        return [$ExampleModuleHeadline];
    }

}





Again, execute this command to update the display of the edited views.

openitcockpit-update --no-system-files


Creating a new NG-State / AngularJS Route

The web interface of openITCOCKPIT is built on top of the AngularJS framework. Every API action requires its own AngularJS state and controller.

All states gets defined in the file: /opt/openitc/frontend/plugins/ExampleModule/webroot/js/scripts/ng.states.js.

openITCOCKPIT.config(function($stateProvider){
    $stateProvider
        .state('TestIndex', { // Name of the NG-State => Same as in Menu.php
            url: '/example_module/test/index', // URL the browser should display
            templateUrl: '/example_module/test/index.html', // URL of the .html Template for AngularJS
            controller: 'TestIndexController' // Name of the AngularJS Controller. Convention: Controller name + Action Name + 'Controller'
        });
});

Creating AngularJS Controller

Each action encapsulates its JavaScript logic into an AngularJS controller. Controllers will always only handle one single action! Each action requires its own controller.

If you want to use the same code in different controllers, please create an Angular Service.


Now, create your TestIndexController in this location: /opt/openitc/frontend/plugins/ExampleModule/webroot/js/scripts/controllers/Test/TestIndexController.js.

Convention: /opt/openitc/frontend/plugins/ExampleModule/webroot/js/scripts/controllers/<PHPController name>/<Controller Name><Action Name>Controller.js

/opt/openitc/frontend/plugins/ExampleModule/webroot/js/scripts/controllers/Test/TestIndexController.js
angular.module('openITCOCKPIT')
    .controller('TestIndexController', function($scope, $http){

        //Name TestIndexController same as in ng.states.js
        //Convention: Controller name + Action Name + 'Controller' = TestIndexController


        console.log('TestIndexController is loaded');

    });


You are now ready to click on your new menu entry and see the result.

Database Access

openITCOCKPIT uses the ORM of CakePHP to access the database. Please use always the ORM and never write plain SQL queries!


Creating a new table in the database

To manage schema updates openITCOCKPIT uses the CakePHP Migrations plugin.

Creating a new Table

Let's assume you want to store additional information to an host. To do so we create a new table which will store the additional information.

Never manipulate existing tables!


To create a new empty migration file please execute this command:

oitc migrations create -p ExampleModule Initial
 CLI output - Click here to expand...

root @ /opt/openitc/frontend/plugins/ExampleModule - [ExampleModule] # oitc migrations create -p ExampleModule Initial
using migration paths
- /opt/openitc/frontend/plugins/ExampleModule/config/Migrations
using seed paths
- /opt/openitc/frontend/plugins/ExampleModule/config/Seeds
using migration base class Migrations\AbstractMigration
using alternative template /opt/openitc/frontend/vendor/cakephp/migrations/templates/Phinx/create.php.template
created config/Migrations/20200331090547_initial.php
renaming file in CamelCase to follow CakePHP convention...
renaming file in CamelCase to follow CakePHP convention...
File successfully renamed to /opt/openitc/frontend/plugins/ExampleModule/config/Migrations/20200331090547_Initial.php
root @ /opt/openitc/frontend/plugins/ExampleModule - [ExampleModule] #

To set file permissions it is recommended to also execute:

oitc rights


The system has created a new empty migration file for you at the following location: /opt/openitc/frontend/plugins/ExampleModule/config/Migrations/<timestamp>_Initial.php

In this file you can now define the schema of your new table

plugins/ExampleModule/config/Migrations/20200331090547_Initial.php
<?php
declare(strict_types=1);

use Migrations\AbstractMigration;

/**
 * Class Initial
 *
 * Created via:
 * oitc migrations create -p ExampleModule Initial
 */
class Initial extends AbstractMigration {

    /**
     * Whether the tables created in this migration
     * should auto-create an `id` field or not
     *
     * This option is global for all tables created in the migration file.
     * If you set it to false, you have to manually add the primary keys for your
     * tables using the Migrations\Table::addPrimaryKey() method
     *
     * @var bool
     */
    public $autoId = false;

    /**
     * Change Method.
     *
     * More information on this method is available here:
     * https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
     * @return void
     */
    public function change() {

        $this->table('example_notes')
            ->addColumn('id', 'integer', [
                'autoIncrement' => true,
                'default'       => null,
                'limit'         => 11,
                'null'          => false,
            ])
            ->addPrimaryKey(['id'])
            ->addColumn('host_id', 'integer', [
                'default' => null,
                'limit'   => 11,
                'null'    => false,
            ])
            ->addColumn('notes', 'string', [
                'default' => null,
                'limit'   => 255,
                'null'    => false,
            ])
            ->create();
    }
}

The given example will create the new table "example_notes" with columns id, host_id and notes.


To create the new table in the database run:

openitcockpit-update --no-system-files


You can use the mysql cli or phpMyAdmin to verify that your table has been created.

TableTable Schema
$ # mysql --defaults-file=/opt/openitc/etc/mysql/mysql.cnf

mysql> show tables like 'example_%';
+-------------------------------------+
| Tables_in_openitcockpit (example_%) |
+-------------------------------------+
| example_module_phinxlog             |
| example_notes                       |
+-------------------------------------+
2 rows in set (0.00 sec)

mysql>
mysql> show create table example_notes\G
*************************** 1. row ***************************
       Table: example_notes
Create Table: CREATE TABLE `example_notes` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `host_id` int(11) NOT NULL,
  `notes` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

mysql>

Inserting some example data

To make the development process less abstract we recommend to insert some sample data into the new table. You would use the following SQL statement to insert one record for each host.

INSERT INTO `example_notes` (`host_id`, `notes`) SELECT id, CONCAT('Notes for the host: ', name) FROM `hosts`;


Creating new Table Object (CakePHP)

To access a database table through the CakePHP ORM it is required to create a Table object first.

oitc bake model ExampleNotes -p ExampleModule
 CLI output - Click here to expand...

root @ /opt/openitc/frontend/plugins/ExampleModule - [ExampleModule] # oitc bake model ExampleNotes -p ExampleModule

One moment while associations are detected.

Baking table class for ExampleNotes...

Creating file /opt/openitc/frontend/plugins/ExampleModule/src/Model/Table/ExampleNotesTable.php
Wrote `/opt/openitc/frontend/plugins/ExampleModule/src/Model/Table/ExampleNotesTable.php`

Baking entity class for ExampleNote...

Creating file /opt/openitc/frontend/plugins/ExampleModule/src/Model/Entity/ExampleNote.php
Wrote `/opt/openitc/frontend/plugins/ExampleModule/src/Model/Entity/ExampleNote.php`

Baking test fixture for ExampleNotes...

Creating file /opt/openitc/frontend/plugins/ExampleModule/tests/Fixture/ExampleNotesFixture.php
Wrote `/opt/openitc/frontend/plugins/ExampleModule/tests/Fixture/ExampleNotesFixture.php`
Bake is detecting possible fixtures...

Baking test case for ExampleModule\Model\Table\ExampleNotesTable ...

Creating file /opt/openitc/frontend/plugins/ExampleModule/tests/TestCase/Model/Table/ExampleNotesTableTest.php
Wrote `/opt/openitc/frontend/plugins/ExampleModule/tests/TestCase/Model/Table/ExampleNotesTableTest.php`
Done

root @ /opt/openitc/frontend/plugins/ExampleModule - [ExampleModule] #

oitc rights

The system has automatically created a new Table and Entity object.


Open up the file /opt/openitc/frontend/plugins/ExampleModule/src/Model/Table/ExampleNotesTable.php

ExampleNotesTable.php
<?php
declare(strict_types=1);

namespace ExampleModule\Model\Table;

use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;

class ExampleNotesTable extends Table {
    /**
     * Initialize method
     *
     * @param array $config The configuration for the Table.
     * @return void
     */
    public function initialize(array $config): void {
        parent::initialize($config);

        $this->setTable('example_notes');
        $this->setDisplayField('id');
        $this->setPrimaryKey('id');

        $this->belongsTo('Hosts', [
            'foreignKey' => 'host_id',
            'joinType'   => 'INNER',
            'className'  => 'ExampleModule.Hosts',
        ]);
    }

    //... more code ...

}

The system detected that there is an association between the new Table and the hosts table and created a belongsTo association.

To use the core Hosts-Table, remove the prefix ExampleModule.

$this->belongsTo('Hosts', [
    'foreignKey' => 'host_id',
    'joinType'   => 'INNER',
    'className'  => 'Hosts',
]);


Query Table through Table objects

Open the code of your TestController again to query the Table.

Tipp

Writing queries directly in the controller as shown below is not recommended!


You should always create an own method inside of your table object for each query. This helps to structure your code.

TestController.php
<?php
declare(strict_types=1);

namespace ExampleModule\Controller;


use Cake\ORM\TableRegistry;
use ExampleModule\Model\Table\ExampleNotesTable;

class TestController extends AppController {

    public function index() {
        if (!$this->isApiRequest()) {
            // The requested URL was: /example_module/test/index.html
            // The controller only sends the HTML template to the client browser / AngularJS

            /**********************************************************/
            /* DO NOT RUN ANY DATABASE QUERY HERE!                    */
            /* THIS CODE IS ONLY TO SHIP THE TEMPLATE                 */
            /**********************************************************/

            // Pass the variable "message" with the content "Hello World (HTML)" to the view for .html requests
            $this->set('message', 'Hello World (HTML)');
            return;
        }

        // This get executed for API requests
        //  The requested URL was: /example_module/test/index.json

        //Load ExampleNotesTable
        /** @var ExampleNotesTable $ExampleNotesTable */
        $ExampleNotesTable = TableRegistry::getTableLocator()->get('ExampleModule.ExampleNotes');

        //Query data
        $result = $ExampleNotesTable->find()
            ->order([
                'ExampleNotes.id' => 'asc'
            ])
            ->all();


        // Pass the variable "message" with the content "Hello World" to the JSON view
        // Pass the variable "result" to the JSON view
        $this->set('message', 'Hello World');
        $this->set('result', $result);

        // Add the variable "message" to .json output
        $this->viewBuilder()->setOption('serialize', ['message', 'result']);
    }

}


Load the URL https://example.org/example_module/test/index.json to view the result in your Browser.


Joining Tables

As long as you define your associations properly you can always join tables together by using the contain method.

To join the hosts table you simply need to contain the "Host" Table Object:

CodeResult
/** @var ExampleNotesTable $ExampleNotesTable */
$ExampleNotesTable = TableRegistry::getTableLocator()->get('ExampleModule.ExampleNotes');

//Query data
$result = $ExampleNotesTable->find()
    ->order([
        'ExampleNotes.id' => 'asc'
    ])
    ->contain([
        'Hosts' => function (Query $query) {
            $query
                ->disableAutoFields()
                ->select([
                    'Hosts.id',
                    'Hosts.name',
                    'Hosts.uuid',
                ]);
            return $query;
        }
    ])
    ->all();


Querying Host and Service status

openITCOCKPIT supports multiple database backends where status information could be stored. For this reason status related tables should always be loaded by the DbBackend object.

The DbBackend Object is available in every controller by default.

// Load Hoststatus table
$HoststatusTable = $this->DbBackend->getHoststatusTable();

// Select fields to load
$HoststatusFields = new HoststatusFields($this->DbBackend);
$HoststatusFields
    ->currentState()
    ->output();

//Query Hoststatus Table
$hoststatus = $HoststatusTable->byUuids(
    Hash::extract($result->toArray(), '{n}.host.uuid'),
    $HoststatusFields
);
CodeResult
<?php
declare(strict_types=1);

namespace ExampleModule\Controller;


use Cake\ORM\Query;
use Cake\ORM\TableRegistry;
use Cake\Utility\Hash;
use ExampleModule\Model\Table\ExampleNotesTable;
use itnovum\openITCOCKPIT\Core\HoststatusFields;

class TestController extends AppController {

    public function index() {
        if (!$this->isApiRequest()) {
            // The requested URL was: /example_module/test/index.html
            // The controller only sends the HTML template to the client browser / AngularJS

            /**********************************************************/
            /* DO NOT RUN ANY DATABASE QUERY HERE!                    */
            /* THIS CODE IS ONLY TO SHIP THE TEMPLATE                 */
            /**********************************************************/

            // Pass the variable "message" with the content "Hello World (HTML)" to the view for .html requests
            $this->set('message', 'Hello World (HTML)');
            return;
        }

        // This get executed for API requests
        //  The requested URL was: /example_module/test/index.json

        //Load ExampleNotesTable
        /** @var ExampleNotesTable $ExampleNotesTable */
        $ExampleNotesTable = TableRegistry::getTableLocator()->get('ExampleModule.ExampleNotes');

        // Load Hoststatus table
        $HoststatusTable = $this->DbBackend->getHoststatusTable();

        //Query data
        $result = $ExampleNotesTable->find()
            ->order([
                'ExampleNotes.id' => 'asc'
            ])
            ->contain([
                'Hosts' => function (Query $query) {
                    $query
                        ->disableAutoFields()
                        ->select([
                            'Hosts.id',
                            'Hosts.name',
                            'Hosts.uuid',
                        ]);
                    return $query;
                }
            ])
            ->all();

        // Select fields to load
        $HoststatusFields = new HoststatusFields($this->DbBackend);
        $HoststatusFields
            ->currentState()
            ->output();

        //Query Hoststatus Table
        $hoststatus = $HoststatusTable->byUuids(
            Hash::extract($result->toArray(), '{n}.host.uuid'),
            $HoststatusFields
        );

        // Pass the variable "message" with the content "Hello World" to the JSON view
        // Pass the variable "result" to the JSON view
        $this->set('message', 'Hello World');
        $this->set('result', $result);
        $this->set('hoststatus', $hoststatus);

        // Add the variable "message" to .json output
        $this->viewBuilder()->setOption('serialize', ['message', 'result', 'hoststatus']);
    }

}


Displaying the data

AngularJS Controller

First of all you need to implement a method to load the data into your TestIndexController.js.

TestIndexController.js
angular.module('openITCOCKPIT')
    .controller('TestIndexController', function($scope, $http){

        //Name TestIndexController same as in ng.states.js
        //Convention: Controller name + Action Name + 'Controller' = TestIndexController


        $scope.load = function(){

            // Query String parameters
            var params = {
                'angular': true
            };

            $http.get("/example_module/test/index.json", {
                params: params
            }).then(function(result){

                //Save notes from json result into local $scope.notes variable
                $scope.notes = result.data.result;

            }, function errorCallback(result){
                if(result.status === 403){
                    $state.go('403');
                }

                if(result.status === 404){
                    $state.go('404');
                }
            });
        };

        //Fire on page load
        $scope.load();

    });


Updating HTML View

In the second step you need to add some template logic into your view file templates/Test/index.php

templates/Test/index.php
<?php
/**
 * @var \App\View\AppView $this
 * @var string $message
 */
?>

<div class="row">
    <div class="col-xl-12">
        <div id="panel-1" class="panel">
            <div class="panel-hdr">
                <h2>
                    <?php echo __('Example Module'); ?>
                    <span class="fw-300"><i><?php echo __('Hello World'); ?></i></span>
                </h2>
            </div>
            <div class="panel-container show">
                <div class="panel-content">

                    <!-- Output "Hello World (HTML)" that was set by the controller -->
                    <?= h($message); ?>

                    <table class="table table-striped m-0 table-bordered table-hover table-sm">
                        <thead>
                        <tr>
                            <th><?= __('Host name') ?></th>
                            <th><?= __('Note') ?></th>
                        </tr>
                        </thead>

                        <tbody>
                        <!-- Repeat this TR for each record in $scope.notes -->
                        <tr ng-repeat="note in notes">
                            <td>
                                <!-- Print the content of the variable -->
                                {{ note.host.name }}
                            </td>
                            <td>{{ note.notes }}</td>
                        </tr>
                        </tbody>

                    </table>

                </div>
            </div>
        </div>
    </div>
</div>


Additional

Linking core tables with plugin tables

In some cases, it is required to link an openITCOCKPIT Core table object with a table object from a Module.

In the current example we also want to delete the additions notes in the module table whenever a host gets deleted. To implement this, we don’t need to touch any core code.

Notice

Only core tables that use the PluginManagerTableTrait could be extended by Modules.

class HostsTable extends Table {
    use PluginManagerTableTrait;
}

If you plan to extend a core table which does not already implemented the PluginManagerTableTrait please don't hesitate to send a pull request.

config/associations.php

Create the File /opt/openitc/frontend/plugins/ExampleModule/config/associations.php to define a list of Core Table which should been associated to your Plugin table.

plugins/ExampleModule/config/associations.php
<?php
return [
    'Hosts' => [ //Core Table
        'ExampleModule.ExampleNotes' //Plugin Tables
    ]
];


Define Table::bindCoreAssociations method

Now you need to create the bindCoreAssociations method in your plugin table class.

Inside of this method you can define your table associations that would normally go into to core HostsTable class.

plugins/ExampleModule/config/associations.php
<?php
declare(strict_types=1);

namespace ExampleModule\Model\Table;

use App\Model\Table\HostsTable;
use Cake\Datasource\RepositoryInterface;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;

class ExampleNotesTable extends Table {
    /**
     * Initialize method
     *
     * @param array $config The configuration for the Table.
     * @return void
     */
    public function initialize(array $config): void {
        parent::initialize($config);

        $this->setTable('example_notes');
        $this->setDisplayField('id');
        $this->setPrimaryKey('id');

        $this->belongsTo('Hosts', [
            'foreignKey' => 'host_id',
            'joinType'   => 'INNER',
            'className'  => 'Hosts',
        ]);
    }


    public function bindCoreAssociations(RepositoryInterface $coreTable) {

        // Link the Core HostsTable with the Plugin table without modifying core code.        

        switch ($coreTable->getAlias()) {
            case 'Hosts':
                $coreTable->hasOne('ExampleNote', [ //Singular => hasOne!
                    'className' => 'ExampleModule.ExampleNotes',
                    'dependent' => true
                ]);
                break;
        }
    }
}


Test Association

CodeResult
//Query core Hosts Table to test Plugin associations
/** @var HostsTable $HostsTable */
$HostsTable = TableRegistry::getTableLocator()->get('Hosts');
$hosts = $HostsTable->find()
    ->select([
        'Hosts.id',
        'Hosts.name',
        'Hosts.uuid'
    ])
    ->contain([
        'ExampleNote' => function(Query $query){
        $query->select([
            'id',
            'host_id',
            'notes'
        ]);
            return $query;
        }
        //'ExampleNote' //Singular => hasOne!
    ])
    ->all();


AclDependencies

By default every single controller action will create a new user permission. This is the reason why you are so flexible with openITCOCKPIT when it comes to user permissions.


However, some actions may depend on other actions or may break the application entirely if a user disabled a certain permission.

To avoid this, you can define ACL dependencies if required.


AclDependencies gets defined in the File /opt/openitc/frontend/plugins/ExampleModule/src/Lib/AclDependencies.php

src/Lib/AclDependencies.php
<?php
namespace ExampleModule\Lib;


use App\Lib\PluginAclDependencies;

class AclDependencies extends PluginAclDependencies {

    public function __construct() {
        parent::__construct();

        // Add actions that should always be allowed.
        $this
            //      Controller name, Action mame
            ->allow('Test', 'foobar');

        ///////////////////////////////
        //    Add dependencies       //
        //////////////////////////////

        $this
            //           Controller name, Action name, depends on: Controller name, Action name
            ->dependency('Test', 'foo', 'Test', 'bar');
    }
}


Actions that are marked as always allowed or set as dependency could not be selected by the user anymore.

They get selected by the system automatically.

Whenever you can your AclDependencies you need to run an update.

openitcockpit-update --no-system-files


Could be used to add custom menu records to core views.

The the moment only the following views supports this feature:

ControllerActions
Hosts

index

notMonitored

disabled

Services

index

notMonitored

disabled

If you need to add a custom link at an unsupported view, please create an issue.


AdditionalLinks gets defined in the File /opt/openitc/frontend/plugins/ExampleModule/src/Lib/AdditionalLinks.php

CodeResult
src/Lib/AdditionalLinks.php
<?php
namespace ExampleModule\Lib;

use App\Lib\PluginAdditionalLinks;

/**
 * Class AdditionalLinks
 * @package itnovum\openITCOCKPIT\ExampleModule\AdditionalLinks
 */
class AdditionalLinks extends PluginAdditionalLinks {

    /**
     * @var array
     */
    private $links = [];

    /**
     * PluginAdditionalLinks constructor.
     */
    public function __construct() {
        // Add a link to hosts index drop down
        $this
            ->link(
                'hosts',
                'index',
                'list',
                'TestIndex({id: host.Host.id})',
                'fas fa-code',
                __('Module Link'),
                'test', //controller for permission check
                'index' //action for permission check
            );
    }
}


AngularAssets

If you want to load custom CSS and JavaScript files (except AngularJS Controllers, Services and Directives) you need to define the files inside of the file: /opt/openitc/frontend/plugins/ExampleModule/src/Lib/AngularAssets.php


Please make sure to implement the AngularAssetsInterface.

src/Lib/AngularAssets.php
<?php

namespace itnovum\openITCOCKPIT\ExampleModule\AngularAssets;

use itnovum\openITCOCKPIT\Core\AngularJS\AngularAssetsInterface;
use itnovum\openITCOCKPIT\Core\AngularJS\PluginAngularAssets;

class AngularAssets extends PluginAngularAssets implements AngularAssetsInterface {

    /**
     * @var array
     */
    protected $jsFiles = [
        'path/in/webroot/lib.min.js'
    ];


    protected $cssFiles = [
        '/path/in/webroot/app.css'
    ];

    /**
     * @return array
     */
    public function getJsFiles() {
        return $this->_getJsFiles('example_module');
    }

    /**
     * @return array
     */
    public function getCssFiles() {
        return $this->_getCssFiles('example_module');
    }


    /**
     * @return array
     */
    public function getJsFilesOnDisk() {
        return $this->_getJsFilesOnDisk('ExampleModule');
    }

    /**
     * @return array
     */
    public function getCssFilesOnDisk() {
        return $this->_getCssFilesOnDisk('ExampleModule');
    }
}


Cronjobs

Cronjobs are CakePHP commands which implements the openITCOCKPIT CronjobInterface.

Please make sure to implement the CronjobInterface.

src/Command/ExampleCronjobCommand.php
<?php
declare(strict_types=1);

namespace ExampleModule\Command;

use App\Model\Table\ProxiesTable;
use App\Model\Table\SystemsettingsTable;
use Cake\Console\Arguments;
use Cake\Console\Command;
use Cake\Console\ConsoleIo;
use Cake\ORM\TableRegistry;
use itnovum\openITCOCKPIT\Core\Interfaces\CronjobInterface;


/**
 * Class ExampleCronjobCommand
 * @package ExampleModule\Command
 */
class ExampleCronjobCommand extends Command implements CronjobInterface {

    /**
     * @param Arguments $args
     * @param ConsoleIo $io
     */
    public function execute(Arguments $args, ConsoleIo $io) {
        $io->setStyle('green', ['text' => 'green']);
        $io->setStyle('red', ['text' => 'red']);

        $io->out('This is an example', 0);

        $io->out('<green>   Ok</green>');
        $this->processQueue();
        $io->hr();
    }
}


Widgets

Plugins could also contain widgets that a user can place onto the dashboard.

All Widgets gets defined in the file: /opt/openitc/frontend/plugins/ExampleModule/src/Lib/AngularAssets.php

src/Lib/AngularAssets.php
<?php
namespace ExampleModule\Lib;

use itnovum\openITCOCKPIT\Core\Dashboards\ModuleWidgetsInterface;

class Widgets implements ModuleWidgetsInterface {

    /**
     * @var array
     */
    private $ACL_PERMISSIONS = [];

    /**
     * Widgets constructor.
     * @param $ACL_PERMISSIONS
     */
    public function __construct($ACL_PERMISSIONS) {
        $this->ACL_PERMISSIONS = $ACL_PERMISSIONS;
    }

    /**
     * @return array
     */
    public function getAvailableWidgets() {
        $widgets = [];
        //Check for user permissions
        if (isset($this->ACL_PERMISSIONS['examplemodule']['test']['index'])) {
            $widgets[] = [
                'type_id'   => 900, //A unique identify
                'title'     => __('Example Overview'),
                'icon'      => 'fas fa-code',
                'directive' => 'examplemodule-widget',
                'width'     => 4,
                'height'    => 13
            ];
        }

        return $widgets;
    }

}
  • No labels