From 9e2de4308959f13664c0653fe362930d0f420033 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Thu, 18 Dec 2014 14:28:58 +0100 Subject: [PATCH 01/40] Remove etc/license_header.txt This file is not in use. --- etc/license_header.txt | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 etc/license_header.txt 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 From b3994f1099d51e6e1b3e9d9bca657c80972ccda2 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Thu, 18 Dec 2014 15:24:06 +0100 Subject: [PATCH 02/40] doc: Add chapter IDs to authentication.md --- doc/authentication.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/doc/authentication.md b/doc/authentication.md index 994d44e48..a4fdc2fcc 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 From 53179981d62b1f05951ed70031e5497dbb9dc50c Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Thu, 18 Dec 2014 15:37:08 +0100 Subject: [PATCH 03/40] doc: Add section 'Database Setup' to authentication.md fixes #7672 --- doc/authentication.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/doc/authentication.md b/doc/authentication.md index a4fdc2fcc..d36051e8f 100644 --- a/doc/authentication.md +++ b/doc/authentication.md @@ -85,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'); ```` From 0bcda60dab4ce7c14a1a2b0b5da60be5aec6cad2 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Thu, 18 Dec 2014 15:40:26 +0100 Subject: [PATCH 04/40] doc: Remove developer information from preferences.md --- doc/preferences.md | 118 ++++++++++++++------------------------------- 1 file changed, 35 insertions(+), 83 deletions(-) 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). From 5b1e9be3169f77de36694bf3ce9887b28e37129f Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 18 Dec 2014 15:41:26 +0100 Subject: [PATCH 05/40] Make command parameters with multiple lines work, again fixes #6088 --- .../IcingaCommandFileCommandRenderer.php | 4 +- .../test/php/regression/Bug6088Test.php | 63 +++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 modules/monitoring/test/php/regression/Bug6088Test.php 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/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' + ); + } +} From 7710fd7b0e3a6fe0501492be2acbd7b5b661c1ff Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 18 Dec 2014 16:20:41 +0100 Subject: [PATCH 06/40] Hide unsupported export formats refs #8112 --- .../Web/Widget/Tabextension/OutputFormat.php | 72 +++++++++++-------- 1 file changed, 42 insertions(+), 30 deletions(-) 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; + } } From cc9ee93311972cf275925ed51d10c9c291a99923 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 18 Dec 2014 16:21:25 +0100 Subject: [PATCH 07/40] Clarify that php-gd is mandatory to export views to PDF refs #8112 --- modules/setup/library/Setup/WebWizard.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/setup/library/Setup/WebWizard.php b/modules/setup/library/Setup/WebWizard.php index 53a6bb8a8..faa123fe8 100644 --- a/modules/setup/library/Setup/WebWizard.php +++ b/modules/setup/library/Setup/WebWizard.php @@ -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') : ( From 0b95892764b76025efae8c2dccb494a4bbf7defc Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Thu, 18 Dec 2014 17:09:28 +0100 Subject: [PATCH 08/40] puppet: Add note to the icingacli module that it's not reuseable --- .puppet/modules/icingacli/manifests/init.pp | 1 + 1 file changed, 1 insertion(+) 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, From 8728b3f12596f7ee4315d26c7cd670a91ce336bf Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Thu, 18 Dec 2014 17:17:59 +0100 Subject: [PATCH 09/40] puppet: Add user 'vagrant' to group 'icingaweb' --- .puppet/manifests/site.pp | 2 ++ 1 file changed, 2 insertions(+) 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 } } From 4dfac2839303178a101d43488dd57d846988471d Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Thu, 18 Dec 2014 17:21:06 +0100 Subject: [PATCH 10/40] lib: Fix PHPDoc of Form::setOnSuccess() --- library/Icinga/Web/Form.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Web/Form.php b/library/Icinga/Web/Form.php index 37d8789fe..647be721d 100644 --- a/library/Icinga/Web/Form.php +++ b/library/Icinga/Web/Form.php @@ -147,7 +147,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 * From 1468ed0a1979574ff20d859cd7f4f6128b93d2b2 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Thu, 18 Dec 2014 17:23:54 +0100 Subject: [PATCH 11/40] lib: Add separator parameter to String::cname() --- library/Icinga/Util/String.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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)))); } } From b0ab6c3a768f4a20a01676eff8520bc9aaf593ce Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Fri, 19 Dec 2014 10:43:14 +0100 Subject: [PATCH 12/40] monitoring: Remove css class 'control-group' from the expire time element This CSS class does no longer exist. --- .../forms/Command/Object/AcknowledgeProblemCommandForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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')) ) ) ); From fa226f261e1d1f3094fd7299d136afab45eb2e3b Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Fri, 19 Dec 2014 10:45:23 +0100 Subject: [PATCH 13/40] monitoring: Remove css class 'control-group' from date and time elements in the schedule downtime command form This CSS class does no longer exist. --- .../Command/Object/ScheduleServiceDowntimeCommandForm.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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') From e5d2d4cec2f5941a3092ef5884cfae0cc9ac2c42 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 19 Dec 2014 11:29:24 +0100 Subject: [PATCH 14/40] Add module-aware Form::translate and Form::translatePlural refs #7551 --- library/Icinga/Util/Translator.php | 7 +++--- library/Icinga/Web/Form.php | 35 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) 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 647be721d..205c4e238 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; @@ -804,6 +805,40 @@ class Form extends Zend_Form return array(); } + /** + * 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->request->getModuleName(), $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->request->getModuleName(), + $context + ); + } + /** * Render this form * From cf43b814009c2109c3990e4743ef0738bce5b459 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 19 Dec 2014 12:08:54 +0100 Subject: [PATCH 15/40] Use the class namespace instead of the request in Form::translate(Plural) refs #7551 --- library/Icinga/Web/Form.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Web/Form.php b/library/Icinga/Web/Form.php index 205c4e238..f3810903c 100644 --- a/library/Icinga/Web/Form.php +++ b/library/Icinga/Web/Form.php @@ -805,6 +805,20 @@ class Form extends Zend_Form return array(); } + /** + * Return the translation domain for this form + * + * @return string + */ + protected function getTranslationDomain() + { + if (preg_match('@^Icinga\\\\Module\\\\([A-z]+)\\\\.*$@', get_called_class(), $matches) === 1) { + return strtolower($matches[0]); + } + + return $this->getRequest()->getModuleName(); + } + /** * Translate a string * @@ -815,7 +829,7 @@ class Form extends Zend_Form */ protected function translate($text, $context = null) { - return Translator::translate($text, $this->request->getModuleName(), $context); + return Translator::translate($text, $this->getTranslationDomain(), $context); } /** @@ -834,7 +848,7 @@ class Form extends Zend_Form $textSingular, $textPlural, $number, - $this->request->getModuleName(), + $this->getTranslationDomain(), $context ); } From 6d263ae316f24d3e27abf42bde15f4f43bff27df Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 19 Dec 2014 13:07:51 +0100 Subject: [PATCH 16/40] Do NOT fetch the translation domain from the request in Form::translate(..) It might be the case that a module is using a library form... refs #7551 --- library/Icinga/Web/Form.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Web/Form.php b/library/Icinga/Web/Form.php index f3810903c..850285baa 100644 --- a/library/Icinga/Web/Form.php +++ b/library/Icinga/Web/Form.php @@ -808,6 +808,9 @@ class Form extends Zend_Form /** * 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() @@ -816,7 +819,7 @@ class Form extends Zend_Form return strtolower($matches[0]); } - return $this->getRequest()->getModuleName(); + return 'icinga'; } /** From 4c4c0c97a5de5ed09d3a6cef9364712e140bad5b Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 22 Dec 2014 08:30:08 +0100 Subject: [PATCH 17/40] Fix incorrect token generation example on the wizard's welcome page fixes #8135 --- .../setup/application/views/scripts/form/setup-welcome.phtml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 ;";

