mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-24 22:34:24 +02:00
Merge branch 'master' into feature/security-gui-5647
This commit is contained in:
commit
2830b13082
4
.gitattributes
vendored
4
.gitattributes
vendored
@ -3,3 +3,7 @@
|
||||
|
||||
# Normalize puppet manifests' line endings to LF on checkin and prevent conversion to CRLF when the files are checked out
|
||||
.vagrant-puppet/* eol=lf
|
||||
|
||||
# Ignore packaging related files when generating and archive
|
||||
icingaweb2.spec export-ignore
|
||||
packages export-ignore
|
||||
|
@ -83,17 +83,17 @@ class ComponentForm extends Form
|
||||
((isset($formData['create_new_pane']) && $formData['create_new_pane'] != false) &&
|
||||
(false === isset($formData['use_existing_dashboard']) || $formData['use_existing_dashboard'] != true))
|
||||
) {
|
||||
$groupElements[] = $this->createElement(
|
||||
$this->addElement(
|
||||
'text',
|
||||
'pane',
|
||||
array(
|
||||
'required' => true,
|
||||
'label' => t("New Pane Title"),
|
||||
'label' => t("New Dashboard Title"),
|
||||
'description' =>
|
||||
t('Enter a title for the new pane.')
|
||||
)
|
||||
);
|
||||
$groupElements[] = $this->createElement( // Prevent the button from being displayed again on validation errors
|
||||
$this->addElement( // Prevent the button from being displayed again on validation errors
|
||||
'hidden',
|
||||
'create_new_pane',
|
||||
array(
|
||||
@ -106,21 +106,20 @@ class ComponentForm extends Form
|
||||
'use_existing_dashboard',
|
||||
array(
|
||||
'ignore' => true,
|
||||
'label' => t('Use An Existing Pane'),
|
||||
'description' =>
|
||||
t('Click on the button to add the dashlet to an existing pane on your dashboard.')
|
||||
'label' => t('Use An Existing Dashboard'),
|
||||
'class' => 'link-like'
|
||||
)
|
||||
);
|
||||
$buttonExistingPane->removeDecorator('Label');
|
||||
$groupElements[] = $buttonExistingPane;
|
||||
$this->addElement($buttonExistingPane);
|
||||
}
|
||||
} else {
|
||||
$groupElements[] = $this->createElement(
|
||||
$this->addElement(
|
||||
'select',
|
||||
'pane',
|
||||
array(
|
||||
'required' => true,
|
||||
'label' => t('Pane'),
|
||||
'label' => t('Dashboard'),
|
||||
'multiOptions' => $panes,
|
||||
'description' =>
|
||||
t('Select a pane you want to add the dashlet.')
|
||||
@ -131,34 +130,13 @@ class ComponentForm extends Form
|
||||
'create_new_pane',
|
||||
array(
|
||||
'ignore' => true,
|
||||
'label' => t('Create A New Pane'),
|
||||
'description' =>
|
||||
t('Click on the button if you want to add the dashlet to a new pane on the dashboard.')
|
||||
'label' => t('Create A New Dashboard'),
|
||||
'class' => 'link-like',
|
||||
)
|
||||
);
|
||||
$buttonNewPane->removeDecorator('Label');
|
||||
$groupElements[] = $buttonNewPane;
|
||||
$this->addElement($buttonNewPane);
|
||||
}
|
||||
$this->addDisplayGroup(
|
||||
$groupElements,
|
||||
'pane_group',
|
||||
array(
|
||||
'legend' => t('Pane'),
|
||||
'description' => t(
|
||||
'Decide if you want add the dashlet to an existing pane'
|
||||
. ' or create a new pane. Have a look on the button below.'
|
||||
),
|
||||
'decorators' => array(
|
||||
'FormElements',
|
||||
array('HtmlTag', array('tag' => 'div', 'class' => 'control-group')),
|
||||
array(
|
||||
'Description',
|
||||
array('tag' => 'span', 'class' => 'description', 'placement' => 'prepend')
|
||||
),
|
||||
'Fieldset'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -1,35 +0,0 @@
|
||||
# Application and Module Configuration
|
||||
|
||||
## Basic usage
|
||||
|
||||
The \Icinga\Application\Config class is a general purpose service to help you find, load and save
|
||||
configuration data. It is used both by the Icinga Web 2 modules and the framework itself. With
|
||||
INI files as source it enables you to store configuration in a familiar format. Icinga Web 2
|
||||
defines some configuration files for its own purposes. Please note that both modules and framework
|
||||
keep their main configuration in the INI file called config.ini. Here's some example code:
|
||||
|
||||
```php
|
||||
<?php
|
||||
use \Icinga\Application\Config as IcingaConfig;
|
||||
|
||||
// Retrieve the default timezone using 'Europe/Berlin' in case it is not set
|
||||
IcingaConfig::app()->global->get('defaultTimezone', 'Europe/Berlin');
|
||||
|
||||
// If you don't pass a configuration name to IcingaConfig::app it tries to load values from the
|
||||
// application's config.ini. For using other files you have to pass this parameter though.
|
||||
// The following example loads a section from the application's authentication.ini:
|
||||
IcingaConfig::app('authentication')->get('ldap-authentication');
|
||||
|
||||
// If you don't pass a configuration name to IcingaConfig::module it tries to load values from
|
||||
// the module's config.ini. For using other files you have to pass this parameter though.
|
||||
// The following example loads values from the example module's extra.ini:
|
||||
IcingaConfig::module('example', 'extra')->logging->get('enabled', true);
|
||||
```
|
||||
|
||||
## Reload from disk
|
||||
|
||||
If you want to force reading a configuration from disk (i.e. after you modified it), you can use the $fromDisk flag in
|
||||
the IcingaConfig::app/IcingaConfig::module call:
|
||||
|
||||
IcingaConfig::app('authentication', true)-> ... // read authentication from disk
|
||||
IcingaConfig::module('example', 'extra', true)->... // read module configuration from disk
|
184
doc/command.md
184
doc/command.md
@ -1,184 +0,0 @@
|
||||
# Commands
|
||||
|
||||
## Abstract
|
||||
|
||||
Commands are one important intersection between the monitoring core and the
|
||||
frontend. This is the writable interface where you can control the core how
|
||||
checks will be processed. Usually you can interact by buttons in the frontend.
|
||||
|
||||
This document describes the URL interface and what commands can be used.
|
||||
|
||||
## Configuration
|
||||
|
||||
**To be done.**
|
||||
|
||||
## URL Interface
|
||||
|
||||
The interface offers to options how to deal with commands:
|
||||
|
||||
1. Show html forms to enter information about the commands (GET requests)
|
||||
2. Send commands when providing post data
|
||||
|
||||
### Endpoint
|
||||
|
||||
Endpoint of commands is specified as follow:
|
||||
|
||||
```
|
||||
http://localhost:8080/icinga2-web/monitoring/command/<name_of_command>
|
||||
```
|
||||
|
||||
### List of commands
|
||||
|
||||
To see which commands are support you can supply the **list** argument:
|
||||
|
||||
```
|
||||
http://localhost:8080/icinga2-web/monitoring/command/list
|
||||
```
|
||||
|
||||
### Examples of command urls:
|
||||
|
||||
```
|
||||
# Schedule downtime for an object
|
||||
http://localhost:8080/icinga2-web/monitoring/command/scheduledowntime
|
||||
|
||||
# Provide a new commend for an object
|
||||
http://localhost:8080/icinga2-web/monitoring/command/addcomment
|
||||
```
|
||||
|
||||
# Provide a global command for host or service checks
|
||||
|
||||
http://localhost:8080/icinga2-web/monitoring/command/disableactivechecks?global=host
|
||||
http://localhost:8080/icinga2-web/monitoring/command/disableactivechecks?global=service
|
||||
|
||||
# Provide a object command globally
|
||||
|
||||
http://localhost:8080/icinga2-web/monitoring/command/disablenotifications?global=1
|
||||
|
||||
## List of commands
|
||||
|
||||
*Please note that the list is not complete yet, more commands will follow*
|
||||
|
||||
<p></p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Command</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>disableactivechecks</td>
|
||||
<td>Disable active checks for an object</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>enableactivechecks</td>
|
||||
<td>Enable active checks for an object</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>reschedulenextcheck</td>
|
||||
<td>Reschedule next active check</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>submitpassivecheckresult</td>
|
||||
<td>Submit a passive result set for this check</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>stopobsessing</td>
|
||||
<td>Stop obsessing over object</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>startobsessing</td>
|
||||
<td>Start obsessing over object</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>stopacceptingpassivechecks</td>
|
||||
<td>Stop accepting passive results for this object</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>startacceptingpassivechecks</td>
|
||||
<td>Start accepting passive results for this object</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>disablenotifications</td>
|
||||
<td>Disable sending messages for problems</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>enablenotifications</td>
|
||||
<td>Enable sending messages for problems</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>sendcustomnotification</td>
|
||||
<td>Send a custom notification for this object</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>scheduledowntime</td>
|
||||
<td>Schedule a downtime for this object</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>scheduledowntimeswithchildren</td>
|
||||
<td>Schedule a downtime for host and all services</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>removedowntimeswithchildren</td>
|
||||
<td>Remove all downtimes from this host and its services</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>disablenotificationswithchildren</td>
|
||||
<td>Disable all notification from this host and its services</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>enablenotificationswithchildren</td>
|
||||
<td>Enable all notification from this host and its services</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>reschedulenextcheckwithchildren</td>
|
||||
<td>Reschedule next check of host ans its services</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>disableactivecheckswithchildren</td>
|
||||
<td>Disable all checks of this host and its services</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>enableactivecheckswithchildren</td>
|
||||
<td>Disable all checks of this host and its services</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>disableeventhandler</td>
|
||||
<td>Disable event handler for this object</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>enableeventhandler</td>
|
||||
<td>Disable event handler for this object</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>disableflapdetection</td>
|
||||
<td>Disable flap detection</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>enableflapdetection</td>
|
||||
<td>Enable flap detection</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>addcomment</td>
|
||||
<td>Add a new comment to this object</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>resetattributes</td>
|
||||
<td>Reset all changed attributes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>acknowledgeproblem</td>
|
||||
<td>Acknowledge problem of this object</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>removeacknowledgement</td>
|
||||
<td>Remove problem acknowledgement</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>delaynotification</td>
|
||||
<td>Delay next object notification</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>removedowntime</td>
|
||||
<td>Remove a specific downtime</td>
|
||||
</tr>
|
||||
</table>
|
@ -1,17 +0,0 @@
|
||||
# The dashboard
|
||||
|
||||
The icingaweb dashboard allows you to display different views on one page. You can create customized overviews over
|
||||
the objects you're interested in and can add and remove elements.
|
||||
|
||||
## Dashboard, Panes and Components
|
||||
|
||||
![Dashboard structure][dashboards1]
|
||||
|
||||
* The building blocks of dashboards are components - those represent a single URL and display it's content (often in
|
||||
a more condensed layout)
|
||||
* Different components can be added to a pane and will be shown there. All panes are shown as tabs on top of the dashboard,
|
||||
whereas the title is used for the text in the tab
|
||||
* The dashboard itself is just the view containing the panes
|
||||
|
||||
|
||||
[dashboards1]: res/Dashboard.png
|
222
doc/form.md
222
doc/form.md
@ -1,222 +0,0 @@
|
||||
# Forms
|
||||
|
||||
## Abstract
|
||||
|
||||
This document describe how to develop forms in Icinga Web 2. This is important
|
||||
if you want to write modules or extend Icinga Web 2 with your flavour of code.
|
||||
|
||||
## Architecture
|
||||
|
||||
Forms are basically Zend_Form classes with Zend_Form_Element items as controls.
|
||||
To ensure common functionallity and control dependent fields Icinga Web 2
|
||||
provides sub classes to build forms on that.
|
||||
|
||||
### Key design
|
||||
|
||||
#### Build of forms
|
||||
|
||||
Creating elements is done within protected function *create()* of your subclass.
|
||||
In here you can add elements to your form, add validations and filters of your
|
||||
choice. The creation method is invoked lazy just before a form is rendered or
|
||||
*isValid()* is called.
|
||||
|
||||
In order to let icingaweb create a submit button for you (which is required for using the *isSubmittedAndValid*
|
||||
method) you have to call the *setSubmitLabel($label)* method, which will add a
|
||||
Zend_Form_Element_Submit element to your form.
|
||||
|
||||
#### Client side behaviour
|
||||
|
||||
A few methods in our Form implementation don't affect the rendering, but the behaviour of a form.
|
||||
|
||||
* **Automatic submission of fields via *\Icinga\Web\Form::enableAutoSubmit(array $forms)***
|
||||
All form element ids passed in the $forms array will cause a submission of the form when changed. This normally
|
||||
doesn't cause validation errors, as the form is not really seen as submitted by the 'isSubmittedAndValid', but allows
|
||||
you to update your form when necessary. For example, when you have a select box whose selection affects which form elements
|
||||
should be shown, you can use `$form->enableAutoSubmit(array('myForm'))`.
|
||||
* **User confirmation when discarding forms.** When a user wants to leave the current page despite having unsaved changes,
|
||||
a popup will appear and asks the user if he **really** wants to leave. This is implemented by the *app/form* componenent
|
||||
and enabled by default. If you don't want this, you can call *setIgnoreChangeDiscarding($bool)* on your form.
|
||||
|
||||
|
||||
#### Calling is *isSubmittedAndValid()*
|
||||
|
||||
*isSubmittedAndValid()* is used to check whether the form is ready to be processed or not.
|
||||
It ensures that the current request method is POST, that the form was manually submitted
|
||||
and that the data provided in the request is valid and gets repopulated in case its invalid. This only works when
|
||||
the sumbit button has been added with the *setSubmitLabel($label)* function, otherwise a form is always considered to be
|
||||
submitted when a POST request is received.
|
||||
|
||||
If the form has been updated, but not submitted (for example, because the a button has been pressed that adds or removes
|
||||
some fields in the form) the form is repopulated but not validated at this time. is SubmittedAndValid() returns false
|
||||
in this case, but no errors are added to the created form.
|
||||
|
||||
In order to be able to use isSubmittedAndValid, you have to define a submitbutton in the form.
|
||||
This is done with the *setSubmitLabel(string)* function, with the first parameter being the
|
||||
label set to the submit button.
|
||||
|
||||
#### Pre validation
|
||||
|
||||
To handle dependend fields you can just override *preValid()* or *postValid()*
|
||||
to dynamically add or remove validations. This behaviour reduces the overhead
|
||||
to write own validator classes.
|
||||
|
||||
* *preValidation()* Work just before pre validation
|
||||
|
||||
#### Autoloading of form code
|
||||
|
||||
Because of forms are no library code we need to put them into application code.
|
||||
The application or the module has an reserved namespace for forms which loads
|
||||
code from special directories:
|
||||
|
||||
<p></p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Class name</th>
|
||||
<th>File path</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\Icinga\Form\Test\MyForm</td>
|
||||
<td>application/forms/Test/MyForm.php</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\MyModule\Form\Test</td>
|
||||
<td>modules/forms/Test.php</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
If you want to create custom elements or organize library code in form context
|
||||
use an other namesoace for, e.g.
|
||||
|
||||
```
|
||||
\Icinga\Web\Form\Element\MySpecialElement
|
||||
\MyModule\Web\Form\Element\FancyDatePicker
|
||||
```
|
||||
|
||||
## Example implementation
|
||||
|
||||
|
||||
namespace MyModule\Form;
|
||||
|
||||
use Icinga\Web\Form;
|
||||
|
||||
class TestForm extends Form
|
||||
{
|
||||
/**
|
||||
* Add elements to this form (used by extending classes)
|
||||
*/
|
||||
protected function create()
|
||||
{
|
||||
$this->addElement(
|
||||
'checkbox',
|
||||
'flag',
|
||||
array(
|
||||
'label' => 'Check this box to user feature 1'
|
||||
)
|
||||
);
|
||||
|
||||
$this->addElement(
|
||||
'text',
|
||||
'flagValue',
|
||||
array(
|
||||
'label' => 'Enter text'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check dependent fields
|
||||
* @param array $data
|
||||
*/
|
||||
protected function preValidation(array $data)
|
||||
{
|
||||
if (isset($data['flag']) && $data['flag'] === '1') {
|
||||
$textField = $this->getElement('flagValue');
|
||||
$textField->setRequired(true);
|
||||
|
||||
$textField->addValidator(
|
||||
'alnum',
|
||||
true,
|
||||
array(
|
||||
'allowWhitespace' => true
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
The example above adds to elements to the form: A checkbox and a textfield.
|
||||
The function *preValid()* set the textfield required if checkbox was
|
||||
checked before.
|
||||
|
||||
### Full overriding example
|
||||
|
||||
The following example shows form with most usefull method utilization of
|
||||
interface methods:
|
||||
|
||||
namespace MyModule\Form;
|
||||
|
||||
use Icinga\Web\Form;
|
||||
|
||||
class TestForm extends Form
|
||||
{
|
||||
/**
|
||||
* When sub-classing replace the constructor
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
// Do some initializing work here if needed
|
||||
}
|
||||
|
||||
/**
|
||||
* Add elements to this form (used by extending classes)
|
||||
*/
|
||||
protected function create()
|
||||
{
|
||||
// Add elements to form
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre validation
|
||||
* @param array $data
|
||||
*/
|
||||
protected function preValidation(array $data)
|
||||
{
|
||||
// Add depending filters or validation here
|
||||
}
|
||||
}
|
||||
|
||||
## Testing forms
|
||||
|
||||
When testing forms it is a good idea to use Zend_Test_PHPUnit_ControllerTestCase
|
||||
instead of others like PHPUnit_Framework_TestCase as this enables you to use a
|
||||
request dummy which can be passed to your form.
|
||||
|
||||
### Example:
|
||||
|
||||
require_once 'Zend/Test/PHPUnit/ControllerTestCase.php';
|
||||
|
||||
class YourTestCase extends Zend_Test_PHPUnit_ControllerTestCase
|
||||
{
|
||||
function exampleTest()
|
||||
{
|
||||
$request = $this->getRequest();
|
||||
$request->setMethod('POST')->setPost(array(
|
||||
'key' => 'value'
|
||||
)
|
||||
);
|
||||
$form = new SomeForm();
|
||||
$form->setRequest($request);
|
||||
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
## Additional resources
|
||||
|
||||
* [API documentation](http://build.icinga.org/jenkins/view/icinga2-web/job/icinga2web-development/javadoc/?)
|
||||
* Live examples: application/forms or modules/monitoring/application/forms
|
||||
* [Zend API documentation](http://framework.zend.com/apidoc/1.10/_Form.html#Zend_Form)
|
||||
|
||||
|
||||
[form1]: res/Form.png
|
@ -1,138 +0,0 @@
|
||||
# Form Elements Shipped With Icinga Web 2
|
||||
|
||||
On top of the elements provided by the Zend Framework, Icinga Web 2 ships its own to offer additional functionality.
|
||||
The following is a list of these classes, as well as descriptions of the functionality they offer.
|
||||
|
||||
## Elements
|
||||
|
||||
### DateTimePicker
|
||||
|
||||
`Icinga\Web\Form\Element\DateTimePicker` represents a control that allows the user to select date/time and to
|
||||
display the date and time with a user specified format. Internally the element returns the input as Unix timestamp after
|
||||
it has been proven valid. That is when the input is valid to `\DateTime::createFromFormat()` according to the user
|
||||
specified format. Input is always timezone aware because the element utilizes `Icinga\Util\DateTimeFactory` which relies
|
||||
on the timezone set by the user.
|
||||
|
||||
**Example #1 DateTimePicker expecting date**
|
||||
|
||||
use Icinga\Web\Form\Element\DateTimePicker;
|
||||
|
||||
$element = new DateTimePicker(
|
||||
array(
|
||||
'name' => 'date',
|
||||
'label' => t('Date'),
|
||||
'patterns' => array('Y-m-d') // Allowed format
|
||||
)
|
||||
)
|
||||
|
||||
**Example #2 DateTimePicker expecting time**
|
||||
|
||||
use Icinga\Web\Form\Element\DateTimePicker;
|
||||
|
||||
$element = new DateTimePicker(
|
||||
array(
|
||||
'name' => 'time',
|
||||
'label' => t('Time'),
|
||||
'patterns' => array('H:i:s') // Allowed format
|
||||
)
|
||||
)
|
||||
|
||||
**Example #3 DateTimePicker expecting date and time**
|
||||
|
||||
use Icinga\Web\Form\Element\DateTimePicker;
|
||||
|
||||
$element = new DateTimePicker(
|
||||
array(
|
||||
'name' => 'datetime',
|
||||
'label' => t('Date And Time'),
|
||||
'patterns' => array('Y-m-d H:i:s') // Allowed format
|
||||
)
|
||||
)
|
||||
|
||||
**Example #4 DateTimePicker expecting date/time w/ default value**
|
||||
|
||||
use Icinga\Web\Form\Element\DateTimePicker;
|
||||
use Icinga\Util\DateTimeFactory;
|
||||
|
||||
$now = DateTimeFactory::create();
|
||||
|
||||
$element = new DateTimePicker(
|
||||
array(
|
||||
'name' => 'datetime',
|
||||
'label' => t('Date/Time'),
|
||||
'value' => $now->getTimestamp() + 3600, // now plus 1 hour
|
||||
'patterns' => array('Y-m-d H:i:s', 'Y-m-d', 'H:i:s') // Allowed format
|
||||
)
|
||||
)
|
||||
|
||||
## Validators
|
||||
|
||||
### WritablePathValidator
|
||||
|
||||
This *Icinga\Web\Form\Validator\WritablePathValidator* validator tests a given (string-)input for being a valid writable
|
||||
path. Normally it just tests for an existing, writable path but when setRequireExistence() is called, the path must
|
||||
exist on form submission and be writable.
|
||||
|
||||
**Example usage of writablePathValidator
|
||||
|
||||
use \Icinga\Web\Form\Validator\WritablePathValidator;
|
||||
$txtLogPath = new Zend_Form_Element_Text(
|
||||
array(
|
||||
'name' => 'logging_app_target',
|
||||
'label' => 'Application Log Path',
|
||||
'helptext' => 'The logfile to write the icingaweb debug logs to.'
|
||||
. 'The webserver must be able to write at this location',
|
||||
'required' => true,
|
||||
'value' => $logging->get('target', '/var/log/icingaweb.log')
|
||||
)
|
||||
);
|
||||
$txtLogPath->addValidator(new WritablePathValidator());
|
||||
|
||||
|
||||
### DateTimeValidator
|
||||
|
||||
The *Icinga\Web\Form\Validator\DateTimeValidator* validator allows you to validate an input against a set of datetime
|
||||
patterns. On successful validation, it either gives a valid pattern via getValidPattern, or null if the entered time
|
||||
is a timestamp. The above DateTimePicker utilizes this validator and should be used instead of directly using the validator.
|
||||
|
||||
|
||||
## Decorators
|
||||
|
||||
### ConditionalHidden Decorator
|
||||
|
||||
The `Icinga\Web\Form\Decorator\ConditionalHidden` allows you to hide a form element with the 'conditional' attribute for
|
||||
users that don't have JavaScript enabled (the form is rendered in a <noscript> tag when conditional is 1). Users with
|
||||
javascript won't see the elements, users with javascript will see it. This is useful in a lot of cases to allow icingaweb
|
||||
to be fully functional without JavaScript: Forms can show only sensible forms for most users (and, for example hide the
|
||||
debug log filepath input when debugging is disabled) and automatically reload the form as soon as the forms should be
|
||||
shown (e.g. when the debug checkbox is clicked), while users with text-browsers or javascript disabled see all forms,
|
||||
but can only fill out the ones relative or them.
|
||||
|
||||
**Example use of ConditionalHidden**
|
||||
|
||||
use Icinga\Web\Form\Decorator\ConditionalHidden;
|
||||
|
||||
$textLoggingDebugPath = new Zend_Form_Element_Text(array(
|
||||
'name' => 'logging_debug_target',
|
||||
'label' => 'Debug Log Path',
|
||||
'required' => $this->shouldDisplayDebugLog($debug),
|
||||
'condition' => $this->shouldDisplayDebugLog($debug), // 1 if displayed, otherwise 0
|
||||
'value' => getLogPath,
|
||||
'helptext' => 'Set the path to the debug log'
|
||||
))
|
||||
$textLoggingDebugPath->addDecorator(new ConditionalHidden());
|
||||
$form->addElement($textLoggingDebugPath);
|
||||
|
||||
### HelpText Decorator ###
|
||||
|
||||
The `Icinga\Web\Form\Decorator\HelpText` decorator allows you to use the 'helptext' property and renders this text in
|
||||
a consistent ways across the application. It is automatically added by our Form implementation, so you can just use
|
||||
the 'helptext' property in your form elements.
|
||||
|
||||
|
||||
### BootstrapForm Decorator
|
||||
|
||||
`Icinga\Web\Form\Decorator\BoostrapForm` is the decorator we use for our forms.
|
||||
It causes the forms to be rendered in a bootstrap friendly manner instead of the <dd> <dt> encapsulated way Zend normally
|
||||
renders the forms. You usually don't have to work with this decorator as our Form implementation automatically uses it,
|
||||
but it's always good to know why forms look how they look.
|
393
doc/graphs.md
393
doc/graphs.md
@ -1,393 +0,0 @@
|
||||
# Drawing Graphs
|
||||
|
||||
## Feature Set
|
||||
|
||||
Icinga Web comes with an SVG based graphing library that supports the basic graph types required for displaying monitoring
|
||||
data. These include:
|
||||
|
||||
* **Pie Charts**, which display a set of data in a typical pie diagram.
|
||||
* **Stacked Pie Charts**, which render one or multiple pies nested in another pie chart
|
||||
* **Line Charts**, which display a set of datapoints as a line graph
|
||||
* **Stacked Line Charts**, which display multiple line charts on top of each other, providing a cumulative view over
|
||||
a set of datapoints
|
||||
* **Bar Charts**, which display a set of datapoints as bars
|
||||
* **Stacked Bar Charts**, which, like the Stacked Line Chart, combines several charts and displays them on top of each other
|
||||
|
||||
## Creating Grid Charts (Line and Bar Charts)
|
||||
|
||||
### Base Api Synopsis
|
||||
|
||||
The `Icinga/Chart/GridChart` class provides the calls required for setting up Grid Charts. A GridChart draws three
|
||||
separate parts: Axis, Legend and the Gridarea.
|
||||
|
||||
To create a new Grid, simply create a `GridChart` object (the constructor takes no parameters):
|
||||
|
||||
**Example #1: Create a grid chart**
|
||||
|
||||
$this->chart = new GridChart();
|
||||
|
||||
Now you can go on and customize the chart to fit your needs (this will be explained in depth in the next sections).
|
||||
|
||||
**Example #2: Customize the grid chart**
|
||||
|
||||
$this->chart
|
||||
->setAxisMin(null, 0) // Set the Y-axis to always start at 0
|
||||
->setAxisMax(null, 100) // Set the Y-Axis to end at 100
|
||||
->setAxisLabel("X axis label", "Y axis label"); // Set labels for X-axis and Y-axis
|
||||
|
||||
And finally you can draw data:
|
||||
|
||||
**Example #3: Drawing graphs**
|
||||
|
||||
$this->chart->drawLines(
|
||||
array(
|
||||
'label' => 'A Graph Line',
|
||||
'color' => 'red',
|
||||
'width' => '5',
|
||||
'data' => array(array(0, 10), array(2, 40), array(3, 55), array(7, 92))
|
||||
)
|
||||
);
|
||||
|
||||
This example would produce a graph like this if rendered:
|
||||
|
||||
![Simple Line Graph][graph1]
|
||||
|
||||
|
||||
|
||||
### Graph Setup Methods
|
||||
|
||||
When creating the above graph without any setup options (like `setAxisMin`), it would use default values when being rendered.
|
||||
This means:
|
||||
|
||||
* No label for X-Axis and Y-Axis
|
||||
* The X/Y axis minimal value is the lowest X/Y value from the dataset
|
||||
* The X/Y axis maximum value is the highest X/Y value from the dataset
|
||||
|
||||
Let's create a minimal example for this:
|
||||
|
||||
**Example #4: The most simple line graph**
|
||||
|
||||
$this->chart = new GridChart();
|
||||
$this->chart->drawLines(
|
||||
array(
|
||||
'data' => array(array(0, 10), array(2, 40), array(3, 55), array(7, 92))
|
||||
)
|
||||
);
|
||||
|
||||
![The Most Simple Line Graph][graph2]
|
||||
|
||||
|
||||
#### Adding Axis Labels
|
||||
|
||||
A graph without axis labels is rather useless. With the `GridChart::setAxisLabel($xAxisLabel, $yAxisLabel)` method you
|
||||
can define the axis labels for both the X and Y axis:
|
||||
|
||||
**Example #5: Adding axis labels**
|
||||
|
||||
$this->chart = new GridChart();
|
||||
$this->chart->setAxisLabel("X axis label", "Y axis label");
|
||||
$this->chart->drawLines(
|
||||
array(
|
||||
'data' => array(array(0, 10), array(2, 40), array(3, 55), array(7, 92))
|
||||
)
|
||||
);
|
||||
|
||||
![Line Graph With Label][graph3]
|
||||
|
||||
#### Defining Axis Types
|
||||
|
||||
Normally, axis display their values as numeric, linear types. You can overwrite the axis for the X or Y direction with
|
||||
one that suits your needs more specifically. Supported axis are:
|
||||
|
||||
* Linear Axis: This is the default axis that displays numeric values with an equal distance between each tick
|
||||
|
||||
**Example #6: Defining A Linear Axis With A Custom Number Of Ticks**
|
||||
|
||||
$this->chart = new GridChart();
|
||||
$this->chart->setAxisLabel("X axis label", "Y axis label");
|
||||
$this->chart->setXAxis(Axis::linearUnit(40));
|
||||
$this->chart->setYAxis(Axis::linearUnit(10));
|
||||
$this->chart->drawLines(
|
||||
array(
|
||||
'data' => array(array(0, 10), array(2, 40), array(3, 55), array(7, 92))
|
||||
)
|
||||
);
|
||||
|
||||
![Line Graph With Custom Tick Count][graph4]
|
||||
|
||||
|
||||
* Calendar Axis: The calendar axis is a special axis for using timestamps in the axis. It will display the ticks as
|
||||
sensible time values
|
||||
|
||||
**Example #7: Defining A Calendar Axis**
|
||||
|
||||
$this->chart = new GridChart();
|
||||
$this->chart->setAxisLabel("X axis label", "Y axis label");
|
||||
$this->chart->setXAxis(Axis::calendarUnit());
|
||||
$this->chart->drawLines(
|
||||
array(
|
||||
'data' => array(
|
||||
array(time()-7200, 10),array(time()-3620, 30), array(time()-1800, 15), array(time(), 92))
|
||||
)
|
||||
);
|
||||
|
||||
![Line Graph With Custom Tick Count][graph5]
|
||||
|
||||
## Line Charts
|
||||
|
||||
We've already seen an example of line charts in the last section, but this was rather minimal. The call for creating
|
||||
Line Charts in the Chart Api is `GridChart::drawLines(array $lineDefinition1, array $lineDefinition2, ...)`, while '...'
|
||||
means 'as many definitions as you want'.
|
||||
|
||||
$lineDefinition is an configuration array that describes how your data will be displayed. Possible configuration options
|
||||
are:
|
||||
|
||||
* **label** The text that will be displayed in the legend of the graph for this line. If none is given simply
|
||||
'Dataset %nr%' will be displayed, with %nr% meaning a number starting at 1 and incrementing for every
|
||||
line without a label
|
||||
* **stack** If provided, this graph will be shown on top of each other graph in the same stack and causes all
|
||||
graphs in the same stack to be rendered cumulative
|
||||
* **discrete** Set to display the line in a discrete manner, i.e. using hard steps between values instead of drawing
|
||||
a interpolated line between points
|
||||
* **color** The color to use for the line or fill, either in Hex form or as a string supported in the SVG style tag
|
||||
* **palette** (Ignored if 'color' is set) The color palette to use for determining the line or fill color
|
||||
* **fill** True to fill the graph instead of drawing a line. Take care of the graph ordering when using this
|
||||
option, as previously drawn graphs will be hidden if they overlap this graph.
|
||||
* **showPoints** Set true to emphasize datapoints with additional dots
|
||||
* **width** Set the thickness of the line stroke in px (default: 5)
|
||||
* **data** The dataset as an two dimensional array in the form `array(array($x1, $y2), array($x2, $y2), ...)
|
||||
|
||||
**Example #8: Various Line Graph Options**
|
||||
|
||||
|
||||
$this->chart->drawLines(
|
||||
array(
|
||||
'label' => 'Hosts critical',
|
||||
'palette' => Palette::PROBLEM,
|
||||
'stack' => 'stack1',
|
||||
'fill' => true,
|
||||
'data' => $data2
|
||||
),
|
||||
array(
|
||||
'label' => 'Hosts warning',
|
||||
'stack' => 'stack1',
|
||||
'palette' => Palette::WARNING,
|
||||
'fill' => true,
|
||||
'showPoints' => true,
|
||||
'data' => $data
|
||||
),
|
||||
array(
|
||||
'label' => 'Hosts ok',
|
||||
'discrete' => true,
|
||||
'color' => '#00ff00',
|
||||
'fill' => false,
|
||||
'showPoints' => true,
|
||||
'width' => '10',
|
||||
'data' => $data3
|
||||
)
|
||||
);
|
||||
|
||||
You can see the effects here, notice how the first two lines are stacked:
|
||||
|
||||
![Various Line Graph Options][graph6]
|
||||
|
||||
|
||||
## Bar Charts
|
||||
|
||||
Bar Charts almost offer the same functionality as Line Charts, but some configuration options from Line Charts don't make sense
|
||||
and are therefore omitted.
|
||||
The call for creating Line Charts in the Chart Api is `GridChart::drawBars(array $lineDefinition1, array $lineDefinition2, ...)`,
|
||||
while '...' means 'as many definitions as you want'. Possible configuration options are:
|
||||
|
||||
* **label** The text that will be displayed in the legend of the graph for this line. If none is given simply
|
||||
'Dataset %nr%' will be displayed, with %nr% meaning a number starting at 1 and incrementing for every
|
||||
line without a label
|
||||
* **stack** If provided, this graph will be shown on top of each other graph in the same stack and causes all
|
||||
graphs in the same stack to be rendered cumulative
|
||||
* **color** The color to use for filling the bar, either in Hex form or as a string supported in the SVG style tag
|
||||
* **palette** (Ignored if 'color' is set) The color palette to use for determining the fill color
|
||||
* **width** Set the thickness of the line stroke in px (default: 1)
|
||||
* **data** The dataset as an two dimensional array in the form `array(array($x1, $y2), array($x2, $y2), ...)
|
||||
|
||||
The same graph as rendered above would look as follows when using `drawBars` instead of `drawLines`. If you don't want
|
||||
the labels to show you can use the 'disableLegend()' call on the GridChart object.
|
||||
|
||||
**Example #9: Various Bar Chart Options**
|
||||
|
||||
$this->chart->drawBars(
|
||||
array(
|
||||
'label' => 'Hosts critical',
|
||||
'palette' => Palette::PROBLEM,
|
||||
'stack' => 'stack1',
|
||||
'data' => $data2
|
||||
),
|
||||
array(
|
||||
'label' => 'Hosts warning',
|
||||
'stack' => 'stack1',
|
||||
'palette' => Palette::WARNING,
|
||||
'data' => $data
|
||||
),
|
||||
array(
|
||||
'label' => 'Hosts ok',
|
||||
'color' => '#00ff00',
|
||||
'width' => '10',
|
||||
'data' => $data3
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
![Various Line Graph Options][graph7]
|
||||
|
||||
|
||||
### Tooltips
|
||||
|
||||
It is possible to specify custom tooltip format strings when creating bar charts.
|
||||
Tooltips provide information about the points of each bar chart column, by aggregating
|
||||
the values of all data sets with the same x-coordinate.
|
||||
|
||||
When no custom format string is given, a sane default format string is used, but its usually
|
||||
clearer for the user to describe the data of each chart more accurately with a custom one.
|
||||
|
||||
|
||||
**Example #9.1: Bar Charts with custom tooltips**
|
||||
|
||||
$this->chart->drawBars(
|
||||
array(
|
||||
'label' => 'Hosts critical',
|
||||
'palette' => Palette::PROBLEM,
|
||||
'stack' => 'stack1',
|
||||
'data' => $data2,
|
||||
'tooltip' => '{title}<br/> {value} of {sum} hosts are ok.'
|
||||
),
|
||||
array(
|
||||
'label' => 'Hosts warning',
|
||||
'stack' => 'stack1',
|
||||
'palette' => Palette::WARNING,
|
||||
'data' => $data,
|
||||
'tooltip' => '{title}<br/> Oh no, {value} of {sum} hosts are down!'
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
As you can see, you can specify a format string for each data set, which allows you to
|
||||
pass a custom message for all "down" hosts, one custom message for all "Ok" hosts and so on.
|
||||
In contrast to that, the aggregation of values works on a column basis and will give you the
|
||||
sum of all y-values with the same x-coordinate and not the aggregation of all values of the data set.
|
||||
|
||||
#### Rich Tooltips
|
||||
|
||||
It is also possible to use HTML in the tooltip strings to create rich tooltip markups, which can
|
||||
be useful to provide extended output that spans over multiple lines. Please keep in mind that
|
||||
users without JavaScript will see the tooltip with all of its html-tags stripped.
|
||||
|
||||
![Various Line Graph Options][graph7.1]
|
||||
|
||||
#### Available replacements
|
||||
|
||||
The available replacements depend on the used chart type, since the tooltip data is
|
||||
instantiated and populated by the chart. All bar graphs have the following replacements available:
|
||||
|
||||
Aggregated values, are calculated from the data points of each column:
|
||||
|
||||
- sum: The amount of all Y-values of the current column
|
||||
- max: The biggest occurring Y-value of the current column
|
||||
- min: The smallest occurring Y-value of the current column
|
||||
|
||||
|
||||
Column values are also defined by the current column, but are not
|
||||
the product of any aggregation
|
||||
|
||||
- title: The x-value of the current column
|
||||
|
||||
|
||||
Row values are defined by the properties the current data set, and are only useful for rendering the
|
||||
generic tooltip correctly, since you could also just directly write
|
||||
those values into your custom tooltip.
|
||||
|
||||
- label: The name of the current data set
|
||||
- color: The color of this data set
|
||||
|
||||
|
||||
|
||||
## Pie Charts
|
||||
|
||||
### The PieChart Object
|
||||
|
||||
Additionally to Line and Bar Charts, the Graphing Api also supports drawing Pie charts. In order to work with Pie charts
|
||||
you have to create an `Icinga\Chart\PieChart` object first:
|
||||
|
||||
**Example #10: Creating a PieChart Object**
|
||||
|
||||
$pie = new PieChart();
|
||||
|
||||
### Drawing Pies
|
||||
|
||||
Pies are now drawn using the `PieChart::drawPies(array $pieDefinition1, array $pieDefinition2, ...)` method:
|
||||
|
||||
**Example #11: Example PieChart Definition**
|
||||
|
||||
$pie->drawPie(array(
|
||||
'data' => array(5,80,1),
|
||||
'palette' => array(Palette::PROBLEM, Palette::OK, Palette::WARNING),
|
||||
'labels' => array(
|
||||
'Hosts down', 'Hosts up', 'Hosts unknown'
|
||||
)
|
||||
));
|
||||
|
||||
This would produce a Pie Chart similar to this:
|
||||
|
||||
![Example Pie Chart][graph8]
|
||||
|
||||
Notice how every datapoint has it's own label and palette definition. Possible attributes for $pieDefinition are:
|
||||
|
||||
* **labels**: An array containing a label for every definition in the 'data' array
|
||||
* **colors**: An array of colors to use for every definition in the 'data' array
|
||||
* **palette**: (ignored when using 'colors') An array containing the palette to user for every definition in the 'data'
|
||||
array
|
||||
* **data** An array containing of numeric values that define the relative sizes of the pie slices to the whole pie
|
||||
|
||||
If you don't want the labels to show you can use the 'disableLegend()' call on the PieChart object.
|
||||
|
||||
### Stacked Pies
|
||||
|
||||
When adding multiple pies, they will be per default shown as a stacked pie:
|
||||
|
||||
**Example #12: Stacked Pie Charts**
|
||||
|
||||
$pie = new PieChart();
|
||||
$pie->drawPie(array(
|
||||
'data' => array(5,80,1),
|
||||
'palette' => array(Palette::PROBLEM, Palette::OK, Palette::WARNING),
|
||||
'labels' => array(
|
||||
'Hosts down', 'Hosts up', 'Hosts unknown'
|
||||
)
|
||||
), array(
|
||||
'data' => array(40,60,90,2),
|
||||
'palette' => array(Palette::PROBLEM, Palette::WARNING, Palette::OK, Palette::NEUTRAL),
|
||||
'labels' => array('Services down', 'Services Warning', 'Services OK', 'Services pending')
|
||||
));
|
||||
|
||||
![Example Pie Chart][graph9]
|
||||
|
||||
## Rendering in templates:
|
||||
|
||||
Rendering is straightforward, assuming $svg is the PieChart/GridChart object, you just call render() to create an SVG:
|
||||
|
||||
myTemplate.phtml
|
||||
|
||||
<div style="border:1px dashed black;width:800px;height:400px">
|
||||
<?=
|
||||
$svg->render();
|
||||
?>
|
||||
</div>
|
||||
|
||||
[graph1]: res/GraphExample#1.png
|
||||
[graph2]: res/GraphExample#2.png
|
||||
[graph3]: res/GraphExample#3.png
|
||||
[graph4]: res/GraphExample#4.png
|
||||
[graph5]: res/GraphExample#5.png
|
||||
[graph6]: res/GraphExample#6.png
|
||||
[graph7]: res/GraphExample#7.png
|
||||
[graph7.1]: res/GraphExample#7.1.png
|
||||
[graph8]: res/GraphExample#8.png
|
||||
[graph9]: res/GraphExample#9.png
|
@ -38,7 +38,7 @@ git clone git://git.icinga.org/icingaweb2.git
|
||||
Choose a target directory and move Icinga Web 2 there.
|
||||
|
||||
````
|
||||
mv icingaweb2 /usr/share/icingaweb2
|
||||
mv icingaweb2 /usr/share/icingaweb
|
||||
````
|
||||
|
||||
**Step 3: Configuring the Web Server**
|
||||
@ -48,13 +48,13 @@ Use `icingacli` to generate web server configuration for either Apache or nginx.
|
||||
*Apache*
|
||||
|
||||
````
|
||||
./bin/icingacli setup config webserver apache
|
||||
./bin/icingacli setup config webserver apache --document-root /usr/share/icingaweb/public
|
||||
````
|
||||
|
||||
*nginx*
|
||||
|
||||
````
|
||||
./bin/icingacli setup config webserver nginx
|
||||
./bin/icingacli setup config webserver nginx --document-root /usr/share/icingaweb/public
|
||||
````
|
||||
|
||||
**Step 4: Web Setup**
|
||||
|
@ -1,57 +0,0 @@
|
||||
# Module Development: Configuration and Preferences Dialogs
|
||||
|
||||
When developing modules, you might want your module's configuration and preferences dialogs to appear in the Icinga Web
|
||||
Configuration/Preferences interface. This is rather easy to accomplish and should be the preferred way to allow user's
|
||||
to customize your module.
|
||||
|
||||
## Terminology
|
||||
|
||||
When talking about 'Configuration' and 'Preference', we have a clear distinction between those words:
|
||||
|
||||
- **Configurations** are application/module wide settings that affect every user when being changed. This could be
|
||||
the data backend of your module or other 'global' settings that affect everyone when being changed
|
||||
- **Preferences** are settings a user can set for *his* account only, like the page size of pagination, entry points, etc.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
The two base classes for preferences and configurations are \Icinga\Web\Controller\BasePreferenceController for preferences and
|
||||
\Icinga\Web\Controller\BaseConfigController for configurations.
|
||||
|
||||
If you want to create a preference or configuration panel you have to create a ConfigController and/or PreferenceController
|
||||
in your Module's a controller directory and make it a subclass of BaseConfigController or BasePreferenceController.
|
||||
|
||||
Those controllers can be used like normal controllers, with two exceptions:
|
||||
|
||||
- If you want your module to appear as a tab in the applications configuration/preference interface you have to implement
|
||||
the static createProvidedTabs function that returns an array of tabs to be displayed
|
||||
- The init() method of the base class must be called in order to make sure tabs are collected and the view's tabs variable
|
||||
is populated
|
||||
|
||||
## Example
|
||||
|
||||
We'll just provide an example for ConfigControllers here, as PreferenceController are the same with a different name
|
||||
|
||||
use \Icinga\Web\Controller\BaseConfigController;
|
||||
use \Icinga\Web\Widget\Tab;
|
||||
use \Icinga\Web\Url;
|
||||
|
||||
class My_ConfigController extends BaseConfigController {
|
||||
|
||||
static public function createProvidedTabs()
|
||||
{
|
||||
return array(
|
||||
"myModuleTab" => new Tab(array(
|
||||
"name" => "myModuleTab", // the internal name of the tab
|
||||
"iconCls" => "myicon", // the icon to be displayed
|
||||
"title" => "The tab title", // The title of the configuration's tab
|
||||
"url" => Url::fromPath("/myModule/config") // The Url that will ne called (can also be just a path)
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
public function indexAction()
|
||||
{
|
||||
// create the form here
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
# Writing PHPUnit tests
|
||||
|
||||
## Test path and filename
|
||||
|
||||
The path where you should put your PHPUnit tests should reflect the path in the sourcetree, with test/php/ prepended. So
|
||||
if you're testing a file library/Icinga/My/File.php the test file should be at test/php/library/Icinga/My/File.php. This
|
||||
also applies for modules, where the tests are underneath modules/myModule/test/php
|
||||
|
||||
## Example test skeleton
|
||||
|
||||
Let's assume you're testing a class MyClass underneath the MyModule module and the file can be found at
|
||||
modules/mymodule/library/MyModule/Helper/MyClass.php.
|
||||
|
||||
<?php
|
||||
// The namespace is the same namespace as the file to test has, but with 'Tests' prepended
|
||||
namespace Tests\Module\MyModule\Helper;
|
||||
|
||||
class MyClassTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testSomething()
|
||||
{
|
||||
$this->assertTrue(true, "Asserting that the world didn't end yet");
|
||||
}
|
||||
}
|
||||
|
||||
## Testing Singletons
|
||||
|
||||
When test methods **modify static** class properties (which is the case when using singletons), do not add the PHPUnit
|
||||
[`@backupStaticAttributes enabled`](http://phpunit.de/manual/3.7/en/appendixes.annotations.html#appendixes.annotations.backupStaticAttributes)
|
||||
annotation to their [DocBlock](http://www.phpdoc.org/docs/latest/for-users/phpdoc/basic-syntax.html#what-is-a-docblock)
|
||||
in order to backup and restore static attributes before and after the test execution respectively. Use the setUp()
|
||||
and tearDown() routines instead to accomplish this task.
|
||||
|
||||
<?php
|
||||
|
||||
namespace My\Test;
|
||||
|
||||
use Icinga\Test\BaseTestCase;
|
||||
use My\CheesecakeFactory;
|
||||
|
||||
class SingletonTest extends BaseTestCase
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
$this->openingHours = CheesecakeFactory::getOpeningHours();
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
parent::tearDown();
|
||||
CheesecakeFactory::setOpeningHours($this->openingHours);
|
||||
}
|
||||
|
||||
public function testThatInteractsWithStaticAttributes()
|
||||
{
|
||||
CheesecakeFactory::setOpeningHours(24);
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
The reason to avoid using @backupStaticAttributes is the fact that if it is necessary to utilize a
|
||||
singleton in your *unit* tests you probably want to rethink what you are going to test and because
|
||||
some tests are using the mock framework [`Mockery`](https://github.com/padraic/mockery) which is
|
||||
using static class properties to implement its caching mechanics.
|
@ -1,88 +0,0 @@
|
||||
# Test organization
|
||||
|
||||
## Testfolders
|
||||
|
||||
Tests for the application can be found underneath the test folder:
|
||||
|
||||
test/
|
||||
php/ PHPUnit tests for backend code
|
||||
regression/ PHPUnit regression tests
|
||||
|
||||
The same structure applies for modules, which also contain a toplevel test folder and suitable subtests. When you fix
|
||||
a bug and write a regression test for it, put it in 'regression' and name it %DESCRIPTION%%TicketNumber% (myBug1234.php)
|
||||
|
||||
## Running tests
|
||||
|
||||
The tests can be run in the specific folder using the runtests script.
|
||||
|
||||
Running PHP tests example:
|
||||
|
||||
cd test/php
|
||||
./runtests
|
||||
|
||||
In this case, all application and all module tests will be executed. The testrunners also support additional flags, which
|
||||
affect they way the test is executed:
|
||||
|
||||
Options:
|
||||
-h, --help show this help message and exit
|
||||
-b, --build Enable reporting.
|
||||
-v, --verbose Be more verbose.
|
||||
-i PATTERN, --include=PATTERN
|
||||
Include only specific files/test cases.
|
||||
-V, --vagrant Run in vagrant VM
|
||||
|
||||
Some tests also support the --exclude method, it's best to use the --help option to see which flags are supported.
|
||||
|
||||
|
||||
## Setting up databases
|
||||
|
||||
Despite running most of the tests should work out of the box, a few specific cases require some setup.
|
||||
At this moment, only database tests require additional setup and expect an icinga_unittest user with an icinga_unittest
|
||||
database to exist and have rights in your database.
|
||||
|
||||
### The database test procedure
|
||||
|
||||
When testing PostgreSQL and MySQL databases, the test library (normally) executes the following test procedure for every
|
||||
test case:
|
||||
|
||||
- Log in to the rdbms as the user icinga_unittest with password icinga_unittest
|
||||
- Use the icinga_unittest database (which must be existing)
|
||||
- **Drop all tables** in the icinga_unittest database
|
||||
- Create a new, clean database schema
|
||||
|
||||
If anything goes wrong during this procedure, the test will be skipped (because maybe you don't have a pgsql database, but
|
||||
want to test mysql, for example).
|
||||
|
||||
### Setting up a test user and database in MySQL
|
||||
|
||||
In MySQL, it's best to create a user icinga_unittest@localhost, a database icinga_unittest and grant all privileges on
|
||||
this database:
|
||||
|
||||
mysql -u root -p
|
||||
mysql> CREATE USER `icinga_unittest`@`localhost` IDENTIFIED BY 'icinga_unittest';
|
||||
mysql> CREATE DATABASE `icinga_unittest`;
|
||||
mysql> GRANT ALL PRIVILEGES ON `icinga_unittest`.* TO `icinga_unittest`@`localhost`;
|
||||
mysql> FLUSH PRIVILEGES;
|
||||
mysql> quit
|
||||
|
||||
### Setting up a test user and database in PostgreSQL
|
||||
|
||||
In PostgreSQL, you have to modify the pg_hba database if you don't have password authentication set up (which often is
|
||||
the case). In this setup the icinga_unittest user is set to trust authentication on localhost, which means that no
|
||||
password is queried when connecting from the local machine:
|
||||
|
||||
sudo su postgres
|
||||
psql
|
||||
postgres=# CREATE USER icinga_unittest WITH PASSWORD 'icinga_unittest';
|
||||
postgres=# CREATE DATABASE icinga_unittest;
|
||||
postgres=# \q
|
||||
bash$ createlang plpgsql icinga;
|
||||
|
||||
|
||||
Add the following lines to your pg_hba.conf (etc/postgresql/X.x/main/pg_hba.conf under debian, /var/lib/pgsql/data/pg_hba.conf for Redhat/Fedora)
|
||||
to enable trust authentication for the icingaweb user when connecting from the localhost.
|
||||
|
||||
local icinga_unittest icinga_unittest trust
|
||||
host icinga_unittest icinga_unittest 127.0.0.1/32 trust
|
||||
host icinga_unittest icinga_unittest ::1/128 trust
|
||||
|
@ -1,359 +0,0 @@
|
||||
# General testing guidelines
|
||||
|
||||
## The short summary
|
||||
|
||||
This list summarizes what will be described in the next few chapters:
|
||||
|
||||
- You really should write tests for your code
|
||||
- Think about your what you want to test and how you can assert the behaviour correctly.
|
||||
- Isolate your tests and start without any assumptions about your test environment (like a specific user existing). The
|
||||
only excuse here is to assume a correct login if you need a database for testing (but not the tables/content of the database!)
|
||||
- Don't just test correct behaviour - test border cases and invalid input or assumptions to make sure the application handles
|
||||
cases that might not occur in a normal test environment
|
||||
- Use description strings in your assertions, this makes it easy to detect what's going wrong when they fail and it's easier
|
||||
to follow what *exactly* you are asserting
|
||||
- Test methods should be one scenario and the method name should describe this scenario.
|
||||
*testLogin* is bad for example (do you test if the login fails? Or if it is correct?
|
||||
*testLoginWithCorrectCredentials*, *testLoginWithWrongPassword* is a far better name here
|
||||
- Your assertions should reflect one test scenario, i.e. don't write one test method that tests if something works **and**
|
||||
if it correctly detects errors after it works. Write one test to determine the behaviour with correct input and one that
|
||||
tests the behaviour with invalid input.
|
||||
- Mock external components and inject them into the class you want to test. If your testsubject is not able to use mocked
|
||||
dependencies, it's often a design flaw and should be considered as a bug (and be fixed)
|
||||
|
||||
|
||||
## What should be tested
|
||||
|
||||
### Writing meaningful tests
|
||||
|
||||
Writing tests doesn't ensure that your code is free of errors, but it can test if, in a specific scenario, the behaviour of
|
||||
your code is as expected. This means that you have to think about your test scenario before writing a test. This involves
|
||||
three steps:
|
||||
|
||||
- Determine what you want to test: Which errors can occur, what is the normal input and what are the border cases.
|
||||
- Define a test scenario: What datasets make sense for the test?
|
||||
- How do I write my assertions so they are really meaningful about the correctness of the testcase
|
||||
|
||||
Especially the border cases are important, often they lay inside of try/catch blocks or error detection routines. When
|
||||
looking at your code coverages, these blocks should be covered.
|
||||
|
||||
### Example
|
||||
|
||||
Let's say you have the following function (the examples in this section are to be considered as php-like pseudocode) :
|
||||
|
||||
function isValidName($name)
|
||||
{
|
||||
if (hasCorrectFormat($name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nameExistsInDatabase($name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#### Determine what to test:
|
||||
|
||||
At first glance there can be 3 scenarios:
|
||||
|
||||
1. The username is unique and valid
|
||||
2. The username has the wrong format
|
||||
3. The username has the correct format, but exists in the database
|
||||
|
||||
But - what happens if the database is down? Should an exception be thrown or should it return false? This case has to be added
|
||||
to your tests, so you have (at least!) the following scenarios:
|
||||
|
||||
1. The username is unique and valid
|
||||
2. The username has the wrong format
|
||||
3. The username has the correct format, but exists in the database
|
||||
4. The username has the correct format, but access to the database fails.
|
||||
|
||||
|
||||
#### Determine meaningful testscenarios
|
||||
|
||||
When it comes to creating your testscenario, we start with the easiest one: Test the wrongly formatted username. This
|
||||
should be pretty straightforward, as we never reach the code where we need the database:
|
||||
|
||||
function testWrongFormat()
|
||||
{
|
||||
assertFalse(isValidName("$$% 1_', "Assert a name with special characters to be considered an invalid name");
|
||||
}
|
||||
|
||||
The first and third scenario are more sophisticated (like always, when a database comes into the game). There are two ways
|
||||
to test this:
|
||||
- Either you create an empty table on each test, fill them with the users and run the test
|
||||
- or you Mock the database call with a class that behaves like querying the users and returns true or false in each case
|
||||
|
||||
You **shouldn't** create a static database for all tests and assume this one to be existing - these are decoupled from the
|
||||
actual test and soon get outdated or difficult to reflect all scenarios. Also it's not considered good practice to create
|
||||
a precondition your tests have to rely on. In this case the second approach makes sense, as the mock class should be rather simple:
|
||||
|
||||
|
||||
function testCorrectUserName()
|
||||
{
|
||||
// set no users in the database mock
|
||||
$db = new UserDatabaseMock(array());
|
||||
setupUserDatabaseMock($db);
|
||||
|
||||
assertTrue(isValidName("hans"), "Assert a correctly formatted and unique name to be considered valid");
|
||||
}
|
||||
|
||||
function testNonUniqueUserName()
|
||||
{
|
||||
// set no users in the database mock
|
||||
$db = new UserDatabaseMock(array("pete"));
|
||||
setupUserDatabaseMock($db);
|
||||
|
||||
assertFalse(isValidName("pete"), "Assert a correctly formatted, but existing name to be considered invalid");
|
||||
}
|
||||
|
||||
The exception can be tested by providing invalid db credentials when using a real databse or by extending the mock (which
|
||||
we will do here):
|
||||
|
||||
function testDatabaseError()
|
||||
{
|
||||
// set no users in the database mock
|
||||
$db = new UserDatabaseMock(array());
|
||||
$db->setThrowsException(true);
|
||||
setupUserDatabaseMock($db);
|
||||
|
||||
assertFalse(isValidName("hans"), "Assert a correct, unique user to be considered invalid when the database doesn't work");
|
||||
}
|
||||
|
||||
This, of course, depends on how you want the behaviour to be when the db is down: Do you want to log the error and proceed
|
||||
as usual or do you want the error to bubble up via an exception.
|
||||
|
||||
#### Writing sensible assertions
|
||||
|
||||
It's crucial to write sensible assertions in your test-classes: You can write a perfect test that covers the right scenario,
|
||||
but don't catch errors because you aren't asking the correct questions.
|
||||
|
||||
- Write assertions that cover your scenario - if you test a correct behaviour don't test what happens if something goes wrong
|
||||
(this is a seperate scenario)
|
||||
- While you should try to write redundant assertions it's better to assert more than to have a missing assertion
|
||||
- A failed assertion means that the implementation is incorrect, other assertions are to be avoided (like testing if a
|
||||
precondition applies)
|
||||
- When testing one function, you have to be naive and assume that everything else is bug free, testing whether other parts
|
||||
worked correctly before testing should be made in a different test.
|
||||
|
||||
## How to test
|
||||
|
||||
Unit tests should only test an isolated, atomic of your code - in theory just one single function - and shouldn't
|
||||
need much dependency handling. An example for a unittest would be to test the following (hypothetical) class method:
|
||||
|
||||
class UserManager
|
||||
{
|
||||
/**
|
||||
* returns true when a user with this name exists and the
|
||||
* password for this user is correct
|
||||
**/
|
||||
public function isCorrectPassword($name, $password)
|
||||
{
|
||||
// You needn't to know the implementation.
|
||||
}
|
||||
}
|
||||
|
||||
### The wrong way
|
||||
|
||||
A unit test for this user could, but should not look like this (we'll explain why):
|
||||
|
||||
use Icinga/MyLibrary/UserManager
|
||||
|
||||
class UserManagerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* Test whether an user is correctly recognized by the UserManager
|
||||
*
|
||||
**/
|
||||
public function testUserManager()
|
||||
{
|
||||
// Connect to the test database that contains jdoe and jsmith
|
||||
$mgrConfg = new \Zend_Config(array(
|
||||
"backend" => "db",
|
||||
"user" => "dbuser.."
|
||||
"pass" =>
|
||||
// all the other db credentials
|
||||
));
|
||||
|
||||
$mgr = new UserManager($mgrConfig);
|
||||
|
||||
$this->assertTrue($mgr->isCorrectPassword("jdoe", "validpassword"));
|
||||
$this->assertTrue($mgr->isCorrectPassword("jsmith", "validpassword"));
|
||||
$this->assertTrue($mgr->isCorrectPassword("jdoe", "nonvalidpassword"));
|
||||
$this->assertTrue($mgr->isCorrectPassword("jsmith", "nonvalidpassword"));
|
||||
$this->assertTrue($mgr->isCorrectPassword("hans", "validpasswor"));
|
||||
}
|
||||
|
||||
This test has a few issues:
|
||||
|
||||
- First, it assert a precondition to apply : A database must exist with the users jdoe and jsmith and the credentials
|
||||
must match the ones provided in the test
|
||||
- There are a lot of dependencies in this code, almost the complete Authentication code must exists. Our test
|
||||
will fail if this code changes or contains bugs, even the isCorrectPassword method is correct.
|
||||
|
||||
### Reducing dependencies
|
||||
|
||||
To avoid these issues, you need to code your classes with testing in mind. Maybe now you're screaming *"Tests shouldn't
|
||||
affect my code, just test if it is correct!"*, but it's not that easy. Testability should be considered as a quality aspect
|
||||
of your code, just like commenting, keeping functions coherent, etc. Non-testable code should be considered as a bug, or
|
||||
at least as a design-flaw.
|
||||
|
||||
One big buzzword in development is now Inversion of Control and Dependency Injection. You can google the details, but in
|
||||
our case it basically means: Instead of your class (in this case UserManager) setting up it's dependencies (creating an Authentication Manager and
|
||||
then fetching users from it), the dependencies are given to the class. This has the advantage that you can mock the
|
||||
dependencies in your testclasses which heavily reduces test-complexity. On the downside this can lead to more complicate
|
||||
Api's, as you have to know the dependencies of your Object when creating it. Therefore we often allow to provide an
|
||||
dependency from the outside (when testing), but normally create the dependencies when nothing is provided (normal use).
|
||||
|
||||
In our case we could say that we allow our UserManager to use a provided set of Users instead of fetching it from the
|
||||
Authmanger:
|
||||
|
||||
class UserManager
|
||||
{
|
||||
|
||||
public function __construct($config, $authMgr = null)
|
||||
{
|
||||
if ($authMgr == null) {
|
||||
// no Authmanager provided, resolve dependency by yourself
|
||||
$this->authMgr = new AuthManager($config);
|
||||
} else {
|
||||
$this->authMgr = $authMgr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
It would of course be best to create an Interface like UserSource which the AuthManger implements, but in this example
|
||||
we trust our Programmer to provide a suitable object. We now can eliminate all the AuthManager dependencies by mocking the
|
||||
AuthManager (lets dumb it down to just providing an array of users):
|
||||
|
||||
use Icinga/MyLibrary/UserManager
|
||||
|
||||
class AuthManagerMock
|
||||
{
|
||||
public $users;
|
||||
|
||||
/**
|
||||
* Create a new mock classw with the provided users and their credentials
|
||||
*
|
||||
* @param array $userPasswordCombinations The users and password combinations to use
|
||||
**/
|
||||
public function __construct(array $userPasswordCombinations)
|
||||
{
|
||||
$this->users = $userPasswordCombinations;
|
||||
}
|
||||
|
||||
public function getUsers()
|
||||
{
|
||||
return $this->users;
|
||||
}
|
||||
}
|
||||
|
||||
class UserManagerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* Test whether an user is correctly recognized by the UserManager
|
||||
*
|
||||
**/
|
||||
public function testUserManager()
|
||||
{
|
||||
$authMock = new AuthManagerMock(array(
|
||||
"jdoe" => "validpassword",
|
||||
"jsmith" => "validpassword"
|
||||
));
|
||||
$mgrConfg = new \Zend_Config(array(), $authMock);
|
||||
$mgr = new UserManager($mgrConfig);
|
||||
|
||||
$this->assertTrue($mgr->isCorrectPassword("jdoe", "validpassword"));
|
||||
$this->assertTrue($mgr->isCorrectPassword("jsmith", "validpassword"));
|
||||
$this->assertFalse($mgr->isCorrectPassword("jdoe", "nonvalidpassword"));
|
||||
$this->assertFalse($mgr->isCorrectPassword("jsmith", "nonvalidpassword"));
|
||||
$this->assertFalse($mgr->isCorrectPassword("hans", "validpassword"));
|
||||
}
|
||||
|
||||
Ok, we might have more code here than before, but our test is now less like prone to fail:
|
||||
|
||||
- Our test doesn't assume any preconditions to apply, like having a db server with correct users
|
||||
|
||||
|
||||
|
||||
### Splitting up assertions
|
||||
|
||||
The test is now not that bad, but still has a few issues:
|
||||
|
||||
- If an assert fails, we don't know which one, as the message will be rather generic ("failed asserting that False is True")
|
||||
- In this case it might be obvious what we test, but if someone sees the class and the assertions, he doesn't know what
|
||||
assumptions are made
|
||||
|
||||
To fix those issues, we have to split up our big test method in several smaller one and give the testmethod **talking names**.
|
||||
Also, the assertions should get an error message that will be printed on failure.
|
||||
|
||||
/**
|
||||
* Testcases for the UserManager class
|
||||
*
|
||||
**/
|
||||
class UserManagerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* Creates a new UserManager with a mocked AuthManage
|
||||
*
|
||||
* @returns UserManager
|
||||
**/
|
||||
public function getUserManager()
|
||||
{
|
||||
$authMock = new AuthManagerMock(array(
|
||||
"jdoe" => "validpassword",
|
||||
"jsmith" => "validpassword"
|
||||
));
|
||||
$mgrConfg = new \Zend_Config(array(), $authMock);
|
||||
return new UserManager($mgrConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether correct user/name combinations are considered valid
|
||||
*
|
||||
**/
|
||||
public function testCorrectUserPasswordCombinations()
|
||||
{
|
||||
$mgr = $this->getUserManager();
|
||||
$this->assertTrue(
|
||||
$mgr->isCorrectPassword("jdoe", "validpassword"),
|
||||
"Asserted that a correct user/password combination is considered valid for jdoe"
|
||||
);
|
||||
$this->assertTrue(
|
||||
$mgr->isCorrectPassword("jsmith", "validpassword"),
|
||||
"Asserted that a correct user/password combination is considered valid for jsmith"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether invalid names are rejected
|
||||
*
|
||||
**/
|
||||
public function testInvalidUsernameRecognition()
|
||||
{
|
||||
$mgr = $this->getUserManager();
|
||||
$this->assertFalse(
|
||||
$mgr->isCorrectPassword("hans", "validpassword"),
|
||||
"Asserted a non-existing user to be be considered invalid"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether invalid passwords for existing users are rejected
|
||||
*
|
||||
**/
|
||||
public function testInvalidPasswordRecognition()
|
||||
{
|
||||
$mgr = $this->getUserManager();
|
||||
$this->assertFalse(
|
||||
$mgr->isCorrectPassword("jsmith", "nonvalidpassword"),
|
||||
"Asserted that an invalid password for an existing user is considered invalid"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Now if something fails, we now see what has been tested via the testmethod and what caused the test to fail in the
|
||||
assertion error message. You could also leave the comments and everybody knows what you are doing.
|
@ -1,93 +0,0 @@
|
||||
# Testing guide
|
||||
|
||||
|
||||
## Testing controllers for compatibility with different monitoring datasources
|
||||
|
||||
|
||||
When it comes to writing controllers, it is important that your actions and queries work on every monitoring
|
||||
datasource supported by icinga2 web. For this, the monitoring module provides a test library for controllers.
|
||||
|
||||
## The database setup for every testcase
|
||||
|
||||
When testing PostgreSQL and MySQL databases, the test library (normally) executes the following test procedure for every
|
||||
test case:
|
||||
|
||||
- Log in to the rdbms as the user icinga_unittest with password icinga_unittest
|
||||
- Use the icinga_unittest database (which must be existing)
|
||||
- Drop all tables in the icinga_unittest database (so *NEVER* run unit tests on your production system)
|
||||
- Create a new, clean database schema
|
||||
|
||||
If anything goes wrong during this procedure, the test will be skipped (because maybe you don't have a pgsql database, but
|
||||
want to test mysql, for example)
|
||||
|
||||
## Setting up a test user and database in MySQL
|
||||
|
||||
In MySQL, it's best to create a user icinga_unittest@localhost, a database icinga_unittest and grant all privileges on
|
||||
this database:
|
||||
|
||||
mysql -u root -p
|
||||
mysql> CREATE USER `icinga_unittest`@`localhost` IDENTIFIED BY 'icinga_unittest';
|
||||
mysql> CREATE DATABASE `icinga_unittest`;
|
||||
mysql> GRANT ALL PRIVILEGES ON `icinga_unittest`.* TO `icinga_unittest`@`localhost`;
|
||||
mysql> FLUSH PRIVILEGES;
|
||||
mysql> quit
|
||||
|
||||
## Setting up a test user and database in PostgreSQL
|
||||
|
||||
In PostgreSQL, you have to modify the pg_hba database if you don't have password authentication set up (which often is
|
||||
the case). In this setup the icinga_unittest user is set to trust authentication on localhost, which means that no
|
||||
password is queried when connecting from the local machine:
|
||||
|
||||
sudo su postgres
|
||||
psql
|
||||
postgres=# CREATE USER icinga_unittest WITH PASSWORD 'icinga_unittest';
|
||||
postgres=# CREATE DATABASE icinga_unittest;
|
||||
postgres=# \q
|
||||
bash$ createlang plpgsql icinga;
|
||||
|
||||
## Writing tests for icinga
|
||||
|
||||
Icinga has it's own base test which lets you easily require libraries, testing database and form functionality. The class resides in
|
||||
library/Icinga/Test. If you write a test, just subclass BaseTestCase.
|
||||
|
||||
### Writing database tests
|
||||
|
||||
The base test uses the PHPUnit dataProvider annotation system to create database connections. Typically a
|
||||
database test looks like this:
|
||||
|
||||
/**
|
||||
* @dataProvider mysqlDb
|
||||
* @param Icinga\Data\Db\DbConnection $mysqlDb
|
||||
*/
|
||||
public function testSomethingWithMySql($mysqlDb)
|
||||
{
|
||||
$this->setupDbProvider($mysqlDb); // Drops everything from existing database
|
||||
|
||||
// Load a dump file into database
|
||||
$this->loadSql($mysqlDb, BaseTestCase::$etcDir . '/etc/schema/mydump.mysql.sql');
|
||||
|
||||
// Test your code
|
||||
}
|
||||
|
||||
Available data providers are: mysqlDb, pgsqlDb, oracleDb. The test will be skipped if a provider
|
||||
could not be initialized.
|
||||
|
||||
### Write form tests
|
||||
|
||||
BaseTestCase holds method to require form libraries and create form classes based on class names.
|
||||
|
||||
public function testShowModifiedOrder()
|
||||
{
|
||||
$this->requireFormLibraries();
|
||||
$form = $this->createForm(
|
||||
'Icinga\Form\Config\AuthenticationForm',
|
||||
array(
|
||||
'priority' => 'test-ldap,test-db'
|
||||
)
|
||||
);
|
||||
|
||||
// Testing your code
|
||||
}
|
||||
|
||||
The second parameter of createForm() can be omitted. You can set initial post request data as
|
||||
an array if needed.
|
122
doc/widgets.md
122
doc/widgets.md
@ -1,122 +0,0 @@
|
||||
# Widgets
|
||||
|
||||
Widgets are reusable UI components that are able to render themselves and return HTML to be included in your template.
|
||||
|
||||
## Basic interface
|
||||
|
||||
The interface needed for implementing widgets can be found under library/Icinga/Web/Widget/Widget.php. This is a rather
|
||||
simple interface, only providing a `render()` method that takes a view and returns HTML:
|
||||
|
||||
interface Widget
|
||||
{
|
||||
public function render(Zend_View_Abstract $view);
|
||||
}
|
||||
|
||||
When implementing own Widgets you just have to make sure that you provide this render method.
|
||||
|
||||
## Using widgets
|
||||
|
||||
Widgets are normally created in the controller and added to the view:
|
||||
|
||||
// in your Controller
|
||||
|
||||
public function myControllerAction()
|
||||
{
|
||||
$this->view->myWidget = new MyWidget();
|
||||
}
|
||||
|
||||
The HTML is then rendered in the template using the `render()` method described above. As the '$this' scope in a view is
|
||||
a reference to your current view, you can just pass it to the `render()` method:
|
||||
|
||||
// in your template
|
||||
|
||||
<div>
|
||||
<h4>Look at my beautiful widget</h4>
|
||||
<?= $this->myWidget->render($this); ?>
|
||||
</div>
|
||||
|
||||
## The 'Tabs' widget
|
||||
|
||||
The Tabs `\Icinga\Web\Widgets\Tabs` widget handles creation of Tab bars and allows you to create and add single tabs to
|
||||
this view. To create an empty Tab bar, you just have to call:
|
||||
|
||||
$tabbar = new Tabs();
|
||||
|
||||
> **Note**: Controllers subclassing `\Icinga\Web\Controller\ActionController` (which all existing controller do so and
|
||||
> yours should too) have already an empty tabs object created under `$this->view->tabs`. This is done in the
|
||||
> `preDispatch` function.
|
||||
|
||||
### Adding tabs
|
||||
|
||||
Afterwards you can add tabs by calling the `add($name, $tab)` function, whereas `$name` is the name of your tab and
|
||||
`$tab` is either an array with tab parameters or an existing Tab object.
|
||||
|
||||
// Add a tab
|
||||
|
||||
$tabbar->add(
|
||||
'myTab',
|
||||
array(
|
||||
'title' => 'My hosts', // Displayed as the tab text
|
||||
'iconCls' => 'myicon', // icon-myicon will be used as an icon in a <i> tag
|
||||
'url' => '/my/url', // The url to use
|
||||
'urlParams' => array('host' => 'localhost') // Will be used as GET parameter
|
||||
)
|
||||
);
|
||||
|
||||
### Adding tabs to the dropdown list
|
||||
|
||||
Sometimes you want additional actions to be displayed in your tabbar. This can be accomplished with the
|
||||
`addAsDropdown()` method. This one is similar to the `add()` method, but displays your tab in a dropdown list on the
|
||||
right side of the tabbar.
|
||||
|
||||
## Using tabextensions
|
||||
|
||||
Often you find yourself adding the same tabs over and over again. You can write a Tabextension that does this for you
|
||||
and just apply them on your tabs. Tabextensions are located at Icinga/Web/Widgets/Tabextension/ and they use the simple
|
||||
Tabextension interface that just defines `apply(Tabs $tab)`. A simple example is the DashboardAction Tabextender which
|
||||
just adds a new field to the dropdown list:
|
||||
|
||||
class DashboardAction implements Tabextension
|
||||
{
|
||||
/**
|
||||
* @see Tabextension::apply()
|
||||
*/
|
||||
public function apply(Tabs $tabs)
|
||||
{
|
||||
$tabs->addAsDropdown(
|
||||
'dashboard',
|
||||
array(
|
||||
'title' => 'Add to Dashboard',
|
||||
'iconCls' => 'dashboard',
|
||||
'url' => Url::fromPath('dashboard/addurl'),
|
||||
'urlParams' => array(
|
||||
'url' => Url::fromRequest()->getRelativeUrl()
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
You can now either extend your Tabs object using the DashboardAction's `apply()` method or by calling the Tabs
|
||||
`extend()` method (which is more fluent):
|
||||
|
||||
$tabs->extend(new DashboardAction());
|
||||
|
||||
## The SortBox widget
|
||||
|
||||
The "SortBox" Widget allows you to create a generic sort input for sortable views.
|
||||
It automatically creates a form containing a select box with all sort options and a dropbox with the sort direction. It
|
||||
also handles automatic submission of sorting changes and draws an additional submit button when JavaScript is disabled.
|
||||
|
||||
The constructor takes an string for the component name ad an array containing the select options, where the key is
|
||||
the value to be submitted and the value is the label that will be shown. You then should call applyRequest in order to
|
||||
make sure the form is correctly populated when a request with a sort parameter is being made.
|
||||
|
||||
$this->view->sortControl = new SortBox(
|
||||
$this->getRequest()->getActionName(),
|
||||
$columns
|
||||
);
|
||||
$this->view->sortControl->applyRequest($this->getRequest());
|
||||
|
||||
|
||||
By default the sortBox uses the GET parameter 'sort' for the sorting key and 'dir' for the sorting direction
|
@ -46,7 +46,7 @@ class IniStore extends PreferencesStore
|
||||
$this->preferencesFile = sprintf(
|
||||
'%s/%s.ini',
|
||||
$this->getStoreConfig()->location,
|
||||
$this->getUser()->getUsername()
|
||||
strtolower($this->getUser()->getUsername())
|
||||
);
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ class IniStore extends PreferencesStore
|
||||
public function load()
|
||||
{
|
||||
if (file_exists($this->preferencesFile)) {
|
||||
if (!is_readable($this->preferencesFile)) {
|
||||
if (! is_readable($this->preferencesFile)) {
|
||||
throw new NotReadableError(
|
||||
'Preferences INI file %s for user %s is not readable',
|
||||
$this->preferencesFile,
|
||||
@ -99,8 +99,8 @@ class IniStore extends PreferencesStore
|
||||
public function write()
|
||||
{
|
||||
if ($this->writer === null) {
|
||||
if (!file_exists($this->preferencesFile)) {
|
||||
if (!is_writable($this->getStoreConfig()->location)) {
|
||||
if (! file_exists($this->preferencesFile)) {
|
||||
if (! is_writable($this->getStoreConfig()->location)) {
|
||||
throw new NotWritableError(
|
||||
'Path to the preferences INI files %s is not writable',
|
||||
$this->getStoreConfig()->location
|
||||
@ -110,7 +110,7 @@ class IniStore extends PreferencesStore
|
||||
File::create($this->preferencesFile, 0664);
|
||||
}
|
||||
|
||||
if (!is_writable($this->preferencesFile)) {
|
||||
if (! is_writable($this->preferencesFile)) {
|
||||
throw new NotWritableError(
|
||||
'Preferences INI file %s for user %s is not writable',
|
||||
$this->preferencesFile,
|
||||
|
@ -81,20 +81,20 @@ class MonitoringMenuItemRenderer implements MenuItemRenderer {
|
||||
}
|
||||
if ($menu->getIcon() && strpos($menu->getIcon(), '.') === false) {
|
||||
return sprintf(
|
||||
'<a href="%s" class="icon-%s">%s%s</a>',
|
||||
'%s <a href="%s" class="icon-%s">%s</a>',
|
||||
$badge,
|
||||
$menu->getUrl() ?: '#',
|
||||
$menu->getIcon(),
|
||||
htmlspecialchars($menu->getTitle()),
|
||||
$badge
|
||||
htmlspecialchars($menu->getTitle())
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<a href="%s">%s%s%s</a>',
|
||||
'%s<a href="%s">%s%s<span></span></a>',
|
||||
$badge,
|
||||
$menu->getUrl() ?: '#',
|
||||
$menu->getIcon() ? '<img src="' . Url::fromPath($menu->getIcon()) . '" class="icon" /> ' : '',
|
||||
htmlspecialchars($menu->getTitle()),
|
||||
$badge
|
||||
htmlspecialchars($menu->getTitle())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -628,7 +628,7 @@ class Monitoring_ListController extends Controller
|
||||
$editor = Widget::create('filterEditor')
|
||||
->setQuery($query)
|
||||
->preserveParams('limit', 'sort', 'dir', 'format', 'view', 'backend', 'renderLayout', 'stateType', 'addColumns')
|
||||
->ignoreParams('page', 'objecttype', 'from', 'to', 'btn_submit')
|
||||
->ignoreParams('page', 'objecttype', 'from', 'to', 'btn_submit', 'icon')
|
||||
->handleRequest($this->getRequest());
|
||||
$query->applyFilter($editor->getFilter());
|
||||
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
3
packages/debian/README.1st
Normal file
3
packages/debian/README.1st
Normal file
@ -0,0 +1,3 @@
|
||||
PLEASE DO NOT BUILD PACKAGES BASES ON THIS
|
||||
|
||||
Packages may still be renamed
|
@ -1,2 +0,0 @@
|
||||
PLEASE DO NOT USE THIS YET. Still preparing basic stuff, should be ready
|
||||
with 2.0.0-beta1
|
@ -1,5 +1,5 @@
|
||||
icingaweb (2.0.0~alpha1) unstable; urgency=low
|
||||
icingaweb (2.0.0) stable; urgency=low
|
||||
|
||||
* First beta release (Closes: #0000)
|
||||
* First stable release (Closes: #0001)
|
||||
|
||||
-- Thomas Gelf <thomas@gelf.net> Fri, 28 Mar 2014 23:37:31 +0100
|
||||
|
@ -6,29 +6,53 @@ Build-Depends: debhelper (>=9)
|
||||
Standards-Version: 3.9.4
|
||||
Homepage: https://www.icinga.org
|
||||
|
||||
Package: libicinga-php
|
||||
Package: php-icinga
|
||||
Architecture: any
|
||||
Depends: php5 (>= 5.3.2), zendframework
|
||||
Depends: php5 (>= 5.3.2)
|
||||
Recommends: php5-ldap, php5-mysql, php5-json
|
||||
Suggests: php5-pgsql
|
||||
Description: Icinga PHP libraries
|
||||
PHP libraries
|
||||
|
||||
Package: libicinga-vendor-php
|
||||
Package: icingaweb-common
|
||||
Architecture: any
|
||||
Depends: libicinga-php
|
||||
Description: Icinga PHP vendor libraries
|
||||
PHP vendor libraries
|
||||
|
||||
Package: libicinga-common-php
|
||||
Architecture: any
|
||||
Depends: libicinga-php, libicinga-vendor-php
|
||||
Depends: php-icinga
|
||||
Description: Icinga PHP common libraries
|
||||
PHP common libraries, application and modules
|
||||
|
||||
Package: icingaweb-module-doc
|
||||
Architecture: any
|
||||
Depends: icingaweb-common
|
||||
Description: Icingaweb documentation module
|
||||
This module renders documentation for Icingaweb and it's modules
|
||||
|
||||
Package: icingaweb-module-monitoring
|
||||
Architecture: any
|
||||
Depends: icingaweb-common
|
||||
Description: Icingaweb monitoring module
|
||||
Use this module to visualize your monitoring environment in Icingaweb
|
||||
|
||||
Package: icingaweb-module-setup
|
||||
Architecture: any
|
||||
Depends: icingaweb-common
|
||||
Description: Icingaweb setup module
|
||||
This setup wizard does your initial Icingaweb configuration
|
||||
|
||||
Package: icingaweb-module-test
|
||||
Architecture: any
|
||||
Depends: icingacli
|
||||
Description: Icingaweb test module
|
||||
Use this module to run unit tests against Icingaweb or any of it's modules
|
||||
|
||||
Package: icingaweb-module-translation
|
||||
Architecture: any
|
||||
Depends: icingaweb-common
|
||||
Description: Icingaweb translation module
|
||||
This module helps translators to get Icingaweb translations done
|
||||
|
||||
Package: icingacli
|
||||
Architecture: any
|
||||
Depends: libicinga-common-php (>= 2.0.0~alpha1), php5-cli (>= 5.3.2)
|
||||
Depends: icingaweb-common, php5-cli (>= 5.3.2)
|
||||
Description: Icinga CLI tool
|
||||
The Icinga CLI allows one to access it's Icinga monitoring
|
||||
system from a terminal.
|
||||
@ -37,8 +61,8 @@ Description: Icinga CLI tool
|
||||
|
||||
Package: icingaweb
|
||||
Architecture: any
|
||||
Depends: libicinga-common-php (>= 2.0.0~alpha1), libapache2-mod-php5
|
||||
Recommends: php5-gd, icingacli
|
||||
Depends: icingaweb-common, libapache2-mod-php5, zendframework | icingaweb-vendor-zend, icingaweb-vendor-parsedown, icingaweb-vendor-lessphp, icingaweb-vendor-jshrink, icingaweb-vendor-htmlpurifier, icingaweb-module-setup
|
||||
Recommends: icingaweb-module-monitoring, icingaweb-module-doc
|
||||
Suggests: php5-ldap
|
||||
Description: Icinga Web Frontend
|
||||
Icinga Web is a modular web frontend
|
||||
Description: Icingaweb Frontend
|
||||
Icingaweb is a modular web frontend
|
||||
|
@ -1,2 +1,3 @@
|
||||
packages/files/bin/icingacli usr/bin
|
||||
etc/bash_completion.d etc/bash_completion.d
|
||||
application/clicommands usr/share/icingaweb/application
|
||||
|
2
packages/debian/icingaweb-common.install
Normal file
2
packages/debian/icingaweb-common.install
Normal file
@ -0,0 +1,2 @@
|
||||
application/locales usr/share/icingaweb/application
|
||||
modules usr/share/icingaweb
|
1
packages/debian/icingaweb-module-doc.install
Normal file
1
packages/debian/icingaweb-module-doc.install
Normal file
@ -0,0 +1 @@
|
||||
modules/doc usr/share/icingaweb/modules
|
1
packages/debian/icingaweb-module-monitoring.install
Normal file
1
packages/debian/icingaweb-module-monitoring.install
Normal file
@ -0,0 +1 @@
|
||||
modules/doc usr/share/icingaweb/modules
|
1
packages/debian/icingaweb-module-setup.install
Normal file
1
packages/debian/icingaweb-module-setup.install
Normal file
@ -0,0 +1 @@
|
||||
modules/setup usr/share/icingaweb/modules
|
1
packages/debian/icingaweb-module-test.install
Normal file
1
packages/debian/icingaweb-module-test.install
Normal file
@ -0,0 +1 @@
|
||||
modules/test usr/share/icingaweb/modules
|
1
packages/debian/icingaweb-module-translation.install
Normal file
1
packages/debian/icingaweb-module-translation.install
Normal file
@ -0,0 +1 @@
|
||||
modules/translation usr/share/icingaweb/modules
|
1
packages/debian/icingaweb-vendor-htmlpurifier.install
Normal file
1
packages/debian/icingaweb-vendor-htmlpurifier.install
Normal file
@ -0,0 +1 @@
|
||||
library/vendor/HTMLPurifier usr/share/icingaweb/library/vendor
|
1
packages/debian/icingaweb-vendor-jshrink.install
Normal file
1
packages/debian/icingaweb-vendor-jshrink.install
Normal file
@ -0,0 +1 @@
|
||||
library/vendor/JShrink usr/share/icingaweb/library/vendor
|
1
packages/debian/icingaweb-vendor-lessphp.install
Normal file
1
packages/debian/icingaweb-vendor-lessphp.install
Normal file
@ -0,0 +1 @@
|
||||
library/vendor/Zend usr/share/icingaweb/library/vendor
|
1
packages/debian/icingaweb-vendor-parsedown.install
Normal file
1
packages/debian/icingaweb-vendor-parsedown.install
Normal file
@ -0,0 +1 @@
|
||||
library/vendor/Parsedown usr/share/icingaweb/library/vendor
|
1
packages/debian/icingaweb-vendor-zend.install
Normal file
1
packages/debian/icingaweb-vendor-zend.install
Normal file
@ -0,0 +1 @@
|
||||
library/vendor/Zend usr/share/icingaweb/library/vendor
|
@ -1,5 +1,10 @@
|
||||
public/css usr/share/icingaweb/public
|
||||
public/img usr/share/icingaweb/public
|
||||
public/js usr/share/icingaweb/public
|
||||
public/js usr/share/icingaweb/public
|
||||
public/error_norewrite.html usr/share/icingaweb/public
|
||||
application/controllers usr/share/icingaweb/application
|
||||
application/fonts usr/share/icingaweb/application
|
||||
application/layouts usr/share/icingaweb/application
|
||||
application/views usr/share/icingaweb/application
|
||||
packages/files/public/index.php usr/share/icingaweb/public
|
||||
packages/files/icingaweb-apache2.conf etc/apache2/conf.d
|
||||
packages/files/apache/icingaweb.conf etc/apache2/conf.d
|
||||
|
@ -1,2 +0,0 @@
|
||||
application usr/share/icingaweb
|
||||
modules usr/share/icingaweb
|
@ -1 +0,0 @@
|
||||
library/IcingaVendor usr/share/php
|
@ -23,7 +23,7 @@ binary:
|
||||
dh_installdebconf
|
||||
dh_installinfo
|
||||
dh_installinit
|
||||
dpkg-statoverride --force --add root www-data 2775 /etc/icingaweb
|
||||
dpkg-statoverride --force --add root www-data 2770 /etc/icingaweb
|
||||
dh_compress
|
||||
dh_fixperms
|
||||
dh_strip
|
||||
|
@ -104,7 +104,7 @@ form.inline {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
form.link-like input[type="submit"], form.link-like button[type="submit"] {
|
||||
form.link-like input[type="submit"], form.link-like button[type="submit"], input.link-like, button.link-like {
|
||||
color: @colorLinkDefault;
|
||||
font-weight: normal;
|
||||
border: none;
|
||||
@ -114,7 +114,11 @@ form.link-like input[type="submit"], form.link-like button[type="submit"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
form.link-like input[type="submit"]:hover, form.link-like input[type="submit"]:focus, form.link-like button[type="submit"]:hover, orm.link-like button[type="submit"]:focus {
|
||||
form.link-like input[type="submit"]:hover,
|
||||
form.link-like input[type="submit"]:focus,
|
||||
form.link-like button[type="submit"]:hover,
|
||||
form.link-like button[type="submit"]:focus,
|
||||
input.link-like:hover, button.link-like:focus {
|
||||
text-decoration: underline;
|
||||
background: none;
|
||||
color: @colorLinkDefault;
|
||||
|
@ -204,17 +204,20 @@ li li .badge-container {
|
||||
margin-right: 0.75em;
|
||||
}
|
||||
|
||||
#menu > ul > li.active > a > .badge-container {
|
||||
/*
|
||||
#layout.hoveredmenu .active > .badge-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#layout.hoveredmenu .hover > a > .badge-container {
|
||||
margin-right: 14.15em;
|
||||
#layout.hoveredmenu .hover > .badge-container {
|
||||
//margin-right: 14.15em;
|
||||
display: none;
|
||||
}
|
||||
*/
|
||||
|
||||
.badge {
|
||||
position: relative;
|
||||
top: -0.1em;
|
||||
top: 0.3em;
|
||||
display: inline-block;
|
||||
min-width: 1em;
|
||||
padding: 3px 7px;
|
||||
@ -228,6 +231,33 @@ li li .badge-container {
|
||||
background-color: @colorInvalid;
|
||||
}
|
||||
|
||||
#menu > ul > li.active > .badge-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#menu > ul > li.hover > .badge-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#menu > ul > li.active > ul > li .badge-container {
|
||||
position: relative;
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
#menu > ul > li.hover > ul > li > a {
|
||||
width: 12.5em;
|
||||
}
|
||||
|
||||
#menu > ul > li.hover > ul > li .badge-container {
|
||||
position: relative;
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
#menu > ul > li.hover > ul > li {
|
||||
// prevent floating badges from resizing list items in webkit
|
||||
//max-height: 2em;
|
||||
}
|
||||
|
||||
li li .badge {
|
||||
font-size: 0.975em;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user