Merge branch 'master' into feature/packages-4075

This commit is contained in:
Eric Lippmann 2014-12-29 12:31:38 +01:00
commit 4ea52161a9
32 changed files with 520 additions and 559 deletions

View File

@ -12,4 +12,6 @@ node default {
file { '/etc/profile.d/env.sh':
source => 'puppet:////vagrant/.puppet/files/etc/profile.d/env.sh'
}
@user { vagrant: ensure => present }
User <| title == vagrant |> { groups +> icingaweb }
}

View File

@ -1,3 +1,4 @@
# TODO(el): This module is not reuseable because it relies on vagrant paths
class icingacli {
file { '/usr/local/bin/icingacli':
ensure => link,

View File

@ -6,6 +6,7 @@ namespace Icinga\Forms\Security;
use InvalidArgumentException;
use LogicException;
use Zend_Form_Element;
use Icinga\Application\Icinga;
use Icinga\Forms\ConfigForm;
use Icinga\Util\String;
@ -18,14 +19,14 @@ class RoleForm extends ConfigForm
/**
* Provided permissions by currently loaded modules
*
* @var array
* @type array
*/
protected $providedPermissions = array();
protected $providedPermissions = array('*' => '*');
/**
* Provided restrictions by currently loaded modules
*
* @var array
* @type array
*/
protected $providedRestrictions = array();
@ -35,14 +36,26 @@ class RoleForm extends ConfigForm
*/
public function init()
{
$helper = new Zend_Form_Element('bogus');
foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) {
foreach ($module->getProvidedPermissions() as $permission) {
/** @var object $permission */
/** @type object $permission */
$this->providedPermissions[$permission->name] = $permission->name . ': ' . $permission->description;
}
foreach ($module->getProvidedRestrictions() as $restriction) {
/** @var object $restriction */
$this->providedRestrictions[$restriction->name] = $restriction->description;
/** @type object $restriction */
$name = $helper->filterName($restriction->name); // Zend only permits alphanumerics, the underscore,
// the circumflex and any ASCII character in range
// \x7f to \xff (127 to 255)
while (isset($this->providedRestrictions[$name])) {
// Because Zend_Form_Element::filterName() replaces any not permitted character with the empty
// string we may have duplicate names, e.g. 're/striction' and 'restriction'
$name .= '_';
}
$this->providedRestrictions[$name] = array(
'description' => $restriction->description,
'name' => $restriction->name
);
}
}
}
@ -90,13 +103,13 @@ class RoleForm extends ConfigForm
)
)
));
foreach ($this->providedRestrictions as $name => $description) {
foreach ($this->providedRestrictions as $name => $spec) {
$this->addElement(
'text',
$name,
array(
'label' => $name,
'description' => $description
'label' => $spec['name'],
'description' => $spec['description']
)
);
}
@ -129,6 +142,15 @@ class RoleForm extends ConfigForm
? String::trimSplit($role['permissions'])
: null;
$role['name'] = $name;
$restrictions = array();
foreach ($this->providedRestrictions as $name => $spec) {
if (isset($role[$spec['name']])) {
// Translate restriction names to filtered element names
$restrictions[$name] = $role[$spec['name']];
unset($role[$spec['name']]);
}
}
$role = array_merge($role, $restrictions);
$this->populate($role);
return $this;
}
@ -230,6 +252,15 @@ class RoleForm extends ConfigForm
if (isset($values['permissions'])) {
$values['permissions'] = implode(', ', $values['permissions']);
}
$restrictions = array();
foreach ($this->providedRestrictions as $name => $spec) {
if (isset($values[$name])) {
// Translate filtered element names to restriction names
$restrictions[$spec['name']] = $values[$name];
unset($values[$name]);
}
}
$values = array_merge($values, $restrictions);
return $values;
}
}

View File

