Merge branch 'master' into feature/security-gui-5647

This commit is contained in:
Eric Lippmann 2014-11-19 16:31:26 +01:00
commit 2830b13082
46 changed files with 3409 additions and 2613 deletions

4
.gitattributes vendored
View File

@ -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

View File

@ -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'
)
)
);
}
/**

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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 &lt;noscript&gt; 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 &lt;dd&gt; &lt;dt&gt; 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.

View File

@ -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

View File

@ -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**

View File

@ -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
}
}

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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,

View File

@ -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())
);
}
}

View File

@ -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());

View File

@ -0,0 +1,3 @@
PLEASE DO NOT BUILD PACKAGES BASES ON THIS
Packages may still be renamed

View File

@ -1,2 +0,0 @@
PLEASE DO NOT USE THIS YET. Still preparing basic stuff, should be ready
with 2.0.0-beta1

View File

@ -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

View File

@ -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

View File

@ -1,2 +1,3 @@
packages/files/bin/icingacli usr/bin
etc/bash_completion.d etc/bash_completion.d
application/clicommands usr/share/icingaweb/application

View File

@ -0,0 +1,2 @@
application/locales usr/share/icingaweb/application
modules usr/share/icingaweb

View File

@ -0,0 +1 @@
modules/doc usr/share/icingaweb/modules

View File

@ -0,0 +1 @@
modules/doc usr/share/icingaweb/modules

View File

@ -0,0 +1 @@
modules/setup usr/share/icingaweb/modules

View File

@ -0,0 +1 @@
modules/test usr/share/icingaweb/modules

View File

@ -0,0 +1 @@
modules/translation usr/share/icingaweb/modules

View File

@ -0,0 +1 @@
library/vendor/HTMLPurifier usr/share/icingaweb/library/vendor

View File

@ -0,0 +1 @@
library/vendor/JShrink usr/share/icingaweb/library/vendor

View File

@ -0,0 +1 @@
library/vendor/Zend usr/share/icingaweb/library/vendor

View File

@ -0,0 +1 @@
library/vendor/Parsedown usr/share/icingaweb/library/vendor

View File

@ -0,0 +1 @@
library/vendor/Zend usr/share/icingaweb/library/vendor

View File

@ -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

View File

@ -1,2 +0,0 @@
application usr/share/icingaweb
modules usr/share/icingaweb

View File

@ -1 +0,0 @@
library/IcingaVendor usr/share/php

View File

@ -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

View File

@ -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;

View File

@ -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;
}