From 62f502d2768ac2c351cbab8fab6ce02b28d78c7e Mon Sep 17 00:00:00 2001 From: Marius Hein Date: Thu, 28 Aug 2014 15:13:15 +0200 Subject: [PATCH 01/47] Postgres/StatusQuery: Fix concatenation and group by refs #5896 --- .../Monitoring/Backend/Ido/Query/StatusQuery.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php index 2006228b1..d5da738a0 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php @@ -487,10 +487,10 @@ class StatusQuery extends IdoQuery { $sub = '(SELECT' . ' lc.object_id,' - . " CASE WHEN lc.entry_type = 1 THEN CONCAT('[' || c.author_name || '] ' || c.comment_data) ELSE NULL END AS last_comment_data," - . " CASE WHEN lc.entry_type = 2 THEN CONCAT('[' || c.author_name || '] ' || c.comment_data) ELSE NULL END AS last_downtime_data," - . " CASE WHEN lc.entry_type = 3 THEN CONCAT('[' || c.author_name || '] ' || c.comment_data) ELSE NULL END AS last_flapping_data," - . " CASE WHEN lc.entry_type = 4 THEN CONCAT('[' || c.author_name || '] ' || c.comment_data) ELSE NULL END AS last_ack_data" + . " CASE WHEN lc.entry_type = 1 THEN '[' || c.author_name || '] ' || c.comment_data ELSE NULL END AS last_comment_data," + . " CASE WHEN lc.entry_type = 2 THEN '[' || c.author_name || '] ' || c.comment_data ELSE NULL END AS last_downtime_data," + . " CASE WHEN lc.entry_type = 3 THEN '[' || c.author_name || '] ' || c.comment_data ELSE NULL END AS last_flapping_data," + . " CASE WHEN lc.entry_type = 4 THEN '[' || c.author_name || '] ' || c.comment_data ELSE NULL END AS last_ack_data" . ' FROM icinga_comments c' . ' JOIN (SELECT' . ' MAX(comment_id) as comment_id,' @@ -499,7 +499,8 @@ class StatusQuery extends IdoQuery . ' FROM icinga_comments' . ' WHERE entry_type = 1 OR entry_type = 4' . ' GROUP BY object_id, entry_type' - . ') lc ON lc.comment_id = c.comment_id GROUP BY lc.object_id)'; + . ') lc ON lc.comment_id = c.comment_id' + . ' GROUP BY lc.object_id, lc.entry_type, c.author_name, c.comment_data)'; return new Zend_Db_Expr($sub); } From 751d2e6d119cf6c8cfe1a578f49c9176b075b9e5 Mon Sep 17 00:00:00 2001 From: Marius Hein Date: Fri, 29 Aug 2014 10:57:05 +0200 Subject: [PATCH 02/47] ErrorController: Log exception and stacktrace refe #5896 --- application/controllers/ErrorController.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/application/controllers/ErrorController.php b/application/controllers/ErrorController.php index 7d30d62e5..074e2fdc2 100644 --- a/application/controllers/ErrorController.php +++ b/application/controllers/ErrorController.php @@ -4,6 +4,7 @@ // namespace Icinga\Application\Controllers; +use Icinga\Logger\Logger; use Icinga\Web\Controller\ActionController; use Icinga\Application\Icinga; @@ -21,6 +22,10 @@ class ErrorController extends ActionController { $error = $this->_getParam('error_handler'); $exception = $error->exception; + + Logger::error($exception); + Logger::error('Stacktrace: %s', $exception->getTraceAsString()); + switch ($error->type) { case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE: case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER: From e46dd4bdfd000dea4aa259f011638ef61385d8c6 Mon Sep 17 00:00:00 2001 From: Marius Hein Date: Fri, 29 Aug 2014 11:37:20 +0200 Subject: [PATCH 03/47] DowntimeQuery: Postgres fixes Change is_fixed to boolean check and illegal postgres timerange of '0000-00-00 00:00:00'. refs #5896 --- .../library/Monitoring/Backend/Ido/Query/DowntimeQuery.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php index 1f5e17c6c..fe1152076 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php @@ -23,8 +23,8 @@ class DowntimeQuery extends IdoQuery 'downtime_triggered_by_id' => 'sd.triggered_by_id', 'downtime_scheduled_start' => 'UNIX_TIMESTAMP(sd.scheduled_start_time)', 'downtime_scheduled_end' => 'UNIX_TIMESTAMP(sd.scheduled_end_time)', - 'downtime_start' => "UNIX_TIMESTAMP(CASE WHEN sd.trigger_time != '0000-00-00 00:00:00' then sd.trigger_time ELSE sd.scheduled_start_time END)", - 'downtime_end' => 'CASE WHEN sd.is_fixed THEN UNIX_TIMESTAMP(sd.scheduled_end_time) ELSE UNIX_TIMESTAMP(sd.trigger_time) + sd.duration END', + 'downtime_start' => "UNIX_TIMESTAMP(CASE WHEN UNIX_TIMESTAMP(sd.trigger_time) > 0 then sd.trigger_time ELSE sd.scheduled_start_time END)", + 'downtime_end' => 'CASE WHEN sd.is_fixed > 0 THEN UNIX_TIMESTAMP(sd.scheduled_end_time) ELSE UNIX_TIMESTAMP(sd.trigger_time) + sd.duration END', 'downtime_duration' => 'sd.duration', 'downtime_is_in_effect' => 'sd.is_in_effect', 'downtime_internal_id' => 'sd.internal_downtime_id', From b4e9bad87a6b04a023289fb6d2e189c79f0edcea Mon Sep 17 00:00:00 2001 From: Marius Hein Date: Fri, 29 Aug 2014 11:45:06 +0200 Subject: [PATCH 04/47] NotificationHistoryQuery: Add missing field to group statement refs #5896 --- .../Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php index e557f4c7b..64c7e69b8 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php @@ -85,7 +85,8 @@ class NotificationhistoryQuery extends IdoQuery $this->select->group('n.object_id') ->group('n.start_time') ->group('n.output') - ->group('n.state'); + ->group('n.state') + ->group('o.objecttype_id'); } $this->joinedVirtualTables = array('history' => true); From 30f391035c0c497e046e29ffa82766042750a80c Mon Sep 17 00:00:00 2001 From: Marius Hein Date: Fri, 29 Aug 2014 13:41:41 +0200 Subject: [PATCH 05/47] Postgres/DbQuery: Add orderfields to select refs #6896 --- library/Icinga/Data/Db/DbQuery.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/library/Icinga/Data/Db/DbQuery.php b/library/Icinga/Data/Db/DbQuery.php index 605e36af9..a89544be8 100644 --- a/library/Icinga/Data/Db/DbQuery.php +++ b/library/Icinga/Data/Db/DbQuery.php @@ -89,6 +89,17 @@ class DbQuery extends SimpleQuery public function getSelectQuery() { $select = $this->dbSelect(); + + // Add order fields to select for postgres distinct queries (#6351) + if ($this->hasOrder() + && $this->getDatasource()->getDbType() === 'pgsql' + && $select->getPart(Zend_Db_Select::DISTINCT) === true) { + foreach ($this->getOrder() as $fieldAndDirection) { + list($alias, $field) = explode('.', $fieldAndDirection[0]); + $this->columns[$field] = $fieldAndDirection[0]; + } + } + $select->columns($this->columns); $this->applyFilterSql($select); @@ -102,6 +113,7 @@ class DbQuery extends SimpleQuery ); } } + return $select; } From 21f0b4c925ed133f1f253994b31f29ae419b118d Mon Sep 17 00:00:00 2001 From: Marius Hein Date: Fri, 29 Aug 2014 16:52:09 +0200 Subject: [PATCH 06/47] CommandQuery: Remove select * from joins (Postgres) refs #5896 --- .../library/Monitoring/Backend/Ido/Query/CommandQuery.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommandQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommandQuery.php index 664cb3576..c730c078e 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommandQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommandQuery.php @@ -51,10 +51,12 @@ class CommandQuery extends IdoQuery { $this->select->join( array('cnc' => $this->prefix . 'contact_notificationcommands'), - 'cnc.command_object_id = co.object_id' + 'cnc.command_object_id = co.object_id', + array() )->join( array('con' => $this->prefix . 'contacts'), - 'con.contact_id = cnc.contact_id' + 'con.contact_id = cnc.contact_id', + array() ); } } \ No newline at end of file From f47bc466547e5bfe5d7b666e4af68292da4f16fc Mon Sep 17 00:00:00 2001 From: Marius Hein Date: Mon, 1 Sep 2014 09:53:17 +0200 Subject: [PATCH 07/47] GroupSummary/Postgres: Fix group by in initial join query refs #5896 --- .../Monitoring/Backend/Ido/Query/GroupsummaryQuery.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php index 914441d03..3b7fdaa06 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Monitoring\Backend\Ido\Query; +use Icinga\Logger\Logger; use Zend_Db_Select; class GroupSummaryQuery extends IdoQuery @@ -69,8 +70,15 @@ class GroupSummaryQuery extends IdoQuery ) ); + $groupColumn = 'hostgroup'; + + if (in_array('servicegroup', $this->desiredColumns)) { + $groupColumn = 'servicegroup'; + } + $union = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL); - $this->select->from(array('statussummary' => $union), '*')->group($columns[0]); + $this->select->from(array('statussummary' => $union), array($groupColumn))->group(array($groupColumn)); + $this->joinedVirtualTables = array( 'servicestatussummary' => true, 'hoststatussummary' => true From 160fc900d0090c601d3e95a2fb696c746f6e27bc Mon Sep 17 00:00:00 2001 From: Marius Hein Date: Mon, 1 Sep 2014 14:46:06 +0200 Subject: [PATCH 08/47] Backend: Set name if create default backend fixes #7043 --- .../monitoring/library/Monitoring/Backend.php | 3 ++ .../test/php/regression/Bug7043Test.php | 31 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 modules/monitoring/test/php/regression/Bug7043Test.php diff --git a/modules/monitoring/library/Monitoring/Backend.php b/modules/monitoring/library/Monitoring/Backend.php index 34be4aff5..f739b1a25 100644 --- a/modules/monitoring/library/Monitoring/Backend.php +++ b/modules/monitoring/library/Monitoring/Backend.php @@ -74,6 +74,9 @@ class Backend implements Selectable, Queryable, ConnectionInterface foreach (IcingaConfig::module('monitoring', 'backends') as $name => $config) { if (!(bool) $config->get('disabled', false) && $defaultBackend === null) { $defaultBackend = $config; + if ($backendName === null) { + $backendName = $name; + } } $allBackends[$name] = $config; } diff --git a/modules/monitoring/test/php/regression/Bug7043Test.php b/modules/monitoring/test/php/regression/Bug7043Test.php new file mode 100644 index 000000000..8759b4377 --- /dev/null +++ b/modules/monitoring/test/php/regression/Bug7043Test.php @@ -0,0 +1,31 @@ + array( + 'type' => 'db', + 'db' => 'mysql', + 'host' => 'localhost', + 'port' => '3306', + 'password' => 'icinga', + 'username' => 'icinga', + 'dbname' => 'icinga' + ) + )); + + ResourceFactory::setConfig($config); + + $defaultBackend = Backend::createBackend(); + + $this->assertNotNull($defaultBackend->getName(), 'Default backend has a name property set'); + } +} \ No newline at end of file From 6b6826f737ae486756736f9e98ed015cf7472c7a Mon Sep 17 00:00:00 2001 From: Marius Hein Date: Mon, 1 Sep 2014 15:53:32 +0200 Subject: [PATCH 09/47] Bug7043Test: Fix test to run without ini configuration fixes #7043 --- library/Icinga/Application/Config.php | 12 +++++ .../test/php/regression/Bug7043Test.php | 49 +++++++++++++------ 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/library/Icinga/Application/Config.php b/library/Icinga/Application/Config.php index f794d46a0..f30684883 100644 --- a/library/Icinga/Application/Config.php +++ b/library/Icinga/Application/Config.php @@ -85,6 +85,18 @@ class Config extends Zend_Config return self::$app[$configname]; } + /** + * Set module config + * + * @param string $moduleName + * @param string $configName + * @param Zend_Config $config + */ + public static function setModuleConfig($moduleName, $configName, Zend_Config $config) + { + self::$modules[$moduleName][$configName] = $config; + } + /** * Retrieve a module config instance * diff --git a/modules/monitoring/test/php/regression/Bug7043Test.php b/modules/monitoring/test/php/regression/Bug7043Test.php index 8759b4377..37618a348 100644 --- a/modules/monitoring/test/php/regression/Bug7043Test.php +++ b/modules/monitoring/test/php/regression/Bug7043Test.php @@ -2,30 +2,49 @@ namespace Tests\Icinga\Module\Monitoring\Regression; -use Icinga\Data\ResourceFactory; +// Necessary as some of these tests disable phpunit's preservation +// of the global state (e.g. autoloaders are in the global state) +require_once realpath(dirname(__FILE__) . '/../../../../../test/php/bootstrap.php'); + +use Icinga\Application\Config; use Icinga\Module\Monitoring\Backend; use Icinga\Test\BaseTestCase; +use Mockery; +use Zend_Config; -class Bug7043 extends BaseTestCase +class Bug7043Test extends BaseTestCase { + public function tearDown() + { + parent::tearDown(); + Mockery::close(); // Necessary because some tests run in a separate process + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ public function testBackendDefaultName() { - $config = new \Zend_Config(array( - 'ido' => array( - 'type' => 'db', - 'db' => 'mysql', - 'host' => 'localhost', - 'port' => '3306', - 'password' => 'icinga', - 'username' => 'icinga', - 'dbname' => 'icinga' - ) - )); + Mockery::mock('alias:Icinga\Data\ResourceFactory') + ->shouldReceive('create') + ->andReturn( + Mockery::mock('Icinga\Data\Db\DbConnection') + ->shouldReceive('getDbType') + ->andReturn('mysql') + ->shouldReceive('setTablePrefix') + ->getMock() + ); - ResourceFactory::setConfig($config); + Config::setModuleConfig('monitoring', 'backends', new Zend_Config(array( + 'backendName' => array( + 'type' => 'ido', + 'resource' => 'ido' + ) + ))); $defaultBackend = Backend::createBackend(); - $this->assertNotNull($defaultBackend->getName(), 'Default backend has a name property set'); + $this->assertEquals('backendName', $defaultBackend->getName(), 'Default backend has name set'); } } \ No newline at end of file From 63cb357d48c9291b75dc3e1d9157971d5b6e0ea9 Mon Sep 17 00:00:00 2001 From: Marius Hein Date: Mon, 1 Sep 2014 15:58:55 +0200 Subject: [PATCH 10/47] TimelineController: Fix The use statement with non-compound ... fixes #7023 --- .../application/controllers/TimelineController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/monitoring/application/controllers/TimelineController.php b/modules/monitoring/application/controllers/TimelineController.php index 2f9b1339d..3fa621f3e 100644 --- a/modules/monitoring/application/controllers/TimelineController.php +++ b/modules/monitoring/application/controllers/TimelineController.php @@ -2,9 +2,9 @@ // {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}} -use DateTime; -use DateInterval; -use Zend_Config; +use \DateTime; +use \DateInterval; +use \Zend_Config; use Icinga\Web\Url; use Icinga\Util\Format; use Icinga\Application\Config; From 1c24273c9ba1b9434d7b5537cd422bbe60f06226 Mon Sep 17 00:00:00 2001 From: Marius Hein Date: Mon, 1 Sep 2014 16:11:34 +0200 Subject: [PATCH 11/47] Backend: Better implementation for createBackend Thanks eric! fixes #7043 --- .../monitoring/library/Monitoring/Backend.php | 41 ++++++++----------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/modules/monitoring/library/Monitoring/Backend.php b/modules/monitoring/library/Monitoring/Backend.php index f739b1a25..a36bbe8a2 100644 --- a/modules/monitoring/library/Monitoring/Backend.php +++ b/modules/monitoring/library/Monitoring/Backend.php @@ -69,36 +69,31 @@ class Backend implements Selectable, Queryable, ConnectionInterface */ public static function createBackend($backendName = null) { - $allBackends = array(); - $defaultBackend = null; - foreach (IcingaConfig::module('monitoring', 'backends') as $name => $config) { - if (!(bool) $config->get('disabled', false) && $defaultBackend === null) { - $defaultBackend = $config; - if ($backendName === null) { - $backendName = $name; - } - } - $allBackends[$name] = $config; + $config = IcingaConfig::module('monitoring', 'backends'); + if ($config->count() === 0) { + throw new ConfigurationError(t('No backend has been configured')); } - if (empty($allBackends)) { - throw new ConfigurationError('No backend has been configured'); - } - if ($defaultBackend === null) { - throw new ConfigurationError('All backends are disabled'); - } - if ($backendName === null) { - $backendConfig = $defaultBackend; - } else { - if (!array_key_exists($backendName, $allBackends)) { + if ($backendName !== null) { + $backendConfig = $config->get($backendName); + if ($backendConfig === null) { throw new ConfigurationError('No configuration for backend %s', $backendName); } - $backendConfig = $allBackends[$backendName]; - if ((bool) $backendConfig->get('disabled', false)) { + if ((bool) $backendConfig->get('disabled', false) === true) { throw new ConfigurationError( - 'Configuration for backend %s available but backend is disabled', + t('Configuration for backend %s available but backend is disabled'), $backendName ); } + } else { + foreach ($config as $name => $backendConfig) { + if ((bool) $config->get('disabled', false) === false) { + $backendName = $name; + break; + } + } + if ($backendName === null) { + throw new ConfigurationError(t('All backends are disabled')); + } } $resource = ResourceFactory::create($backendConfig->resource); if ($backendConfig->type === 'ido' && $resource->getDbType() !== 'oracle') { From baf768040f0e9613f0fd8d738e57eb405f06b28c Mon Sep 17 00:00:00 2001 From: Marius Hein Date: Tue, 2 Sep 2014 09:55:57 +0200 Subject: [PATCH 12/47] LdapQuery: Use Ldap/Exception for errors refs #5536 --- library/Icinga/Protocol/Ldap/Query.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/library/Icinga/Protocol/Ldap/Query.php b/library/Icinga/Protocol/Ldap/Query.php index ca8f5519f..b5c5e6858 100644 --- a/library/Icinga/Protocol/Ldap/Query.php +++ b/library/Icinga/Protocol/Ldap/Query.php @@ -4,8 +4,6 @@ namespace Icinga\Protocol\Ldap; -use Icinga\Exception\IcingaException; - /** * Search class * @@ -84,7 +82,7 @@ class Query public function limit($count = null, $offset = null) { if (! preg_match('~^\d+~', $count . $offset)) { - throw new IcingaException( + throw new Exception( 'Got invalid limit: %s, %s', $count, $offset @@ -316,7 +314,7 @@ class Query { $parts = array(); if (! isset($this->filters['objectClass']) || $this->filters['objectClass'] === null) { - // throw new IcingaException('Object class is mandatory'); + throw new Exception('Object class is mandatory'); } foreach ($this->filters as $key => $value) { $parts[] = sprintf( From ee6145a173671a7ab25785627a45a59983450d89 Mon Sep 17 00:00:00 2001 From: Marius Hein Date: Tue, 2 Sep 2014 10:17:01 +0200 Subject: [PATCH 13/47] Ldap/Query: Remove __toString() This is needed because of exception handling. Exceptions can now bubble up for default handling. Method render() was renamed to create() because the method create a query. Adjust the test for method create(). refs #5536 --- library/Icinga/Protocol/Ldap/Connection.php | 4 ++-- library/Icinga/Protocol/Ldap/Query.php | 12 +----------- test/php/library/Icinga/Protocol/Ldap/QueryTest.php | 4 ++-- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/library/Icinga/Protocol/Ldap/Connection.php b/library/Icinga/Protocol/Ldap/Connection.php index 531a399c8..6fa41e2aa 100644 --- a/library/Icinga/Protocol/Ldap/Connection.php +++ b/library/Icinga/Protocol/Ldap/Connection.php @@ -307,7 +307,7 @@ class Connection $results = @ldap_search( $this->ds, $base, - (string) $query, + $query->create(), $fields, 0, // Attributes and values 0 // No limit - at least where possible @@ -619,7 +619,7 @@ class Connection $result = @ldap_read( $ds, '', - (string) $query, + $query->create(), $query->listFields() ); diff --git a/library/Icinga/Protocol/Ldap/Query.php b/library/Icinga/Protocol/Ldap/Query.php index b5c5e6858..462c83d61 100644 --- a/library/Icinga/Protocol/Ldap/Query.php +++ b/library/Icinga/Protocol/Ldap/Query.php @@ -300,17 +300,7 @@ class Query * * @string */ - public function __toString() - { - return $this->render(); - } - - /** - * Returns the LDAP filter that will be applied - * - * @string - */ - protected function render() + public function create() { $parts = array(); if (! isset($this->filters['objectClass']) || $this->filters['objectClass'] === null) { diff --git a/test/php/library/Icinga/Protocol/Ldap/QueryTest.php b/test/php/library/Icinga/Protocol/Ldap/QueryTest.php index b710a0934..f3fa2c727 100644 --- a/test/php/library/Icinga/Protocol/Ldap/QueryTest.php +++ b/test/php/library/Icinga/Protocol/Ldap/QueryTest.php @@ -109,10 +109,10 @@ class QueryTest extends BaseTestCase $this->assertEquals('testIntColumn', $cols[0][0]); } - public function test__toString() + public function testCreateQuery() { $select = $this->prepareSelect(); $res = '(&(objectClass=dummyClass)(testIntColumn=1)(testStringColumn=test)(testWildcard=abc*))'; - $this->assertEquals($res, (string) $select); + $this->assertEquals($res, $select->create()); } } From a47c376fb3f993d0b821196a011dea06bff19f15 Mon Sep 17 00:00:00 2001 From: Alexander Fuhr Date: Tue, 2 Sep 2014 04:19:38 -0400 Subject: [PATCH 14/47] Revert "Fixes dashboard title configuration" This reverts commit 968fa36cfd5123137b2d8189b3df723974c6a46d. --- modules/monitoring/configuration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php index ed5251ec1..9b12049e4 100644 --- a/modules/monitoring/configuration.php +++ b/modules/monitoring/configuration.php @@ -130,7 +130,7 @@ $section->add('performance info', $this->translate('Performance Info'), array( /* * Dashboard */ -$dashboard = $this->dashboard('current incidents')->setTitle($this->translate('Current Incidents')); +$dashboard = $this->dashboard('current-incidents', $this->translate('Current Incidents')); $dashboard->add( 'service problems', $this->translate('Service Problems'), From bb0e1dc105a08f14bb32e9aa95d8d2ab2b743516 Mon Sep 17 00:00:00 2001 From: Alexander Fuhr Date: Tue, 2 Sep 2014 04:21:56 -0400 Subject: [PATCH 15/47] Revert "Fixes unaccepted behavior in module configuration" This reverts commit 236d384bab3c1b64f3fd9954487accc33aafe48a. --- library/Icinga/Application/Modules/Module.php | 15 ++--- library/Icinga/Web/Menu.php | 20 +++---- .../Icinga/Web/Widget/Dashboard/Component.php | 37 ++---------- library/Icinga/Web/Widget/Dashboard/Pane.php | 56 +++++++++--------- modules/doc/configuration.php | 3 +- modules/monitoring/configuration.php | 58 +++++++++---------- 6 files changed, 74 insertions(+), 115 deletions(-) diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php index fc7c25b17..9cda4afdc 100644 --- a/library/Icinga/Application/Modules/Module.php +++ b/library/Icinga/Application/Modules/Module.php @@ -177,7 +177,6 @@ class Module /** * Add a pane to dashboard * - * @param $id * @param $name * @return Pane */ @@ -201,21 +200,19 @@ class Module /** * Add a menu Section to the Sidebar menu * - * @param string $id - * @param string $name + * @param $name * @param array $properties * @return mixed */ - protected function menuSection($id, $name, array $properties = array()) + protected function menuSection($name, array $properties = array()) { - if (array_key_exists($id, $this->menuItems)) { - $this->menuItems[$id]->setProperties($properties); + if (array_key_exists($name, $this->menuItems)) { + $this->menuItems[$name]->setProperties($properties); } else { - $this->menuItems[$id] = new Menu($id, new Zend_Config($properties)); - $this->menuItems[$id]->setTitle($name); + $this->menuItems[$name] = new Menu($name, new Zend_Config($properties)); } - return $this->menuItems[$id]; + return $this->menuItems[$name]; } /** diff --git a/library/Icinga/Web/Menu.php b/library/Icinga/Web/Menu.php index c5640ac09..2fa49b99c 100644 --- a/library/Icinga/Web/Menu.php +++ b/library/Icinga/Web/Menu.php @@ -5,7 +5,6 @@ namespace Icinga\Web; use Icinga\Exception\ConfigurationError; -use Icinga\Logger\Logger; use Zend_Config; use RecursiveIterator; use Icinga\Application\Config; @@ -173,34 +172,34 @@ class Menu implements RecursiveIterator */ protected function addMainMenuItems() { - $this->add('dashboard', t('Dashboard'), array( + $this->add(t('Dashboard'), array( 'url' => 'dashboard', 'icon' => 'img/icons/dashboard.png', 'priority' => 10 )); - $section = $this->add('system', t('System'), array( + $section = $this->add(t('System'), array( 'icon' => 'img/icons/configuration.png', 'priority' => 200 )); - $section->add('preferences', t('Preferences'), array( + $section->add(t('Preferences'), array( 'url' => 'preference', 'priority' => 200 )); - $section->add('configuration', t('Configuration'), array( + $section->add(t('Configuration'), array( 'url' => 'config', 'priority' => 300 )); - $section->add('modules', t('Modules'), array( + $section->add(t('Modules'), array( 'url' => 'config/modules', 'priority' => 400 )); - $section->add('applicationlog', t('ApplicationLog'), array( + $section->add(t('ApplicationLog'), array( 'url' => 'list/applicationlog', 'priority' => 500 )); - $this->add('logout', t('Logout'), array( + $this->add(t('Logout'), array( 'url' => 'authentication/logout', 'icon' => 'img/icons/logout.png', 'priority' => 300 @@ -428,10 +427,9 @@ class Menu implements RecursiveIterator * @param array $config * @return Menu */ - public function add($id, $name, $config = array()) + public function add($name, $config = array()) { - $config['title'] = $name; - return $this->addSubMenu($id, new Zend_Config($config)); + return $this->addSubMenu($name, new Zend_Config($config)); } /** diff --git a/library/Icinga/Web/Widget/Dashboard/Component.php b/library/Icinga/Web/Widget/Dashboard/Component.php index 71615c572..87f756282 100644 --- a/library/Icinga/Web/Widget/Dashboard/Component.php +++ b/library/Icinga/Web/Widget/Dashboard/Component.php @@ -30,13 +30,6 @@ class Component extends AbstractWidget */ private $url; - /** - * The id of this Component - * - * @var string - */ - private $id; - /** * The title being displayed on top of the component * @var @@ -67,14 +60,12 @@ EOD; /** * Create a new component displaying the given url in the provided pane * - * @param string $id The id to use for this component * @param string $title The title to use for this component * @param Url|string $url The url this component uses for displaying information * @param Pane $pane The pane this Component will be added to */ - public function __construct($id, $title, $url, Pane $pane) + public function __construct($title, $url, Pane $pane) { - $this->id = $id; $this->title = $title; $this->pane = $pane; if ($url instanceof Url) { @@ -195,14 +186,14 @@ EOD; /** * Create a @see Component instance from the given Zend config, using the provided title - * @param $id The id for this component + * * @param $title The title for this component * @param Zend_Config $config The configuration defining url, parameters, height, width, etc. * @param Pane $pane The pane this component belongs to * * @return Component A newly created Component for use in the Dashboard */ - public static function fromIni($id, $title, Zend_Config $config, Pane $pane) + public static function fromIni($title, Zend_Config $config, Pane $pane) { $height = null; $width = null; @@ -210,27 +201,7 @@ EOD; $parameters = $config->toArray(); unset($parameters['url']); // otherwise there's an url = parameter in the Url - $cmp = new Component($id, $title, Url::fromPath($url, $parameters), $pane); + $cmp = new Component($title, Url::fromPath($url, $parameters), $pane); return $cmp; } - - /** - * Set the components id - * - * @param $id string - */ - public function setId($id) - { - $this->id = $id; - } - - /** - * Retrieve the components id - * - * @return string - */ - public function getId() - { - return $this->id; - } } diff --git a/library/Icinga/Web/Widget/Dashboard/Pane.php b/library/Icinga/Web/Widget/Dashboard/Pane.php index edb3b6b77..39ad1fda3 100644 --- a/library/Icinga/Web/Widget/Dashboard/Pane.php +++ b/library/Icinga/Web/Widget/Dashboard/Pane.php @@ -83,44 +83,44 @@ class Pane extends AbstractWidget /** * Return true if a component with the given title exists in this pane * - * @param string $id The id of the component to check for existence + * @param string $title The title of the component to check for existence * * @return bool */ - public function hasComponent($id) + public function hasComponent($title) { - return array_key_exists($id, $this->components); + return array_key_exists($title, $this->components); } /** * Return a component with the given name if existing * - * @param string $id The id of the component to return + * @param string $title The title of the component to return * * @return Component The component with the given title * @throws ProgrammingError If the component doesn't exist */ - public function getComponent($id) + public function getComponent($title) { - if ($this->hasComponent($id)) { - return $this->components[$id]; + if ($this->hasComponent($title)) { + return $this->components[$title]; } throw new ProgrammingError( 'Trying to access invalid component: %s', - $id + $title ); } /** - * Removes the component with the given id if it exists in this pane + * Removes the component with the given title if it exists in this pane * - * @param string $id The pane + * @param string $title The pane * @return Pane $this */ - public function removeComponent($id) + public function removeComponent($title) { - if ($this->hasComponent($id)) { - unset($this->components[$id]); + if ($this->hasComponent($title)) { + unset($this->components[$title]); } return $this; } @@ -146,7 +146,6 @@ class Pane extends AbstractWidget /** * Add a component to this pane, optionally creating it if $component is a string * - * @param string $id An unique Identifier * @param string|Component $component The component object or title * (if a new component will be created) * @param string|null $url An Url to be used when component is a string @@ -154,12 +153,12 @@ class Pane extends AbstractWidget * @return self * @throws \Icinga\Exception\ConfigurationError */ - public function addComponent($id, $component, $url = null) + public function addComponent($component, $url = null) { if ($component instanceof Component) { - $this->components[$component->getId()] = $component; - } elseif (is_string($id) && is_string($component) && $url !== null) { - $this->components[$id] = new Component($id, $component, $url, $this); + $this->components[$component->getTitle()] = $component; + } elseif (is_string($component) && $url !== null) { + $this->components[$component] = new Component($component, $url, $this); } else { throw new ConfigurationError('Invalid component added: %s', $component); } @@ -176,15 +175,15 @@ class Pane extends AbstractWidget { /* @var $component Component */ foreach ($components as $component) { - if (array_key_exists($component->getId(), $this->components)) { - if (preg_match('/-(\d+)$/', $component->getId(), $m)) { - $name = preg_replace('/-\d+$/', $m[1]++, $component->getId()); + if (array_key_exists($component->getTitle(), $this->components)) { + if (preg_match('/_(\d+)$/', $component->getTitle(), $m)) { + $name = preg_replace('/_\d+$/', $m[1]++, $component->getTitle()); } else { - $name = $component->getId() . '-2'; + $name = $component->getTitle() . '_2'; } $this->components[$name] = $component; } else { - $this->components[$component->getId()] = $component; + $this->components[$component->getTitle()] = $component; } } @@ -194,18 +193,17 @@ class Pane extends AbstractWidget /** * Add a component to the current pane * - * @param $id * @param $title - * @param null $url - * @return mixed + * @param $url + * @return Component * * @see addComponent() */ - public function add($id, $title, $url = null) + public function add($title, $url = null) { - $this->addComponent($id, $title, $url); + $this->addComponent($title, $url); - return $this->components[$id]; + return $this->components[$title]; } /** diff --git a/modules/doc/configuration.php b/modules/doc/configuration.php index 06e900300..ce3f99113 100644 --- a/modules/doc/configuration.php +++ b/modules/doc/configuration.php @@ -4,7 +4,8 @@ /* @var $this \Icinga\Application\Modules\Module */ -$section = $this->menuSection('documentation', $this->translate('Documentation'), array( +$section = $this->menuSection($this->translate('Documentation'), array( + 'title' => 'Documentation', 'icon' => 'img/icons/comment.png', 'url' => 'doc', 'priority' => 80 diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php index 9b12049e4..412581d24 100644 --- a/modules/monitoring/configuration.php +++ b/modules/monitoring/configuration.php @@ -22,73 +22,72 @@ $this->provideConfigTab('security', array( /* * Problems Section */ -$section = $this->menuSection('problems', $this->translate('Problems'), array( +$section = $this->menuSection($this->translate('Problems'), array( 'icon' => 'img/icons/error.png', 'priority' => 20 )); -$section->add('unhandled hosts', $this->translate('Unhandled Hosts'), array( +$section->add($this->translate('Unhandled Hosts'), array( 'url' => 'monitoring/list/hosts?host_problem=1&host_handled=0', 'priority' => 40 )); -$section->add('unhandled services', $this->translate('Unhandled Services'), array( +$section->add($this->translate('Unhandled Services'), array( 'url' => 'monitoring/list/services?service_problem=1&service_handled=0&sort=service_severity', 'priority' => 40 )); -$section->add('host problems', $this->translate('Host Problems'), array( +$section->add($this->translate('Host Problems'), array( 'url' => 'monitoring/list/hosts?host_problem=1&sort=host_severity', 'priority' => 50 )); -$section->add('service prolems', $this->translate('Service Problems'), array( +$section->add($this->translate('Service Problems'), array( 'url' => 'monitoring/list/services?service_problem=1&sort=service_severity&dir=desc', 'priority' => 50 )); -$section->add('current downtimes', $this->translate('Current Downtimes')) - ->setUrl('monitoring/list/downtimes?downtime_is_in_effect=1'); +$section->add($this->translate('Current Downtimes'))->setUrl('monitoring/list/downtimes?downtime_is_in_effect=1'); /* * Overview Section */ -$section = $this->menuSection('overview', $this->translate('Overview'), array( +$section = $this->menuSection($this->translate('Overview'), array( 'icon' => 'img/icons/hostgroup.png', 'priority' => 30 )); -$section->add('tactical overview', $this->translate('Tactical Overview'), array( +$section->add($this->translate('Tactical Overview'), array( 'url' => 'monitoring/tactical', 'priority' => 40 )); -$section->add('hosts', $this->translate('Hosts'), array( +$section->add($this->translate('Hosts'), array( 'url' => 'monitoring/list/hosts', 'priority' => 50 )); -$section->add('services', $this->translate('Services'), array( +$section->add($this->translate('Services'), array( 'url' => 'monitoring/list/services', 'priority' => 50 )); -$section->add('servicematrix', $this->translate('Servicematrix'), array( +$section->add($this->translate('Servicematrix'), array( 'url' => 'monitoring/list/servicematrix?service_problem=1', 'priority' => 51 )); -$section->add('servicegroups', $this->translate('Servicegroups'), array( +$section->add($this->translate('Servicegroups'), array( 'url' => 'monitoring/list/servicegroups', 'priority' => 60 )); -$section->add('hostgroups', $this->translate('Hostgroups'), array( +$section->add($this->translate('Hostgroups'), array( 'url' => 'monitoring/list/hostgroups', 'priority' => 60 )); -$section->add('contactgroups', $this->translate('Contactgroups'), array( +$section->add($this->translate('Contactgroups'), array( 'url' => 'monitoring/list/contactgroups', 'priority' => 61 )); -$section->add('downtimes', $this->translate('Downtimes'), array( +$section->add($this->translate('Downtimes'), array( 'url' => 'monitoring/list/downtimes', 'priority' => 71 )); -$section->add('comments', $this->translate('Comments'), array( +$section->add($this->translate('Comments'), array( 'url' => 'monitoring/list/comments?comment_type=(comment|ack)', 'priority' => 70 )); -$section->add('contacts', $this->translate('Contacts'), array( +$section->add($this->translate('Contacts'), array( 'url' => 'monitoring/list/contacts', 'priority' => 70 )); @@ -96,33 +95,31 @@ $section->add('contacts', $this->translate('Contacts'), array( /* * History Section */ -$section = $this->menuSection('history', $this->translate('History'), array( - 'title' => $this->translate('History'), +$section = $this->menuSection($this->translate('History'), array( 'icon' => 'img/icons/history.png' )); -$section->add('critical events', $this->translate('Critical Events'), array( - 'title' => $this->translate('Critical Events'), +$section->add($this->translate('Critical Events'), array( 'url' => 'monitoring/list/statehistorysummary', 'priority' => 50 )); -$section->add('notifications', $this->translate('Notifications'), array( +$section->add($this->translate('Notifications'), array( 'url' => 'monitoring/list/notifications' )); -$section->add('events', $this->translate('Events'), array( +$section->add($this->translate('Events'), array( 'title' => $this->translate('All Events'), 'url' => 'monitoring/list/eventhistory?timestamp>=-7%20days' )); -$section->add('timeline', $this->translate('Timeline'))->setUrl('monitoring/timeline'); +$section->add($this->translate('Timeline'))->setUrl('monitoring/timeline'); /* * System Section */ -$section = $this->menuSection('system', $this->translate('System')); -$section->add('process info', $this->translate('Process Info'), array( +$section = $this->menuSection($this->translate('System')); +$section->add($this->translate('Process Info'), array( 'url' => 'monitoring/process/info', 'priority' => 120 )); -$section->add('performance info', $this->translate('Performance Info'), array( +$section->add($this->translate('Performance Info'), array( 'url' => 'monitoring/process/performance', 'priority' => 130 )); @@ -130,19 +127,16 @@ $section->add('performance info', $this->translate('Performance Info'), array( /* * Dashboard */ -$dashboard = $this->dashboard('current-incidents', $this->translate('Current Incidents')); +$dashboard = $this->dashboard($this->translate('Current Incidents')); $dashboard->add( - 'service problems', $this->translate('Service Problems'), 'monitoring/list/services?service_problem=1&limit=10&sort=service_severity' ); $dashboard->add( - 'recently recovered services', $this->translate('Recently Recovered Services'), 'monitoring/list/services?service_state=0&limit=10&sort=service_last_state_change&dir=desc' ); $dashboard->add( - 'host problems', $this->translate('Host Problems'), 'monitoring/list/hosts?host_problem=1&sort=host_severity' ); From 8f124051e33ca59dcfddb86ae9034882521bbc8d Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 11:32:48 +0200 Subject: [PATCH 16/47] MonitoringBackend: use correct config object --- modules/monitoring/library/Monitoring/Backend.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/monitoring/library/Monitoring/Backend.php b/modules/monitoring/library/Monitoring/Backend.php index a36bbe8a2..454a33cef 100644 --- a/modules/monitoring/library/Monitoring/Backend.php +++ b/modules/monitoring/library/Monitoring/Backend.php @@ -86,7 +86,7 @@ class Backend implements Selectable, Queryable, ConnectionInterface } } else { foreach ($config as $name => $backendConfig) { - if ((bool) $config->get('disabled', false) === false) { + if ((bool) $backendConfig->get('disabled', false) === false) { $backendName = $name; break; } From 153013bbc6da7d6f056167b19d85d16035bdb5eb Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 12:28:25 +0200 Subject: [PATCH 17/47] Ido\StatusQuery: pending hosts are not problems --- .../library/Monitoring/Backend/Ido/Query/StatusQuery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php index d5da738a0..af52eb066 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php @@ -41,7 +41,7 @@ class StatusQuery extends IdoQuery 'host_next_check' => 'CASE hs.should_be_scheduled WHEN 1 THEN UNIX_TIMESTAMP(hs.next_check) ELSE NULL END', 'host_check_execution_time' => 'hs.execution_time', 'host_check_latency' => 'hs.latency', - 'host_problem' => 'CASE WHEN hs.current_state = 0 THEN 0 ELSE 1 END', + 'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END', 'host_notifications_enabled' => 'hs.notifications_enabled', From 4a95b9d9427e9349c314681d7dca50f8f339530f Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 12:45:15 +0200 Subject: [PATCH 18/47] StatusSummaryQuery: respect pending, add columns --- .../Backend/Ido/Query/StatusSummaryQuery.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusSummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusSummaryQuery.php index 75db67b55..6555010c2 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusSummaryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusSummaryQuery.php @@ -33,23 +33,27 @@ class StatusSummaryQuery extends IdoQuery 'hosts_flapping' => 'SUM(CASE WHEN object_type = \'host\' AND is_flapping = 1 THEN 1 ELSE 0 END)' ), 'servicestatussummary' => array( + 'services_total' => 'SUM(CASE WHEN object_type = \'service\' THEN 1 ELSE 0 END)', + 'services_problem' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 THEN 1 ELSE 0 END)', + 'services_problem_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 AND (acknowledged + in_downtime + host_problem) > 0 THEN 1 ELSE 0 END)', + 'services_problem_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 AND (acknowledged + in_downtime + host_problem) = 0 THEN 1 ELSE 0 END)', 'services_ok' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 THEN 1 ELSE 0 END)', 'services_ok_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'services_pending' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 THEN 1 ELSE 0 END)', 'services_pending_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'services_warning' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 THEN 1 ELSE 0 END)', - 'services_warning_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) > 0 THEN 1 ELSE 0 END)', - 'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) = 0 THEN 1 ELSE 0 END)', + 'services_warning_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND (acknowledged + in_downtime + host_problem) > 0 THEN 1 ELSE 0 END)', + 'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND (acknowledged + in_downtime + host_problem) = 0 THEN 1 ELSE 0 END)', 'services_warning_passive' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', 'services_warning_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'services_critical' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 THEN 1 ELSE 0 END)', - 'services_critical_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) > 0 THEN 1 ELSE 0 END)', - 'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) = 0 THEN 1 ELSE 0 END)', + 'services_critical_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND (acknowledged + in_downtime + host_problem) > 0 THEN 1 ELSE 0 END)', + 'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND (acknowledged + in_downtime + host_problem) = 0 THEN 1 ELSE 0 END)', 'services_critical_passive' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', 'services_critical_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'services_unknown' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 THEN 1 ELSE 0 END)', - 'services_unknown_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) > 0 THEN 1 ELSE 0 END)', - 'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) = 0 THEN 1 ELSE 0 END)', + 'services_unknown_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND (acknowledged + in_downtime + host_problem) > 0 THEN 1 ELSE 0 END)', + 'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND (acknowledged + in_downtime + host_problem) = 0 THEN 1 ELSE 0 END)', 'services_unknown_passive' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', 'services_unknown_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'services_active' => 'SUM(CASE WHEN object_type = \'service\' AND is_active_checked = 1 THEN 1 ELSE 0 END)', @@ -131,6 +135,7 @@ class StatusSummaryQuery extends IdoQuery 'acknowledged' => 'hs.problem_has_been_acknowledged', 'in_downtime' => 'CASE WHEN (hs.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END', 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END', + 'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END', 'is_passive_checked' => 'CASE WHEN hs.active_checks_enabled = 0 AND hs.passive_checks_enabled = 1 THEN 1 ELSE 0 END', 'is_active_checked' => 'hs.active_checks_enabled', 'is_processing_events' => 'hs.event_handler_enabled', @@ -144,6 +149,7 @@ class StatusSummaryQuery extends IdoQuery 'acknowledged' => 'ss.problem_has_been_acknowledged', 'in_downtime' => 'CASE WHEN (ss.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END', 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END', + 'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END', 'is_passive_checked' => 'CASE WHEN ss.active_checks_enabled = 0 AND ss.passive_checks_enabled = 1 THEN 1 ELSE 0 END', 'is_active_checked' => 'ss.active_checks_enabled', 'is_processing_events' => 'ss.event_handler_enabled', From 6be31f4f51d6f1c6515afe1736861967267a9d59 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 12:47:03 +0200 Subject: [PATCH 19/47] Downtime*Query: use 1970-01-02 as "null" refs #7000 --- .../Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php | 2 +- .../Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php index 5d600be03..f9ec831a7 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php @@ -47,7 +47,7 @@ class DowntimeendhistoryQuery extends IdoQuery array('h' => $this->prefix . 'downtimehistory'), 'o.' . $this->object_id . ' = h.' . $this->object_id . ' AND o.is_active = 1', array() - )->where('h.actual_end_time > ?', '1970-01-01 00:00:00'); + )->where('h.actual_end_time > ?', '1970-01-02 00:00:00'); $this->joinedVirtualTables = array('downtimehistory' => true); } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php index e5a1bb8dc..01c56cd4d 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php @@ -47,7 +47,7 @@ class DowntimestarthistoryQuery extends IdoQuery array('h' => $this->prefix . 'downtimehistory'), 'o.' . $this->object_id . ' = h.' . $this->object_id . ' AND o.is_active = 1', array() - )->where('h.actual_start_time > ?', '1970-01-01 00:00:00'); + )->where('h.actual_start_time > ?', '1970-01-02 00:00:00'); $this->joinedVirtualTables = array('downtimehistory' => true); } } From 307787cfb76a2db12bb06be7d830467c776bce1d Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 12:54:38 +0200 Subject: [PATCH 20/47] DataView: use default sort order if none given We should not be forced to order(null) to have the default order, that should be the default in case order has not been called. refs #6644 --- .../library/Monitoring/DataView/DataView.php | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/modules/monitoring/library/Monitoring/DataView/DataView.php b/modules/monitoring/library/Monitoring/DataView/DataView.php index b8ccbb852..b7035a850 100644 --- a/modules/monitoring/library/Monitoring/DataView/DataView.php +++ b/modules/monitoring/library/Monitoring/DataView/DataView.php @@ -31,6 +31,8 @@ abstract class DataView implements Browsable, Filterable, Sortable protected $connection; + protected $isSorted = false; + /** * Create a new view * @@ -99,6 +101,7 @@ public function dump() protected function applyUrlFilter($request = null) { $url = Url::fromRequest(); + $limit = $url->shift('limit'); $sort = $url->shift('sort'); $dir = $url->shift('dir'); @@ -132,20 +135,19 @@ public function dump() } } - $order = isset($params['order']) ? $params['order'] : null; - if ($order !== null) { - if (strtolower($order) === 'desc') { - $order = self::SORT_DESC; - } else { - $order = self::SORT_ASC; + if (isset($params['sort'])) { + + $order = isset($params['order']) ? $params['order'] : null; + if ($order !== null) { + if (strtolower($order) === 'desc') { + $order = self::SORT_DESC; + } else { + $order = self::SORT_ASC; + } } + + $view->sort($params['sort'], $order); } - - $view->sort( - isset($params['sort']) ? $params['sort'] : null, - $order - ); - return $view; } @@ -226,6 +228,7 @@ public function dump() foreach ($sortColumns['columns'] as $column) { $this->query->order($column, $order); } + $this->isSorted = true; } return $this; } @@ -285,6 +288,7 @@ public function dump() */ public function getQuery() { + if (! $this->isSorted) { $this->sort(); } return $this->query; } From e75de8cf60c9be63f4505775cc8789b8d82c0c83 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 13:07:20 +0200 Subject: [PATCH 21/47] monitoring/list: redirect when posting new order refs #6644 --- .../application/controllers/ListController.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/modules/monitoring/application/controllers/ListController.php b/modules/monitoring/application/controllers/ListController.php index e123cdb0f..bd380a787 100644 --- a/modules/monitoring/application/controllers/ListController.php +++ b/modules/monitoring/application/controllers/ListController.php @@ -34,8 +34,21 @@ class Monitoring_ListController extends Controller protected function hasBetterUrl() { + $request = $this->getRequest(); $url = clone($this->url); + if ($this->getRequest()->isPost()) { + + if ($request->getPost('sort')) { + $url->setParam('sort', $request->getPost('sort')); + if ($request->getPost('dir')) { + $url->setParam('dir', $request->getPost('dir')); + } else { + $url->removeParam('dir'); + } + return $url; + } + $q = $this->getRequest()->getPost('q'); } else { $q = $url->shift('q'); From 26339b128a9815f13c27a2042bd59c42c0a58d29 Mon Sep 17 00:00:00 2001 From: Alexander Fuhr Date: Tue, 2 Sep 2014 13:16:21 +0200 Subject: [PATCH 22/47] Add disabled property and implement the functionality refs #6986 --- .../Icinga/Web/Widget/Dashboard/Component.php | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/library/Icinga/Web/Widget/Dashboard/Component.php b/library/Icinga/Web/Widget/Dashboard/Component.php index 87f756282..7410c6210 100644 --- a/library/Icinga/Web/Widget/Dashboard/Component.php +++ b/library/Icinga/Web/Widget/Dashboard/Component.php @@ -42,6 +42,13 @@ class Component extends AbstractWidget */ private $pane; + /** + * The disabled option is used to "delete" default dashlets provided by modules + * + * @var bool + */ + private $disabled = false; + /** * The template string used for rendering this widget * @@ -117,6 +124,26 @@ EOD; return $this; } + /** + * Set the disabled property + * + * @param boolean $disabled + */ + public function setDisabled($disabled) + { + $this->disabled = $disabled; + } + + /** + * Get the disabled property + * + * @return boolean + */ + public function getDisabled() + { + return $this->disabled; + } + /** * Return this component's structure as array * @@ -136,6 +163,10 @@ EOD; */ public function render() { + if ($this->disabled === true) { + return ''; + } + $view = $this->view(); $url = clone($this->url); $url->setParam('view', 'compact'); From 62c0f0be030454bb6a15240995911c84fa9a0c05 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 13:23:15 +0200 Subject: [PATCH 23/47] Web\Session: implement lazy loading fixes #7055 --- library/Icinga/Application/Web.php | 22 ---------------------- library/Icinga/Web/Session.php | 2 +- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/library/Icinga/Application/Web.php b/library/Icinga/Application/Web.php index 2b45ad683..ecf863e9e 100644 --- a/library/Icinga/Application/Web.php +++ b/library/Icinga/Application/Web.php @@ -13,8 +13,6 @@ use Icinga\Logger\Logger; use Icinga\Web\Request; use Icinga\Web\Response; use Icinga\Web\View; -use Icinga\Web\Session\Session as BaseSession; -use Icinga\Web\Session; use Icinga\User; use Icinga\Util\Translator; use Icinga\Util\DateTimeFactory; @@ -59,13 +57,6 @@ class Web extends ApplicationBootstrap */ private $request; - /** - * Session object - * - * @var BaseSession - */ - private $session; - /** * User object * @@ -92,7 +83,6 @@ class Web extends ApplicationBootstrap ->setupErrorHandling() ->loadConfig() ->setupResourceFactory() - ->setupSession() ->setupUser() ->setupTimezone() ->setupLogger() @@ -172,7 +162,6 @@ class Web extends ApplicationBootstrap $this->setupFrontController(); $this->setupViewRenderer(); - return $this; } @@ -192,17 +181,6 @@ class Web extends ApplicationBootstrap return $this; } - /** - * Initialize a session provider - * - * @return self - */ - private function setupSession() - { - $this->session = Session::create(); - return $this; - } - /** * Inject dependencies into request * diff --git a/library/Icinga/Web/Session.php b/library/Icinga/Web/Session.php index 58bfc70bd..4723903e9 100644 --- a/library/Icinga/Web/Session.php +++ b/library/Icinga/Web/Session.php @@ -47,7 +47,7 @@ class Session public static function getSession() { if (self::$session === null) { - throw new ProgrammingError('No session created yet'); + self::create(); } return self::$session; From 8a5e274c8b41e47b09bf67c40dd4818d67271314 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 13:26:48 +0200 Subject: [PATCH 24/47] monitoring/show: handle format=sql after pagination --- modules/monitoring/application/controllers/ShowController.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/monitoring/application/controllers/ShowController.php b/modules/monitoring/application/controllers/ShowController.php index 1ad0f29dd..0200284ce 100644 --- a/modules/monitoring/application/controllers/ShowController.php +++ b/modules/monitoring/application/controllers/ShowController.php @@ -89,9 +89,8 @@ class Monitoring_ShowController extends Controller $this->getTabs()->activate('history'); //$this->view->object->populate(); $this->view->object->fetchEventHistory(); + $this->view->history = $this->view->object->eventhistory->paginate($this->params->get('limit', 50)); $this->handleFormatRequest($this->view->object->eventhistory); - $this->view->history = $this->view->object->eventhistory - ->paginate($this->params->get('limit', 50)); } public function servicesAction() From 19136b548a572e0141f10dafaf45fb015e868758 Mon Sep 17 00:00:00 2001 From: Marius Hein Date: Tue, 2 Sep 2014 13:56:18 +0200 Subject: [PATCH 25/47] StatusQuery: Separate last comment fields fixes #7057 --- .../Backend/Ido/Query/StatusQuery.php | 159 ++++++++++++++---- 1 file changed, 127 insertions(+), 32 deletions(-) diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php index af52eb066..db5b316e1 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php @@ -278,20 +278,41 @@ class StatusQuery extends IdoQuery ELSE 0 END' ), + 'serviceproblemsummary' => array( 'host_unhandled_services' => 'sps.unhandled_services_count' ), - 'lasthostcomment' => array( - 'host_last_comment' => 'hlc.last_comment_data', - 'host_last_downtime' => 'hlc.last_downtime_data', - 'host_last_flapping' => 'hlc.last_flapping_data', - 'host_last_ack' => 'hlc.last_ack_data', + + 'lasthostcommentgeneric' => array( + 'host_last_comment' => 'hlcg.last_comment_data' ), - 'lastservicecomment' => array( - 'service_last_comment' => 'slc.last_comment_data', - 'service_last_downtime' => 'slc.last_downtime_data', - 'service_last_flapping' => 'slc.last_flapping_data', - 'service_last_ack' => 'slc.last_ack_data', + + 'lasthostcommentdowntime' => array( + 'host_last_downtime' => 'hlcd.last_downtime_data' + ), + + 'lasthostcommentflapping' => array( + 'host_last_flapping' => 'hlcf.last_flapping_data' + ), + + 'lasthostcommentack' => array( + 'host_last_ack' => 'hlca.last_ack_data' + ), + + 'lastservicecommentgeneric' => array( + 'service_last_comment' => 'slcg.last_comment_data' + ), + + 'lastservicecommentdowntime' => array( + 'service_last_downtime' => 'slcd.last_downtime_data' + ), + + 'lastservicecommentflapping' => array( + 'service_last_flapping' => 'slcf.last_flapping_data' + ), + + 'lastservicecommentack' => array( + 'service_last_ack' => 'slca.last_ack_data' ) ); @@ -483,42 +504,116 @@ class StatusQuery extends IdoQuery ); } - protected function getLastCommentSubQuery() + /** + * Create a subquery to join comments into status query + * @param int $entryType + * @param string $fieldName + * @return Zend_Db_Expr + */ + protected function getLastCommentSubQuery($entryType, $fieldName) { $sub = '(SELECT' - . ' lc.object_id,' - . " CASE WHEN lc.entry_type = 1 THEN '[' || c.author_name || '] ' || c.comment_data ELSE NULL END AS last_comment_data," - . " CASE WHEN lc.entry_type = 2 THEN '[' || c.author_name || '] ' || c.comment_data ELSE NULL END AS last_downtime_data," - . " CASE WHEN lc.entry_type = 3 THEN '[' || c.author_name || '] ' || c.comment_data ELSE NULL END AS last_flapping_data," - . " CASE WHEN lc.entry_type = 4 THEN '[' || c.author_name || '] ' || c.comment_data ELSE NULL END AS last_ack_data" + . ' c.object_id,' + . " '[' || c.author_name || '] ' || c.comment_data AS $fieldName" . ' FROM icinga_comments c' - . ' JOIN (SELECT' - . ' MAX(comment_id) as comment_id,' - . ' object_id,' - . ' entry_type' - . ' FROM icinga_comments' - . ' WHERE entry_type = 1 OR entry_type = 4' - . ' GROUP BY object_id, entry_type' - . ') lc ON lc.comment_id = c.comment_id' - . ' GROUP BY lc.object_id, lc.entry_type, c.author_name, c.comment_data)'; + . ' WHERE c.entry_type = ' . $entryType + . ' ORDER BY c.comment_id DESC LIMIT 1)'; + return new Zend_Db_Expr($sub); } - protected function joinLasthostcomment() + /** + * Join last host comment + */ + protected function joinLasthostcommentgeneric() { $this->select->joinLeft( - array('hlc' => $this->getLastCommentSubQuery()), - 'hlc.object_id = hs.host_object_id', + array('hlcg' => $this->getLastCommentSubQuery(1, 'last_comment_data')), + 'hlcg.object_id = hs.host_object_id', array() ); } - // TODO: Terribly slow. As I have no idea of how to fix this we should remove it. - protected function joinLastservicecomment() + /** + * Join last host downtime comment + */ + protected function joinLasthostcommentdowntime() { $this->select->joinLeft( - array('slc' => $this->getLastCommentSubQuery()), - 'slc.object_id = ss.service_object_id', + array('hlcd' => $this->getLastCommentSubQuery(2, 'last_downtime_data')), + 'hlcg.object_id = hs.host_object_id', + array() + ); + } + + /** + * Join last host flapping comment + */ + protected function joinLastHostcommentflapping() + { + $this->select->joinLeft( + array('hlcf' => $this->getLastCommentSubQuery(3, 'last_flapping_data')), + 'hlcg.object_id = hs.host_object_id', + array() + ); + } + + /** + * Join last host acknowledgement comment + */ + protected function joinLasthostcommentack() + { + $this->select->joinLeft( + array('hlca' => $this->getLastCommentSubQuery(4, 'last_ack_data')), + 'hlca.object_id = hs.host_object_id', + array() + ); + } + + /** + * Join last service comment + */ + protected function joinLastservicecommentgeneric() + { + $this->select->joinLeft( + array('slcg' => $this->getLastCommentSubQuery(1, 'last_comment_data')), + 'slcg.object_id = ss.service_object_id', + array() + ); + } + + /** + * Join last service downtime comment + */ + protected function joinLastservicecommentdowntime() + { + $this->select->joinLeft( + array('slcd' => $this->getLastCommentSubQuery(2, 'last_downtime_data')), + 'slcd.object_id = ss.service_object_id', + array() + ); + } + + /** + * Join last service flapping comment + */ + protected function joinLastservicecommentflapping() + { + $this->select->joinLeft( + array('slcf' => $this->getLastCommentSubQuery(3, 'last_flapping_data')), + 'slcf.object_id = ss.service_object_id', + array() + ); + } + + /** + * Join last service acknowledgement comment + */ + protected function joinLastservicecommentack() + { + $this->select->joinLeft( + array('slca' => $this->getLastCommentSubQuery(4, 'last_ack_data')), + 'slca.object_id = ss.service_object_id', array() ); } From d8e71d379063630a5f99cf63dd99f645fcd96fd4 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 14:07:46 +0200 Subject: [PATCH 26/47] monitoring/list: ignore post in applyFilter refs #6644 --- .../application/controllers/ListController.php | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/modules/monitoring/application/controllers/ListController.php b/modules/monitoring/application/controllers/ListController.php index bd380a787..b71d78acf 100644 --- a/modules/monitoring/application/controllers/ListController.php +++ b/modules/monitoring/application/controllers/ListController.php @@ -501,15 +501,8 @@ class Monitoring_ListController extends Controller $request = $this->getRequest(); $limit = $params->shift('limit'); - - $sort = null; - $dir = null; - if ($request->isPost()) { - $sort = $request->getPost('sort', null); - $dir = $request->getPost('dir', null); - } - $sort = $params->shift('sort', $sort); - $dir = $params->shift('dir', $dir); + $sort = $params->shift('sort'); + $dir = $params->shift('dir'); $page = $params->shift('page'); $format = $params->shift('format'); $view = $params->shift('view'); @@ -546,7 +539,9 @@ class Monitoring_ListController extends Controller $query->applyFilter($filter); } $this->view->filter = $filter; - $query->order($sort, $dir); + if ($sort) { + $query->order($sort, $dir); + } $this->applyRestrictions($query); $this->handleFormatRequest($query); return $query; From a58b2aac004427dd43cfa549f99f71cb39ecf333 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 15:31:06 +0200 Subject: [PATCH 27/47] Ido\StatusQuery: join ALL latest comment And it was soooo fast :p refs #7057 --- .../library/Monitoring/Backend/Ido/Query/StatusQuery.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php index db5b316e1..d3aca9934 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php @@ -515,9 +515,10 @@ class StatusQuery extends IdoQuery $sub = '(SELECT' . ' c.object_id,' . " '[' || c.author_name || '] ' || c.comment_data AS $fieldName" - . ' FROM icinga_comments c' - . ' WHERE c.entry_type = ' . $entryType - . ' ORDER BY c.comment_id DESC LIMIT 1)'; + . ' FROM icinga_comments c JOIN (' + . ' SELECT MAX(comment_id) AS comment_id, object_id FROM icinga_comments' + . ' WHERE entry_type = ' . $entryType . ' GROUP BY object_id' + . ' ) lc ON c.comment_id = lc.comment_id)'; return new Zend_Db_Expr($sub); } From ff6483cec5793202c9b5f4ddaea3e4abf7fee37e Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 16:22:48 +0200 Subject: [PATCH 28/47] Web\FileCache: initial implementation refs #6927 --- library/Icinga/Web/FileCache.php | 275 +++++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 library/Icinga/Web/FileCache.php diff --git a/library/Icinga/Web/FileCache.php b/library/Icinga/Web/FileCache.php new file mode 100644 index 000000000..16c89a2a4 --- /dev/null +++ b/library/Icinga/Web/FileCache.php @@ -0,0 +1,275 @@ +name = $name; + $tmpdir = sys_get_temp_dir(); + $basedir = $tmpdir . '/FileCache_' . $name; + + if (file_exists($basedir) && is_writeable($basedir)) { + + $this->basedir = $basedir; + $this->enabled = true; + + } elseif (file_exists($tmpdir) && is_writeable($tmpdir)) { + + if (mkdir($basedir, '0750', true)) { + $this->enabled = true; + $this->basedir = $basedir; + } + + } + } + + /** + * Store the given content to the desired file name + * + * @param string $file new (relative) filename + * @param string $content the content to be stored + * + * @return bool whether the file has been stored + */ + public function store($file, $content) + { + if (! $this->enabled) { + + return false; + } + + return file_put_contents($this->filename($file), $content); + } + + /** + * Find out whether a given file exists + * + * @param string $file the (relative) filename + * @param int $newerThan optional timestamp to compare against + * + * @return bool whether such file exists + */ + public function has($file, $newerThan = null) + { + if (! $this->enabled) { + + return false; + } + + $filename = $this->filename($file); + + if (! file_exists($filename) || ! is_readable($filename)) { + + return false; + } + + if ($newerThan === null) { + + return true; + } + + $info = stat($file); + + if ($info === false) { + + return false; + } + + return (int) $newerThan < $info['mtime']; + } + + /** + * Get a specific file or false if no such file available + * + * @param string $file the disired file name + * + * @return string|bool Filename content or false + */ + public function get($file) + { + if ($this->has($file)) { + return file_get_contents($this->filename($file)); + } + + return false; + } + + /** + * Send a specific file to the browser (output) + * + * @param string $file the disired file name + * + * @return bool Whether the file has been sent + */ + public function send($file) + { + if ($this->has($file)) { + readfile($this->filename($file)); + + return true; + } + + return false; + } + + /** + * Get absolute filename for a given file + * + * @param string $file the disired file name + * + * @return string absolute filename + */ + protected function filename($file) + { + return $this->basedir . '/' . $file; + } + + /** + * Whether the given ETag matches a cached file + * + * If no ETag is given we'll try to fetch the one from the current + * HTTP request. + * + * @param string $file The cached file you want to check + * @param string $match The ETag to match against + * + * @return string|bool ETag on match, otherwise false + */ + public function etagMatchesCachedFile($file, $match = null) + { + return self::etagMatchesFiles($this->filename($file), $match); + } + + /** + * Create an ETag for the given file + * + * @param string $file The desired cache file + * + * @return string your ETag + */ + public function etagForCachedFile($file) + { + return self::etagForFiles($this->filename($file)); + } + + /** + * Whether the given ETag matchesspecific file(s) on disk + * + * If no ETag is given we'll try to fetch the one from the current + * HTTP request. + * + * @param string|array $files file(s) to check + * @param string $match ETag to match against + * + * @return string|bool ETag on match, otherwise false + */ + public static function etagMatchesFiles($files, $match = null) + { + if ($match === null) { + $match = isset($_SERVER['HTTP_IF_NONE_MATCH']) + ? trim($_SERVER['HTTP_IF_NONE_MATCH'], '"') + : false; + } + if (! $match) { + return false; + } + + $etag = self::etagForFiles($files); + return $match === $etag ? $etag : false; + } + + /** + * Create ETag for the given files + * + * Custom algorithm creating an ETag based on filenames, mtimes + * and file sizes. Supports single files or a list of files. This + * way we are able to create ETags for virtual files depending on + * multiple source files (e.g. compressed JS, CSS). + * + * @param string|array $files Single file or a list of such + * + * @return string The generated ETag + */ + public static function etagForFiles($files) + { + if (is_string($files)) { + $files = array($files); + } + + $sizes = array(); + $mtimes = array(); + + foreach ($files as $file) { + $file = realpath($file); + if ($file !== false && $info = stat($file)) { + $mtimes[] = $info['mtime']; + $sizes[] = $info['size']; + } else { + $mtimes[] = time(); + $sizes[] = 0; + } + } + + return sprintf( + '%s-%s-%s', + hash('crc32', implode('|', $files)), + hash('crc32', implode('|', $sizes)), + hash('crc32', implode('|', $mtimes)) + ); + } + + /** + * Factory creating your cache instance + * + * @param string $name Instance name + * + * @return FileCache + */ + public static function instance($name = 'icingaweb') + { + if ($name !== 'icingaweb') { + $name = 'icingaweb/modules/' . $name; + } + + if (!array_key_exists($name, self::$instances)) { + self::$instances[$name] = new static($name); + } + + return self::$instances[$name]; + } +} From f0d6cf994e56eba3fd370b1b666dabe7f04bfcd5 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 16:24:55 +0200 Subject: [PATCH 29/47] Web\StyleSheet: use Cache and ETags --- library/Icinga/Web/StyleSheet.php | 40 ++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/library/Icinga/Web/StyleSheet.php b/library/Icinga/Web/StyleSheet.php index fedd8207a..808c43c70 100644 --- a/library/Icinga/Web/StyleSheet.php +++ b/library/Icinga/Web/StyleSheet.php @@ -5,6 +5,7 @@ namespace Icinga\Web; use Icinga\Application\Icinga; +use Icinga\Web\FileCache; use Icinga\Web\LessCompiler; class StyleSheet @@ -44,28 +45,45 @@ class StyleSheet public static function send($minified = false) { + $app = Icinga::app(); + $basedir = $app->getBootstrapDirecory(); + foreach (self::$lessFiles as $file) { + $lessFiles[] = $basedir . '/' . $file; + } + $files = $lessFiles; + foreach ($app->getModuleManager()->getLoadedModules() as $name => $module) { + if ($module->hasCss()) { + $files[] = $module->getCssFilename(); + } + } + + if ($etag = FileCache::etagMatchesFiles($files)) { + header("HTTP/1.1 304 Not Modified"); + return; + } else { + $etag = FileCache::etagForFiles($files); + } + header('Cache-Control: public'); + header('ETag: "' . $etag . '"'); header('Content-Type: text/css'); $min = $minified ? '.min' : ''; - $cacheFile = '/tmp/cache_icinga' . $min . '.css'; - if (file_exists($cacheFile)) { - readfile($cacheFile); - exit; + $cacheFile = 'icinga-' . $etag . $min . '.css'; + $cache = FileCache::instance(); + if ($cache->has($cacheFile)) { + $cache->send($cacheFile); + return; } - $less = new LessCompiler(); - $basedir = Icinga::app()->getBootstrapDirecory(); - foreach (self::$lessFiles as $file) { - $less->addFile($basedir . '/' . $file); + foreach ($lessFiles as $file) { + $less->addFile($file); } $less->addLoadedModules(); if ($minified) { $less->compress(); } $out = $less->compile(); - // Not yet, this is for tests only. Waiting for Icinga\Web\Cache - // file_put_contents($cacheFile, $out); + $cache->store($cacheFile, $out); echo $out; - } } From e4687a60f939be8d9a349d00f102e8210953d04b Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 16:25:44 +0200 Subject: [PATCH 30/47] Web\JavaScript: use Cache and ETags refs #6927 --- library/Icinga/Web/JavaScript.php | 51 ++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/library/Icinga/Web/JavaScript.php b/library/Icinga/Web/JavaScript.php index 2f4ef0485..e2b170cec 100644 --- a/library/Icinga/Web/JavaScript.php +++ b/library/Icinga/Web/JavaScript.php @@ -5,6 +5,7 @@ namespace Icinga\Web; use Icinga\Application\Icinga; +use Icinga\Web\FileCache; use JShrink\Minifier; class JavaScript @@ -62,36 +63,58 @@ class JavaScript $js = $out = ''; $min = $minified ? '.min' : ''; - // TODO: Cache header - header('Content-Type: application/javascript'); - $cacheFile = '/tmp/cache_icinga' . $min . '.js'; - if (file_exists($cacheFile)) { - readfile($cacheFile); - exit; - } - - // We do not minify vendor files + // Prepare vendor file list + $vendorFiles = array(); foreach (self::$vendorFiles as $file) { - $out .= file_get_contents($basedir . '/' . $file . $min . '.js'); + $vendorFiles[] = $basedir . '/' . $file . $min . '.js'; } + // Prepare Icinga JS file list + $jsFiles = array(); foreach (self::$jsFiles as $file) { - $js .= file_get_contents($basedir . '/' . $file); + $jsFiles[] = $basedir . '/' . $file; } foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $name => $module) { if ($module->hasJs()) { - $js .= file_get_contents($module->getJsFilename()); + $jsFiles[] = $module->getJsFilename(); } } + $files = array_merge($vendorFiles, $jsFiles); + + if ($etag = FileCache::etagMatchesFiles($files)) { + header("HTTP/1.1 304 Not Modified"); + return; + } else { + $etag = FileCache::etagForFiles($files); + } + header('Cache-Control: public'); + header('ETag: "' . $etag . '"'); + header('Content-Type: application/javascript'); + + $cacheFile = 'icinga-' . $etag . $min . '.js'; + $cache = FileCache::instance(); + if ($cache->has($cacheFile)) { + $cache->send($cacheFile); + return; + } + + // We do not minify vendor files + foreach ($vendorFiles as $file) { + $out .= file_get_contents($file); + } + + foreach ($jsFiles as $file) { + $js .= file_get_contents($file); + } + if ($minified) { require_once 'IcingaVendor/JShrink/Minifier.php'; $out .= Minifier::minify($js, array('flaggedComments' => false)); } else { $out .= $js; } - // Not yet, this is for tests only. Waiting for Icinga\Web\Cache - // file_put_contents($cacheFile, $out); + $cache->store($cacheFile, $out); echo $out; } } From 4cef333add9b12a52b26c14362fcb4dcba286817 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 16:28:27 +0200 Subject: [PATCH 31/47] Modules\Module: implement listLocales fixes #7054 --- library/Icinga/Application/Modules/Module.php | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php index 9cda4afdc..d0bf451ee 100644 --- a/library/Icinga/Application/Modules/Module.php +++ b/library/Icinga/Application/Modules/Module.php @@ -708,12 +708,44 @@ class Module */ protected function registerLocales() { - if (file_exists($this->localedir) && is_dir($this->localedir)) { + if ($this->hasLocales()) { Translator::registerDomain($this->name, $this->localedir); } return $this; } + /** + * return bool Whether this module has translations + */ + public function hasLocales() + { + return file_exists($this->localedir) && is_dir($this->localedir); + } + + /** + * List all available locales + * + * return array Locale list + */ + public function listLocales() + { + $locales = array(); + if (! $this->hasLocales()) { + return $locales; + } + + $dh = opendir($this->localedir); + while (false !== ($file = readdir($dh))) { + $filename = $this->localedir . DIRECTORY_SEPARATOR . $file; + if (preg_match('/^[a-z]{2}_[A-Z]{2}$/', $file) && is_dir($filename)) { + $locales[] = $file; + } + } + closedir($dh); + sort($locales); + return $locales; + } + /** * Register web integration * From 8577940e1e476167690e0af0bdc44bb38e8e660e Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 16:31:38 +0200 Subject: [PATCH 32/47] StaticController: cache gravatar images fixes #7061 --- application/controllers/StaticController.php | 22 ++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/application/controllers/StaticController.php b/application/controllers/StaticController.php index 51b5fdb62..c90272ffc 100644 --- a/application/controllers/StaticController.php +++ b/application/controllers/StaticController.php @@ -2,10 +2,11 @@ // {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}} -use Zend_Controller_Action_Exception as ActionException; use Icinga\Web\Controller\ActionController; use Icinga\Application\Icinga; use Icinga\Logger\Logger; +use Icinga\Web\FileCache; +use Zend_Controller_Action_Exception as ActionException; /** * Delivery static content to clients @@ -30,8 +31,25 @@ class StaticController extends ActionController public function gravatarAction() { + $cache = FileCache::instance(); + $filename = md5(strtolower(trim($this->_request->getParam('email')))); + $cacheFile = 'gravatar-' . $filename; + header('Cache-Control: public'); + header('Pragma: cache'); + if ($etag = $cache->etagMatchesCachedFile($cacheFile)) { + header("HTTP/1.1 304 Not Modified"); + return; + } + header('Content-Type: image/jpg'); - $img = file_get_contents('http://www.gravatar.com/avatar/' . md5(strtolower(trim($this->_request->getParam('email')))) . '?s=200&d=mm'); + if ($cache->has($cacheFile)) { + header('ETag: "' . $cache->etagForCachedFile($cacheFile) . '"'); + $cache->send($cacheFile); + return; + } + $img = file_get_contents('http://www.gravatar.com/avatar/' . $filename . '?s=200&d=mm'); + $cache->store($cacheFile, $img); + header('ETag: "' . $cache->etagForCachedFile($cacheFile) . '"'); echo $img; } From 6faedf573368e992867efdafe31c9d6558327f52 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 16:49:28 +0200 Subject: [PATCH 33/47] ActionController: no benchmark for no renderer Benchmarks should not be shown when the renderer is no longer available. --- library/Icinga/Web/Controller/ActionController.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Web/Controller/ActionController.php b/library/Icinga/Web/Controller/ActionController.php index bfbb35b77..aead4043a 100644 --- a/library/Icinga/Web/Controller/ActionController.php +++ b/library/Icinga/Web/Controller/ActionController.php @@ -348,7 +348,9 @@ class ActionController extends Zend_Controller_Action // Cast preference app.show_benchmark to bool because preferences loaded from a preferences storage are // always strings if ((bool) $user->getPreferences()->get('app.show_benchmark', false) === true) { - $layout->benchmark = $this->renderBenchmark(); + if (!$this->_helper->viewRenderer->getNoRender()) { + $layout->benchmark = $this->renderBenchmark(); + } } } From b60b81133575ed763da0e95a62fddd1bb9a99fb5 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 16:51:17 +0200 Subject: [PATCH 34/47] ApplicationBootstrap: list "core" translations Just to make application fit module capabilities. We might find a better place for both later on. refs #7054 --- .../Application/ApplicationBootstrap.php | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/library/Icinga/Application/ApplicationBootstrap.php b/library/Icinga/Application/ApplicationBootstrap.php index 3f68ce43b..7736f8cf9 100644 --- a/library/Icinga/Application/ApplicationBootstrap.php +++ b/library/Icinga/Application/ApplicationBootstrap.php @@ -452,9 +452,8 @@ abstract class ApplicationBootstrap */ protected function setupInternationalization() { - $localeDir = $this->getApplicationDir('locale'); - if (file_exists($localeDir) && is_dir($localeDir)) { - Translator::registerDomain(Translator::DEFAULT_DOMAIN, $localeDir); + if ($this->hasLocales()) { + Translator::registerDomain(Translator::DEFAULT_DOMAIN, $this->getLocaleDir()); } try { @@ -469,4 +468,48 @@ abstract class ApplicationBootstrap return $this; } + + /** + * @return string Our locale directory + */ + public function getLocaleDir() + { + return $this->getApplicationDir('locale'); + } + + /** + * return bool Whether Icinga Web has translations + */ + public function hasLocales() + { + $localedir = $this->getLocaleDir(); + return file_exists($localedir) && is_dir($localedir); + } + + /** + * List all available locales + * + * NOTE: Might be a candidate for a static function in Translator + * + * return array Locale list + */ + public function listLocales() + { + $locales = array(); + if (! $this->hasLocales()) { + return $locales; + } + $localedir = $this->getLocaleDir(); + + $dh = opendir($localedir); + while (false !== ($file = readdir($dh))) { + $filename = $localedir . DIRECTORY_SEPARATOR . $file; + if (preg_match('/^[a-z]{2}_[A-Z]{2}$/', $file) && is_dir($filename)) { + $locales[] = $file; + } + } + closedir($dh); + sort($locales); + return $locales; + } } From 1734f01040475fd528575d7968ae432b2dfaf522 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 17:02:58 +0200 Subject: [PATCH 35/47] CommentdeletionhistoryQuery: 1970-01-01 -> -02 refs #7000 --- .../Backend/Ido/Query/CommentdeletionhistoryQuery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php index 8149c1c67..4ee17de6a 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php @@ -45,7 +45,7 @@ class CommentdeletionhistoryQuery extends IdoQuery array() )->join( array('h' => $this->prefix . 'commenthistory'), - 'o.' . $this->object_id . ' = h.' . $this->object_id . " AND o.is_active = 1 AND h.deletion_time > '1970-01-01 00:00:00' AND h.entry_type <> 2", + 'o.' . $this->object_id . ' = h.' . $this->object_id . " AND o.is_active = 1 AND h.deletion_time > '1970-01-02 00:00:00' AND h.entry_type <> 2", array() ); $this->joinedVirtualTables = array('commenthistory' => true); From f12a5741b8a4c53b7e5d94e43094e18d5a946934 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 19:05:49 +0200 Subject: [PATCH 36/47] js/loader: postpone redirection handling Redirections should not be executed unless the original request has been terminated and removed from pending requests. fixes #6989 --- public/js/icinga/loader.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js index f91aa20b9..a80fb28d1 100644 --- a/public/js/icinga/loader.js +++ b/public/js/icinga/loader.js @@ -329,8 +329,7 @@ this.icinga.ui.reloadCss(); } - var redirect = req.getResponseHeader('X-Icinga-Redirect'); - if (this.processRedirectHeader(req)) return; + if (req.getResponseHeader('X-Icinga-Redirect')) return; // div helps getting an XML tree var $resp = $('
' + req.responseText + '
'); @@ -567,6 +566,7 @@ delete this.requests[req.$target.attr('id')]; this.icinga.ui.fadeNotificationsAway(); + this.processRedirectHeader(req); if (typeof req.loadNext !== 'undefined') { if ($('#col2').length) { From 3d352ba44686deb660a1f55f191a2140fb13da85 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 19:51:56 +0200 Subject: [PATCH 37/47] Filter: add FilterNotEqual and FilterMatch fixes #6557 --- library/Icinga/Data/Filter/Filter.php | 4 ++-- library/Icinga/Data/Filter/FilterMatch.php | 22 +++++++++++++++++++ library/Icinga/Data/Filter/FilterMatchNot.php | 22 +++++++++++++++++++ library/Icinga/Data/Filter/FilterNotEqual.php | 13 +++++++++++ library/Icinga/Web/Widget/FilterEditor.php | 7 +++--- 5 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 library/Icinga/Data/Filter/FilterMatch.php create mode 100644 library/Icinga/Data/Filter/FilterMatchNot.php create mode 100644 library/Icinga/Data/Filter/FilterNotEqual.php diff --git a/library/Icinga/Data/Filter/Filter.php b/library/Icinga/Data/Filter/Filter.php index 7e995bb59..64607a4a0 100644 --- a/library/Icinga/Data/Filter/Filter.php +++ b/library/Icinga/Data/Filter/Filter.php @@ -132,12 +132,12 @@ abstract class Filter public static function expression($col, $op, $expression) { switch ($op) { - case '=': return new FilterEqual($col, $op, $expression); + case '=': return new FilterMatch($col, $op, $expression); case '<': return new FilterLessThan($col, $op, $expression); case '>': return new FilterGreaterThan($col, $op, $expression); case '>=': return new FilterEqualOrGreaterThan($col, $op, $expression); case '<=': return new FilterEqualOrLessThan($col, $op, $expression); - case '!=': return new FilterNotEqual($col, $op, $expression); + case '!=': return new FilterMatchNot($col, $op, $expression); default: throw new ProgrammingError( 'There is no such filter sign: %s', $op diff --git a/library/Icinga/Data/Filter/FilterMatch.php b/library/Icinga/Data/Filter/FilterMatch.php new file mode 100644 index 000000000..ef3cad801 --- /dev/null +++ b/library/Icinga/Data/Filter/FilterMatch.php @@ -0,0 +1,22 @@ +expression; + if (strpos($expression, '*') === false) { + return (string) $row->{$this->column} === $expression; + } else { + $parts = array(); + foreach (preg_split('/\*/', $expression) as $part) { + $parts[] = preg_quote($part); + } + return preg_match('/^' . implode('.*', $parts) . '$/', $row->{$this->column}); + } + } +} diff --git a/library/Icinga/Data/Filter/FilterMatchNot.php b/library/Icinga/Data/Filter/FilterMatchNot.php new file mode 100644 index 000000000..2ce5c2336 --- /dev/null +++ b/library/Icinga/Data/Filter/FilterMatchNot.php @@ -0,0 +1,22 @@ +expression; + if (strpos($expression, '*') === false) { + return (string) $row->{$this->column} !== $expression; + } else { + $parts = array(); + foreach (preg_split('/\*/', $expression) as $part) { + $parts[] = preg_quote($part); + } + return ! preg_match('/^' . implode('.*', $parts) . '$/', $row->{$this->column}); + } + } +} diff --git a/library/Icinga/Data/Filter/FilterNotEqual.php b/library/Icinga/Data/Filter/FilterNotEqual.php new file mode 100644 index 000000000..d1a5e9b64 --- /dev/null +++ b/library/Icinga/Data/Filter/FilterNotEqual.php @@ -0,0 +1,13 @@ +{$this->column} !== (string) $this->expression; + } +} diff --git a/library/Icinga/Web/Widget/FilterEditor.php b/library/Icinga/Web/Widget/FilterEditor.php index 4ce6ac6e6..af46a40f2 100644 --- a/library/Icinga/Web/Widget/FilterEditor.php +++ b/library/Icinga/Web/Widget/FilterEditor.php @@ -183,9 +183,10 @@ class FilterEditor extends AbstractWidget { $name = 'sign_' . $filter->getId(); $signs = array( - '=' => '=', - '>' => '>', - '<' => '<', + '=' => '=', + '!=' => '!=', + '>' => '>', + '<' => '<', '>=' => '>=', '<=' => '<=', ); From ef847801f2d19987285d9b8ea45fee1d4750bce7 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 2 Sep 2014 20:11:37 +0200 Subject: [PATCH 38/47] js/loader.js: preserve scrolling position... ...on autorefresh and reset it otherwise. fixes #6285 fixes #6988 --- public/js/icinga/loader.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js index a80fb28d1..f70bc0522 100644 --- a/public/js/icinga/loader.js +++ b/public/js/icinga/loader.js @@ -665,7 +665,11 @@ var self = this; var containerId = $container.attr('id'); if (typeof containerId !== 'undefined') { - scrollPos = $container.scrollTop(); + if (autorefresh) { + scrollPos = $container.scrollTop(); + } else { + scrollPos = 0; + } } var origFocus = document.activeElement; From 8e5deb8029b87f33234f5f43f1a758d8f141d468 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 3 Sep 2014 10:00:04 +0200 Subject: [PATCH 39/47] Fix TranslatorTest not expecting the default locale as being "available" refs #7063 --- test/php/library/Icinga/Util/TranslatorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/php/library/Icinga/Util/TranslatorTest.php b/test/php/library/Icinga/Util/TranslatorTest.php index 1c63e6c68..23bc154dd 100644 --- a/test/php/library/Icinga/Util/TranslatorTest.php +++ b/test/php/library/Icinga/Util/TranslatorTest.php @@ -27,7 +27,7 @@ class TranslatorTest extends BaseTestCase public function testWhetherGetAvailableLocaleCodesReturnsAllAvailableLocaleCodes() { $this->assertEquals( - array('de_DE', 'fr_FR'), + array(Translator::DEFAULT_LOCALE, 'de_DE', 'fr_FR'), Translator::getAvailableLocaleCodes(), 'Translator::getAvailableLocaleCodes does not return all available locale codes' ); From 3f9cb00b1fcff8143c837b498699e548e612bbc8 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 3 Sep 2014 10:00:41 +0200 Subject: [PATCH 40/47] Fix DbBackendFormTest not creating a valid ResourceFactory mock object refs #7063 --- .../Authentication/DbBackendFormTest.php | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/test/php/application/forms/Config/Authentication/DbBackendFormTest.php b/test/php/application/forms/Config/Authentication/DbBackendFormTest.php index c504955ad..51bec896f 100644 --- a/test/php/application/forms/Config/Authentication/DbBackendFormTest.php +++ b/test/php/application/forms/Config/Authentication/DbBackendFormTest.php @@ -26,10 +26,10 @@ class DbBackendFormTest extends BaseTestCase */ public function testValidBackendIsValid() { - $this->setUpUserBackendMock() + $this->setUpResourceFactoryMock(); + Mockery::mock('overload:Icinga\Authentication\Backend\DbUserBackend') ->shouldReceive('count') ->andReturn(2); - $this->setUpResourceFactoryMock(); $form = new DbBackendForm(); $form->setBackendName('test'); @@ -49,10 +49,10 @@ class DbBackendFormTest extends BaseTestCase */ public function testInvalidBackendIsNotValid() { - $this->setUpUserBackendMock() + $this->setUpResourceFactoryMock(); + Mockery::mock('overload:Icinga\Authentication\Backend\DbUserBackend') ->shouldReceive('count') ->andReturn(0); - $this->setUpResourceFactoryMock(); $form = new DbBackendForm(); $form->setBackendName('test'); @@ -66,18 +66,10 @@ class DbBackendFormTest extends BaseTestCase ); } - protected function setUpUserBackendMock() - { - return Mockery::mock('overload:Icinga\Authentication\Backend\DbUserBackend'); - } - protected function setUpResourceFactoryMock() { Mockery::mock('alias:Icinga\Data\ResourceFactory') - ->shouldReceive('getResourceConfig') - ->andReturn(new \Zend_Config(array())) - ->shouldReceive('createResource') - ->with(Mockery::type('\Zend_Config')) + ->shouldReceive('create') ->andReturn(Mockery::mock('Icinga\Data\Db\DbConnection')); } } From 07151d0a5ed0d4b26f45c81890aa47b357e5379c Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 3 Sep 2014 10:01:20 +0200 Subject: [PATCH 41/47] Fix that the MonitoringProperties view helper returns locale aware floats refs #7063 --- .../application/views/helpers/MonitoringProperties.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/monitoring/application/views/helpers/MonitoringProperties.php b/modules/monitoring/application/views/helpers/MonitoringProperties.php index ed52c5a04..568f63f57 100644 --- a/modules/monitoring/application/views/helpers/MonitoringProperties.php +++ b/modules/monitoring/application/views/helpers/MonitoringProperties.php @@ -121,7 +121,7 @@ class Zend_View_Helper_MonitoringProperties extends Zend_View_Helper_Abstract */ private function floatFormatter($value) { - return sprintf('%.4f', $value); + return sprintf('%.4F', $value); } /** From 06c7c4bd3e8a7583236291e71cb600a6088cc788 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 3 Sep 2014 10:09:43 +0200 Subject: [PATCH 42/47] Fix that the MonitoringProperties view helper returns locale aware floats refs #7063 --- .../application/views/helpers/MonitoringProperties.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/monitoring/application/views/helpers/MonitoringProperties.php b/modules/monitoring/application/views/helpers/MonitoringProperties.php index 568f63f57..efbc80749 100644 --- a/modules/monitoring/application/views/helpers/MonitoringProperties.php +++ b/modules/monitoring/application/views/helpers/MonitoringProperties.php @@ -222,7 +222,7 @@ class Zend_View_Helper_MonitoringProperties extends Zend_View_Helper_Abstract $val .= self::VALUE_YES; } - $val .= sprintf(' (%.2f%% state change)', $object->percent_state_change); + $val .= sprintf(' (%.2F%% state change)', $object->percent_state_change); return $val; } From c4d17a3509605f0cd52bb5aefa2513ab65ced443 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 3 Sep 2014 10:27:24 +0200 Subject: [PATCH 43/47] Drop Zend_View_Helper_MonitoringProperties as it's not used anywhere --- .../views/helpers/MonitoringProperties.php | 284 ------------------ .../helpers/MonitoringPropertiesTest.php | 104 ------- 2 files changed, 388 deletions(-) delete mode 100644 modules/monitoring/application/views/helpers/MonitoringProperties.php delete mode 100644 modules/monitoring/test/php/application/views/helpers/MonitoringPropertiesTest.php diff --git a/modules/monitoring/application/views/helpers/MonitoringProperties.php b/modules/monitoring/application/views/helpers/MonitoringProperties.php deleted file mode 100644 index efbc80749..000000000 --- a/modules/monitoring/application/views/helpers/MonitoringProperties.php +++ /dev/null @@ -1,284 +0,0 @@ - 'Current Attempt', - 'buildCheckType' => 'Check Type', - 'buildLatency' => 'Check Latency / Duration', - 'buildLastStateChange' => 'Last State Change', - 'buildLastNotification' => 'Last Notification', - 'buildFlapping' => 'Is This %s Flapping?', - 'buildScheduledDowntime' => 'In Scheduled Downtime?', - 'status_update_time' => 'Last Update' - ); - - private static $notificationReasons = array( - 0 => 'NORMAL', - 1 => 'ACKNOWLEDGEMENT', - 2 => 'FLAPPING START', - 3 => 'FLAPPING STOP', - 4 => 'FLAPPING DISABLED', - 5 => 'DOWNTIME START', - 6 => 'DOWNTIME END', - 7 => 'DOWNTIME CANCELLED', - 8 => 'CUSTOM', - 9 => 'STALKING' - ); - - /** - * Return the object type - * @param stdClass $object - * @return mixed - */ - private function getObjectType($object) - { - $keys = array_keys(get_object_vars($object)); - $keyParts = explode('_', array_shift($keys), 2); - return array_shift($keyParts); - } - - /** - * Drop all object specific attribute prefixes - * @param stdClass $object - * @param $type - * @return object - */ - private function dropObjectType($object, $type) - { - $vars = get_object_vars($object); - $out = array(); - foreach ($vars as $name => $value) { - $name = str_replace($type. '_', '', $name); - $out[$name] = $value; - } - return (object)$out; - } - - /** - * Get string for attempt - * @param stdClass $object - * @return string - */ - private function buildAttempt($object) - { - return sprintf( - '%s/%s (%s state)', - $object->current_check_attempt, - $object->max_check_attempts, - ($object->state_type === '1') ? 'HARD' : 'SOFT' - ); - } - - /** - * Generic fomatter for float values - * @param $value - * @return string - */ - private function floatFormatter($value) - { - return sprintf('%.4F', $value); - } - - /** - * Get the string for check type - * @param stdClass $object - * @return string - */ - private function buildCheckType($object) - { - if ($object->passive_checks_enabled === '1' && $object->active_checks_enabled === '0') { - return self::CHECK_PASSIVE; - } elseif ($object->passive_checks_enabled === '0' && $object->active_checks_enabled === '0') { - return self::CHECK_DISABLED; - } - - return self::CHECK_ACTIVE; - } - - /** - * Get string for latency - * @param stdClass $object - * @return string - */ - private function buildLatency($object) - { - $val = ''; - if ($this->buildCheckType($object) === self::CHECK_PASSIVE) { - $val .= self::VALUE_NA; - } else { - $val .= $this->floatFormatter( - (isset($object->check_latency)) ? $object->check_latency : 0 - ); - } - - $val .= ' / '. $this->floatFormatter( - isset($object->check_execution_time) ? $object->check_execution_time : 0 - ). ' seconds'; - - return $val; - } - - /** - * Get string for next check - * @param stdClass $object - * @return string - */ - private function buildNextCheck($object) - { - if ($this->buildCheckType($object) === self::CHECK_PASSIVE) { - return self::VALUE_NA; - } else { - return $object->next_check; - } - } - - /** - * Get date for last state change - * @param stdClass $object - * @return string - */ - private function buildLastStateChange($object) - { - return strftime('%Y-%m-%d %H:%M:%S', $object->last_state_change); - } - - /** - * Get string for "last notification" - * @param stdClass $object - * @return string - */ - private function buildLastNotification($object) - { - $val = ''; - - if ($object->last_notification === '0000-00-00 00:00:00') { - $val .= self::VALUE_NA; - } else { - $val .= $object->last_notification; - } - - $val .= sprintf(' (notification %d)', $object->current_notification_number); - - return $val; - } - - /** - * Get string for "is flapping" - * @param stdClass $object - * @return string - */ - private function buildFlapping($object) - { - $val = ''; - - if ($object->is_flapping === '0') { - $val .= self::VALUE_NO; - } else { - $val .= self::VALUE_YES; - } - - $val .= sprintf(' (%.2F%% state change)', $object->percent_state_change); - - return $val; - } - - /** - * Get string for scheduled downtime - * @param stdClass $object - * @return string - */ - private function buildScheduledDowntime($object) - { - if ($object->in_downtime === '1') { - return self::VALUE_YES; - } - - return self::VALUE_NO; - } - - /** - * Get an array which represent monitoring properties - * - * @param stdClass $object - * @return array - */ - public function monitoringProperties($object) - { - $type = $this->getObjectType($object); - //$object = $this->dropObjectType($object, $type); - - $out = array(); - foreach (self::$keys as $property => $label) { - $label = sprintf($label, ucfirst($type)); - if (is_callable(array(&$this, $property))) { - $out[$label] = $this->$property($object); - } elseif (isset($object->{$property})) { - $out[$label] = $object->{$property}; - } - } - - return $out; - } - - public function getNotificationType($notification) - { - $reason = intval($notification->notification_reason); - if (!isset(self::$notificationReasons[$reason])) { - return 'N/A'; - } - $type = self::$notificationReasons[$reason]; - if ($reason === 8) { - if (intval($notification->notification_type) === 0) { - $type .= '(UP)'; - } else { - $type .= '(OK)'; - } - } - return $type; - } -} diff --git a/modules/monitoring/test/php/application/views/helpers/MonitoringPropertiesTest.php b/modules/monitoring/test/php/application/views/helpers/MonitoringPropertiesTest.php deleted file mode 100644 index 5a476aaef..000000000 --- a/modules/monitoring/test/php/application/views/helpers/MonitoringPropertiesTest.php +++ /dev/null @@ -1,104 +0,0 @@ -current_check_attempt = '5'; - - $propertyHelper = new Zend_View_Helper_MonitoringProperties(); - $items = $propertyHelper->monitoringProperties($host); - - $this->assertEquals('5/10 (HARD state)', $items['Current Attempt']); - } - - public function testOutput2() - { - $host = new HostStruct4Properties(); - $host->current_check_attempt = '5'; - $host->active_checks_enabled = '1'; - $host->passive_checks_enabled = '0'; - $host->is_flapping = '1'; - - $propertyHelper = new Zend_View_Helper_MonitoringProperties(); - $items = $propertyHelper->monitoringProperties($host); - - $test = array( - 'Current Attempt' => "5/10 (HARD state)", - 'Check Type' => "ACTIVE", - 'Check Latency / Duration' => "0.1204 / 0.0000 seconds", - 'Last State Change' => "2013-07-04 11:24:43", - 'Last Notification' => "N/A (notification 0)", - 'Is This Host Flapping?' => "YES (12.37% state change)", - 'In Scheduled Downtime?' => "YES" - ); - - $this->assertEquals($test, $items); - } -} From 3ade6da44dc5eb7197de1cce3a75178e30b9529a Mon Sep 17 00:00:00 2001 From: Alexander Klimov Date: Wed, 3 Sep 2014 11:12:15 +0200 Subject: [PATCH 44/47] FileReaderException: extend IcingaException --- .../Icinga/Protocol/File/Exception/FileReaderException.php | 4 ++-- library/Icinga/Protocol/File/Reader.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/Icinga/Protocol/File/Exception/FileReaderException.php b/library/Icinga/Protocol/File/Exception/FileReaderException.php index 3e0890a57..a7db5d701 100644 --- a/library/Icinga/Protocol/File/Exception/FileReaderException.php +++ b/library/Icinga/Protocol/File/Exception/FileReaderException.php @@ -2,9 +2,9 @@ namespace Icinga\Protocol\File; -use RuntimeException; +use Icinga\Exception\IcingaException; /** * Exception thrown if a file reader specific error occurs */ -class FileReaderException extends RuntimeException {} +class FileReaderException extends IcingaException {} diff --git a/library/Icinga/Protocol/File/Reader.php b/library/Icinga/Protocol/File/Reader.php index 50842bc4a..651846b58 100644 --- a/library/Icinga/Protocol/File/Reader.php +++ b/library/Icinga/Protocol/File/Reader.php @@ -40,7 +40,7 @@ class Reader extends FilterIterator { foreach (array('filename', 'fields') as $key) { if (! isset($config->{$key})) { - throw new FileReaderException('The directive `' . $key . '\' is required'); + throw new FileReaderException('The directive `%s\' is required', $key); } } $this->fields = $config->fields; From 7cfc051228588f1635f478184274c9d436929a1d Mon Sep 17 00:00:00 2001 From: Alexander Fuhr Date: Wed, 3 Sep 2014 14:36:04 +0200 Subject: [PATCH 45/47] Add tests for Dashboard and fix doc blocks refs #6986 --- library/Icinga/Web/Widget/Dashboard.php | 47 +- library/Icinga/Web/Widget/Dashboard/Pane.php | 12 +- .../library/Icinga/Widget/DashboardTest.php | 509 ++++++++++++++++++ 3 files changed, 546 insertions(+), 22 deletions(-) create mode 100644 test/php/library/Icinga/Widget/DashboardTest.php diff --git a/library/Icinga/Web/Widget/Dashboard.php b/library/Icinga/Web/Widget/Dashboard.php index d8b09b15a..a86204d87 100644 --- a/library/Icinga/Web/Widget/Dashboard.php +++ b/library/Icinga/Web/Widget/Dashboard.php @@ -8,7 +8,6 @@ use Icinga\Application\Icinga; use Icinga\Application\Config as IcingaConfig; use Icinga\Exception\ConfigurationError; use Icinga\Exception\ProgrammingError; -use Icinga\Web\Widget\AbstractWidget; use Icinga\Web\Widget\Dashboard\Pane; use Icinga\Web\Widget\Dashboard\Component as DashboardComponent; use Icinga\Web\Url; @@ -96,7 +95,7 @@ class Dashboard extends AbstractWidget $current = $this->panes[$pane->getName()]; $current->addComponents($pane->getComponents()); } else { - $this->panes = array_filter(array_merge($this->panes, $panes)); + $this->panes[$pane->getName()] = $pane; } } @@ -128,6 +127,16 @@ class Dashboard extends AbstractWidget return $this->tabs; } + /** + * Return all panes of this dashboard + * + * @return array + */ + public function getPanes() + { + return $this->panes; + } + /** * Populate this dashboard via the given configuration file * @@ -164,9 +173,9 @@ class Dashboard extends AbstractWidget * * @TODO: Should only allow component objects to be added directly as soon as we store more information * - * @param string $pane The pane to add the component to - * @param Component|string $component The component to add or the title of the newly created component - * @param $url The url to use for the component + * @param string $pane The pane to add the component to + * @param Component|string $component The component to add or the title of the newly created component + * @param string|null $url The url to use for the component * * @return self */ @@ -198,20 +207,14 @@ class Dashboard extends AbstractWidget } /** - * Return true if a pane doesn't exist or doesn't have any components in it - * - * @param string $pane The name of the pane to check for emptyness + * Check if this dashboard has a specific pane * + * @param $pane string The name of the pane * @return bool */ - public function isEmptyPane($pane) + public function hasPane($pane) { - $paneObj = $this->getPane($pane); - if ($paneObj === null) { - return true; - } - $cmps = $paneObj->getComponents(); - return !empty($cmps); + return array_key_exists($pane, $this->panes); } /** @@ -305,11 +308,11 @@ class Dashboard extends AbstractWidget return $active; } + /** + * @see determineActivePane() + */ public function getActivePane() { - if ($active = $this->getTabs()->getActiveName()) { - return $this->getPane($active); - } return $this->determineActivePane(); } @@ -323,10 +326,12 @@ class Dashboard extends AbstractWidget $active = $this->getTabs()->getActiveName(); if (! $active) { if ($active = Url::fromRequest()->getParam($this->tabParam)) { - if ($this->isEmptyPane($active)) { - $active = $this->setDefaultPane(); - } else { + if ($this->hasPane($active)) { $this->activate($active); + } else { + throw new ProgrammingError( + 'Try to get an inexistent pane.' + ); } } else { $active = $this->setDefaultPane(); diff --git a/library/Icinga/Web/Widget/Dashboard/Pane.php b/library/Icinga/Web/Widget/Dashboard/Pane.php index 39ad1fda3..45bd9c558 100644 --- a/library/Icinga/Web/Widget/Dashboard/Pane.php +++ b/library/Icinga/Web/Widget/Dashboard/Pane.php @@ -39,7 +39,7 @@ class Pane extends AbstractWidget /** * Create a new pane * - * @param $name The pane to create + * @param string $name The pane to create */ public function __construct($name) { @@ -92,6 +92,16 @@ class Pane extends AbstractWidget return array_key_exists($title, $this->components); } + /** + * Checks if the current pane has any components + * + * @return bool + */ + public function hasComponents() + { + return ! empty($this->components); + } + /** * Return a component with the given name if existing * diff --git a/test/php/library/Icinga/Widget/DashboardTest.php b/test/php/library/Icinga/Widget/DashboardTest.php new file mode 100644 index 000000000..cdb4d8ff2 --- /dev/null +++ b/test/php/library/Icinga/Widget/DashboardTest.php @@ -0,0 +1,509 @@ +shouldReceive('escape'); + + return $mock; + } +} + +class DashboardWithPredefinableActiveName extends Dashboard +{ + public $activeName = ''; + + public function getTabs() + { + return Mockery::mock('Icinga\Web\Widget\Tabs') + ->shouldReceive('getActiveName')->andReturn($this->activeName) + ->shouldReceive('activate') + ->getMock(); + } +} + +class DashboardTest extends BaseTestCase +{ + public function tearDown() + { + parent::tearDown(); + Mockery::close(); // Necessary because some tests run in a separate process + } + + protected function setupIcingaMock(\Zend_Controller_Request_Abstract $request) + { + $moduleMock = Mockery::mock('Icinga\Application\Modules\Module'); + $moduleMock->shouldReceive('getPaneItems')->andReturn(array( + 'test-pane' => new Pane('Test Pane') + )); + + $moduleManagerMock = Mockery::mock('Icinga\Application\Modules\Manager'); + $moduleManagerMock->shouldReceive('getLoadedModules')->andReturn(array( + 'test-module' => $moduleMock + )); + + $bootstrapMock = Mockery::mock('Icinga\Application\ApplicationBootstrap')->shouldDeferMissing(); + $bootstrapMock->shouldReceive('getFrontController->getRequest')->andReturnUsing( + function () use ($request) { return $request; } + )->shouldReceive('getApplicationDir')->andReturn(self::$appDir); + + $bootstrapMock->shouldReceive('getModuleManager')->andReturn($moduleManagerMock); + + Icinga::setApp($bootstrapMock, true); + } + + public function testWhetherCreatePaneCreatesAPane() + { + $dashboard = new Dashboard(); + $pane = $dashboard->createPane('test')->getPane('test'); + + $this->assertEquals('test', $pane->getTitle(), 'Dashboard::createPane() could not create a pane'); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testMergePanesWithDifferentPaneName() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $dashboard->createPane('test2'); + + $panes = array( + new Pane('test1a'), + new Pane('test2a') + ); + + $dashboard->mergePanes($panes); + + $this->assertCount(4, $dashboard->getPanes(), 'Dashboard::mergePanes() could not merge different panes'); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testMergePanesWithSamePaneName() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $dashboard->createPane('test2'); + + $panes = array( + new Pane('test1'), + new Pane('test3') + ); + + $dashboard->mergePanes($panes); + + $this->assertCount(3, $dashboard->getPanes(), 'Dashboard::mergePanes() could not merge same panes'); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherGetPaneReturnsAPaneByName() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + + $pane = $dashboard->getPane('test1'); + + $this->assertEquals( + 'test1', + $pane->getName(), + 'Dashboard:getPane() could not return pane by name' + ); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testLoadPaneItemsProvidedByEnabledModules() + { + $dashboard = Dashboard::load(); + + $this->assertCount( + 1, + $dashboard->getPanes(), + 'Dashboard::load() could not load panes from enabled modules' + ); + } + + /** + * @expectedException \Icinga\Exception\ProgrammingError + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherGetPaneThrowsAnExceptionOnNotExistentPaneName() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + + $dashboard->getPane('test2'); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherRenderNotRendersPanesDisabledComponent() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + $component = new ComponentWithMockedView('test', 'test', $pane); + $component->setDisabled(true); + $pane->addComponent($component); + + $rendered = $dashboard->render(); + + $greaterThanOne = strlen($rendered) > 1; + + $this->assertFalse( + $greaterThanOne, + 'Dashboard::render() disabled component is rendered, but should not' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherRenderRendersPanesEnabledComponent() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + $component = new ComponentWithMockedView('test', 'test', $pane); + $pane->addComponent($component); + + $rendered = $dashboard->render(); + + $greaterThanOne = strlen($rendered) > 1; + + $this->assertTrue( + $greaterThanOne, + 'Dashboard::render() could not render enabled component' + ); + } + + public function testWhetherRenderNotRendersNotExistentPane() + { + $dashboard = new Dashboard(); + + $rendered = $dashboard->render(); + + $greaterThanOne = strlen($rendered) > 1; + + $this->assertFalse( + $greaterThanOne, + 'Dashboard::render() not existent pane ist rendered, but should not' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherGetPaneKeyTitleArrayReturnFormedArray() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1')->getPane('test1')->setTitle('Test1'); + $dashboard->createPane('test2')->getPane('test2')->setTitle('Test2'); + + $result = $dashboard->getPaneKeyTitleArray(); + + $expected = array( + 'test1' => 'Test1', + 'test2' => 'Test2' + ); + + $this->assertEquals( + $expected, + $result, + 'Dashboard::getPaneKeyTitleArray() could not return valid expectation' + ); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherHasPanesHasPanes() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $dashboard->createPane('test2'); + + $hasPanes = $dashboard->hasPanes(); + + $this->assertTrue($hasPanes, 'Dashboard::hasPanes() could not return valid expectation'); + } + + public function testWhetherHasPanesHasNoPanes() + { + $dashboard = new Dashboard(); + + $hasPanes = $dashboard->hasPanes(); + + $this->assertFalse($hasPanes, 'Dashboard::hasPanes() has panes but should not'); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherRemoveComponentRemovesComponent() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + + $component = new Component('test', 'test', $pane); + $pane->addComponent($component); + + $component2 = new Component('test2', 'test2', $pane); + $pane->addComponent($component2); + + $dashboard->removeComponent('test1', 'test'); + + $result = $dashboard->getPane('test1')->hasComponent('test'); + + $this->assertFalse( + $result, + 'Dashboard::removeComponent() could not remove component from the pane' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherRemoveComponentRemovesComponentByConcatenation() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + + $component = new Component('test', 'test', $pane); + $pane->addComponent($component); + + $component2 = new Component('test2', 'test2', $pane); + $pane->addComponent($component2); + + $dashboard->removeComponent('test1.test', null); + + $result = $dashboard->getPane('test1')->hasComponent('test'); + + $this->assertFalse( + $result, + 'Dashboard::removeComponent() could not remove component from the pane' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherToArrayReturnsDashboardStructureAsArray() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + + $component = new Component('test', 'test', $pane); + $pane->addComponent($component); + + $result = $dashboard->toArray(); + + $expected = array( + 'test1' => array( + 'title' => 'test1' + ), + 'test1.test' => array( + 'url' => 'test' + ) + ); + + $this->assertEquals( + $expected, + $result, + 'Dashboard::toArray() could not return valid expectation' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherSetComponentUrlUpdatesTheComponentUrl() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + $component = new Component('test', 'test', $pane); + $pane->addComponent($component); + + $dashboard->setComponentUrl('test1', 'test', 'new'); + + $this->assertEquals( + 'new', + $component->getUrl()->getPath(), + 'Dashboard::setComponentUrl() could not return valid expectation' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherSetComponentUrlUpdatesTheComponentUrlConcatenation() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + $component = new Component('test', 'test', $pane); + $pane->addComponent($component); + + $dashboard->setComponentUrl('test1.test', null, 'new'); + + $this->assertEquals( + 'new', + $component->getUrl()->getPath(), + 'Dashboard::setComponentUrl() could not return valid expectation' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherSetComponentUrlUpdatesTheComponentUrlNotExistentPane() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + $component = new Component('test', 'test', $pane); + $pane->addComponent($component); + + $dashboard->setComponentUrl('test3.test', null, 'new'); + + $result = $dashboard->getPane('test3')->getComponent('test'); + + $this->assertEquals( + 'new', + $result->getUrl()->getPath(), + 'Dashboard::setComponentUrl() could not return valid expectation' + ); + } + + /** + * @expectedException \Icinga\Exception\ConfigurationError + */ + public function testWhetherDetermineActivePaneThrowsAnExceptionIfCouldNotDetermine() + { + $dashboard = new Dashboard(); + $dashboard->determineActivePane(); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @expectedException \Icinga\Exception\ProgrammingError + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherDetermineActivePaneThrowsAnExceptionIfCouldNotDetermineInvalidPane() + { + $dashboard = new DashboardWithPredefinableActiveName(); + $dashboard->createPane('test1'); + + Mockery::mock('alias:Icinga\Web\Url') + ->shouldReceive('fromRequest->getParam')->andReturn('test2'); + + $dashboard->determineActivePane(); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherDetermineActivePaneDeterminesActivePane() + { + $dashboard = new DashboardWithPredefinableActiveName(); + $dashboard->activeName = 'test2'; + $dashboard->createPane('test1'); + $dashboard->createPane('test2'); + + $activePane = $dashboard->determineActivePane(); + + $this->assertEquals( + 'test2', + $activePane->getTitle(), + 'Dashboard::determineActivePane() could not determine active pane' + ); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherDetermineActivePaneDeterminesActiveValidPane() + { + $dashboard = new DashboardWithPredefinableActiveName(); + $dashboard->createPane('test1'); + $dashboard->createPane('test2'); + + Mockery::mock('alias:Icinga\Web\Url') + ->shouldReceive('fromRequest->getParam')->andReturn('test2'); + + $activePane = $dashboard->determineActivePane(); + + $this->assertEquals( + 'test2', + $activePane->getTitle(), + 'Dashboard::determineActivePane() could not determine active pane' + ); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherGetActivePaneReturnsActivePane() + { + $dashboard = new DashboardWithPredefinableActiveName(); + $dashboard->activeName = 'test2'; + $dashboard->createPane('test1'); + $dashboard->createPane('test2'); + + $activePane = $dashboard->getActivePane(); + + $this->assertEquals( + 'test2', + $activePane->getTitle(), + 'Dashboard::determineActivePane() could not get expected active pane' + ); + } + + public function testWhetherLoadConfigPanes() + { + $this->markTestIncomplete( + 'Dashboard::loadConfigPanes() is not fully implemented yet or rather not used' + ); + } + + /** + * @depends testWhetherLoadConfigPanes + */ + public function testWhetherReadConfigPopulatesDashboard() + { + $this->markTestIncomplete( + 'Dashboard::readConfig() is not fully implemented yet or rather not used' + ); + } +} From 65fb246b7e588e233a78d28692b5fa2d32439167 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Wed, 3 Sep 2014 15:03:24 +0200 Subject: [PATCH 46/47] FilterTest: fix complex filter example The filter in the test was not a valid filter. fixes #7069 --- test/php/library/Icinga/Data/Filter/FilterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/php/library/Icinga/Data/Filter/FilterTest.php b/test/php/library/Icinga/Data/Filter/FilterTest.php index cc91522dd..dbd640b91 100644 --- a/test/php/library/Icinga/Data/Filter/FilterTest.php +++ b/test/php/library/Icinga/Data/Filter/FilterTest.php @@ -182,7 +182,7 @@ class FilterTest extends BaseTestCase public function testComplexFilterFromQueryString() { - $q = 'host=localhost|nohost*&problem&service=*www*|ups*&state!=1&!handled'; + $q = '(host=localhost|host=nohost*)&problem&(service=*www*|service=ups*)&state!=1&!handled'; $filter = Filter::fromQueryString($q); $this->assertFalse($filter->matches($this->row(0))); $this->assertTrue($filter->matches($this->row(1))); From 1d812114272eefe07e1eed536401b9a853fa6fce Mon Sep 17 00:00:00 2001 From: Alexander Fuhr Date: Wed, 3 Sep 2014 15:11:11 +0200 Subject: [PATCH 47/47] Move DashboardTest in correct directory resf #6986 --- test/php/library/Icinga/{ => Web}/Widget/DashboardTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename test/php/library/Icinga/{ => Web}/Widget/DashboardTest.php (99%) diff --git a/test/php/library/Icinga/Widget/DashboardTest.php b/test/php/library/Icinga/Web/Widget/DashboardTest.php similarity index 99% rename from test/php/library/Icinga/Widget/DashboardTest.php rename to test/php/library/Icinga/Web/Widget/DashboardTest.php index cdb4d8ff2..9114a8f90 100644 --- a/test/php/library/Icinga/Widget/DashboardTest.php +++ b/test/php/library/Icinga/Web/Widget/DashboardTest.php @@ -6,7 +6,7 @@ namespace Tests\Icinga\Web; // Necessary as some of these tests disable phpunit's preservation // of the global state (e.g. autoloaders are in the global state) -require_once realpath(dirname(__FILE__) . '/../../../bootstrap.php'); +require_once realpath(dirname(__FILE__) . '/../../../../bootstrap.php'); use Mockery; use Icinga\Application\Icinga;