Date: Mon, 22 Dec 2014 09:14:19 +0100 Subject: [PATCH 18/40] Fix the config warning's grammar on the login screen --- application/views/scripts/authentication/login.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 @@

Date: Mon, 22 Dec 2014 09:37:01 +0100 Subject: [PATCH 19/40] There is no copy-on-write for objects in PHP fixes #8088 --- application/views/scripts/roles/index.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/views/scripts/roles/index.phtml b/application/views/scripts/roles/index.phtml index 0f40f8279..79eda5dac 100644 --- a/application/views/scripts/roles/index.phtml +++ b/application/views/scripts/roles/index.phtml @@ -30,7 +30,7 @@ without(...) or $role->shift(...) would be nice! - $restrictions = $role; + $restrictions = clone $role; unset($restrictions['users']); unset($restrictions['groups']); unset($restrictions['permissions']); From 706e5504e651e63b7fee1e5a116f924452bf33d3 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 22 Dec 2014 10:46:29 +0100 Subject: [PATCH 20/40] Escape restriction names manually in Forms\Security\RoleForm fixes #8086 --- application/forms/Security/RoleForm.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/application/forms/Security/RoleForm.php b/application/forms/Security/RoleForm.php index 0fb53e858..4edb33d07 100644 --- a/application/forms/Security/RoleForm.php +++ b/application/forms/Security/RoleForm.php @@ -93,7 +93,7 @@ class RoleForm extends ConfigForm foreach ($this->providedRestrictions as $name => $description) { $this->addElement( 'text', - $name, + str_replace('/', '_', $name), array( 'label' => $name, 'description' => $description @@ -129,6 +129,12 @@ class RoleForm extends ConfigForm ? String::trimSplit($role['permissions']) : null; $role['name'] = $name; + foreach (array_keys($role) as $key) { + // Slashes are not allowed in a form's element name + $value = $role[$key]; + unset($role[$key]); + $role[str_replace('/', '_', $key)] = $value; + } $this->populate($role); return $this; } @@ -230,6 +236,12 @@ class RoleForm extends ConfigForm if (isset($values['permissions'])) { $values['permissions'] = implode(', ', $values['permissions']); } + foreach (array_keys($values) as $key) { + // Slashes are not allowed in a form's element name + $value = $values[$key]; + unset($values[$key]); + $values[str_replace('/', '_', $key)] = $value; + } return $values; } } From f7d11ce11f82b8e2716ed065ecdb0f4109ddd25a Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 22 Dec 2014 11:02:48 +0100 Subject: [PATCH 21/40] Relax session storage check to the `files' save handler fixes #8053 --- library/Icinga/Web/Session/PhpSession.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Web/Session/PhpSession.php b/library/Icinga/Web/Session/PhpSession.php index bef978c0b..094685fe4 100644 --- a/library/Icinga/Web/Session/PhpSession.php +++ b/library/Icinga/Web/Session/PhpSession.php @@ -78,7 +78,7 @@ class PhpSession extends Session } } - if (!is_writable(session_save_path())) { + if (ini_get('session.save_handler') === 'files' && !is_writable(session_save_path())) { throw new ConfigurationError('Can\'t save session'); } From 086334c8618ce218f5df872b1bca5de33604dbc6 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 22 Dec 2014 13:59:03 +0100 Subject: [PATCH 22/40] Properly build the unhandled action links when showing multiple objects The links designated to acknowledge unhandled problems or to schedule a downtime for them were generated based on the full list of objects and limited by non-supported filter parameters. As we are already aware of the exact "unhandled" objects this list is now used to generate proper links. (Btw applying filters to URLs is a mess...) fixes #8017 --- .../controllers/HostsController.php | 12 ++++++------ .../controllers/ServicesController.php | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/modules/monitoring/application/controllers/HostsController.php b/modules/monitoring/application/controllers/HostsController.php index 4944cc237..37611a001 100644 --- a/modules/monitoring/application/controllers/HostsController.php +++ b/modules/monitoring/application/controllers/HostsController.php @@ -108,12 +108,12 @@ 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)); + $this->view->acknowledgeUnhandledLink = Url::fromPath('monitoring/hosts/acknowledge-problem')->setQueryString( + Filter::where('host', array_map(function ($h) { return $h->host; }, $unhandledObjects))->toQueryString() + ); + $this->view->downtimeUnhandledLink = Url::fromPath('monitoring/hosts/schedule-downtime')->setQueryString( + Filter::where('host', array_map(function ($h) { return $h->host; }, $unhandledObjects))->toQueryString() + ); $this->view->acknowledgedObjects = $acknowledgedObjects; $this->view->objectsInDowntime = $objectsInDowntime; $this->view->inDowntimeLink = Url::fromRequest() diff --git a/modules/monitoring/application/controllers/ServicesController.php b/modules/monitoring/application/controllers/ServicesController.php index cdf86f460..f859f6790 100644 --- a/modules/monitoring/application/controllers/ServicesController.php +++ b/modules/monitoring/application/controllers/ServicesController.php @@ -125,12 +125,18 @@ 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)); + $unhandledFilterExpressions = array(); + foreach ($unhandledObjects as $service) { + $unhandledFilterExpressions[] = Filter::matchAll( + Filter::expression('host', '=', $service->getHost()->getName()), + Filter::expression('service', '=', $service->getName()) + ); + } + $queryString = Filter::matchAny($unhandledFilterExpressions)->toQueryString(); + $this->view->acknowledgeUnhandledLink = Url::fromPath('monitoring/services/acknowledge-problem'); + $this->view->acknowledgeUnhandledLink->setQueryString($queryString); + $this->view->downtimeUnhandledLink = Url::fromPath('monitoring/services/schedule-downtime'); + $this->view->downtimeUnhandledLink->setQueryString($queryString); $this->view->acknowledgedObjects = $acknowledgedObjects; $this->view->objectsInDowntime = $objectsInDowntime; $this->view->inDowntimeLink = Url::fromRequest() From c5915f24cc4f1ae3846cac0ac129b3e61d8e644e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 22 Dec 2014 14:44:09 +0100 Subject: [PATCH 23/40] Make it easier to distinguish host and service downtimes Added an icon to the left of a host's or service's label and an additional textual indicator. refs #8008 --- .../views/scripts/list/downtimes.phtml | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) 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), From 5b0c8763620a474fe8bfb173490f27283f426375 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 22 Dec 2014 16:18:39 +0100 Subject: [PATCH 24/40] Use the service_handled column instead of acknowledged and in_downtime Only service_handled takes the host's state into consideration. refs #8013 --- .../monitoring/application/views/scripts/list/hosts.phtml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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') ) ?>) From f513e7959e1c2c515fe0304571ea1d154a108fd5 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 22 Dec 2014 16:20:12 +0100 Subject: [PATCH 25/40] Consider also the host's state when counting its service problems refs #8013 --- .../library/Monitoring/Backend/Ido/Query/StatusQuery.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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), From 64a2acd12eb84f6d62d577b7a49b637efa11a418 Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Thu, 18 Dec 2014 16:44:55 +0100 Subject: [PATCH 26/40] Do not display labels for single data sets in perfdata piecharts Display generic chart titles for the whole piechart that only contain title and percentage and don't style perfdata piecharts using HTML properties. refs #7077 refs #6200 refs #7304 --- library/Icinga/Web/Widget/Chart/InlinePie.php | 210 +++++------------- .../controllers/MultiController.php | 2 +- .../controllers/ServicesController.php | 3 +- .../application/views/helpers/Perfdata.php | 14 +- public/css/icinga/widgets.less | 12 + public/js/icinga/behavior/sparkline.js | 51 ++--- 6 files changed, 107 insertions(+), 185 deletions(-) diff --git a/library/Icinga/Web/Widget/Chart/InlinePie.php b/library/Icinga/Web/Widget/Chart/InlinePie.php index 618d2903b..3acb83f5c 100644 --- a/library/Icinga/Web/Widget/Chart/InlinePie.php +++ b/library/Icinga/Web/Widget/Chart/InlinePie.php @@ -34,30 +34,20 @@ class InlinePie extends AbstractWidget * @var string */ private $template =<<<'EOD' - - + + +{noscript} EOD; + private $noscript =<<<'EOD' + +EOD; + + /** * @var Url */ @@ -70,34 +60,6 @@ EOD; */ 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'; - /** * The title of the chart * @@ -106,11 +68,9 @@ EOD; private $title; /** - * The style for the HtmlElement - * - * @var string + * @var */ - private $style = ''; + private $size; /** * The data displayed by the pie-chart @@ -126,19 +86,17 @@ EOD; */ 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}}%)'; + private $tooltipFormat = '{{title}}
{{label}} {{formatted}} ({{percent}}%)'; + + /** + * @var string + */ + private $disableTooltips = ''; /** * The number format used to render numeric values in tooltips @@ -147,19 +105,6 @@ EOD; */ 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; - } - /** * Set the data to be displayed. * @@ -174,6 +119,27 @@ EOD; return $this; } + /** + * Set the size of the inline pie + * + * @param int $size Sets both, the height and width + * + * @return $this + */ + public function setSize($size = null) + { + $this->size = $size; + return $this; + } + + /** + * Do not display the NoScript fallback html + */ + public function disableNoScript() + { + $this->noscript = ''; + } + /** * The labels to be displayed in the pie-chart * @@ -186,7 +152,7 @@ EOD; if (is_array($label)) { $this->url->setParam('labels', implode(',', array_keys($label))); } elseif ($label != null) { - $labelArr = array($label, $label, $label, ''); + $labelArr = array($label, $label, $label, $label); $this->url->setParam('labels', implode(',', $labelArr)); $label = $labelArr; } else { @@ -250,69 +216,6 @@ EOD; 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,10 +225,20 @@ EOD; */ public function setTitle($title) { - $this->title = $title; + $this->title = 'title="' . htmlspecialchars($title) . '"'; return $this; } + /** + * Whether to display tooltips for the InlinePie + * + * @param bool $val + */ + public function setDisableTooltip($val = true) + { + $this->disableTooltips = $val !== true ? '' : 'sparkDisableTooltips="true"'; + } + /** * Create a new InlinePie * @@ -335,7 +248,7 @@ 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']; @@ -386,7 +299,7 @@ EOD; )); try { - $png = $pie->toPng($this->width, $this->height); + $png = $pie->toPng($this->size, $this->size); return ''; } catch (IcingaException $_) { return ''; @@ -394,17 +307,15 @@ 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); // 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 @@ -423,6 +334,7 @@ EOD; $template = str_replace('{formatted}', htmlspecialchars(implode('|', $formatted)), $template); $template = str_replace('{labels}', htmlspecialchars($this->createLabelString()), $template); $template = str_replace('{tooltipFormat}', $this->tooltipFormat, $template); + $template = str_replace('{disableTooltips}', $this->disableTooltips, $template); return $template; } 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 f859f6790..a7b2d0294 100644 --- a/modules/monitoring/application/controllers/ServicesController.php +++ b/modules/monitoring/application/controllers/ServicesController.php @@ -160,8 +160,7 @@ 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) + ->setSize(100) ->setTitle($title); } diff --git a/modules/monitoring/application/views/helpers/Perfdata.php b/modules/monitoring/application/views/helpers/Perfdata.php index eb649e77d..6e168c90f 100644 --- a/modules/monitoring/application/views/helpers/Perfdata.php +++ b/modules/monitoring/application/views/helpers/Perfdata.php @@ -84,21 +84,19 @@ class Zend_View_Helper_Perfdata extends Zend_View_Helper_Abstract protected function createInlinePie(Perfdata $perfdata) { - $pieChart = new InlinePie($this->calculatePieChartData($perfdata), $perfdata->getLabel()); - $pieChart->setLabel(htmlspecialchars($perfdata->getLabel())); - $pieChart->setHideEmptyLabel(); + $pieChart = new InlinePie($this->calculatePieChartData($perfdata), + $perfdata->getLabel() . ' ' . (int)$perfdata->getPercentage() . '%'); + $pieChart->setDisableTooltip(); + if (Zend_Controller_Front::getInstance()->getRequest()->isXmlHttpRequest()) { + $pieChart->disableNoScript(); + } - //$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/public/css/icinga/widgets.less b/public/css/icinga/widgets.less index 93625b0dc..87941157c 100644 --- a/public/css/icinga/widgets.less +++ b/public/css/icinga/widgets.less @@ -285,3 +285,15 @@ li li .badge { .widgetFilter li.active { background-color: #eee; } + +.sparkline { + width: 12px; + height: 12px; +} + +.inlinepie { + width: 12px; + height: 12px; + position: relative; + top: 10px; +} \ No newline at end of file diff --git a/public/js/icinga/behavior/sparkline.js b/public/js/icinga/behavior/sparkline.js index 33cc0d34b..1992bae0a 100644 --- a/public/js/icinga/behavior/sparkline.js +++ b/public/js/icinga/behavior/sparkline.js @@ -21,31 +21,32 @@ 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', - { - 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; - } - }); + var title = $spark.attr('title'); + var format = $spark.attr('tooltipFormat'); + + if ($spark.attr('labels')) { + $spark.removeAttr('original-title'); + } + var options = { + enableTagOptions: true, + width: $spark.attr('sparkWidth') || 12, + height: $spark.attr('sparkHeight') || 12, + tooltipFormatter: function (sparkline, options, fields) { + var out = format; + var replace = { + title: title, + 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; + } + }; + $spark.sparkline('html', options); }); }; From cda5a6a9034cd961cc6b6faeed732e55ab4cce5a Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Tue, 23 Dec 2014 15:26:45 +0100 Subject: [PATCH 27/40] Streamline chart implementation Define sparkline layout through CSS class and remove label/tooltip formatting. fixes #7077 --- library/Icinga/Web/Widget/Chart/InlinePie.php | 150 ++---------------- .../controllers/HostsController.php | 3 - .../controllers/ServicesController.php | 7 +- public/js/icinga/behavior/sparkline.js | 48 +++--- 4 files changed, 39 insertions(+), 169 deletions(-) diff --git a/library/Icinga/Web/Widget/Chart/InlinePie.php b/library/Icinga/Web/Widget/Chart/InlinePie.php index 3acb83f5c..69de8ebd5 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,15 +29,12 @@ 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; @@ -80,30 +78,9 @@ EOD; private $data; /** - * The labels to display for each data set - * - * @var array + * @var */ - private $labels = array(); - - /** - * The format string used to display tooltips - * - * @var string - */ - private $tooltipFormat = '{{title}}
{{label}} {{formatted}} ({{percent}}%)'; - - /** - * @var string - */ - private $disableTooltips = ''; - - /** - * The number format used to render numeric values in tooltips - * - * @var array - */ - private $format = self::NUMBER_FORMAT_NONE; + private $class = ''; /** * Set the data to be displayed. @@ -141,24 +118,15 @@ EOD; } /** - * The labels to be displayed in the pie-chart + * Set the class to define the * - * @param mixed $label The label of the displayed value, or null for no labels + * @param $class * - * @return $this + * @return $this */ - public function setLabel($label) + public function setSparklineClass($class) { - if (is_array($label)) { - $this->url->setParam('labels', implode(',', array_keys($label))); - } elseif ($label != null) { - $labelArr = array($label, $label, $label, $label); - $this->url->setParam('labels', implode(',', $labelArr)); - $label = $labelArr; - } else { - $this->url->removeKey('labels'); - } - $this->labels = $label; + $this->class = $class; return $this; } @@ -180,42 +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: - *
    - *
  • label: The description for the current value
  • - *
  • formatted: A string representing the formatted value
  • - *
  • value: The raw (non-formatted) value used to render the piechart
  • - *
  • percent: The percentage of the current value
  • - *
