diff --git a/.puppet/manifests/site.pp b/.puppet/manifests/site.pp index 77ebfa0a7..08120d911 100644 --- a/.puppet/manifests/site.pp +++ b/.puppet/manifests/site.pp @@ -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 } } diff --git a/.puppet/modules/icingacli/manifests/init.pp b/.puppet/modules/icingacli/manifests/init.pp index 7eef7833a..701cb6e14 100644 --- a/.puppet/modules/icingacli/manifests/init.pp +++ b/.puppet/modules/icingacli/manifests/init.pp @@ -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, diff --git a/application/forms/Security/RoleForm.php b/application/forms/Security/RoleForm.php index 0fb53e858..b27a4ec8b 100644 --- a/application/forms/Security/RoleForm.php +++ b/application/forms/Security/RoleForm.php @@ -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; } } diff --git a/application/views/scripts/authentication/login.phtml b/application/views/scripts/authentication/login.phtml index 584d8ad16..416e651d1 100644 --- a/application/views/scripts/authentication/login.phtml +++ b/application/views/scripts/authentication/login.phtml @@ -18,7 +18,7 @@
without(...) or $role->shift(...) would be nice! - $restrictions = $role; + $restrictions = clone $role; unset($restrictions['users']); unset($restrictions['groups']); unset($restrictions['permissions']); diff --git a/doc/authentication.md b/doc/authentication.md index 994d44e48..d36051e8f 100644 --- a/doc/authentication.md +++ b/doc/authentication.md @@ -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 +## 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 +### 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 +### 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 +#### LDAP Directive | Description ------------------------|------------ @@ -52,7 +54,7 @@ user_class = inetOrgPerson user_name_attribute = uid ``` -### Active Directory +#### Active Directory Directive | Description ------------------------|------------ @@ -67,10 +69,10 @@ backend = ad resource = my_ad ``` -## Database Authentication +### 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 ``` +#### 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'); ```` diff --git a/doc/installation.md b/doc/installation.md index 88c9c3650..6eb6737ec 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -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. ```` diff --git a/doc/preferences.md b/doc/preferences.md index 0ac15034e..4ece5edf6 100644 --- a/doc/preferences.md +++ b/doc/preferences.md @@ -1,101 +1,53 @@ -# Preferences +# 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 +## 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 +### 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 +### 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 +#### 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). diff --git a/etc/license_header.txt b/etc/license_header.txt deleted file mode 100644 index db19a8436..000000000 --- a/etc/license_header.txt +++ /dev/null @@ -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 \ No newline at end of file diff --git a/library/Icinga/Util/String.php b/library/Icinga/Util/String.php index 0bebb06e7..248ab0d34 100644 --- a/library/Icinga/Util/String.php +++ b/library/Icinga/Util/String.php @@ -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)))); } } diff --git a/library/Icinga/Util/Translator.php b/library/Icinga/Util/Translator.php index 52a84bd62..87d9e4c88 100644 --- a/library/Icinga/Util/Translator.php +++ b/library/Icinga/Util/Translator.php @@ -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 * diff --git a/library/Icinga/Web/Form.php b/library/Icinga/Web/Form.php index 37d8789fe..850285baa 100644 --- a/library/Icinga/Web/Form.php +++ b/library/Icinga/Web/Form.php @@ -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 * diff --git a/library/Icinga/Web/Session/PhpSession.php b/library/Icinga/Web/Session/PhpSession.php old mode 100644 new mode 100755 index bef978c0b..fa545fd8d --- a/library/Icinga/Web/Session/PhpSession.php +++ b/library/Icinga/Web/Session/PhpSession.php @@ -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()) { diff --git a/library/Icinga/Web/Widget/Chart/InlinePie.php b/library/Icinga/Web/Widget/Chart/InlinePie.php index 618d2903b..04e3e7318 100644 --- a/library/Icinga/Web/Widget/Chart/InlinePie.php +++ b/library/Icinga/Web/Widget/Chart/InlinePie.php @@ -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' - - + + +{noscript} EOD; + private $noscript =<<<'EOD' + +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 = '{{title}}
{{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: - * - * 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 ''; } 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; - } - } } diff --git a/library/Icinga/Web/Widget/Tabextension/OutputFormat.php b/library/Icinga/Web/Widget/Tabextension/OutputFormat.php index b5426ea47..0b533fd82 100644 --- a/library/Icinga/Web/Widget/Tabextension/OutputFormat.php +++ b/library/Icinga/Web/Widget/Tabextension/OutputFormat.php @@ -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; + } } diff --git a/modules/monitoring/application/controllers/HostsController.php b/modules/monitoring/application/controllers/HostsController.php index 4944cc237..09957a4a0 100644 --- a/modules/monitoring/application/controllers/HostsController.php +++ b/modules/monitoring/application/controllers/HostsController.php @@ -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); } diff --git a/modules/monitoring/application/controllers/MultiController.php b/modules/monitoring/application/controllers/MultiController.php index 52d927ffb..6deff2fc3 100644 --- a/modules/monitoring/application/controllers/MultiController.php +++ b/modules/monitoring/application/controllers/MultiController.php @@ -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; } diff --git a/modules/monitoring/application/controllers/ServicesController.php b/modules/monitoring/application/controllers/ServicesController.php index cdf86f460..4d08622c2 100644 --- a/modules/monitoring/application/controllers/ServicesController.php +++ b/modules/monitoring/application/controllers/ServicesController.php @@ -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'); } /** diff --git a/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php b/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php index 3456e1bfb..25ac76c25 100644 --- a/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php +++ b/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php @@ -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')) ) ) ); diff --git a/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php b/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php index 9fd86a82b..9961d4b65 100644 --- a/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php +++ b/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php @@ -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') diff --git a/modules/monitoring/application/views/helpers/Perfdata.php b/modules/monitoring/application/views/helpers/Perfdata.php index eb649e77d..47c34d156 100644 --- a/modules/monitoring/application/views/helpers/Perfdata.php +++ b/modules/monitoring/application/views/helpers/Perfdata.php @@ -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[] = '' . $pieChart->render() + . htmlspecialchars($perfdata->getLabel()) + . ' ' + . htmlspecialchars($this->formatPerfdataValue($perfdata)) . + ' '; } - // $pieChart->setStyle('margin: 0.2em 0.5em 0.2em 0.5em;'); - $table[] = '' . $pieChart->render() - . htmlspecialchars($perfdata->getLabel()) - . ' ' - . htmlspecialchars($this->formatPerfdataValue($perfdata)) . - ' '; + } else { + $table[] = (string)$perfdata; } } @@ -43,32 +46,10 @@ class Zend_View_Helper_Perfdata extends Zend_View_Helper_Abstract return $result; } else { $pieCharts = empty($table) ? '' : '' . implode("\n", $table) . '
'; - return $pieCharts . "\n" . implode("
\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; - } } diff --git a/modules/monitoring/application/views/scripts/list/downtimes.phtml b/modules/monitoring/application/views/scripts/list/downtimes.phtml index 37b34fe5e..8a8e5576c 100644 --- a/modules/monitoring/application/views/scripts/list/downtimes.phtml +++ b/modules/monitoring/application/views/scripts/list/downtimes.phtml @@ -53,7 +53,7 @@ use Icinga\Module\Monitoring\Object\Service; service)): ?> - href('monitoring/service/show', array( 'host' => $downtime->host, 'service' => $downtime->service )); ?>"> @@ -63,7 +63,7 @@ use Icinga\Module\Monitoring\Object\Service; translate('on'); ?> host; ?> - href('monitoring/host/show', array( 'host' => $downtime->host )); ?>"> host; ?> @@ -76,7 +76,9 @@ use Icinga\Module\Monitoring\Object\Service; is_flexible): ?> is_in_effect): ?> 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; ); ?> 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; is_in_effect): ?> 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; ); ?> 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), diff --git a/modules/monitoring/application/views/scripts/list/hosts.phtml b/modules/monitoring/application/views/scripts/list/hosts.phtml index debef375c..2f339b1bf 100644 --- a/modules/monitoring/application/views/scripts/list/hosts.phtml +++ b/modules/monitoring/application/views/scripts/list/hosts.phtml @@ -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') ) ?>) diff --git a/modules/monitoring/application/views/scripts/list/services.phtml b/modules/monitoring/application/views/scripts/list/services.phtml index 994304f08..8ef6f7fe8 100644 --- a/modules/monitoring/application/views/scripts/list/services.phtml +++ b/modules/monitoring/application/views/scripts/list/services.phtml @@ -1,6 +1,7 @@ getHelper('MonitoringState'); diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php index e390aa114..28b06dc80 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php @@ -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), diff --git a/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandFileCommandRenderer.php b/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandFileCommandRenderer.php index 2843974ea..651383255 100644 --- a/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandFileCommandRenderer.php +++ b/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandFileCommandRenderer.php @@ -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) diff --git a/modules/monitoring/library/Monitoring/Plugin/Perfdata.php b/modules/monitoring/library/Monitoring/Plugin/Perfdata.php index 6af3cde17..7da4559fd 100644 --- a/modules/monitoring/library/Monitoring/Plugin/Perfdata.php +++ b/modules/monitoring/library/Monitoring/Plugin/Perfdata.php @@ -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; + } } diff --git a/modules/monitoring/test/php/regression/Bug6088Test.php b/modules/monitoring/test/php/regression/Bug6088Test.php new file mode 100644 index 000000000..0a4f60a58 --- /dev/null +++ b/modules/monitoring/test/php/regression/Bug6088Test.php @@ -0,0 +1,63 @@ +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' + ); + } +} diff --git a/modules/setup/application/views/scripts/form/setup-welcome.phtml b/modules/setup/application/views/scripts/form/setup-welcome.phtml index 6c8228541..9aad09dbc 100644 --- a/modules/setup/application/views/scripts/form/setup-welcome.phtml +++ b/modules/setup/application/views/scripts/form/setup-welcome.phtml @@ -51,9 +51,7 @@ $cliPath = realpath(Icinga::app()->getApplicationDir() . '/../bin/icingacli');

- su && mkdir -m 2770 ; - head -c 12 /dev/urandom | base64 | tee ; - chmod 0660 ; + su -c "mkdir -m 2770 ; head -c 12 /dev/urandom | base64 | tee ; chmod 0660 ;";