@ -18,7 +18,7 @@
<?php if ($configMissing): ?>
<div class="config-note"><?= sprintf(
t(
'You seem not to have Icinga Web 2 configured yet so it\'s not possible to log in without any defined '
'It appears that you did not configure Icinga Web 2 yet so it\'s not possible to log in without any defined '
. 'authentication method. Please define a authentication method by following the instructions in the'
. ' %1$sdocumentation%3$s or by using our %2$sweb-based setup-wizard%3$s.'
),

View File

@ -30,7 +30,7 @@
<td>
<?php
// TODO(el): $role->without(...) or $role->shift(...) would be nice!
$restrictions = $role;
$restrictions = clone $role;
unset($restrictions['users']);
unset($restrictions['groups']);
unset($restrictions['permissions']);

View File

@ -2,21 +2,23 @@
**Choosing the Authentication Method**
With Icinga Web 2 you can authenticate against Active Directory, LDAP, a MySQL or PostgreSQL database or delegate
authentication to the web server. Authentication methods can be chained to set up fallback authentication methods
With Icinga Web 2 you can authenticate against Active Directory, LDAP, a MySQL or a PostgreSQL database or delegate
authentication to the web server.
Authentication methods can be chained to set up fallback authentication methods
or if users are spread over multiple places.
## Configuration
## <a id="authentication-configuration"></a> Configuration
Authentication methods are configured in the INI file **config/authentication.ini**.
Each section in the authentication configuration represents a single authentication method.
The order of entries in the authentication configuration determines the order of the authentication methods.
If the current authentication method errors or the current authentication method does not know the account being
If the current authentication method errors or if the current authentication method does not know the account being
authenticated, the next authentication method will be used.
## External Authentication
### <a id="authentication-configuration-external-authentication"></a> External Authentication
For delegating authentication to the web server simply add `autologin` to your authentication configuration:
@ -27,13 +29,13 @@ backend = autologin
If your web server is not configured for authentication though the `autologin` section has no effect.
## Active Directory or LDAP Authentication
### <a id="authentication-configuration-ad-or-ldap-authentication"></a> Active Directory or LDAP Authentication
If you want to authenticate against Active Directory or LDAP, you have to define a
[LDAP resource](#resources-configuration-ldap) first which will be referenced as data source for the Active Directory
[LDAP resource](#resources-configuration-ldap) which will be referenced as data source for the Active Directory
or LDAP configuration method.
### LDAP
#### <a id="authentication-configuration-ldap-authentication"></a> LDAP
Directive | Description
------------------------|------------
@ -52,7 +54,7 @@ user_class = inetOrgPerson
user_name_attribute = uid
```
### Active Directory
#### <a id="authentication-configuration-ad-authentication"></a> Active Directory
Directive | Description
------------------------|------------
@ -67,10 +69,10 @@ backend = ad
resource = my_ad
```
## Database Authentication
### <a id="authentication-configuration-db-authentication"></a> Database Authentication
If you want to authenticate against a MySQL or PostgreSQL database, you have to define a
[database resource](#resources-configuration-database) first which will be referenced as data source for the database
If you want to authenticate against a MySQL or a PostgreSQL database, you have to define a
[database resource](#resources-configuration-database) which will be referenced as data source for the database
authentication method.
Directive | Description
@ -83,13 +85,31 @@ Directive | Description
```
[auth_ad]
backend = ad
resource = my_db
resource = icingaweb-mysql
```
#### <a id="authentication-configuration-db-setup"></a> Database Setup
For authenticating against a database, you have to import one of the following database schemas:
* **etc/schema/preferences.mysql.sql** (for **MySQL** database)
* **etc/schema/preferences.pgsql.sql** (for **PostgreSQL** databases)
After that you have to define the [database resource](#resources-configuration-database).
**Manually Creating Users**
Icinga Web 2 uses the MD5 based BSD password algorithm. For generating a password hash, please use the following
command:
````
openssl passwd -1 "password"
````
> Note: The switch to `openssl passwd` is the **number one** (`-1`) for using the MD5 based BSD password algorithm.
Insert the user into the database using the generated password hash:
````
INSERT INTO icingaweb_user (name, active, password_hash) VALUES ('icingaadmin', 1, 'hash from openssl');
````

View File

@ -26,7 +26,7 @@ repository either via git or http protocol using the following URLs:
* http://git.icinga.org/icingaweb2.git
There is also a browsable version available at
[gi.icinga.org](https://git.icinga.org/?p=icingaweb2.git;a=summary "Icinga Web 2 Git Repository").
[git.icinga.org](https://git.icinga.org/?p=icingaweb2.git;a=summary "Icinga Web 2 Git Repository").
This version also offers snapshots for easy download which you can use if you do not have git present on your system.
````

View File

@ -1,101 +1,53 @@
# Preferences
# <a id="preferences"></a> Preferences
Preferences are user based configuration for Icinga Web 2. For example max page
items, languages or date time settings can controlled by users.
Preferences are settings a user can set for his account only, for example his language and time zone.
# Architecture
**Choosing Where to Store Preferences**
Preferences are initially loaded from a provider (ini files or database) and
stored into session at login time. After this step preferences are only
persisted to the configured backend, but never reloaded from them.
Preferences can be stored either in INI files or in a MySQL or in a PostgreSQL database. By default, Icinga Web 2 stores
preferences in INI files beneath Icinga Web 2's configuration directory.
# Configuration
## <a id="preferences-configuration"></a> Configuration
Preferences can be configured in config.ini in **preferences** section, default
settings are this:
Where to store preferences is defined in the INI file **config/config.ini** in the *preferences* section.
[preferences]
type=ini
### <a id="preferences-configuration-ini"></a> Store Preferences in INI Files
The ini provider uses the directory **config/preferences** to create one ini
file per user and persists the data into a single file. If you want to drop your
preferences just drop the file from disk and you'll start with a new profile.
If preferences are stored in INI Files, Icinga Web 2 automatically creates one file per user using the username as
file name for storing preferences. A INI file is created once a user saves changed preferences the first time.
The files are located beneath the `preferences` directory beneath Icinga Web 2's configuration directory.
## Database Provider
For storing preferences in INI files you have to add the following section to the INI file **config/config.ini**:
To be more flexible in distributed setups you can store preferences in a
database (pgsql or mysql), a typical configuration looks like the following
example:
```
[preferences]
type = ini
````
[preferences]
type=db
resource=icingaweb-pgsql
### <a id="preferences-configuration-db"></a> Store Preferences in a Database
## Null Provider
In order to be more flexible in distributed setups you can store preferences in a MySQL or in a PostgreSQL database.
For storing preferences in a database, you have to define a [database resource](#resources-configuration-database)
which will be referenced as resource for the preferences storage.
The Null Provider discards all preferences and is mainly used as a fallback when no provider could be
created (due to permission errors, database outtakes, etc.).
Directive | Description
------------------------|------------
**type** | `db`
**resource** | The name of the database resource defined in [resources.ini](resources).
[preferences]
type=null
**Example:**
If your preferences aren't stored it's best to take a look into the logfiles - errors during the preference setup
are displayed as warnings here.
```
[preferences]
type = db
resource = icingaweb-mysql
```
### Settings
#### <a id="preferences-configuration-db-setup"></a> Database Setup
* **resource**: A reference to a database declared in *resources.ini*. Please read the chapter about
resources for a detailed description about how to set up resources.
For storing preferences in a database, you have to import one of the following database schemas:
### Preparation
* **etc/schema/preferences.mysql.sql** (for **MySQL** database)
* **etc/schema/preferences.pgsql.sql** (for **PostgreSQL** databases)
To use this feature you need a running database environment. After creating a
database and a writable user you need to import the initial table file:
* etc/schema/preferences.mysql.sql (for mysql database)
* etc/schema/preferemces.pgsql.sql (for postgres databases)
#### Example for mysql
# mysql -u root -p
mysql> create database icingaweb;
mysql> GRANT SELECT,INSERT,UPDATE,DELETE ON icingaweb.* TO \
'icingaweb'@'localhost' IDENTIFIED BY 'icingaweb';
mysql> exit
# mysql -u root -p icingaweb < /path/to/icingaweb/etc/schema/preferences.mysql.sql
After following these steps above you can configure your preferences provider.
## Coding API
You can set, update or remove preferences using the Preference data object
which is bound to the user. Here are some simple examples how to work with
that:
$preferences = $user->getPreferences();
// Get language with en_US as fallback
$preferences->get('app.language', 'en_US');
$preferences->set('app.language', 'de_DE');
$preferences->remove('app.language');
// Using transactional mode
$preferences->startTransaction();
$preferences->set('test.pref1', 'pref1');
$preferences->set('test.pref2', 'pref2');
$preferences->remove('test.pref3');
$preferemces->commit(); // Stores 3 changes in one operation
More information can be found in the api docs.
## Namespaces and behaviour
If you are using this API please obey the following rules:
* Use dotted notation for preferences
* Namespaces starting with one context identifier
* **app** as global identified (e.g. app.language)
* **mymodule** for your module
* **monitoring** for the monitoring module
* Use preferences wisely (set only when needed and write small settings)
* Use only simple data types, e.g. strings or numbers
* If you need complex types you have to do it your self (e.g. serialization)
After that you have to define the [database resource](#resources-configuration-database).

View File

@ -1,5 +0,0 @@
Icinga Web 2
@link https://www.icinga.org/icingaweb2/
@copyright Copyright (c) 2013-%(YEAR)s Icinga Development Team (https://www.icinga.org)
@license http://www.gnu.org/licenses/gpl-2.0.txt, or any later version

View File

@ -23,16 +23,17 @@ class String
}
/**
* Uppercase the first character of each word in a string assuming and removing the underscore as word separator
* Uppercase the first character of each word in a string
*
* Converts 'first_name' to 'firstName' for example.
*
* @param string $name
* @param string $separator Word separator
*
* @return string
*/
public static function cname($name)
public static function cname($name, $separator = '_')
{
return str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($name))));
return str_replace(' ', '', ucwords(str_replace($separator, ' ', strtolower($name))));
}
}

View File

@ -4,7 +4,6 @@
namespace Icinga\Util;
use Exception;
use Icinga\Exception\IcingaException;
/**
@ -34,8 +33,8 @@ class Translator
*
* Falls back to the default domain in case the string cannot be translated using the given domain
*
* @param string $text The string to translate
* @param string $domain The primary domain to use
* @param string $text The string to translate
* @param string $domain The primary domain to use
* @param string|null $context Optional parameter for context based translation
*
* @return string The translated string
@ -64,7 +63,7 @@ class Translator
*
* @param string $textSingular The string in singular form to translate
* @param string $textPlural The string in plural form to translate
* @param integer $number The number to get the plural or singular string
* @param integer $number The amount to determine from whether to return singular or plural
* @param string $domain The primary domain to use
* @param string|null $context Optional parameter for context based translation
*

View File

@ -9,6 +9,7 @@ use Zend_Config;
use Zend_Form;
use Zend_View_Interface;
use Icinga\Application\Icinga;
use Icinga\Util\Translator;
use Icinga\Web\Form\Decorator\NoScriptApply;
use Icinga\Web\Form\Element\CsrfCounterMeasure;
@ -147,7 +148,7 @@ class Form extends Zend_Form
/**
* Set a callback that is called instead of this form's onSuccess method
*
* It is called using the following signature: (Request $request, Form $form).
* It is called using the following signature: (Form $this).
*
* @param callable $onSuccess Callback
*
@ -804,6 +805,57 @@ class Form extends Zend_Form
return array();
}
/**
* Return the translation domain for this form
*
* The returned translation domain is either determined based on
* this form's class path or it is the default `icinga' domain
*
* @return string
*/
protected function getTranslationDomain()
{
if (preg_match('@^Icinga\\\\Module\\\\([A-z]+)\\\\.*$@', get_called_class(), $matches) === 1) {
return strtolower($matches[0]);
}
return 'icinga';
}
/**
* Translate a string
*
* @param string $text The string to translate
* @param string|null $context Optional parameter for context based translation
*
* @return string The translated string
*/
protected function translate($text, $context = null)
{
return Translator::translate($text, $this->getTranslationDomain(), $context);
}
/**
* Translate a plural string
*
* @param string $textSingular The string in singular form to translate
* @param string $textPlural The string in plural form to translate
* @param integer $number The amount to determine from whether to return singular or plural
* @param string|null $context Optional parameter for context based translation
*
* @return string The translated string
*/
protected function translatePlural($textSingular, $textPlural, $number, $context = null)
{
return Translator::translatePlural(
$textSingular,
$textPlural,
$number,
$this->getTranslationDomain(),
$context
);
}
/**
* Render this form
*

5
library/Icinga/Web/Session/PhpSession.php Normal file → Executable file
View File

@ -78,8 +78,9 @@ class PhpSession extends Session
}
}
if (!is_writable(session_save_path())) {
throw new ConfigurationError('Can\'t save session');
$sessionSavePath = session_save_path();
if (session_module_name() === 'files' && !is_writable($sessionSavePath)) {
throw new ConfigurationError("Can't save session, path '$sessionSavePath' is not writable.");
}
if ($this->exists()) {

View File

@ -5,6 +5,7 @@
namespace Icinga\Web\Widget\Chart;
use Icinga\Chart\PieChart;
use Icinga\Module\Monitoring\Plugin\PerfdataSet;
use Icinga\Web\Widget\AbstractWidget;
use Icinga\Web\Url;
use Icinga\Util\Format;
@ -28,36 +29,23 @@ class InlinePie extends AbstractWidget
const NUMBER_FORMAT_RATIO = 'ratio';
/**
* The template string used for rendering this widget
* The template string used for rendering this widget
*
* @var string
*/
private $template =<<<'EOD'
<span
class="sparkline"
sparkTitle="{title}"
sparkWidth="{width}"
sparkHeight="{height}"
sparkBorderWidth="{borderWidth}"
sparkBorderColor="{borderColor}"
sparkTooltipChartTitle="{title}"
style="{style}"
labels="{labels}"
formatted="{formatted}"
hideEmptyLabel={hideEmptyLabel}
values="{data}"
tooltipFormat="{tooltipFormat}"
sparkSliceColors="[{colors}]"
sparkType="pie"></span>
<noscript>
<img class="inlinepie"
title="{title}" src="{url}" style="position: relative; top: 10px; width: {width}px; height: {height}px; {style}"
data-icinga-colors="{colors}" data-icinga-values="{data}"
/>
</noscript>
<span sparkType="pie" class="sparkline {class}" {title} {size} sparkSliceColors="[{colors}]" values="{data}">
</span>
{noscript}
EOD;
private $noscript =<<<'EOD'
<noscript>
<img class="inlinepie {class}" {title} src="{url}" data-icinga-colors="{colors}" data-icinga-values="{data}"/>
</noscript>
EOD;
/**
* @var Url
*/
@ -68,35 +56,7 @@ EOD;
*
* @var array
*/
private $colors = array('#44bb77', '#ffaa44', '#ff5566', '#ddccdd');
/**
* The width of the rendered chart
*
* @var int The value in px
*/
private $width = 16;
/**
* The height of the rendered chart
*
* @var int The value in px
*/
private $height = 16;
/**
* PieChart border width
*
* @var float
*/
private $borderWidth = 1;
/**
* The color of the border
*
* @var string
*/
private $borderColor = '#fff';
private $colors = array('#049BAF', '#ffaa44', '#ff5566', '#ddccdd');
/**
* The title of the chart
@ -106,11 +66,9 @@ EOD;
private $title;
/**
* The style for the HtmlElement
*
* @var string
* @var
*/
private $style = '';
private $size;
/**
* The data displayed by the pie-chart
@ -120,45 +78,9 @@ EOD;
private $data;
/**
* The labels to display for each data set
*
* @var array
* @var
*/
private $labels = array();
/**
* If the tooltip for the "empty" area should be hidden
*
* @var bool
*/
private $hideEmptyLabel = false;
/**
* The format string used to display tooltips
*
* @var string
*/
private $tooltipFormat = '<b>{{title}}</b></br> {{label}}: {{formatted}} ({{percent}}%)';
/**
* The number format used to render numeric values in tooltips
*
* @var array
*/
private $format = self::NUMBER_FORMAT_NONE;
/**
* Set if the tooltip for the empty area should be hidden
*
* @param bool $hide Whether to hide the empty area
*
* @return $this
*/
public function setHideEmptyLabel($hide = true)
{
$this->hideEmptyLabel = $hide;
return $this;
}
private $class = '';
/**
* Set the data to be displayed.
@ -175,24 +97,36 @@ EOD;
}
/**
* The labels to be displayed in the pie-chart
* Set the size of the inline pie
*
* @param mixed $label The label of the displayed value, or null for no labels
* @param int $size Sets both, the height and width
*
* @return $this
* @return $this
*/
public function setLabel($label)
public function setSize($size = null)
{
if (is_array($label)) {
$this->url->setParam('labels', implode(',', array_keys($label)));
} elseif ($label != null) {
$labelArr = array($label, $label, $label, '');
$this->url->setParam('labels', implode(',', $labelArr));
$label = $labelArr;
} else {
$this->url->removeKey('labels');
}
$this->labels = $label;
$this->size = $size;
return $this;
}
/**
* Do not display the NoScript fallback html
*/
public function disableNoScript()
{
$this->noscript = '';
}
/**
* Set the class to define the
*
* @param $class
*
* @return $this
*/
public function setSparklineClass($class)
{
$this->class = $class;
return $this;
}
@ -214,105 +148,6 @@ EOD;
return $this;
}
/**
* Set the used number format
*
* @param $format string 'bytes' or 'time'
*
* @return $this
*/
public function setNumberFormat($format)
{
$this->format = $format;
return $this;
}
/**
* A format string used to render the content of the piechart tooltips
*
* Placeholders using curly braces '{FOO}' are replace with their specific values. The format
* String may contain HTML-Markup. The available replaceable values are:
* <ul>
* <li><b>label</b>: The description for the current value </li>
* <li><b>formatted</b>: A string representing the formatted value </li>
* <li><b>value</b>: The raw (non-formatted) value used to render the piechart </li>
* <li><b>percent</b>: The percentage of the current value </li>
* </ul>
* Note: Changes will only affect JavaScript sparklines and not the SVG charts used for fallback
*
* @param $format
*
* @return $this
*/
public function setTooltipFormat($format)
{
$this->tooltipFormat = $format;
return $this;
}
/**
* Set the height
*
* @param $height
*
* @return $this
*/
public function setHeight($height)
{
$this->height = $height;
return $this;
}
/**
* Set the border width of the pie chart
*
* @param float $width Width in px
*
* @return $this
*/
public function setBorderWidth($width)
{
$this->borderWidth = $width;
return $this;
}
/**
* Set the color of the pie chart border
*
* @param string $col The color string
*
* @return $this
*/
public function setBorderColor($col)
{
$this->borderColor = $col;
}
/**
* Set the width
*
* @param $width
*
* @return $this
*/
public function setWidth($width)
{
$this->width = $width;
return $this;
}
/**
* Set the styling of the created HtmlElement
*
* @param string $style
*
* @return $this
*/
public function setStyle($style)
{
$this->style = $style;
}
/**
* Set the title of the displayed Data
*
@ -322,7 +157,7 @@ EOD;
*/
public function setTitle($title)
{
$this->title = $title;
$this->title = 'title="' . htmlspecialchars($title) . '"';
return $this;
}
@ -335,13 +170,10 @@ EOD;
*/
public function __construct(array $data, $title, $colors = null)
{
$this->title = $title;
$this->setTitle($title);
$this->url = Url::fromPath('svg/chart.php');
if (array_key_exists('data', $data)) {
$this->data = $data['data'];
if (array_key_exists('labels', $data)) {
$this->labels = $data['labels'];
}
if (array_key_exists('colors', $data)) {
$this->colors = $data['colors'];
}
@ -354,21 +186,6 @@ EOD;
$this->setColors($this->colors);
}
}
/**
* Create a serialization containing the current label array
*
* @return string A serialized array of labels
*/
private function createLabelString ()
{
$labels = $this->labels;
foreach ($labels as $key => $label) {
$labels[$key] = str_replace('|', '', $label);
}
return isset($this->labels) && is_array($this->labels) ? implode('|', $this->labels) : '';
}
/**
* Renders this widget via the given view and returns the
* HTML as a string
@ -382,11 +199,11 @@ EOD;
$pie->alignTopLeft();
$pie->disableLegend();
$pie->drawPie(array(
'data' => $this->data, 'colors' => $this->colors, 'labels' => $this->labels
'data' => $this->data, 'colors' => $this->colors
));
try {
$png = $pie->toPng($this->width, $this->height);
$png = $pie->toPng($this->size, $this->size);
return '<img class="inlinepie" src="data:image/png;base64,' . base64_encode($png) . '" />';
} catch (IcingaException $_) {
return '';
@ -394,17 +211,17 @@ EOD;
}
$template = $this->template;
// TODO: Check whether we are XHR and don't send
$template = str_replace('{noscript}', $this->noscript, $template);
$template = str_replace('{url}', $this->url, $template);
$template = str_replace('{class}', $this->class, $template);
// style
$template = str_replace('{width}', $this->width, $template);
$template = str_replace('{height}', $this->height, $template);
$template = str_replace('{title}', htmlspecialchars($this->title), $template);
$template = str_replace('{style}', $this->style, $template);
$template = str_replace('{size}',
isset($this->size) ? 'sparkWidth="' . $this->size . '" sparkHeight="' . $this->size . '" ' : '', $template);
$template = str_replace('{title}', $this->title, $template);
$template = str_replace('{colors}', implode(',', $this->colors), $template);
$template = str_replace('{borderWidth}', $this->borderWidth, $template);
$template = str_replace('{borderColor}', $this->borderColor, $template);
$template = str_replace('{hideEmptyLabel}', $this->hideEmptyLabel ? 'true' : 'false', $template);
// Locale-ignorant string cast. Please. Do. NOT. Remove. This. Again.
// Problem is that implode respects locales when casting floats. This means
@ -414,38 +231,7 @@ EOD;
$data[] = sprintf('%F', $dat);
}
// values
$formatted = array();
foreach ($this->data as $key => $value) {
$formatted[$key] = $this->formatValue($value);
}
$template = str_replace('{data}', htmlspecialchars(implode(',', $data)), $template);
$template = str_replace('{formatted}', htmlspecialchars(implode('|', $formatted)), $template);
$template = str_replace('{labels}', htmlspecialchars($this->createLabelString()), $template);
$template = str_replace('{tooltipFormat}', $this->tooltipFormat, $template);
return $template;
}
/**
* Format the given value depending on the current value of numberFormat
*
* @param float $value The value to format
*
* @return string The formatted value
*/
private function formatValue($value)
{
if ($this->format === self::NUMBER_FORMAT_NONE) {
return (string)$value;
} elseif ($this->format === self::NUMBER_FORMAT_BYTES) {
return Format::bytes($value);
} elseif ($this->format === self::NUMBER_FORMAT_TIME) {
return Format::duration($value);
} elseif ($this->format === self::NUMBER_FORMAT_RATIO) {
return $value;
} else {
Logger::warning('Unknown format string "' . $this->format . '" for InlinePie, value not formatted.');
return $value;
}
}
}

View File

@ -4,6 +4,7 @@
namespace Icinga\Web\Widget\Tabextension;
use Icinga\Application\Platform;
use Icinga\Web\Url;
use Icinga\Web\Widget\Tab;
use Icinga\Web\Widget\Tabs;
@ -28,35 +29,6 @@ class OutputFormat implements Tabextension
*/
const TYPE_CSV = 'csv';
/**
* An array containing the tab definitions for all supported types
*
* Using array_keys on this array or isset allows to check whether a
* requested type is supported
*
* @var array
*/
private $supportedTypes = array(
self::TYPE_PDF => array(
'name' => 'pdf',
'title' => 'PDF',
'icon' => 'file-pdf',
'urlParams' => array('format' => 'pdf'),
),
self::TYPE_CSV => array(
'name' => 'csv',
'title' => 'CSV',
'icon' => 'file-excel',
'urlParams' => array('format' => 'csv')
),
self::TYPE_JSON => array(
'name' => 'json',
'title' => 'JSON',
'icon' => 'img/icons/json.png',
'urlParams' => array('format' => 'json')
)
);
/**
* An array of tabs to be added to the dropdown area
*
@ -74,7 +46,7 @@ class OutputFormat implements Tabextension
*/
public function __construct(array $disabled = array())
{
foreach ($this->supportedTypes as $type => $tabConfig) {
foreach ($this->getSupportedTypes() as $type => $tabConfig) {
if (!in_array($type, $disabled)) {
$tabConfig['url'] = Url::fromRequest();
$tabConfig['tagParams'] = array(
@ -98,4 +70,44 @@ class OutputFormat implements Tabextension
$tabs->addAsDropdown($tab->getName(), $tab);
}
}
/**
* Return an array containing the tab definitions for all supported types
*
* Using array_keys on this array or isset allows to check whether a
* requested type is supported
*
* @return array
*/
public function getSupportedTypes()
{
$supportedTypes = array();
if (Platform::extensionLoaded('gd')) {
$supportedTypes[self::TYPE_PDF] = array(
'name' => 'pdf',
'title' => 'PDF',
'icon' => 'file-pdf',
'urlParams' => array('format' => 'pdf'),
);
}
$supportedTypes[self::TYPE_CSV] = array(
'name' => 'csv',
'title' => 'CSV',
'icon' => 'file-excel',
'urlParams' => array('format' => 'csv')
);
if (Platform::extensionLoaded('json')) {
$supportedTypes[self::TYPE_JSON] = array(
'name' => 'json',
'title' => 'JSON',
'icon' => 'img/icons/json.png',
'urlParams' => array('format' => 'json')
);
}
return $supportedTypes;
}
}

View File

@ -72,8 +72,10 @@ class Monitoring_HostsController extends Controller
'host_obsessing'*/
));
$unhandledObjects = array();
$unhandledFilterExpressions = array();
$acknowledgedObjects = array();
$objectsInDowntime = array();
$downtimeFilterExpressions = array();
$hostStates = array(
Host::getStateText(Host::STATE_UP) => 0,
Host::getStateText(Host::STATE_DOWN) => 0,
@ -81,15 +83,17 @@ class Monitoring_HostsController extends Controller
Host::getStateText(Host::STATE_PENDING) => 0,
);
foreach ($this->hostList as $host) {
/** @var Service $host */
/** @var Host $host */
if ((bool) $host->problem === true && (bool) $host->handled === false) {
$unhandledObjects[] = $host;
$unhandledFilterExpressions[] = Filter::where('host', $host->getName());
}
if ((bool) $host->acknowledged === true) {
$acknowledgedObjects[] = $host;
}
if ((bool) $host->in_downtime === true) {
$objectsInDowntime[] = $host;
$downtimeFilterExpressions[] = Filter::where('downtime_host', $host->getName());
}
++$hostStates[$host::getStateText($host->state)];
}
@ -108,16 +112,15 @@ class Monitoring_HostsController extends Controller
$this->view->hostStates = $hostStates;
$this->view->objects = $this->hostList;
$this->view->unhandledObjects = $unhandledObjects;
$this->view->acknowledgeUnhandledLink = Url::fromRequest()
->setPath('monitoring/hosts/acknowledge-problem')
->addParams(array('host_problem' => 1, 'host_handled' => 0));
$this->view->downtimeUnhandledLink = Url::fromRequest()
->setPath('monitoring/hosts/schedule-downtime')
->addParams(array('host_problem' => 1, 'host_handled' => 0));
$unhandledFilterQueryString = Filter::matchAny($unhandledFilterExpressions)->toQueryString();
$this->view->acknowledgeUnhandledLink = Url::fromPath('monitoring/hosts/acknowledge-problem')
->setQueryString($unhandledFilterQueryString);
$this->view->downtimeUnhandledLink = Url::fromPath('monitoring/hosts/schedule-downtime')
->setQueryString($unhandledFilterQueryString);
$this->view->acknowledgedObjects = $acknowledgedObjects;
$this->view->objectsInDowntime = $objectsInDowntime;
$this->view->inDowntimeLink = Url::fromRequest()
->setPath('monitoring/list/downtimes');
$this->view->inDowntimeLink = Url::fromPath('monitoring/list/downtimes')
->setQueryString(Filter::matchAny($downtimeFilterExpressions)->toQueryString());
$this->view->havingCommentsLink = Url::fromRequest()
->setPath('monitoring/list/comments');
$this->view->hostStatesPieChart = $this->createPieChart(
@ -131,9 +134,6 @@ class Monitoring_HostsController extends Controller
{
$chart = new InlinePie(array_values($states), $title, $colors);
return $chart
->setLabel(array_map('strtoupper', array_keys($states)))
->setHeight(100)
->setWidth(100)
->setTitle($title);
}

View File

@ -221,7 +221,7 @@ class Monitoring_MultiController extends Controller
private function createPie($states, $colors, $title)
{
$chart = new InlinePie(array_values($states), $title, $colors);
$chart->setLabel(array_keys($states))->setHeight(100)->setWidth(100);
$chart->setLabel(array_keys($states))->setSize(100);
$chart->setTitle($title);
return $chart;
}

View File

@ -74,8 +74,10 @@ class Monitoring_ServicesController extends Controller
'service_obsessing'*/
));
$unhandledObjects = array();
$unhandledFilterExpressions = array();
$acknowledgedObjects = array();
$objectsInDowntime = array();
$downtimeFilterExpressions = array();
$serviceStates = array(
Service::getStateText(Service::STATE_OK) => 0,
Service::getStateText(Service::STATE_WARNING) => 0,
@ -94,12 +96,20 @@ class Monitoring_ServicesController extends Controller
/** @var Service $service */
if ((bool) $service->problem === true && (bool) $service->handled === false) {
$unhandledObjects[] = $service;
$unhandledFilterExpressions[] = Filter::matchAll(
Filter::where('host', $service->getHost()->getName()),
Filter::where('service', $service->getName())
);
}
if ((bool) $service->acknowledged === true) {
$acknowledgedObjects[] = $service;
}
if ((bool) $service->in_downtime === true) {
$objectsInDowntime[] = $service;
$downtimeFilterExpressions[] = Filter::matchAll(
Filter::where('downtime_host', $service->getHost()->getName()),
Filter::where('downtime_service', $service->getName())
);
}
++$serviceStates[$service::getStateText($service->state)];
if (! isset($knownHostStates[$service->getHost()->getName()])) {
@ -125,16 +135,15 @@ class Monitoring_ServicesController extends Controller
$this->view->serviceStates = $serviceStates;
$this->view->objects = $this->serviceList;
$this->view->unhandledObjects = $unhandledObjects;
$this->view->acknowledgeUnhandledLink = Url::fromRequest()
->setPath('monitoring/services/acknowledge-problem')
->addParams(array('service_problem' => 1, 'service_handled' => 0));
$this->view->downtimeUnhandledLink = Url::fromRequest()
->setPath('monitoring/services/schedule-downtime')
->addParams(array('service_problem' => 1, 'service_handled' => 0));
$unhandledFilterQueryString = Filter::matchAny($unhandledFilterExpressions)->toQueryString();
$this->view->acknowledgeUnhandledLink = Url::fromPath('monitoring/services/acknowledge-problem')
->setQueryString($unhandledFilterQueryString);
$this->view->downtimeUnhandledLink = Url::fromPath('monitoring/services/schedule-downtime')
->setQueryString($unhandledFilterQueryString);
$this->view->acknowledgedObjects = $acknowledgedObjects;
$this->view->objectsInDowntime = $objectsInDowntime;
$this->view->inDowntimeLink = Url::fromRequest()
->setPath('monitoring/list/downtimes');
$this->view->inDowntimeLink = Url::fromPath('monitoring/list/downtimes')
->setQueryString(Filter::matchAny($downtimeFilterExpressions)->toQueryString());
$this->view->havingCommentsLink = Url::fromRequest()
->setPath('monitoring/list/comments');
$this->view->serviceStatesPieChart = $this->createPieChart(
@ -153,10 +162,10 @@ class Monitoring_ServicesController extends Controller
{
$chart = new InlinePie(array_values($states), $title, $colors);
return $chart
->setLabel(array_map('strtoupper', array_keys($states)))
->setHeight(100)
->setWidth(100)
->setTitle($title);
// ->setLabel(array_map('strtoupper', array_keys($states)))
->setSize(50)
->setTitle($title)
->setSparklineClass('sparkline-multi');
}
/**

View File

@ -104,7 +104,7 @@ class AcknowledgeProblemCommandForm extends ObjectsCommandForm
array(
'decorators' => array(
'FormElements',
array('HtmlTag', array('tag' => 'div', 'class' => 'control-group'))
array('HtmlTag', array('tag' => 'div'))
)
)
);

View File

@ -130,7 +130,7 @@ class ScheduleServiceDowntimeCommandForm extends ObjectsCommandForm
array(
'decorators' => array(
'FormElements',
array('HtmlTag', array('tag' => 'div', 'class' => 'control-group'))
array('HtmlTag', array('tag' => 'div'))
)
)
);
@ -169,7 +169,7 @@ class ScheduleServiceDowntimeCommandForm extends ObjectsCommandForm
),
'decorators' => array(
'FormElements',
array('HtmlTag', array('tag' => 'div', 'class' => 'control-group')),
array('HtmlTag', array('tag' => 'div')),
array(
'Description',
array('tag' => 'span', 'class' => 'description', 'placement' => 'prepend')

View File

@ -9,33 +9,36 @@ use Icinga\Module\Monitoring\Plugin\PerfdataSet;
class Zend_View_Helper_Perfdata extends Zend_View_Helper_Abstract
{
public function perfdata($perfdataStr, $compact = false)
/**
* Display the given perfdata string to the user
*
* @param $perfdataStr The perfdata string
* @param bool $compact Whether to display the perfdata in compact mode
* @param $color The color indicating the perfdata state
*
* @return string
*/
public function perfdata($perfdataStr, $compact = false, $color = Perfdata::PERFDATA_DEFAULT)
{
$pset = PerfdataSet::fromString($perfdataStr)->asArray();
$onlyPieChartData = array_filter($pset, function ($e) { return $e->getPercentage() > 0; });
if ($compact) {
$onlyPieChartData = array_slice($onlyPieChartData, 0, 5);
} else {
$nonPieChartData = array_filter($pset, function ($e) { return $e->getPercentage() == 0; });
}
$pieChartData = PerfdataSet::fromString($perfdataStr)->asArray();
$result = '';
$table = array();
foreach ($onlyPieChartData as $perfdata) {
$pieChart = $this->createInlinePie($perfdata);
if ($compact) {
$result .= $pieChart->render();
} else {
if (! $perfdata->isPercentage()) {
// TODO: Should we trust sprintf-style placeholders in perfdata titles?
$pieChart->setTooltipFormat('{{label}}: {{formatted}} ({{percent}}%)');
foreach ($pieChartData as $perfdata) {
if ($perfdata->isVisualizable()) {
$pieChart = $perfdata->asInlinePie($color);
if ($compact) {
$result .= $pieChart->render();
} else {
$table[] = '<tr><th>' . $pieChart->render()
. htmlspecialchars($perfdata->getLabel())
. '</th><td> '
. htmlspecialchars($this->formatPerfdataValue($perfdata)) .
' </td></tr>';
}
// $pieChart->setStyle('margin: 0.2em 0.5em 0.2em 0.5em;');
$table[] = '<tr><th>' . $pieChart->render()
. htmlspecialchars($perfdata->getLabel())
. '</th><td> '
. htmlspecialchars($this->formatPerfdataValue($perfdata)) .
' </td></tr>';
} else {
$table[] = (string)$perfdata;
}
}
@ -43,32 +46,10 @@ class Zend_View_Helper_Perfdata extends Zend_View_Helper_Abstract
return $result;
} else {
$pieCharts = empty($table) ? '' : '<table class="perfdata">' . implode("\n", $table) . '</table>';
return $pieCharts . "\n" . implode("<br>\n", $nonPieChartData);
return $pieCharts;
}
}
protected function calculatePieChartData(Perfdata $perfdata)
{
$rawValue = $perfdata->getValue();
$minValue = $perfdata->getMinimumValue() !== null ? $perfdata->getMinimumValue() : 0;
$maxValue = $perfdata->getMaximumValue();
$usedValue = ($rawValue - $minValue);
$unusedValue = ($maxValue - $minValue) - $usedValue;
$gray = $unusedValue;
$green = $orange = $red = 0;
// TODO(#6122): Add proper treshold parsing.
if ($perfdata->getCriticalThreshold() && $perfdata->getValue() > $perfdata->getCriticalThreshold()) {
$red = $usedValue;
} elseif ($perfdata->getWarningThreshold() && $perfdata->getValue() > $perfdata->getWarningThreshold()) {
$orange = $usedValue;
} else {
$green = $usedValue;
}
return array($green, $orange, $red, $gray);
}
protected function formatPerfdataValue(Perfdata $perfdata)
{
if ($perfdata->isBytes()) {
@ -82,24 +63,4 @@ class Zend_View_Helper_Perfdata extends Zend_View_Helper_Abstract
return $perfdata->getValue();
}
protected function createInlinePie(Perfdata $perfdata)
{
$pieChart = new InlinePie($this->calculatePieChartData($perfdata), $perfdata->getLabel());
$pieChart->setLabel(htmlspecialchars($perfdata->getLabel()));
$pieChart->setHideEmptyLabel();
//$pieChart->setHeight(32)->setWidth(32);
if ($perfdata->isBytes()) {
$pieChart->setTooltipFormat('{{label}}: {{formatted}} ({{percent}}%)');
$pieChart->setNumberFormat(InlinePie::NUMBER_FORMAT_BYTES);
} else if ($perfdata->isSeconds()) {
$pieChart->setTooltipFormat('{{label}}: {{formatted}} ({{percent}}%)');
$pieChart->setNumberFormat(InlinePie::NUMBER_FORMAT_TIME);
} else {
$pieChart->setTooltipFormat('{{label}}: {{formatted}}%');
$pieChart->setNumberFormat(InlinePie::NUMBER_FORMAT_RATIO);
$pieChart->setHideEmptyLabel();
}
return $pieChart;
}
}

View File

@ -53,7 +53,7 @@ use Icinga\Module\Monitoring\Object\Service;
</td>
<td>
<?php if (isset($downtime->service)): ?>
<a href="<?= $this->href('monitoring/service/show', array(
<?= $this->icon('service'); ?> <a href="<?= $this->href('monitoring/service/show', array(
'host' => $downtime->host,
'service' => $downtime->service
)); ?>">
@ -63,7 +63,7 @@ use Icinga\Module\Monitoring\Object\Service;
<?= $this->translate('on'); ?> <?= $downtime->host; ?>
</small>
<?php else: ?>
<a href="<?= $this->href('monitoring/host/show', array(
<?= $this->icon('host'); ?> <a href="<?= $this->href('monitoring/host/show', array(
'host' => $downtime->host
)); ?>">
<?= $downtime->host; ?>
@ -76,7 +76,9 @@ use Icinga\Module\Monitoring\Object\Service;
<?php if ($downtime->is_flexible): ?>
<?php if ($downtime->is_in_effect): ?>
<?= sprintf(
$this->translate('This flexible downtime was started on %s at %s and lasts for %s until %s at %s.'),
isset($downtime->service)
? $this->translate('This flexible service downtime was started on %s at %s and lasts for %s until %s at %s.')
: $this->translate('This flexible host downtime was started on %s at %s and lasts for %s until %s at %s.'),
date('d.m.y', $downtime->start),
date('H:i', $downtime->start),
$this->format()->duration($downtime->duration),
@ -85,7 +87,9 @@ use Icinga\Module\Monitoring\Object\Service;
); ?>
<?php else: ?>
<?= sprintf(
$this->translate('This flexible downtime has been scheduled to start between %s - %s and to last for %s.'),
isset($downtime->service)
? $this->translate('This flexible service downtime has been scheduled to start between %s - %s and to last for %s.')
: $this->translate('This flexible host downtime has been scheduled to start between %s - %s and to last for %s.'),
date('d.m.y H:i', $downtime->scheduled_start),
date('d.m.y H:i', $downtime->scheduled_end),
$this->format()->duration($downtime->duration)
@ -94,7 +98,9 @@ use Icinga\Module\Monitoring\Object\Service;
<?php else: ?>
<?php if ($downtime->is_in_effect): ?>
<?= sprintf(
$this->translate('This fixed downtime was started on %s at %s and expires on %s at %s.'),
isset($downtime->service)
? $this->translate('This fixed service downtime was started on %s at %s and expires on %s at %s.')
: $this->translate('This fixed host downtime was started on %s at %s and expires on %s at %s.'),
date('d.m.y', $downtime->start),
date('H:i', $downtime->start),
date('d.m.y', $downtime->end),
@ -102,7 +108,9 @@ use Icinga\Module\Monitoring\Object\Service;
); ?>
<?php else: ?>
<?= sprintf(
$this->translate('This fixed downtime has been scheduled to start on %s at %s and to end on %s at %s.'),
isset($downtime->service)
? $this->translate('This fixed service downtime has been scheduled to start on %s at %s and to end on %s at %s.')
: $this->translate('This fixed host downtime has been scheduled to start on %s at %s and to end on %s at %s.'),
date('d.m.y', $downtime->scheduled_start),
date('H:i', $downtime->scheduled_start),
date('d.m.y', $downtime->scheduled_end),

View File

@ -108,10 +108,9 @@ if ($hosts->count() === 0) {
$host->host_unhandled_services),
'monitoring/show/services',
array(
'host' => $host->host_name,
'service_problem' => 1,
'service_acknowledged' => 0,
'service_in_downtime' => 0
'host' => $host->host_name,
'service_problem' => 1,
'service_handled' => 0
),
array('style' => 'font-weight: normal')
) ?>)</span>

View File

@ -1,6 +1,7 @@
<?php
use Icinga\Module\Monitoring\Object\Host;
use Icinga\Module\Monitoring\Object\Service;
use Icinga\Module\Monitoring\Plugin\Perfdata;
$helper = $this->getHelper('MonitoringState');

View File

@ -518,12 +518,14 @@ class StatusQuery extends IdoQuery
protected function joinServiceproblemsummary()
{
$sub = new Zend_Db_Expr('(SELECT'
. ' SUM(CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth) > 0 THEN 0 ELSE 1 END) AS unhandled_services_count,'
. ' SUM(CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END) AS handled_services_count,'
. ' SUM(CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) > 0 THEN 0 ELSE 1 END) AS unhandled_services_count,'
. ' SUM(CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) > 0 THEN 1 ELSE 0 END) AS handled_services_count,'
. ' s.host_object_id FROM icinga_servicestatus ss'
. ' JOIN icinga_services s'
. ' ON s.service_object_id = ss.service_object_id'
. ' AND ss.current_state > 0'
. ' JOIN icinga_hoststatus hs'
. ' ON hs.host_object_id = s.host_object_id'
. ' GROUP BY s.host_object_id)');
$this->select->joinLeft(
array('sps' => $sub),

View File

@ -30,7 +30,7 @@ class IcingaCommandFileCommandRenderer implements IcingaCommandRendererInterface
*
* @return string
*/
public function escape($commandString)
protected function escape($commandString)
{
return str_replace(array("\r", "\n"), array('\r', '\n'), $commandString);
}
@ -52,7 +52,7 @@ class IcingaCommandFileCommandRenderer implements IcingaCommandRendererInterface
if ($now === null) {
$now = time();
}
return sprintf('[%u] %s', $now, $this->$renderMethod($command));
return sprintf('[%u] %s', $now, $this->escape($this->$renderMethod($command)));
}
public function renderAddComment(AddCommentCommand $command)

View File

@ -5,9 +5,15 @@
namespace Icinga\Module\Monitoring\Plugin;
use InvalidArgumentException;
use Icinga\Exception\ProgrammingError;
use Icinga\Web\Widget\Chart\InlinePie;
use Zend_Controller_Front;
class Perfdata
{
const PERFDATA_DEFAULT = 'green';
const PERFDATA_RED = 'red';
/**
* The performance data value being parsed
*
@ -159,6 +165,16 @@ class Perfdata
return $this->unit === 'c';
}
/**
* Returns whether it is possible to display a visual representation
*
* @return bool True when the perfdata is visualizable
*/
public function isVisualizable()
{
return isset($this->minValue) && isset($this->maxValue) && isset($this->value);
}
/**
* Return this perfomance data's label
*/
@ -316,4 +332,50 @@ class Perfdata
}
}
}
protected function calculatePieChartData( $color)
{
$rawValue = $this->getValue();
$minValue = $this->getMinimumValue() !== null ? $this->getMinimumValue() : 0;
$maxValue = $this->getMaximumValue();
$usedValue = ($rawValue - $minValue);
$unusedValue = ($maxValue - $minValue) - $usedValue;
$gray = $unusedValue;
$green = $orange = $red = 0;
switch ($color) {
case self::PERFDATA_DEFAULT:
$green = $usedValue;
break;
case self::PERFDATA_RED:
$red = $usedValue;
break;
case self::PERFDATA_ORANGE:
$orange = $usedValue;
break;
}
// TODO(#6122): Add proper treshold parsing.
return array($green, $orange, $red, $gray);
}
public function asInlinePie($color)
{
if (! $this->isVisualizable()) {
throw new ProgrammingError('Cannot calculate piechart data for unvisualizable perfdata entry.');
}
$data = $this->calculatePieChartData($color);
$pieChart = new InlinePie($data, $this->getLabel() . ' ' . number_format($this->getPercentage(), 2) . '%');
$pieChart->setSparklineClass('sparkline-perfdata');
if (Zend_Controller_Front::getInstance()->getRequest()->isXmlHttpRequest()) {
$pieChart->disableNoScript();
}
return $pieChart;
}
}

View File

@ -0,0 +1,63 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Tests\Icinga\Regression;
use Icinga\Test\BaseTestCase;
use Icinga\Module\Monitoring\Command\IcingaCommand;
use Icinga\Module\Monitoring\Command\Renderer\IcingaCommandFileCommandRenderer;
/**
* A command that has a hardcoded parameter with newlines
*/
class Bug6088Command extends IcingaCommand
{
public function getParameterWithCarriageReturnAndLineFeed()
{
return "foo\r\nbar";
}
public function getBug()
{
return '6088';
}
}
/**
* A subclass of IcingaCommandFileCommandRenderer to utiliseIcingaCommandFileCommandRenderer
* to render an instance of Bug6088Command
*/
class Bug6088CommandFileCommandRenderer extends IcingaCommandFileCommandRenderer
{
public function renderBug6088(Bug6088Command $command)
{
return 'SOLVE_BUG;' . $command->getBug() . ';' . $command->getParameterWithCarriageReturnAndLineFeed();
}
}
/**
* Class Bug6088
*
* Multi-line comments don't work
*
* @see https://dev.icinga.org/issues/6088
*/
class Bug6088Test extends BaseTestCase
{
public function testWhetherCommandParametersWithMultipleLinesAreProperlyEscaped()
{
$command = new Bug6088Command();
$renderer = new Bug6088CommandFileCommandRenderer();
$commandString = $renderer->render($command);
$this->assertEquals(
'SOLVE_BUG;6088;foo\r\nbar',
substr($commandString, strpos($commandString, ' ') + 1),
'Command parameters with multiple lines are not properly escaped'
);
}
}

View File

@ -51,9 +51,7 @@ $cliPath = realpath(Icinga::app()->getApplicationDir() . '/../bin/icingacli');
</div>
<p><?= mt('setup', 'In case the IcingaCLI is missing you can create the token manually:'); ?></p>
<div class="code">
<span>su <?= ($user = Platform::getPhpUser()) !== null ? $user : 'your_webserver_group'; ?> && mkdir -m 2770 <?= dirname($setupTokenPath); ?>;</span>
<span>head -c 12 /dev/urandom | base64 | tee <?= $setupTokenPath; ?>;</span>
<span>chmod 0660 <?= $setupTokenPath; ?>;</span>
<span>su <?= ($user = Platform::getPhpUser()) !== null ? $user : 'your_webserver_user'; ?> -c "mkdir -m 2770 <?= dirname($setupTokenPath); ?>; head -c 12 /dev/urandom | base64 | tee <?= $setupTokenPath; ?>; chmod 0660 <?= $setupTokenPath; ?>;";</span>
</div>
<p><?= sprintf(
mt('setup', 'Please see the %s for an extensive description on how to access and use this wizard.'),

View File

@ -461,8 +461,8 @@ class WebWizard extends Wizard implements SetupWizard
mt('setup', 'PHP Module: GD'),
mt(
'setup',
'In case you want icons being exported to PDF as'
. ' well, you\'ll need the GD extension for PHP.'
'In case you want views being exported to PDF,'
. ' you\'ll need the GD extension for PHP.'
),
Platform::extensionLoaded('gd'),
Platform::extensionLoaded('gd') ? mt('setup', 'The PHP module GD is available') : (

View File

@ -285,3 +285,10 @@ li li .badge {
.widgetFilter li.active {
background-color: #eee;
}
.sparkline {
width: 12px;
height: 12px;
position: relative;
top: 4px;
}

View File

@ -18,34 +18,33 @@
$('span.sparkline', el).each(function(i, element) {
// read custom options
var $spark = $(element);
var labels = $spark.attr('labels').split('|');
var formatted = $spark.attr('formatted').split('|');
var tooltipChartTitle = $spark.attr('sparkTooltipChartTitle') || '';
var format = $spark.attr('tooltipformat');
var hideEmpty = $spark.attr('hideEmptyLabel') === 'true';
$spark.sparkline(
'html',
{
var $spark = $(element);
var title = $spark.attr('title');
if ($spark.attr('labels')) {
$spark.removeAttr('original-title');
}
var options;
if ($spark.hasClass('sparkline-perfdata')) {
options = {
enableTagOptions: true,
tooltipFormatter: function (sparkline, options, fields) {
var out = format;
if (hideEmpty && fields.offset === 3) {
return '';
}
var replace = {
title: tooltipChartTitle,
label: labels[fields.offset] ? labels[fields.offset] : fields.offset,
formatted: formatted[fields.offset] ? formatted[fields.offset] : '',
value: fields.value,
percent: Math.round(fields.percent * 100) / 100
};
$.each(replace, function(key, value) {
out = out.replace('{{' + key + '}}', value);
});
return out;
}
});
width: 12,
height: 12,
title: title,
disableTooltips: true
};
$spark.sparkline('html', options);
} else if ($spark.hasClass('sparkline-multi')) {
options = {
width: 100,
height: 100,
title: title,
enableTagOptions: true
};
$spark.sparkline('html', options);
}
});
};