- * 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 title of the displayed Data * @@ -229,16 +161,6 @@ EOD; return $this; } - /** - * Whether to display tooltips for the InlinePie - * - * @param bool $val - */ - public function setDisableTooltip($val = true) - { - $this->disableTooltips = $val !== true ? '' : 'sparkDisableTooltips="true"'; - } - /** * Create a new InlinePie * @@ -252,9 +174,6 @@ EOD; $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']; } @@ -267,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 @@ -295,7 +199,7 @@ EOD; $pie->alignTopLeft(); $pie->disableLegend(); $pie->drawPie(array( - 'data' => $this->data, 'colors' => $this->colors, 'labels' => $this->labels + 'data' => $this->data, 'colors' => $this->colors )); try { @@ -310,11 +214,13 @@ EOD; // 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('{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); // Locale-ignorant string cast. Please. Do. NOT. Remove. This. Again. @@ -325,39 +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); - $template = str_replace('{disableTooltips}', $this->disableTooltips, $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/modules/monitoring/application/controllers/HostsController.php b/modules/monitoring/application/controllers/HostsController.php index 37611a001..451f6e44d 100644 --- a/modules/monitoring/application/controllers/HostsController.php +++ b/modules/monitoring/application/controllers/HostsController.php @@ -131,9 +131,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/ServicesController.php b/modules/monitoring/application/controllers/ServicesController.php index a7b2d0294..315737b31 100644 --- a/modules/monitoring/application/controllers/ServicesController.php +++ b/modules/monitoring/application/controllers/ServicesController.php @@ -159,9 +159,10 @@ class Monitoring_ServicesController extends Controller { $chart = new InlinePie(array_values($states), $title, $colors); return $chart - ->setLabel(array_map('strtoupper', array_keys($states))) - ->setSize(100) - ->setTitle($title); + // ->setLabel(array_map('strtoupper', array_keys($states))) + ->setSize(50) + ->setTitle($title) + ->setSparklineClass('sparkline-multi'); } /** diff --git a/public/js/icinga/behavior/sparkline.js b/public/js/icinga/behavior/sparkline.js index 1992bae0a..e0d0fb0c7 100644 --- a/public/js/icinga/behavior/sparkline.js +++ b/public/js/icinga/behavior/sparkline.js @@ -18,35 +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 title = $spark.attr('title'); - var format = $spark.attr('tooltipFormat'); + var $spark = $(element); + var title = $spark.attr('title'); if ($spark.attr('labels')) { $spark.removeAttr('original-title'); } - var options = { - enableTagOptions: true, - width: $spark.attr('sparkWidth') || 12, - height: $spark.attr('sparkHeight') || 12, - tooltipFormatter: function (sparkline, options, fields) { - var out = format; - var replace = { - title: title, - 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; - } - }; - $spark.sparkline('html', options); + + var options; + if ($spark.hasClass('sparkline-perfdata')) { + options = { + enableTagOptions: true, + 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); + } + }); }; From c93b13b1387c1017d4ef4e58d7b9e078a2d4e0bb Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Tue, 23 Dec 2014 15:45:45 +0100 Subject: [PATCH 28/40] Rework behavior of perfdata visualization Determine perfdata pie color from host or service state, display zero percent piecharts, only render displayabl PieCharts with min and max values, move perfdata to piechart conversion functions into the Perfdata object. fixes #6423 fixes #6200 fixes #7170 fixes #7304 --- .../application/views/helpers/Perfdata.php | 89 ++++++------------- .../library/Monitoring/Plugin/Perfdata.php | 63 +++++++++++++ public/css/icinga/widgets.less | 7 +- 3 files changed, 90 insertions(+), 69 deletions(-) diff --git a/modules/monitoring/application/views/helpers/Perfdata.php b/modules/monitoring/application/views/helpers/Perfdata.php index 6e168c90f..ada99da9f 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_GREEN) { - $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,22 +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() . ' ' . (int)$perfdata->getPercentage() . '%'); - $pieChart->setDisableTooltip(); - if (Zend_Controller_Front::getInstance()->getRequest()->isXmlHttpRequest()) { - $pieChart->disableNoScript(); - } - - if ($perfdata->isBytes()) { - $pieChart->setNumberFormat(InlinePie::NUMBER_FORMAT_BYTES); - } else if ($perfdata->isSeconds()) { - $pieChart->setNumberFormat(InlinePie::NUMBER_FORMAT_TIME); - } else { - $pieChart->setNumberFormat(InlinePie::NUMBER_FORMAT_RATIO); - } - return $pieChart; - } } diff --git a/modules/monitoring/library/Monitoring/Plugin/Perfdata.php b/modules/monitoring/library/Monitoring/Plugin/Perfdata.php index 6af3cde17..1b687d243 100644 --- a/modules/monitoring/library/Monitoring/Plugin/Perfdata.php +++ b/modules/monitoring/library/Monitoring/Plugin/Perfdata.php @@ -5,9 +5,16 @@ 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_GREEN = 'green'; + const PERFDATA_ORANGE = 'orange'; + const PERFDATA_RED = 'red'; + /** * The performance data value being parsed * @@ -159,6 +166,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 +333,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_GREEN: + $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/public/css/icinga/widgets.less b/public/css/icinga/widgets.less index 87941157c..e2e37d7ab 100644 --- a/public/css/icinga/widgets.less +++ b/public/css/icinga/widgets.less @@ -289,11 +289,6 @@ li li .badge { .sparkline { width: 12px; height: 12px; -} - -.inlinepie { - width: 12px; - height: 12px; position: relative; - top: 10px; + top: 4px; } \ No newline at end of file From 41c101d99fa8ec161b211b851bbf234907333feb Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Tue, 23 Dec 2014 16:12:25 +0100 Subject: [PATCH 29/40] Use neutral default color in perfdata piecharts If nothing is known about the state of the monitoring object, the piecharts should neither indicate OK nor Critical. --- library/Icinga/Web/Widget/Chart/InlinePie.php | 2 +- modules/monitoring/application/views/helpers/Perfdata.php | 2 +- .../monitoring/application/views/scripts/list/services.phtml | 1 + modules/monitoring/library/Monitoring/Plugin/Perfdata.php | 5 ++--- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/library/Icinga/Web/Widget/Chart/InlinePie.php b/library/Icinga/Web/Widget/Chart/InlinePie.php index 69de8ebd5..04e3e7318 100644 --- a/library/Icinga/Web/Widget/Chart/InlinePie.php +++ b/library/Icinga/Web/Widget/Chart/InlinePie.php @@ -56,7 +56,7 @@ EOD; * * @var array */ - private $colors = array('#44bb77', '#ffaa44', '#ff5566', '#ddccdd'); + private $colors = array('#049BAF', '#ffaa44', '#ff5566', '#ddccdd'); /** * The title of the chart diff --git a/modules/monitoring/application/views/helpers/Perfdata.php b/modules/monitoring/application/views/helpers/Perfdata.php index ada99da9f..47c34d156 100644 --- a/modules/monitoring/application/views/helpers/Perfdata.php +++ b/modules/monitoring/application/views/helpers/Perfdata.php @@ -19,7 +19,7 @@ class Zend_View_Helper_Perfdata extends Zend_View_Helper_Abstract * * @return string */ - public function perfdata($perfdataStr, $compact = false, $color = Perfdata::PERFDATA_GREEN) + public function perfdata($perfdataStr, $compact = false, $color = Perfdata::PERFDATA_DEFAULT) { $pieChartData = PerfdataSet::fromString($perfdataStr)->asArray(); 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/Plugin/Perfdata.php b/modules/monitoring/library/Monitoring/Plugin/Perfdata.php index 1b687d243..7da4559fd 100644 --- a/modules/monitoring/library/Monitoring/Plugin/Perfdata.php +++ b/modules/monitoring/library/Monitoring/Plugin/Perfdata.php @@ -11,8 +11,7 @@ use Zend_Controller_Front; class Perfdata { - const PERFDATA_GREEN = 'green'; - const PERFDATA_ORANGE = 'orange'; + const PERFDATA_DEFAULT = 'green'; const PERFDATA_RED = 'red'; /** @@ -346,7 +345,7 @@ class Perfdata $green = $orange = $red = 0; switch ($color) { - case self::PERFDATA_GREEN: + case self::PERFDATA_DEFAULT: $green = $usedValue; break; From 7f5ba135eee69a1ec3378e02131fff624e9138e3 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Mon, 29 Dec 2014 09:25:30 +0100 Subject: [PATCH 30/40] Revert "Relax session storage check to the `files' save handler" This reverts commit f7d11ce11f82b8e2716ed065ecdb0f4109ddd25a. Sorry mate but a guy on GitHub was faster ;) refs #8053 --- library/Icinga/Web/Session/PhpSession.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Web/Session/PhpSession.php b/library/Icinga/Web/Session/PhpSession.php index 094685fe4..bef978c0b 100644 --- a/library/Icinga/Web/Session/PhpSession.php +++ b/library/Icinga/Web/Session/PhpSession.php @@ -78,7 +78,7 @@ class PhpSession extends Session } } - if (ini_get('session.save_handler') === 'files' && !is_writable(session_save_path())) { + if (!is_writable(session_save_path())) { throw new ConfigurationError('Can\'t save session'); } From 5e40ce2088ed1a0ae8da5ab4a6148d9e8528387c Mon Sep 17 00:00:00 2001 From: Boden Garman Date: Mon, 15 Dec 2014 13:24:18 +1100 Subject: [PATCH 31/40] Only check the session save path is writable if the session handler is 'files' fixes #8053 Signed-off-by: Eric Lippmann --- library/Icinga/Web/Session/PhpSession.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) mode change 100644 => 100755 library/Icinga/Web/Session/PhpSession.php 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()) { From 6960a08de04bedc1594dbfdef57d58505bcc13cc Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Mon, 29 Dec 2014 09:39:23 +0100 Subject: [PATCH 32/40] Revert "Escape restriction names manually in Forms\Security\RoleForm" This reverts commit 706e5504e651e63b7fee1e5a116f924452bf33d3. HTML5 does allow any non-empty value for the name attribute but Zend only permits alphanumerics, the underscore, the circumflex and any ASCII character in range \x7f to \xff (127 to 255). Thus only escaping the slash (/) is wrong. refs #8086 --- application/forms/Security/RoleForm.php | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/application/forms/Security/RoleForm.php b/application/forms/Security/RoleForm.php index 4edb33d07..0fb53e858 100644 --- a/application/forms/Security/RoleForm.php +++ b/application/forms/Security/RoleForm.php @@ -93,7 +93,7 @@ class RoleForm extends ConfigForm foreach ($this->providedRestrictions as $name => $description) { $this->addElement( 'text', - str_replace('/', '_', $name), + $name, array( 'label' => $name, 'description' => $description @@ -129,12 +129,6 @@ class RoleForm extends ConfigForm ? String::trimSplit($role['permissions']) : null; $role['name'] = $name; - foreach (array_keys($role) as $key) { - // Slashes are not allowed in a form's element name - $value = $role[$key]; - unset($role[$key]); - $role[str_replace('/', '_', $key)] = $value; - } $this->populate($role); return $this; } @@ -236,12 +230,6 @@ class RoleForm extends ConfigForm if (isset($values['permissions'])) { $values['permissions'] = implode(', ', $values['permissions']); } - foreach (array_keys($values) as $key) { - // Slashes are not allowed in a form's element name - $value = $values[$key]; - unset($values[$key]); - $values[str_replace('/', '_', $key)] = $value; - } return $values; } } From 2c1a37afa3a5be52af78fd1a395a5671df5fc54b Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Mon, 29 Dec 2014 10:51:12 +0100 Subject: [PATCH 33/40] Use Zend_Form_Element::filterName() for translating restriction names to element names fixes #8086 --- application/forms/Security/RoleForm.php | 39 ++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/application/forms/Security/RoleForm.php b/application/forms/Security/RoleForm.php index 0fb53e858..a45ca16ac 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; @@ -35,6 +36,7 @@ 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 */ @@ -42,7 +44,18 @@ class RoleForm extends ConfigForm } foreach ($module->getProvidedRestrictions() as $restriction) { /** @var object $restriction */ - $this->providedRestrictions[$restriction->name] = $restriction->description; + $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; } } From 49dad43a0affbab49285b80ba5e0de668cedad24 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Mon, 29 Dec 2014 11:53:06 +0100 Subject: [PATCH 34/40] Use @type instead of @var in Security/RoleForm.php --- application/forms/Security/RoleForm.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/application/forms/Security/RoleForm.php b/application/forms/Security/RoleForm.php index a45ca16ac..eed9ea441 100644 --- a/application/forms/Security/RoleForm.php +++ b/application/forms/Security/RoleForm.php @@ -19,14 +19,14 @@ class RoleForm extends ConfigForm /** * Provided permissions by currently loaded modules * - * @var array + * @type array */ protected $providedPermissions = array(); /** * Provided restrictions by currently loaded modules * - * @var array + * @type array */ protected $providedRestrictions = array(); @@ -39,11 +39,11 @@ class RoleForm extends ConfigForm $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 */ + /** @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) From 4441c1d4de54295ade7fc57ff290534a33da2189 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Mon, 29 Dec 2014 11:53:55 +0100 Subject: [PATCH 35/40] Allow to grant every permission --- application/forms/Security/RoleForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/forms/Security/RoleForm.php b/application/forms/Security/RoleForm.php index eed9ea441..b27a4ec8b 100644 --- a/application/forms/Security/RoleForm.php +++ b/application/forms/Security/RoleForm.php @@ -21,7 +21,7 @@ class RoleForm extends ConfigForm * * @type array */ - protected $providedPermissions = array(); + protected $providedPermissions = array('*' => '*'); /** * Provided restrictions by currently loaded modules From 2db286543385f569f18c0901ee59608ed560a2b5 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Mon, 29 Dec 2014 12:11:49 +0100 Subject: [PATCH 36/40] Do not use an extra loop for generating the unhandled objects filter in ServicesController.php --- .../controllers/ServicesController.php | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/modules/monitoring/application/controllers/ServicesController.php b/modules/monitoring/application/controllers/ServicesController.php index 315737b31..fdc040d29 100644 --- a/modules/monitoring/application/controllers/ServicesController.php +++ b/modules/monitoring/application/controllers/ServicesController.php @@ -74,6 +74,7 @@ class Monitoring_ServicesController extends Controller 'service_obsessing'*/ )); $unhandledObjects = array(); + $unhandledFilterExpressions = array(); $acknowledgedObjects = array(); $objectsInDowntime = array(); $serviceStates = array( @@ -94,6 +95,10 @@ 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; @@ -125,18 +130,11 @@ class Monitoring_ServicesController extends Controller $this->view->serviceStates = $serviceStates; $this->view->objects = $this->serviceList; $this->view->unhandledObjects = $unhandledObjects; - $unhandledFilterExpressions = array(); - foreach ($unhandledObjects as $service) { - $unhandledFilterExpressions[] = Filter::matchAll( - Filter::expression('host', '=', $service->getHost()->getName()), - Filter::expression('service', '=', $service->getName()) - ); - } - $queryString = Filter::matchAny($unhandledFilterExpressions)->toQueryString(); - $this->view->acknowledgeUnhandledLink = Url::fromPath('monitoring/services/acknowledge-problem'); - $this->view->acknowledgeUnhandledLink->setQueryString($queryString); - $this->view->downtimeUnhandledLink = Url::fromPath('monitoring/services/schedule-downtime'); - $this->view->downtimeUnhandledLink->setQueryString($queryString); + $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() From f1f808b7a3d820eaf8a004954dfc41a7e799d798 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Mon, 29 Dec 2014 12:17:17 +0100 Subject: [PATCH 37/40] Do not use array_map for generating the unhandled objects filter in HostsController.php --- .../application/controllers/HostsController.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/monitoring/application/controllers/HostsController.php b/modules/monitoring/application/controllers/HostsController.php index 451f6e44d..8fe431ca2 100644 --- a/modules/monitoring/application/controllers/HostsController.php +++ b/modules/monitoring/application/controllers/HostsController.php @@ -72,6 +72,7 @@ class Monitoring_HostsController extends Controller 'host_obsessing'*/ )); $unhandledObjects = array(); + $unhandledFilterExpressions = array(); $acknowledgedObjects = array(); $objectsInDowntime = array(); $hostStates = array( @@ -81,9 +82,10 @@ 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; @@ -108,12 +110,11 @@ class Monitoring_HostsController extends Controller $this->view->hostStates = $hostStates; $this->view->objects = $this->hostList; $this->view->unhandledObjects = $unhandledObjects; - $this->view->acknowledgeUnhandledLink = Url::fromPath('monitoring/hosts/acknowledge-problem')->setQueryString( - Filter::where('host', array_map(function ($h) { return $h->host; }, $unhandledObjects))->toQueryString() - ); - $this->view->downtimeUnhandledLink = Url::fromPath('monitoring/hosts/schedule-downtime')->setQueryString( - Filter::where('host', array_map(function ($h) { return $h->host; }, $unhandledObjects))->toQueryString() - ); + $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() From 26bc56e9df306cea1e1937720d07c1dc5b6f519b Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Mon, 29 Dec 2014 12:19:37 +0100 Subject: [PATCH 38/40] monitoring: Fix the link to hosts in downtime when multiple hosts are selected --- .../monitoring/application/controllers/HostsController.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/monitoring/application/controllers/HostsController.php b/modules/monitoring/application/controllers/HostsController.php index 8fe431ca2..09957a4a0 100644 --- a/modules/monitoring/application/controllers/HostsController.php +++ b/modules/monitoring/application/controllers/HostsController.php @@ -75,6 +75,7 @@ class Monitoring_HostsController extends Controller $unhandledFilterExpressions = array(); $acknowledgedObjects = array(); $objectsInDowntime = array(); + $downtimeFilterExpressions = array(); $hostStates = array( Host::getStateText(Host::STATE_UP) => 0, Host::getStateText(Host::STATE_DOWN) => 0, @@ -92,6 +93,7 @@ class Monitoring_HostsController extends Controller } if ((bool) $host->in_downtime === true) { $objectsInDowntime[] = $host; + $downtimeFilterExpressions[] = Filter::where('downtime_host', $host->getName()); } ++$hostStates[$host::getStateText($host->state)]; } @@ -117,8 +119,8 @@ class Monitoring_HostsController extends Controller ->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( From bf92f9fa85476450e898333293da3c49575f2e0b Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Mon, 29 Dec 2014 12:25:41 +0100 Subject: [PATCH 39/40] monitoring: Fix the link to services in downtime when multiple services are selected --- .../application/controllers/ServicesController.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/monitoring/application/controllers/ServicesController.php b/modules/monitoring/application/controllers/ServicesController.php index fdc040d29..4d08622c2 100644 --- a/modules/monitoring/application/controllers/ServicesController.php +++ b/modules/monitoring/application/controllers/ServicesController.php @@ -77,6 +77,7 @@ class Monitoring_ServicesController extends Controller $unhandledFilterExpressions = array(); $acknowledgedObjects = array(); $objectsInDowntime = array(); + $downtimeFilterExpressions = array(); $serviceStates = array( Service::getStateText(Service::STATE_OK) => 0, Service::getStateText(Service::STATE_WARNING) => 0, @@ -105,6 +106,10 @@ class Monitoring_ServicesController extends Controller } 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()])) { @@ -137,8 +142,8 @@ class Monitoring_ServicesController extends Controller ->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( From a48adb5e4bea9fd72ddc2f39be60ec1b671cda7f Mon Sep 17 00:00:00 2001 From: root Date: Wed, 24 Dec 2014 16:30:30 +0000 Subject: [PATCH 40/40] doc: Fix typo in installation.md Signed-off-by: Eric Lippmann --- doc/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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. ````