diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:46:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:46:43 +0000 |
commit | 3e02d5aff85babc3ffbfcf52313f2108e313aa23 (patch) | |
tree | b01f3923360c20a6a504aff42d45670c58af3ec5 /modules/monitoring/library | |
parent | Initial commit. (diff) | |
download | icingaweb2-3e02d5aff85babc3ffbfcf52313f2108e313aa23.tar.xz icingaweb2-3e02d5aff85babc3ffbfcf52313f2108e313aa23.zip |
Adding upstream version 2.12.1.upstream/2.12.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/monitoring/library')
189 files changed, 25100 insertions, 0 deletions
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/IdoBackend.php b/modules/monitoring/library/Monitoring/Backend/Ido/IdoBackend.php new file mode 100644 index 0000000..71fc6a1 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/IdoBackend.php @@ -0,0 +1,10 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido; + +use Icinga\Module\Monitoring\Backend\MonitoringBackend; + +class IdoBackend extends MonitoringBackend +{ +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/AllcontactsQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/AllcontactsQuery.php new file mode 100644 index 0000000..3598726 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/AllcontactsQuery.php @@ -0,0 +1,75 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Zend_Db_Select; + +class AllcontactsQuery extends IdoQuery +{ + protected $columnMap = array( + 'contacts' => array( + 'contact_name' => 'c.contact_name', + 'host_object_id' => 'c.host_object_id', + 'host_name' => 'c.host_name', + 'service_object_id' => 'c.service_object_id', + 'service_host_name' => 'c.service_host_name', + 'service_description' => 'c.service_description', + + 'contact_alias' => 'c.contact_alias', + 'contact_email' => 'c.contact_email', + 'contact_pager' => 'c.contact_pager', + 'contact_has_host_notfications' => 'c.contact_has_host_notfications', + 'contact_has_service_notfications' => 'c.contact_has_service_notfications', + 'contact_can_submit_commands' => 'c.contact_can_submit_commands', + 'contact_notify_service_recovery' => 'c.notify_service_recovery', + 'contact_notify_service_warning' => 'c.notify_service_warning', + 'contact_notify_service_critical' => 'c.notify_service_critical', + 'contact_notify_service_unknown' => 'c.notify_service_unknown', + 'contact_notify_service_flapping' => 'c.notify_service_flapping', + 'contact_notify_service_downtime' => 'c.notify_service_downtime', + 'contact_notify_host_recovery' => 'c.notify_host_recovery', + 'contact_notify_host_down' => 'c.notify_host_down', + 'contact_notify_host_unreachable' => 'c.notify_host_unreachable', + 'contact_notify_host_flapping' => 'c.notify_host_flapping', + 'contact_notify_host_downtime' => 'c.notify_host_downtime', + + + ) + ); + + protected $contacts; + protected $contactgroups; + protected $baseQuery; + protected $useSubqueryCount = true; + + public function requireColumn($alias) + { + $this->contacts->addColumn($alias); + $this->contactgroups->addColumn($alias); + return parent::requireColumn($alias); + } + + protected function joinBaseTables() + { + $this->contacts = $this->createSubQuery( + 'contact', + array('contact_name') + ); + $this->contactgroups = $this->createSubQuery( + 'contactgroup', + array('contact_name') + ); + $sub = $this->db->select()->union( + array($this->contacts, $this->contactgroups), + Zend_Db_Select::SQL_UNION_ALL + ); + + $this->baseQuery = $this->db->select()->distinct()->from( + array('c' => $sub), + array() + ); + + $this->joinedVirtualTables = array('contacts' => true); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommandQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommandQuery.php new file mode 100644 index 0000000..59a4ccb --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommandQuery.php @@ -0,0 +1,61 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Query for commands + */ +class CommandQuery extends IdoQuery +{ + /** + * @var array + */ + protected $columnMap = array( + 'commands' => array( + 'command_id' => 'c.command_id', + 'command_instance_id' => 'c.instance_id', + 'command_config_type' => 'c.config_type', + 'command_line' => 'c.command_line', + 'command_name' => 'co.name1' + ), + + 'contacts' => array( + 'contact_id' => 'con.contact_id', + 'contact_alias' => 'con.contact_alias' + ) + ); + + /** + * Fetch basic information about commands + */ + protected function joinBaseTables() + { + $this->select->from( + array('c' => $this->prefix . 'commands'), + array() + )->join( + array('co' => $this->prefix . 'objects'), + 'co.object_id = c.object_id', + array() + ); + + $this->joinedVirtualTables = array('commands' => true); + } + + /** + * Join contacts + */ + protected function joinContacts() + { + $this->select->join( + array('cnc' => $this->prefix . 'contact_notificationcommands'), + 'cnc.command_object_id = co.object_id', + array() + )->join( + array('con' => $this->prefix . 'contacts'), + 'con.contact_id = cnc.contact_id', + array() + ); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentQuery.php new file mode 100644 index 0000000..6c01931 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentQuery.php @@ -0,0 +1,158 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\FilterExpression; +use Zend_Db_Expr; +use Zend_Db_Select; +use Icinga\Data\Filter\Filter; + +/** + * Query for host and service comments + */ +class CommentQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'comments' => array( + 'comment_author' => 'c.comment_author', + 'comment_author_name' => 'c.comment_author_name', + 'comment_data' => 'c.comment_data', + 'comment_expiration' => 'c.comment_expiration', + 'comment_internal_id' => 'c.comment_internal_id', + 'comment_is_persistent' => 'c.comment_is_persistent', + 'comment_name' => 'c.comment_name', + 'comment_timestamp' => 'c.comment_timestamp', + 'comment_type' => 'c.comment_type', + 'instance_name' => 'c.instance_name', + 'object_type' => 'c.object_type' + ), + 'hosts' => array( + 'host_display_name' => 'c.host_display_name', + 'host_name' => 'c.host_name', + 'host_state' => 'c.host_state' + ), + 'services' => array( + 'service_description' => 'c.service_description', + 'service_display_name' => 'c.service_display_name', + 'service_host_name' => 'c.service_host_name', + 'service_state' => 'c.service_state' + ) + ); + + /** + * The union + * + * @var Zend_Db_Select + */ + protected $commentQuery; + + /** + * Subqueries used for the comment query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * {@inheritdoc} + */ + public function allowsCustomVars() + { + foreach ($this->subQueries as $query) { + if (! $query->allowsCustomVars()) { + return false; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + if (version_compare($this->getIdoVersion(), '1.14.0', '<')) { + $this->columnMap['comments']['comment_name'] = '(NULL)'; + } + $this->commentQuery = $this->db->select(); + $this->select->from( + array('c' => $this->commentQuery), + array() + ); + $this->joinedVirtualTables['comments'] = true; + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $columns = array_keys($this->columnMap['comments'] + $this->columnMap['hosts']); + foreach (array_keys($this->columnMap['services']) as $column) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + $hosts = $this->createSubQuery('hostcomment', $columns); + $this->subQueries[] = $hosts; + $this->commentQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * Join services + */ + protected function joinServices() + { + $columns = array_keys($this->columnMap['comments'] + $this->columnMap['hosts'] + $this->columnMap['services']); + $services = $this->createSubQuery('servicecomment', $columns); + $this->subQueries[] = $services; + $this->commentQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + public function whereEx(FilterExpression $ex) + { + $this->requireColumn($ex->getColumn()); + foreach ($this->subQueries as $sub) { + $sub->whereEx($ex); + } + + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php new file mode 100644 index 0000000..8cb4ddb --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php @@ -0,0 +1,179 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\FilterExpression; +use Zend_Db_Expr; +use Zend_Db_Select; +use Icinga\Data\Filter\Filter; + +/** + * Query for host and service comment removal records + */ +class CommentdeletionhistoryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'commenthistory' => array( + 'id' => 'cdh.id', + 'object_type' => 'cdh.object_type' + ), + 'history' => array( + 'type' => 'cdh.type', + 'timestamp' => 'cdh.timestamp', + 'object_id' => 'cdh.object_id', + 'state' => 'cdh.state', + 'output' => 'cdh.output' + ), + 'hosts' => array( + 'host_display_name' => 'cdh.host_display_name', + 'host_name' => 'cdh.host_name' + ), + 'services' => array( + 'service_description' => 'cdh.service_description', + 'service_display_name' => 'cdh.service_display_name', + 'service_host_name' => 'cdh.service_host_name' + ) + ); + + /** + * The union + * + * @var Zend_Db_Select + */ + protected $commentDeletionHistoryQuery; + + /** + * Subqueries used for the comment history query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * Whether to additionally select all history columns + * + * @var bool + */ + protected $fetchHistoryColumns = false; + + /** + * {@inheritdoc} + */ + public function allowsCustomVars() + { + foreach ($this->subQueries as $query) { + if (! $query->allowsCustomVars()) { + return false; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->commentDeletionHistoryQuery = $this->db->select(); + $this->select->from( + array('cdh' => $this->commentDeletionHistoryQuery), + array() + ); + $this->joinedVirtualTables['commenthistory'] = true; + } + + /** + * Join history related columns and tables + */ + protected function joinHistory() + { + // TODO: Ensure that one is selecting the history columns first... + $this->fetchHistoryColumns = true; + $this->requireVirtualTable('hosts'); + $this->requireVirtualTable('services'); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $columns = array_keys( + $this->columnMap['commenthistory'] + $this->columnMap['hosts'] + ); + foreach ($this->columnMap['services'] as $column => $_) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $hosts = $this->createSubQuery('Hostcommentdeletionhistory', $columns); + $this->subQueries[] = $hosts; + $this->commentDeletionHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * Join services + */ + protected function joinServices() + { + $columns = array_keys( + $this->columnMap['commenthistory'] + $this->columnMap['hosts'] + $this->columnMap['services'] + ); + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $services = $this->createSubQuery('Servicecommentdeletionhistory', $columns); + $this->subQueries[] = $services; + $this->commentDeletionHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } + + public function whereEx(FilterExpression $ex) + { + $this->requireColumn($ex->getColumn()); + foreach ($this->subQueries as $sub) { + $sub->whereEx($ex); + } + + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenteventQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenteventQuery.php new file mode 100644 index 0000000..c85adff --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenteventQuery.php @@ -0,0 +1,39 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Query for host and service comment entry and deletion events + */ +class CommenteventQuery extends IdoQuery +{ + protected $columnMap = array( + 'commentevent' => array( + 'commentevent_id' => 'ch.commenthistory_id', + 'commentevent_entry_type' => "(CASE ch.entry_type WHEN 1 THEN 'comment' WHEN 2 THEN 'downtime' WHEN 3 THEN 'flapping' WHEN 4 THEN 'ack' ELSE NULL END)", + 'commentevent_comment_time' => 'UNIX_TIMESTAMP(ch.comment_time)', + 'commentevent_author_name' => 'ch.author_name', + 'commentevent_comment_data' => 'ch.comment_data', + 'commentevent_is_persistent' => 'ch.is_persistent', + 'commentevent_comment_source' => "(CASE ch.comment_source WHEN 0 THEN 'icinga' WHEN 1 THEN 'user' ELSE NULL END)", + 'commentevent_expires' => 'ch.expires', + 'commentevent_expiration_time' => 'UNIX_TIMESTAMP(ch.expiration_time)', + 'commentevent_deletion_time' => 'UNIX_TIMESTAMP(ch.deletion_time)' + ), + 'object' => array( + 'host_name' => 'o.name1', + 'service_description' => 'o.name2' + ) + ); + + protected function joinBaseTables() + { + $this->select() + ->from(array('ch' => $this->prefix . 'commenthistory'), array()) + ->join(array('o' => $this->prefix . 'objects'), 'ch.object_id = o.object_id', array()); + + $this->joinedVirtualTables['commentevent'] = true; + $this->joinedVirtualTables['object'] = true; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenthistoryQuery.php new file mode 100644 index 0000000..47dd97c --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenthistoryQuery.php @@ -0,0 +1,179 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\FilterExpression; +use Zend_Db_Expr; +use Zend_Db_Select; +use Icinga\Data\Filter\Filter; + +/** + * Query for host and service comment history records + */ +class CommenthistoryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'commenthistory' => array( + 'id' => 'ch.id', + 'object_type' => 'ch.object_type' + ), + 'history' => array( + 'type' => 'ch.type', + 'timestamp' => 'ch.timestamp', + 'object_id' => 'ch.object_id', + 'state' => 'ch.state', + 'output' => 'ch.output' + ), + 'hosts' => array( + 'host_display_name' => 'ch.host_display_name', + 'host_name' => 'ch.host_name' + ), + 'services' => array( + 'service_description' => 'ch.service_description', + 'service_display_name' => 'ch.service_display_name', + 'service_host_name' => 'ch.service_host_name' + ) + ); + + /** + * The union + * + * @var Zend_Db_Select + */ + protected $commentHistoryQuery; + + /** + * Subqueries used for the comment history query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * Whether to additionally select all history columns + * + * @var bool + */ + protected $fetchHistoryColumns = false; + + /** + * {@inheritdoc} + */ + public function allowsCustomVars() + { + foreach ($this->subQueries as $query) { + if (! $query->allowsCustomVars()) { + return false; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->commentHistoryQuery = $this->db->select(); + $this->select->from( + array('ch' => $this->commentHistoryQuery), + array() + ); + $this->joinedVirtualTables['commenthistory'] = true; + } + + /** + * Join history related columns and tables + */ + protected function joinHistory() + { + // TODO: Ensure that one is selecting the history columns first... + $this->fetchHistoryColumns = true; + $this->requireVirtualTable('hosts'); + $this->requireVirtualTable('services'); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $columns = array_keys( + $this->columnMap['commenthistory'] + $this->columnMap['hosts'] + ); + foreach ($this->columnMap['services'] as $column => $_) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $hosts = $this->createSubQuery('Hostcommenthistory', $columns); + $this->subQueries[] = $hosts; + $this->commentHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * Join services + */ + protected function joinServices() + { + $columns = array_keys( + $this->columnMap['commenthistory'] + $this->columnMap['hosts'] + $this->columnMap['services'] + ); + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $services = $this->createSubQuery('Servicecommenthistory', $columns); + $this->subQueries[] = $services; + $this->commentHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } + + public function whereEx(FilterExpression $ex) + { + $this->requireColumn($ex->getColumn()); + foreach ($this->subQueries as $sub) { + $sub->whereEx($ex); + } + + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactQuery.php new file mode 100644 index 0000000..ca10323 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactQuery.php @@ -0,0 +1,139 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\FilterExpression; +use Zend_Db_Select; +use Icinga\Data\Filter\Filter; + +/** + * Query for contacts + */ +class ContactQuery extends IdoQuery +{ + protected $columnMap = [ + 'contacts' => [ + 'contact_id' => 'c.contact_id', + 'contact' => 'c.contact', + 'contact_name' => 'c.contact_name', + 'contact_alias' => 'c.contact_alias', + 'contact_email' => 'c.contact_email', + 'contact_pager' => 'c.contact_pager', + 'contact_object_id' => 'c.contact_object_id', + 'contact_has_host_notfications' => 'c.contact_has_host_notfications', + 'contact_has_service_notfications' => 'c.contact_has_service_notfications', + 'contact_can_submit_commands' => 'c.contact_can_submit_commands', + 'contact_notify_service_recovery' => 'c.contact_notify_service_recovery', + 'contact_notify_service_warning' => 'c.contact_notify_service_warning', + 'contact_notify_service_critical' => 'c.contact_notify_service_critical', + 'contact_notify_service_unknown' => 'c.contact_notify_service_unknown', + 'contact_notify_service_flapping' => 'c.contact_notify_service_flapping', + 'contact_notify_service_downtime' => 'c.contact_notify_service_downtime', + 'contact_notify_host_recovery' => 'c.contact_notify_host_recovery', + 'contact_notify_host_down' => 'c.contact_notify_host_down', + 'contact_notify_host_unreachable' => 'c.contact_notify_host_unreachable', + 'contact_notify_host_flapping' => 'c.contact_notify_host_flapping', + 'contact_notify_host_downtime' => 'c.contact_notify_host_downtime', + 'contact_notify_host_timeperiod' => 'c.contact_notify_host_timeperiod', + 'contact_notify_service_timeperiod' => 'c.contact_notify_service_timeperiod' + ] + ]; + + /** @var Zend_Db_Select The union */ + protected $contactQuery; + + /** @var IdoQuery[] Subqueries used for the contact query */ + protected $subQueries = []; + + public function allowsCustomVars() + { + foreach ($this->subQueries as $query) { + if (! $query->allowsCustomVars()) { + return false; + } + } + + return true; + } + + public function addFilter(Filter $filter) + { + $strangers = array_diff( + $filter->listFilteredColumns(), + array_keys($this->columnMap['contacts']) + ); + if (! empty($strangers)) { + $this->transformToUnion(); + } + + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + + return $this; + } + + protected function joinBaseTables() + { + $this->contactQuery = $this->createSubQuery('Hostcontact', array_keys($this->columnMap['contacts'])); + $this->contactQuery->setIsSubQuery(); + $this->subQueries[] = $this->contactQuery; + + $this->select->from( + ['c' => $this->contactQuery], + [] + ); + + $this->joinedVirtualTables['contacts'] = true; + } + + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + + return parent::order($columnOrAlias, $dir); + } + + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + + return $this; + } + + public function whereEx(FilterExpression $ex) + { + $this->requireColumn($ex->getColumn()); + foreach ($this->subQueries as $sub) { + $sub->whereEx($ex); + } + + return $this; + } + + public function transformToUnion() + { + $this->contactQuery = $this->db->select(); + $this->select->reset(); + $this->subQueries = []; + + $this->select->distinct()->from( + ['c' => $this->contactQuery], + [] + ); + + $hosts = $this->createSubQuery('Hostcontact', array_keys($this->columnMap['contacts'])); + $this->subQueries[] = $hosts; + $this->contactQuery->union([$hosts], Zend_Db_Select::SQL_UNION_ALL); + + $services = $this->createSubQuery('Servicecontact', array_keys($this->columnMap['contacts'])); + $this->subQueries[] = $services; + $this->contactQuery->union([$services], Zend_Db_Select::SQL_UNION_ALL); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactgroupQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactgroupQuery.php new file mode 100644 index 0000000..7d4cbc1 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactgroupQuery.php @@ -0,0 +1,214 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Query for contact groups + */ +class ContactgroupQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + /** + * {@inheritdoc} + */ + protected $groupBase = array('contactgroups' => array('cg.contactgroup_id', 'cgo.object_id')); + + /** + * {@inheritdoc} + */ + protected $groupOrigin = array('hosts', 'members', 'services'); + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'contactgroups' => array( + 'contactgroup' => 'cgo.name1 COLLATE latin1_general_ci', + 'contactgroup_name' => 'cgo.name1', + 'contactgroup_alias' => 'cg.alias COLLATE latin1_general_ci' + ), + 'members' => array( + 'contact_count' => 'SUM(CASE WHEN cgmo.object_id IS NOT NULL THEN 1 ELSE 0 END)' + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1', + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ) + ); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('cg' => $this->prefix . 'contactgroups'), + array() + )->join( + array('cgo' => $this->prefix . 'objects'), + 'cgo.object_id = cg.contactgroup_object_id AND cgo.is_active = 1 AND cgo.objecttype_id = 11', + array() + ); + $this->joinedVirtualTables['contactgroups'] = true; + } + + /** + * Join contact group members + */ + protected function joinMembers() + { + $this->select->joinLeft( + array('cgm' => $this->prefix . 'contactgroup_members'), + 'cgm.contactgroup_id = cg.contactgroup_id', + array() + )->joinLeft( + array('cgmo' => $this->prefix . 'objects'), + 'cgmo.object_id = cgm.contact_object_id AND cgmo.is_active = 1 AND cgmo.objecttype_id = 10', + array() + ); + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('hosts'); + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = ho.object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->joinLeft( + array('hcg' => $this->prefix . 'host_contactgroups'), + 'hcg.contactgroup_object_id = cg.contactgroup_object_id', + array() + )->joinLeft( + array('h' => $this->prefix . 'hosts'), + 'h.host_id = hcg.host_id', + array() + )->joinLeft( + array('ho' => $this->prefix . 'objects'), + 'ho.object_id = h.host_object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = cg.instance_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.servicegroup_id = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->joinLeft( + array('scg' => $this->prefix . 'service_contactgroups'), + 'scg.contactgroup_object_id = cg.contactgroup_object_id', + array() + )->joinLeft( + array('s' => $this->prefix . 'services'), + 's.service_id = scg.service_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $this->requireVirtualTable('hosts'); + + $query->joinVirtualTable('members'); + + return ['hgm.host_object_id', 'ho.object_id']; + } elseif ($name === 'servicegroup') { + $this->requireVirtualTable('services'); + + $query->joinVirtualTable('members'); + + return ['sgm.service_object_id', 'so.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CustomvarQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CustomvarQuery.php new file mode 100644 index 0000000..1492894 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CustomvarQuery.php @@ -0,0 +1,116 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Application\Config; +use Icinga\Data\Filter\FilterExpression; + +class CustomvarQuery extends IdoQuery +{ + protected $columnMap = array( + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'customvariablestatus' => array( + 'varname' => 'cvs.varname', + 'varvalue' => 'cvs.varvalue', + 'is_json' => 'cvs.is_json', + ), + 'objects' => array( + 'host' => 'cvo.name1 COLLATE latin1_general_ci', + 'host_name' => 'cvo.name1', + 'service' => 'cvo.name2 COLLATE latin1_general_ci', + 'service_description' => 'cvo.name2', + 'contact' => 'cvo.name1 COLLATE latin1_general_ci', + 'contact_name' => 'cvo.name1', + 'object_type' => "CASE cvo.objecttype_id WHEN 1 THEN 'host' WHEN 2 THEN 'service' WHEN 10 THEN 'contact' ELSE 'invalid' END", + 'object_type_id' => 'cvo.objecttype_id' +// 'object_type' => "CASE cvo.objecttype_id WHEN 1 THEN 'host' WHEN 2 THEN 'service' WHEN 3 THEN 'hostgroup' WHEN 4 THEN 'servicegroup' WHEN 5 THEN 'hostescalation' WHEN 6 THEN 'serviceescalation' WHEN 7 THEN 'hostdependency' WHEN 8 THEN 'servicedependency' WHEN 9 THEN 'timeperiod' WHEN 10 THEN 'contact' WHEN 11 THEN 'contactgroup' WHEN 12 THEN 'command' ELSE 'other' END" + ), + ); + + public function where($expression, $parameters = null) + { + $types = array('host' => 1, 'service' => 2, 'contact' => 10); + if ($expression === 'object_type') { + parent::where('object_type_id', $types[$parameters]); + } else { + parent::where($expression, $parameters); + } + return $this; + } + + public function whereEx(FilterExpression $ex) + { + $types = ['host' => 1, 'service' => 2, 'contact' => 10]; + if ($ex->getColumn() === 'object_type') { + $ex = clone $ex; + $ex->setColumn('object_type_id'); + $ex->setExpression($types[$ex->getExpression()]); + } + + parent::whereEx($ex); + + return $this; + } + + protected function joinBaseTables() + { + if (version_compare($this->getIdoVersion(), '1.12.0', '<')) { + $this->columnMap['customvariablestatus']['is_json'] = '(0)'; + } + + if (! (bool) Config::module('monitoring')->get('ido', 'use_customvar_status_table', true)) { + $table = 'customvariables'; + } else { + $table = 'customvariablestatus'; + } + + $this->select->from( + array('cvs' => $this->prefix . $table), + array() + )->join( + array('cvo' => $this->prefix . 'objects'), + 'cvs.object_id = cvo.object_id AND cvo.is_active = 1', + array() + ); + $this->joinedVirtualTables = array( + 'customvariablestatus' => true, + 'objects' => true + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = cvs.instance_id', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = parent::getGroup(); + if (! empty($group) && $this->ds->getDbType() === 'pgsql') { + foreach ($this->columnMap as $table => $columns) { + $pk = ($table === 'objects' ? 'cvo.' : 'cvs.') . $this->getPrimaryKeyColumn($table); + foreach ($columns as $alias => $_) { + if (! in_array($pk, $group, true) && in_array($alias, $group, true)) { + $group[] = $pk; + break; + } + } + } + } + + return $group; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php new file mode 100644 index 0000000..9bc1d88 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php @@ -0,0 +1,163 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\FilterExpression; +use Zend_Db_Expr; +use Zend_Db_Select; +use Icinga\Data\Filter\Filter; + +/** + * Query for host and service downtimes + */ +class DowntimeQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'downtimes' => array( + 'downtime_author' => 'd.downtime_author', + 'downtime_author_name' => 'd.downtime_author_name', + 'downtime_comment' => 'd.downtime_comment', + 'downtime_duration' => 'd.downtime_duration', + 'downtime_end' => 'd.downtime_end', + 'downtime_entry_time' => 'd.downtime_entry_time', + 'downtime_internal_id' => 'd.downtime_internal_id', + 'downtime_is_fixed' => 'd.downtime_is_fixed', + 'downtime_is_flexible' => 'd.downtime_is_flexible', + 'downtime_is_in_effect' => 'd.downtime_is_in_effect', + 'downtime_name' => 'd.downtime_name', + 'downtime_scheduled_end' => 'd.downtime_scheduled_end', + 'downtime_scheduled_start' => 'd.downtime_scheduled_start', + 'downtime_start' => 'd.downtime_start', + 'object_type' => 'd.object_type', + 'instance_name' => 'd.instance_name' + ), + 'hosts' => array( + 'host_display_name' => 'd.host_display_name', + 'host_name' => 'd.host_name', + 'host_state' => 'd.host_state' + ), + 'services' => array( + 'service_description' => 'd.service_description', + 'service_display_name' => 'd.service_display_name', + 'service_host_name' => 'd.service_host_name', + 'service_state' => 'd.service_state' + ) + ); + + /** + * The union + * + * @var Zend_Db_Select + */ + protected $downtimeQuery; + + /** + * Subqueries used for the downtime query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * {@inheritdoc} + */ + public function allowsCustomVars() + { + foreach ($this->subQueries as $query) { + if (! $query->allowsCustomVars()) { + return false; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + if (version_compare($this->getIdoVersion(), '1.14.0', '<')) { + $this->columnMap['downtimes']['downtime_name'] = '(NULL)'; + } + $this->downtimeQuery = $this->db->select(); + $this->select->from( + array('d' => $this->downtimeQuery), + array() + ); + $this->joinedVirtualTables['downtimes'] = true; + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $columns = array_keys($this->columnMap['downtimes'] + $this->columnMap['hosts']); + foreach (array_keys($this->columnMap['services']) as $column) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + $hosts = $this->createSubQuery('hostdowntime', $columns); + $this->subQueries[] = $hosts; + $this->downtimeQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * Join services + */ + protected function joinServices() + { + $columns = array_keys($this->columnMap['downtimes'] + $this->columnMap['hosts'] + $this->columnMap['services']); + $services = $this->createSubQuery('servicedowntime', $columns); + $this->subQueries[] = $services; + $this->downtimeQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + public function whereEx(FilterExpression $ex) + { + $this->requireColumn($ex->getColumn()); + foreach ($this->subQueries as $sub) { + $sub->whereEx($ex); + } + + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php new file mode 100644 index 0000000..de47418 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php @@ -0,0 +1,179 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\FilterExpression; +use Zend_Db_Expr; +use Zend_Db_Select; +use Icinga\Data\Filter\Filter; + +/** + * Query for host and service downtime end history records + */ +class DowntimeendhistoryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'downtimehistory' => array( + 'id' => 'deh.id', + 'object_type' => 'deh.object_type' + ), + 'history' => array( + 'type' => 'deh.type', + 'timestamp' => 'deh.timestamp', + 'object_id' => 'deh.object_id', + 'state' => 'deh.state', + 'output' => 'deh.output' + ), + 'hosts' => array( + 'host_display_name' => 'deh.host_display_name', + 'host_name' => 'deh.host_name' + ), + 'services' => array( + 'service_description' => 'deh.service_description', + 'service_display_name' => 'deh.service_display_name', + 'service_host_name' => 'deh.service_host_name' + ) + ); + + /** + * The union + * + * @var Zend_Db_Select + */ + protected $downtimeEndHistoryQuery; + + /** + * Subqueries used for the downtime end history query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * Whether to additionally select all history columns + * + * @var bool + */ + protected $fetchHistoryColumns = false; + + /** + * {@inheritdoc} + */ + public function allowsCustomVars() + { + foreach ($this->subQueries as $query) { + if (! $query->allowsCustomVars()) { + return false; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->downtimeEndHistoryQuery = $this->db->select(); + $this->select->from( + array('deh' => $this->downtimeEndHistoryQuery), + array() + ); + $this->joinedVirtualTables['downtimehistory'] = true; + } + + /** + * Join history related columns and tables + */ + protected function joinHistory() + { + // TODO: Ensure that one is selecting the history columns first... + $this->fetchHistoryColumns = true; + $this->requireVirtualTable('hosts'); + $this->requireVirtualTable('services'); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $columns = array_keys( + $this->columnMap['downtimehistory'] + $this->columnMap['hosts'] + ); + foreach ($this->columnMap['services'] as $column => $_) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $hosts = $this->createSubQuery('Hostdowntimeendhistory', $columns); + $this->subQueries[] = $hosts; + $this->downtimeEndHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * Join services + */ + protected function joinServices() + { + $columns = array_keys( + $this->columnMap['downtimehistory'] + $this->columnMap['hosts'] + $this->columnMap['services'] + ); + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $services = $this->createSubQuery('Servicedowntimeendhistory', $columns); + $this->subQueries[] = $services; + $this->downtimeEndHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } + + public function whereEx(FilterExpression $ex) + { + $this->requireColumn($ex->getColumn()); + foreach ($this->subQueries as $sub) { + $sub->whereEx($ex); + } + + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeeventQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeeventQuery.php new file mode 100644 index 0000000..04e6aa5 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeeventQuery.php @@ -0,0 +1,42 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Query for host and service downtime events + */ +class DowntimeeventQuery extends IdoQuery +{ + protected $columnMap = array( + 'downtimeevent' => array( + 'downtimeevent_id' => 'dth.downtimehistory_id', + 'downtimeevent_entry_time' => 'UNIX_TIMESTAMP(dth.entry_time)', + 'downtimeevent_author_name' => 'dth.author_name', + 'downtimeevent_comment_data' => 'dth.comment_data', + 'downtimeevent_is_fixed' => 'dth.is_fixed', + 'downtimeevent_scheduled_start_time' => 'UNIX_TIMESTAMP(dth.scheduled_start_time)', + 'downtimeevent_scheduled_end_time' => 'UNIX_TIMESTAMP(dth.scheduled_end_time)', + 'downtimeevent_was_started' => 'dth.was_started', + 'downtimeevent_actual_start_time' => 'UNIX_TIMESTAMP(dth.actual_start_time)', + 'downtimeevent_actual_end_time' => 'UNIX_TIMESTAMP(dth.actual_end_time)', + 'downtimeevent_was_cancelled' => 'dth.was_cancelled', + 'downtimeevent_is_in_effect' => 'dth.is_in_effect', + 'downtimeevent_trigger_time' => 'UNIX_TIMESTAMP(dth.trigger_time)' + ), + 'object' => array( + 'host_name' => 'o.name1', + 'service_description' => 'o.name2' + ) + ); + + protected function joinBaseTables() + { + $this->select() + ->from(array('dth' => $this->prefix . 'downtimehistory'), array()) + ->join(array('o' => $this->prefix . 'objects'), 'dth.object_id = o.object_id', array()); + + $this->joinedVirtualTables['downtimeevent'] = true; + $this->joinedVirtualTables['object'] = true; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php new file mode 100644 index 0000000..3ba600d --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php @@ -0,0 +1,179 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\FilterExpression; +use Zend_Db_Expr; +use Zend_Db_Select; +use Icinga\Data\Filter\Filter; + +/** + * Query for host and service downtime start history records + */ +class DowntimestarthistoryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'downtimehistory' => array( + 'id' => 'dsh.id', + 'object_type' => 'dsh.object_type' + ), + 'history' => array( + 'type' => 'dsh.type', + 'timestamp' => 'dsh.timestamp', + 'object_id' => 'dsh.object_id', + 'state' => 'dsh.state', + 'output' => 'dsh.output' + ), + 'hosts' => array( + 'host_display_name' => 'dsh.host_display_name', + 'host_name' => 'dsh.host_name' + ), + 'services' => array( + 'service_description' => 'dsh.service_description', + 'service_display_name' => 'dsh.service_display_name', + 'service_host_name' => 'dsh.service_host_name' + ) + ); + + /** + * The union + * + * @var Zend_Db_Select + */ + protected $downtimeStartHistoryQuery; + + /** + * Subqueries used for the downtime start history query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * Whether to additionally select all history columns + * + * @var bool + */ + protected $fetchHistoryColumns = false; + + /** + * {@inheritdoc} + */ + public function allowsCustomVars() + { + foreach ($this->subQueries as $query) { + if (! $query->allowsCustomVars()) { + return false; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->downtimeStartHistoryQuery = $this->db->select(); + $this->select->from( + array('dsh' => $this->downtimeStartHistoryQuery), + array() + ); + $this->joinedVirtualTables['downtimehistory'] = true; + } + + /** + * Join history related columns and tables + */ + protected function joinHistory() + { + // TODO: Ensure that one is selecting the history columns first... + $this->fetchHistoryColumns = true; + $this->requireVirtualTable('hosts'); + $this->requireVirtualTable('services'); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $columns = array_keys( + $this->columnMap['downtimehistory'] + $this->columnMap['hosts'] + ); + foreach ($this->columnMap['services'] as $column => $_) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $hosts = $this->createSubQuery('Hostdowntimestarthistory', $columns); + $this->subQueries[] = $hosts; + $this->downtimeStartHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * Join services + */ + protected function joinServices() + { + $columns = array_keys( + $this->columnMap['downtimehistory'] + $this->columnMap['hosts'] + $this->columnMap['services'] + ); + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $services = $this->createSubQuery('Servicedowntimestarthistory', $columns); + $this->subQueries[] = $services; + $this->downtimeStartHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } + + public function whereEx(FilterExpression $ex) + { + $this->requireColumn($ex->getColumn()); + foreach ($this->subQueries as $sub) { + $sub->whereEx($ex); + } + + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/EmptyhostgroupQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EmptyhostgroupQuery.php new file mode 100644 index 0000000..a99d6b7 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EmptyhostgroupQuery.php @@ -0,0 +1,38 @@ +<?php +/* Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +class EmptyhostgroupQuery extends HostgroupQuery +{ + protected $subQueryTargets = []; + + protected $columnMap = [ + 'hostgroups' => [ + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1', + 'host_name' => '(NULL)', + 'service_description' => '(NULL)', + 'servicegroup_name' => '(NULL)', + 'host_contact' => '(NULL)', + 'host_contactgroup' => '(NULL)' + ], + 'instances' => [ + 'instance_name' => 'i.instance_name' + ] + ]; + + protected function joinBaseTables() + { + parent::joinBaseTables(); + + $this->select->joinLeft( + ['ehgm' => $this->prefix . 'hostgroup_members'], + 'ehgm.hostgroup_id = hg.hostgroup_id', + [] + ); + $this->select->group(['hgo.object_id', 'hg.hostgroup_id']); + $this->select->having('COUNT(ehgm.hostgroup_member_id) = ?', 0); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/EmptyservicegroupQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EmptyservicegroupQuery.php new file mode 100644 index 0000000..88ee4c3 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EmptyservicegroupQuery.php @@ -0,0 +1,51 @@ +<?php +/* Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +class EmptyservicegroupQuery extends ServicegroupQuery +{ + protected $subQueryTargets = []; + + protected $columnMap = [ + 'servicegroups' => [ + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'host_name' => '(NULL)', + 'hostgroup_name' => '(NULL)', + 'service_description' => '(NULL)', + 'host_contact' => '(NULL)', + 'host_contactgroup' => '(NULL)', + 'service_contact' => '(NULL)', + 'service_contactgroup' => '(NULL)' + ], + 'instances' => [ + 'instance_name' => 'i.instance_name' + ] + ]; + + protected function joinBaseTables() + { + parent::joinBaseTables(); + + $this->select->joinLeft( + ['esgm' => $this->prefix . 'servicegroup_members'], + 'esgm.servicegroup_id = sg.servicegroup_id', + [] + ); + $this->select->group(['sgo.object_id', 'sg.servicegroup_id']); + $this->select->having('COUNT(esgm.servicegroup_member_id) = ?', 0); + } + + protected function joinHosts() + { + parent::joinHosts(); + + $this->select->joinLeft( + ['h' => 'icinga_hosts'], + 'h.host_object_id = s.host_object_id', + [] + ); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridQuery.php new file mode 100644 index 0000000..4a75bf2 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridQuery.php @@ -0,0 +1,56 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +abstract class EventgridQuery extends StatehistoryQuery +{ + /** + * The columns additionally provided by this query + * + * @var array + */ + protected $additionalColumns = array( + 'day' => 'DATE(FROM_UNIXTIME(sth.timestamp))', + 'cnt_up' => "SUM(CASE WHEN sth.state = 0 THEN 1 ELSE 0 END)", + 'cnt_down_hard' => "SUM(CASE WHEN sth.state = 1 AND sth.type = 'hard_state' THEN 1 ELSE 0 END)", + 'cnt_down' => "SUM(CASE WHEN sth.state = 1 THEN 1 ELSE 0 END)", + 'cnt_unreachable_hard' => "SUM(CASE WHEN sth.state = 2 AND sth.type = 'hard_state' THEN 1 ELSE 0 END)", + 'cnt_unreachable' => "SUM(CASE WHEN sth.state = 2 THEN 1 ELSE 0 END)", + 'cnt_unknown' => "SUM(CASE WHEN sth.state = 3 THEN 1 ELSE 0 END)", + 'cnt_unknown_hard' => "SUM(CASE WHEN sth.state = 3 AND sth.type = 'hard_state' THEN 1 ELSE 0 END)", + 'cnt_critical' => "SUM(CASE WHEN sth.state = 2 THEN 1 ELSE 0 END)", + 'cnt_critical_hard' => "SUM(CASE WHEN sth.state = 2 AND sth.type = 'hard_state' THEN 1 ELSE 0 END)", + 'cnt_warning' => "SUM(CASE WHEN sth.state = 1 THEN 1 ELSE 0 END)", + 'cnt_warning_hard' => "SUM(CASE WHEN sth.state = 1 AND sth.type = 'hard_state' THEN 1 ELSE 0 END)", + 'cnt_ok' => "SUM(CASE WHEN sth.state = 0 THEN 1 ELSE 0 END)" + ); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + parent::joinBaseTables(); + $this->requireVirtualTable('history'); + $this->columnMap['statehistory'] += $this->additionalColumns; + $this->select->group(array('DATE(FROM_UNIXTIME(sth.timestamp))')); + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + if (array_key_exists($columnOrAlias, $this->additionalColumns)) { + $subQueries = $this->subQueries; + $this->subQueries = array(); + parent::order($columnOrAlias, $dir); + $this->subQueries = $subQueries; + } else { + parent::order($columnOrAlias, $dir); + } + + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridhostsQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridhostsQuery.php new file mode 100644 index 0000000..62d92e4 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridhostsQuery.php @@ -0,0 +1,16 @@ +<?php + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +class EventgridhostsQuery extends EventgridQuery +{ + + /** + * Join history related columns and tables, hosts only + */ + protected function joinHistory() + { + $this->fetchHistoryColumns = true; + $this->requireVirtualTable('hosts'); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridservicesQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridservicesQuery.php new file mode 100644 index 0000000..424de45 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridservicesQuery.php @@ -0,0 +1,15 @@ +<?php + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +class EventgridservicesQuery extends EventgridQuery +{ + /** + * Join history related columns and tables, services only + */ + protected function joinHistory() + { + $this->fetchHistoryColumns = true; + $this->requireVirtualTable('services'); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventhistoryQuery.php new file mode 100644 index 0000000..680e2ca --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventhistoryQuery.php @@ -0,0 +1,134 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\FilterExpression; +use Zend_Db_Select; +use Icinga\Data\Filter\Filter; + +/** + * Query for event history records + */ +class EventhistoryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $useSubqueryCount = true; + + /** + * Subqueries used for the event history query + * + * @type IdoQuery[] + */ + protected $subQueries = array(); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'eventhistory' => array( + 'id' => 'eh.id', + 'host_name' => 'eh.host_name', + 'service_description' => 'eh.service_description', + 'object_type' => 'eh.object_type', + 'timestamp' => 'eh.timestamp', + 'state' => 'eh.state', + 'output' => 'eh.output', + 'type' => 'eh.type', + 'host_display_name' => 'eh.host_display_name', + 'service_display_name' => 'eh.service_display_name' + ) + ); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $columns = array( + 'id', + 'timestamp', + 'output', + 'type', + 'state', + 'object_type', + 'host_name', + 'service_description', + 'host_display_name', + 'service_display_name' + ); + $this->subQueries = array( + $this->createSubQuery('Notificationhistory', $columns), + $this->createSubQuery('Statehistory', $columns), + $this->createSubQuery('Downtimestarthistory', $columns), + $this->createSubQuery('Downtimeendhistory', $columns), + $this->createSubQuery('Commenthistory', $columns), + $this->createSubQuery('Commentdeletionhistory', $columns), + $this->createSubQuery('Flappingstarthistory', $columns), + $this->createSubQuery('Flappingendhistory', $columns) + ); + $sub = $this->db->select()->union($this->subQueries, Zend_Db_Select::SQL_UNION_ALL); + $this->select->from(array('eh' => $sub), array()); + $this->joinedVirtualTables['eventhistory'] = true; + } + + /** + * {@inheritdoc} + */ + public function allowsCustomVars() + { + foreach ($this->subQueries as $query) { + if (! $query->allowsCustomVars()) { + return false; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } + + public function whereEx(FilterExpression $ex) + { + $this->requireColumn($ex->getColumn()); + foreach ($this->subQueries as $sub) { + $sub->whereEx($ex); + } + + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingendhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingendhistoryQuery.php new file mode 100644 index 0000000..7bdf332 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingendhistoryQuery.php @@ -0,0 +1,49 @@ +<?php +/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Zend_Db_Expr; +use Zend_Db_Select; +use Icinga\Data\Filter\Filter; + +/** + * Query for host and service flapping end history records + */ +class FlappingendhistoryQuery extends FlappingstarthistoryQuery +{ + /** + * Join hosts + */ + protected function joinHosts() + { + $columns = array_keys( + $this->columnMap['flappinghistory'] + $this->columnMap['hosts'] + ); + foreach ($this->columnMap['services'] as $column => $_) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $hosts = $this->createSubQuery('Hostflappingendhistory', $columns); + $this->subQueries[] = $hosts; + $this->flappingStartHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * Join services + */ + protected function joinServices() + { + $columns = array_keys( + $this->columnMap['flappinghistory'] + $this->columnMap['hosts'] + $this->columnMap['services'] + ); + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $services = $this->createSubQuery('Serviceflappingendhistory', $columns); + $this->subQueries[] = $services; + $this->flappingStartHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingeventQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingeventQuery.php new file mode 100644 index 0000000..d993467 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingeventQuery.php @@ -0,0 +1,36 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Query for host and service flapping events + */ +class FlappingeventQuery extends IdoQuery +{ + protected $columnMap = array( + 'flappingevent' => array( + 'flappingevent_id' => 'fh.flappinghistory_id', + 'flappingevent_event_time' => 'UNIX_TIMESTAMP(fh.event_time)', + 'flappingevent_event_type' => "(CASE fh.event_type WHEN 1000 THEN 'flapping' WHEN 1001 THEN 'flapping_deleted' ELSE NULL END)", + 'flappingevent_reason_type' => "(CASE fh.reason_type WHEN 1 THEN 'stopped' WHEN 2 THEN 'disabled' ELSE NULL END)", + 'flappingevent_percent_state_change' => 'fh.percent_state_change', + 'flappingevent_low_threshold' => 'fh.low_threshold', + 'flappingevent_high_threshold' => 'fh.high_threshold' + ), + 'object' => array( + 'host_name' => 'o.name1', + 'service_description' => 'o.name2' + ) + ); + + protected function joinBaseTables() + { + $this->select() + ->from(array('fh' => $this->prefix . 'flappinghistory'), array()) + ->join(array('o' => $this->prefix . 'objects'), 'fh.object_id = o.object_id', array()); + + $this->joinedVirtualTables['flappingevent'] = true; + $this->joinedVirtualTables['object'] = true; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingstarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingstarthistoryQuery.php new file mode 100644 index 0000000..5c8bec5 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingstarthistoryQuery.php @@ -0,0 +1,179 @@ +<?php +/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\FilterExpression; +use Zend_Db_Expr; +use Zend_Db_Select; +use Icinga\Data\Filter\Filter; + +/** + * Query for host and service flapping start history records + */ +class FlappingstarthistoryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'flappinghistory' => array( + 'id' => 'fsh.id', + 'object_type' => 'fsh.object_type' + ), + 'history' => array( + 'type' => 'fsh.type', + 'timestamp' => 'fsh.timestamp', + 'object_id' => 'fsh.object_id', + 'state' => 'fsh.state', + 'output' => 'fsh.output' + ), + 'hosts' => array( + 'host_display_name' => 'fsh.host_display_name', + 'host_name' => 'fsh.host_name' + ), + 'services' => array( + 'service_description' => 'fsh.service_description', + 'service_display_name' => 'fsh.service_display_name', + 'service_host_name' => 'fsh.service_host_name' + ) + ); + + /** + * The union + * + * @var Zend_Db_Select + */ + protected $flappingStartHistoryQuery; + + /** + * Subqueries used for the flapping start history query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * Whether to additionally select all history columns + * + * @var bool + */ + protected $fetchHistoryColumns = false; + + /** + * {@inheritdoc} + */ + public function allowsCustomVars() + { + foreach ($this->subQueries as $query) { + if (! $query->allowsCustomVars()) { + return false; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->flappingStartHistoryQuery = $this->db->select(); + $this->select->from( + array('fsh' => $this->flappingStartHistoryQuery), + array() + ); + $this->joinedVirtualTables['flappinghistory'] = true; + } + + /** + * Join history related columns and tables + */ + protected function joinHistory() + { + // TODO: Ensure that one is selecting the history columns first... + $this->fetchHistoryColumns = true; + $this->requireVirtualTable('hosts'); + $this->requireVirtualTable('services'); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $columns = array_keys( + $this->columnMap['flappinghistory'] + $this->columnMap['hosts'] + ); + foreach ($this->columnMap['services'] as $column => $_) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $hosts = $this->createSubQuery('Hostflappingstarthistory', $columns); + $this->subQueries[] = $hosts; + $this->flappingStartHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * Join services + */ + protected function joinServices() + { + $columns = array_keys( + $this->columnMap['flappinghistory'] + $this->columnMap['hosts'] + $this->columnMap['services'] + ); + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $services = $this->createSubQuery('Serviceflappingstarthistory', $columns); + $this->subQueries[] = $services; + $this->flappingStartHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } + + public function whereEx(FilterExpression $ex) + { + $this->requireColumn($ex->getColumn()); + foreach ($this->subQueries as $sub) { + $sub->whereEx($ex); + } + + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php new file mode 100644 index 0000000..60ea5ef --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php @@ -0,0 +1,131 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Zend_Db_Select; + +/** + * Query for host and service group summaries + */ +class GroupsummaryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'hoststatussummary' => array( + 'hostgroup' => 'hostgroup COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hostgroup_alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hostgroup_name', + 'hosts_up' => 'SUM(CASE WHEN object_type = \'host\' AND state = 0 THEN 1 ELSE 0 END)', + 'hosts_unreachable' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 THEN 1 ELSE 0 END)', + 'hosts_unreachable_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime != 0 THEN 1 ELSE 0 END)', + 'hosts_unreachable_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)', + 'hosts_down' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 THEN 1 ELSE 0 END)', + 'hosts_down_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime != 0 THEN 1 ELSE 0 END)', + 'hosts_down_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime != 0 THEN state_change ELSE 0 END)', + 'hosts_down_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime = 0 THEN state_change ELSE 0 END)', + 'hosts_down_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)', + 'hosts_pending' => 'SUM(CASE WHEN object_type = \'host\' AND state = 99 THEN 1 ELSE 0 END)', + 'hosts_pending_last_state_change' => 'MAX(CASE WHEN object_type = \'host\' AND state = 99 THEN state_change ELSE 0 END)', + 'hosts_severity' => 'MAX(CASE WHEN object_type = \'host\' THEN severity ELSE 0 END)', + 'hosts_total' => 'SUM(CASE WHEN object_type = \'host\' THEN 1 ELSE 0 END)', + 'hosts_unreachable_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime != 0 THEN state_change ELSE 0 END)', + 'hosts_unreachable_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime = 0 THEN state_change ELSE 0 END)', + 'hosts_up_last_state_change' => 'MAX(CASE WHEN object_type = \'host\' AND state = 0 THEN state_change ELSE 0 END)' + ), + 'servicestatussummary' => array( + 'servicegroup' => 'servicegroup COLLATE latin1_general_ci', + 'servicegroup_alias' => 'servicegroup_alias COLLATE latin1_general_ci', + 'servicegroup_name' => 'servicegroup_name', + '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 + host_state > 0 THEN 1 ELSE 0 END)', + 'services_critical_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state > 0 THEN state_change ELSE 0 END)', + 'services_critical_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state = 0 THEN state_change ELSE 0 END)', + 'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state = 0 THEN 1 ELSE 0 END)', + 'services_ok' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 THEN 1 ELSE 0 END)', + 'services_ok_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 0 THEN state_change ELSE 0 END)', + 'services_pending' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 THEN 1 ELSE 0 END)', + 'services_pending_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 99 THEN state_change ELSE 0 END)', + 'services_severity' => 'MAX(CASE WHEN object_type = \'service\' THEN severity ELSE 0 END)', + 'services_total' => 'SUM(CASE WHEN object_type = \'service\' 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 + host_state > 0 THEN 1 ELSE 0 END)', + 'services_unknown_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state > 0 THEN state_change ELSE 0 END)', + 'services_unknown_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state = 0 THEN state_change ELSE 0 END)', + 'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state = 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 + host_state > 0 THEN 1 ELSE 0 END)', + 'services_warning_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state > 0 THEN state_change ELSE 0 END)', + 'services_warning_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state = 0 THEN state_change ELSE 0 END)', + 'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state = 0 THEN 1 ELSE 0 END)' + ) + ); + + /** + * {@inheritdoc} + */ + protected $useSubqueryCount = true; + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $columns = array( + 'object_type', + 'host_state' + ); + + if (in_array('servicegroup', $this->desiredColumns) || in_array('servicegroup_name', $this->desiredColumns)) { + $columns[] = 'servicegroup'; + $columns[] = 'servicegroup_name'; + $columns[] = 'servicegroup_alias'; + $groupColumns = array('servicegroup_name', 'servicegroup_alias'); + } else { + $columns[] = 'hostgroup'; + $columns[] = 'hostgroup_name'; + $columns[] = 'hostgroup_alias'; + $groupColumns = array('hostgroup_name', 'hostgroup_alias'); + } + $hosts = $this->createSubQuery( + 'Hoststatus', + $columns + array( + 'state' => 'host_state', + 'acknowledged' => 'host_acknowledged', + 'in_downtime' => 'host_in_downtime', + 'state_change' => 'host_last_state_change', + 'severity' => 'host_severity' + ) + ); + if (in_array('servicegroup_name', $this->desiredColumns)) { + $hosts->group(array( + 'sgo.name1', + 'ho.object_id', + 'sg.alias', + 'state', + 'acknowledged', + 'in_downtime', + 'state_change', + 'severity' + )); + } + $services = $this->createSubQuery( + 'Status', + $columns + array( + 'state' => 'service_state', + 'acknowledged' => 'service_acknowledged', + 'in_downtime' => 'service_in_downtime', + 'state_change' => 'service_last_state_change', + 'severity' => 'service_severity' + ) + ); + $union = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL); + $this->select->from(array('statussummary' => $union), array())->group($groupColumns); + $this->joinedVirtualTables = array( + 'servicestatussummary' => true, + 'hoststatussummary' => true + ); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentQuery.php new file mode 100644 index 0000000..b388204 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentQuery.php @@ -0,0 +1,202 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Query for host comments + */ +class HostcommentQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + /** + * {@inheritdoc} + */ + protected $groupBase = array('comments' => array('c.comment_id', 'ho.object_id')); + + /** + * {@inheritdoc} + */ + protected $groupOrigin = array('hostgroups', 'services'); + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'comments' => array( + 'comment_author' => 'c.author_name COLLATE latin1_general_ci', + 'comment_author_name' => 'c.author_name', + 'comment_data' => 'c.comment_data', + 'comment_expiration' => 'CASE c.expires WHEN 1 THEN UNIX_TIMESTAMP(c.expiration_time) ELSE NULL END', + 'comment_internal_id' => 'c.internal_comment_id', + 'comment_is_persistent' => 'c.is_persistent', + 'comment_name' => 'c.name', + 'comment_timestamp' => 'UNIX_TIMESTAMP(c.comment_time)', + 'comment_type' => "CASE c.entry_type WHEN 1 THEN 'comment' WHEN 2 THEN 'downtime' WHEN 3 THEN 'flapping' WHEN 4 THEN 'ack' END", + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1', + 'object_type' => '(\'host\')' + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'hoststatus' => array( + 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END' + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + ) + ); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + if (version_compare($this->getIdoVersion(), '1.14.0', '<')) { + $this->columnMap['comments']['comment_name'] = '(NULL)'; + } + $this->select->from( + array('c' => $this->prefix . 'comments'), + array() + )->join( + array('ho' => $this->prefix . 'objects'), + 'ho.object_id = c.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + ); + $this->joinedVirtualTables['comments'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = ho.object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join host status + */ + protected function joinHoststatus() + { + $this->select->join( + array('hs' => $this->prefix . 'hoststatus'), + 'hs.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = c.instance_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sgm.servicegroup_id = sg.' . $this->servicegroup_id, + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->joinLeft( + array('s' => $this->prefix . 'services'), + 's.host_object_id = ho.object_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $query->joinVirtualTable('members'); + + return ['hgm.host_object_id', 'ho.object_id']; + } elseif ($name === 'servicegroup') { + $query->joinVirtualTable('services'); + + return ['s.host_object_id', 'ho.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentdeletionhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentdeletionhistoryQuery.php new file mode 100644 index 0000000..d798d56 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentdeletionhistoryQuery.php @@ -0,0 +1,44 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; + +/** + * Query for host comment removal records + */ +class HostcommentdeletionhistoryQuery extends HostcommenthistoryQuery +{ + protected function requireFilterColumns(Filter $filter) + { + if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') { + $this->requireColumn('timestamp'); + $filter->setColumn('hch.deletion_time'); + $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression()))); + return null; + } + + return parent::requireFilterColumns($filter); + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + parent::joinBaseTables(); + $this->select->where("hch.deletion_time > '1970-01-02 00:00:00'"); + $this->columnMap['commenthistory']['timestamp'] = str_replace( + 'comment_time', + 'deletion_time', + $this->columnMap['commenthistory']['timestamp'] + ); + $this->columnMap['commenthistory']['type'] = str_replace( + 'END)', + "END || '_deleted')", + $this->columnMap['commenthistory']['type'] + ); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommenthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommenthistoryQuery.php new file mode 100644 index 0000000..b8f166a --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommenthistoryQuery.php @@ -0,0 +1,197 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; + +/** + * Query for host comment history records + */ +class HostcommenthistoryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + /** + * {@inheritdoc} + */ + protected $groupBase = array('commenthistory' => array('hch.commenthistory_id', 'ho.object_id')); + + /** + * {@inheritdoc} + */ + protected $groupOrigin = array('hostgroups', 'services'); + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'commenthistory' => array( + 'id' => 'hch.commenthistory_id', + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1', + 'object_id' => 'hch.object_id', + 'object_type' => '(\'host\')', + 'output' => "('[' || hch.author_name || '] ' || hch.comment_data)", + 'state' => '(-1)', + 'timestamp' => 'UNIX_TIMESTAMP(hch.comment_time)', + 'type' => "(CASE hch.entry_type WHEN 1 THEN 'comment' WHEN 2 THEN 'dt_comment' WHEN 3 THEN 'flapping' WHEN 4 THEN 'ack' END)" + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ) + ); + + protected function requireFilterColumns(Filter $filter) + { + if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') { + $this->requireColumn('timestamp'); + $filter->setColumn('hch.comment_time'); + $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression()))); + return null; + } + + return parent::requireFilterColumns($filter); + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('hch' => $this->prefix . 'commenthistory'), + array() + )->join( + array('ho' => $this->prefix . 'objects'), + 'ho.object_id = hch.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + ); + $this->joinedVirtualTables['commenthistory'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = ho.object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = hch.instance_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->joinLeft( + array('s' => $this->prefix . 'services'), + 's.host_object_id = ho.object_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $query->joinVirtualTable('members'); + + return ['hgm.host_object_id', 'ho.object_id']; + } elseif ($name === 'servicegroup') { + $query->joinVirtualTable('services'); + + return ['s.host_object_id', 'ho.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcontactQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcontactQuery.php new file mode 100644 index 0000000..23b0e90 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcontactQuery.php @@ -0,0 +1,247 @@ +<?php +/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Query for host contacts + */ +class HostcontactQuery extends IdoQuery +{ + protected $allowCustomVars = true; + + protected $groupBase = [ + 'contacts' => ['co.object_id', 'c.contact_id'], + 'timeperiods' => ['ht.timeperiod_id', 'st.timeperiod_id'] + ]; + + protected $groupOrigin = ['contactgroups', 'hosts', 'services']; + + protected $subQueryTargets = [ + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ]; + + protected $columnMap = [ + 'contactgroups' => [ + 'contactgroup' => 'cgo.name1 COLLATE latin1_general_ci', + 'contactgroup_name' => 'cgo.name1', + 'contactgroup_alias' => 'cg.alias COLLATE latin1_general_ci' + ], + 'contacts' => [ + 'contact_id' => 'c.contact_id', + 'contact' => 'co.name1 COLLATE latin1_general_ci', + 'contact_name' => 'co.name1', + 'contact_alias' => 'c.alias COLLATE latin1_general_ci', + 'contact_email' => 'c.email_address COLLATE latin1_general_ci', + 'contact_pager' => 'c.pager_address', + 'contact_object_id' => 'c.contact_object_id', + 'contact_has_host_notfications' => 'c.host_notifications_enabled', + 'contact_has_service_notfications' => 'c.service_notifications_enabled', + 'contact_can_submit_commands' => 'c.can_submit_commands', + 'contact_notify_service_recovery' => 'c.notify_service_recovery', + 'contact_notify_service_warning' => 'c.notify_service_warning', + 'contact_notify_service_critical' => 'c.notify_service_critical', + 'contact_notify_service_unknown' => 'c.notify_service_unknown', + 'contact_notify_service_flapping' => 'c.notify_service_flapping', + 'contact_notify_service_downtime' => 'c.notify_service_downtime', + 'contact_notify_host_recovery' => 'c.notify_host_recovery', + 'contact_notify_host_down' => 'c.notify_host_down', + 'contact_notify_host_unreachable' => 'c.notify_host_unreachable', + 'contact_notify_host_flapping' => 'c.notify_host_flapping', + 'contact_notify_host_downtime' => 'c.notify_host_downtime' + ], + 'hostgroups' => [ + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ], + 'hosts' => [ + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1', + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ], + 'instances' => [ + 'instance_name' => 'i.instance_name' + ], + 'servicegroups' => [ + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ], + 'services' => [ + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ], + 'timeperiods' => [ + 'contact_notify_host_timeperiod' => 'ht.alias COLLATE latin1_general_ci', + 'contact_notify_service_timeperiod' => 'st.alias COLLATE latin1_general_ci' + ] + ]; + + protected function joinBaseTables() + { + $this->select->from( + ['c' => $this->prefix . 'contacts'], + [] + )->join( + ['co' => $this->prefix . 'objects'], + 'co.object_id = c.contact_object_id AND co.is_active = 1 AND co.objecttype_id = 10', + [] + ); + + $this->joinedVirtualTables = array('contacts' => true); + } + + /** + * Join contact groups + */ + protected function joinContactgroups() + { + $this->select->joinLeft( + ['cgm' => $this->prefix . 'contactgroup_members'], + 'co.object_id = cgm.contact_object_id', + [] + )->joinLeft( + ['cg' => $this->prefix . 'contactgroups'], + 'cgm.contactgroup_id = cg.contactgroup_id', + [] + )->joinLeft( + ['cgo' => $this->prefix . 'objects'], + 'cg.contactgroup_object_id = cgo.object_id AND cgo.is_active = 1 AND cgo.objecttype_id = 11', + [] + ); + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('hosts'); + + $this->select->joinLeft( + ['hgm' => $this->prefix . 'hostgroup_members'], + 'hgm.host_object_id = ho.object_id', + [] + )->joinLeft( + ['hg' => $this->prefix . 'hostgroups'], + 'hg.hostgroup_id = hgm.hostgroup_id', + [] + )->joinLeft( + ['hgo' => $this->prefix . 'objects'], + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + [] + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->joinLeft( + ['hc' => $this->prefix . 'host_contacts'], + 'hc.contact_object_id = c.contact_object_id', + [] + )->joinLeft( + ['h' => $this->prefix . 'hosts'], + 'h.host_id = hc.host_id', + [] + )->joinLeft( + ['ho' => $this->prefix . 'objects'], + 'ho.object_id = h.host_object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + [] + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + ['i' => $this->prefix . 'instances'], + 'i.instance_id = c.instance_id', + [] + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + ['sgm' => $this->prefix . 'servicegroup_members'], + 'sgm.service_object_id = s.service_object_id', + [] + )->joinLeft( + ['sg' => $this->prefix . 'servicegroups'], + 'sg.servicegroup_id = sgm.servicegroup_id', + [] + )->joinLeft( + ['sgo' => $this->prefix . 'objects'], + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + [] + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->requireVirtualTable('hosts'); + + $this->select->joinLeft( + ['s' => $this->prefix . 'services'], + 's.host_object_id = ho.object_id', + [] + )->joinLeft( + ['so' => $this->prefix . 'objects'], + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + [] + ); + } + + /** + * Join time periods + */ + protected function joinTimeperiods() + { + $this->select->joinLeft( + ['ht' => $this->prefix . 'timeperiods'], + 'ht.timeperiod_object_id = c.host_timeperiod_object_id', + [] + ); + $this->select->joinLeft( + ['st' => $this->prefix . 'timeperiods'], + 'st.timeperiod_object_id = c.service_timeperiod_object_id', + [] + ); + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $this->requireVirtualTable('hosts'); + + $query->joinVirtualTable('members'); + + return ['hgm.host_object_id', 'ho.object_id']; + } elseif ($name === 'servicegroup') { + $this->requireVirtualTable('services'); + + $query->joinVirtualTable('members'); + + return ['sgm.service_object_id', 'so.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeQuery.php new file mode 100644 index 0000000..62f5ceb --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeQuery.php @@ -0,0 +1,208 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Query for host downtimes + */ +class HostdowntimeQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + /** + * {@inheritdoc} + */ + protected $groupBase = array('downtimes' => array('sd.scheduleddowntime_id', 'ho.object_id')); + + /** + * {@inheritdoc} + */ + protected $groupOrigin = array('hostgroups', 'servicegroups', 'services'); + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'downtimes' => array( + 'downtime_author' => 'sd.author_name COLLATE latin1_general_ci', + 'downtime_author_name' => 'sd.author_name', + 'downtime_comment' => 'sd.comment_data', + 'downtime_duration' => 'sd.duration', + '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_entry_time' => 'UNIX_TIMESTAMP(sd.entry_time)', + 'downtime_internal_id' => 'sd.internal_downtime_id', + 'downtime_is_fixed' => 'sd.is_fixed', + 'downtime_is_flexible' => 'CASE WHEN sd.is_fixed = 0 THEN 1 ELSE 0 END', + 'downtime_is_in_effect' => 'sd.is_in_effect', + 'downtime_name' => 'sd.name', + 'downtime_scheduled_end' => 'UNIX_TIMESTAMP(sd.scheduled_end_time)', + 'downtime_scheduled_start' => 'UNIX_TIMESTAMP(sd.scheduled_start_time)', + 'downtime_start' => 'UNIX_TIMESTAMP(CASE WHEN UNIX_TIMESTAMP(sd.trigger_time) > 0 then sd.trigger_time ELSE sd.scheduled_start_time END)', + 'downtime_triggered_by_id' => 'sd.triggered_by_id', + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1', + 'object_type' => '(\'host\')' + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'hoststatus' => array( + 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END' + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + ) + ); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + if (version_compare($this->getIdoVersion(), '1.14.0', '<')) { + $this->columnMap['downtimes']['downtime_name'] = '(NULL)'; + } + $this->select->from( + array('sd' => $this->prefix . 'scheduleddowntime'), + array() + )->join( + array('ho' => $this->prefix . 'objects'), + 'sd.object_id = ho.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + ); + $this->joinedVirtualTables['downtimes'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = ho.object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join host status + */ + protected function joinHoststatus() + { + $this->select->join( + array('hs' => $this->prefix . 'hoststatus'), + 'hs.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = sd.instance_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sgm.servicegroup_id = sg.' . $this->servicegroup_id, + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->joinLeft( + array('s' => $this->prefix . 'services'), + 's.host_object_id = ho.object_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $query->joinVirtualTable('members'); + + return ['hgm.host_object_id', 'ho.object_id']; + } elseif ($name === 'servicegroup') { + $query->joinVirtualTable('services'); + + return ['s.host_object_id', 'ho.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeendhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeendhistoryQuery.php new file mode 100644 index 0000000..808b3f2 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeendhistoryQuery.php @@ -0,0 +1,42 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; + +/** + * Query for host downtime end history records + */ +class HostdowntimeendhistoryQuery extends HostdowntimestarthistoryQuery +{ + protected function requireFilterColumns(Filter $filter) + { + if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') { + $this->requireColumn('timestamp'); + $filter->setColumn('hdh.actual_end_time'); + $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression()))); + return null; + } + + return parent::requireFilterColumns($filter); + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + parent::joinBaseTables(true); + $this->select->where( + "hdh.actual_end_time > '1970-01-02 00:00:00' AND hdh.was_started = 1 AND hdh.was_cancelled = 0" + ); + $this->columnMap['downtimehistory']['type'] = "('dt_end')"; + $this->columnMap['downtimehistory']['timestamp'] = str_replace( + 'actual_start_time', + 'actual_end_time', + $this->columnMap['downtimehistory']['timestamp'] + ); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimestarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimestarthistoryQuery.php new file mode 100644 index 0000000..a6f97e7 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimestarthistoryQuery.php @@ -0,0 +1,206 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; + +/** + * Query for host downtime start history records + */ +class HostdowntimestarthistoryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + /** + * {@inheritdoc} + */ + protected $groupBase = array('downtimehistory' => array('hdh.downtimehistory_id', 'ho.object_id')); + + /** + * {@inheritdoc} + */ + protected $groupOrigin = array('hostgroups', 'services'); + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'downtimehistory' => array( + 'id' => 'hdh.downtimehistory_id', + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1', + 'object_id' => 'hdh.object_id', + 'object_type' => '(\'host\')', + 'output' => "('[' || hdh.author_name || '] ' || hdh.comment_data)", + 'state' => '(-1)', + 'timestamp' => 'UNIX_TIMESTAMP(hdh.actual_start_time)', + 'type' => "('dt_start')" + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ) + ); + + protected function requireFilterColumns(Filter $filter) + { + if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') { + $this->requireColumn('timestamp'); + $filter->setColumn('hdh.actual_start_time'); + $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression()))); + return null; + } + + return parent::requireFilterColumns($filter); + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('hdh' => $this->prefix . 'downtimehistory'), + array() + )->join( + array('ho' => $this->prefix . 'objects'), + 'ho.object_id = hdh.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + ); + + if (func_num_args() === 0 || func_get_arg(0) === false) { + $this->select->where( + "hdh.actual_start_time > '1970-01-02 00:00:00'" + ); + } + $this->select->where( + "hdh.was_started = 1 AND hdh.was_cancelled = 0" + ); + $this->joinedVirtualTables['downtimehistory'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = ho.object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = hdh.instance_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->joinLeft( + array('s' => $this->prefix . 'services'), + 's.host_object_id = ho.object_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $query->joinVirtualTable('members'); + + return ['hgm.host_object_id', 'ho.object_id']; + } elseif ($name === 'servicegroup') { + $query->joinVirtualTable('services'); + + return ['s.host_object_id', 'ho.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostflappingendhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostflappingendhistoryQuery.php new file mode 100644 index 0000000..ebc346b --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostflappingendhistoryQuery.php @@ -0,0 +1,31 @@ +<?php +/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Query for host flapping end history records + */ +class HostflappingendhistoryQuery extends HostflappingstarthistoryQuery +{ + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('hfh' => $this->prefix . 'flappinghistory'), + array() + )->join( + array('ho' => $this->prefix . 'objects'), + 'ho.object_id = hfh.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + ); + + $this->select->where('hfh.event_type = 1001'); + + $this->joinedVirtualTables['flappinghistory'] = true; + + $this->columnMap['flappinghistory']['type'] = '(\'flapping_deleted\')'; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostflappingstarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostflappingstarthistoryQuery.php new file mode 100644 index 0000000..497a493 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostflappingstarthistoryQuery.php @@ -0,0 +1,200 @@ +<?php +/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; + +/** + * Query for host flapping start history records + */ +class HostflappingstarthistoryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + /** + * {@inheritdoc} + */ + protected $groupBase = array('flappinghistory' => array('hfh.flappinghistory_id', 'ho.object_id')); + + /** + * {@inheritdoc} + */ + protected $groupOrigin = array('hostgroups', 'services'); + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'flappinghistory' => array( + 'id' => 'hfh.flappinghistory_id', + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1', + 'object_id' => 'hfh.object_id', + 'object_type' => '(\'host\')', + 'output' => '(hfh.percent_state_change || \'\')', + 'state' => '(-1)', + 'timestamp' => 'UNIX_TIMESTAMP(hfh.event_time)', + 'type' => '(\'flapping\')' + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'servicegroups' => array( + 'servicegroup_name' => 'sgo.name1', + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ) + ); + + protected function requireFilterColumns(Filter $filter) + { + if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') { + $this->requireColumn('timestamp'); + $filter->setColumn('hfh.event_time'); + $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression()))); + return null; + } + + return parent::requireFilterColumns($filter); + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('hfh' => $this->prefix . 'flappinghistory'), + array() + )->join( + array('ho' => $this->prefix . 'objects'), + 'ho.object_id = hfh.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + ); + + $this->select->where('hfh.event_type = 1000'); + + $this->joinedVirtualTables['flappinghistory'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = ho.object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = hfh.instance_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->joinLeft( + array('s' => $this->prefix . 'services'), + 's.host_object_id = ho.object_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $query->joinVirtualTable('members'); + + return ['hgm.host_object_id', 'ho.object_id']; + } elseif ($name === 'servicegroup') { + $query->joinVirtualTable('services'); + + return ['s.host_object_id', 'ho.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupQuery.php new file mode 100644 index 0000000..463fba9 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupQuery.php @@ -0,0 +1,295 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Query for host groups + */ +class HostgroupQuery extends IdoQuery +{ + protected $allowCustomVars = true; + + protected $groupBase = array( + 'hostgroups' => array('hgo.object_id', 'hg.hostgroup_id'), + 'hoststatus' => array('hs.hoststatus_id'), + 'servicestatus' => array('ss.servicestatus_id') + ); + + protected $groupOrigin = array('members'); + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + protected $columnMap = array( + 'contacts' => [ + 'host_contact' => 'hco.name1' + ], + 'contactgroups' => [ + 'host_contactgroup' => 'hcgo.name1' + ], + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hoststatus' => array( + 'host_handled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END', + 'host_severity' => ' + CASE + WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL + THEN 16 + ELSE + CASE + WHEN hs.current_state = 0 + THEN 1 + ELSE + CASE + WHEN hs.current_state = 1 THEN 64 + WHEN hs.current_state = 2 THEN 32 + ELSE 256 + END + + + CASE + WHEN hs.problem_has_been_acknowledged = 1 THEN 2 + WHEN hs.scheduled_downtime_depth > 0 THEN 1 + ELSE 256 + END + END + END', + 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END' + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'members' => array( + 'host_name' => 'ho.name1' + ), + 'servicegroups' => array( + 'servicegroup_name' => 'sgo.name1' + ), + 'services' => array( + 'service_description' => 'so.name2' + ), + 'servicestatus' => array( + 'service_handled' => 'CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) > 0 THEN 1 ELSE 0 END', + 'service_severity' => ' + CASE WHEN ss.current_state = 0 + THEN + CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL + THEN 16 + ELSE 0 + END + + + CASE WHEN ss.problem_has_been_acknowledged = 1 + THEN 2 + ELSE + CASE WHEN ss.scheduled_downtime_depth > 0 + THEN 1 + ELSE 4 + END + END + ELSE + CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 16 + WHEN ss.current_state = 1 THEN 32 + WHEN ss.current_state = 2 THEN 128 + WHEN ss.current_state = 3 THEN 64 + ELSE 256 + END + + + CASE WHEN hs.current_state > 0 + THEN 1024 + ELSE + CASE WHEN ss.problem_has_been_acknowledged = 1 + THEN 512 + ELSE + CASE WHEN ss.scheduled_downtime_depth > 0 + THEN 256 + ELSE 2048 + END + END + END + END', + 'service_state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE ss.current_state END' + ) + ); + + protected function joinBaseTables() + { + $this->select->from( + array('hgo' => $this->prefix . 'objects'), + array() + )->join( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_object_id = hgo.object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + $this->joinedVirtualTables['hostgroups'] = true; + } + + /** + * Join contacts + */ + protected function joinContacts() + { + $this->requireVirtualTable('hosts'); + + $this->select->joinLeft( + ['hc' => 'icinga_host_contacts'], + 'hc.host_id = h.host_id', + [] + )->joinLeft( + ['hco' => 'icinga_objects'], + 'hco.object_id = hc.contact_object_id AND hco.is_active = 1 AND hco.objecttype_id = 10', + [] + ); + } + + /** + * Join contact groups + */ + protected function joinContactgroups() + { + $this->requireVirtualTable('hosts'); + + $this->select->joinLeft( + ['hcg' => 'icinga_host_contactgroups'], + 'hcg.host_id = h.host_id', + [] + )->joinLeft( + ['hcgo' => 'icinga_objects'], + 'hcgo.object_id = hcg.contactgroup_object_id AND hcgo.is_active = 1 AND hcgo.objecttype_id = 11', + [] + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->requireVirtualTable('members'); + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join host status + */ + protected function joinHoststatus() + { + $this->requireVirtualTable('members'); + $this->select->join( + array('hs' => $this->prefix . 'hoststatus'), + 'hs.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = hg.instance_id', + array() + ); + } + + /** + * Join members + */ + protected function joinMembers() + { + $this->select->join( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.hostgroup_id = hg.hostgroup_id', + array() + )->join( + array('ho' => $this->prefix . 'objects'), + 'hgm.host_object_id = ho.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sgm.servicegroup_id = sg.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->requireVirtualTable('hosts'); + $this->select->joinLeft( + array('s' => $this->prefix . 'services'), + 's.host_object_id = h.host_object_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + /** + * Join service status + */ + protected function joinServicestatus() + { + $this->requireVirtualTable('services'); + $this->requireVirtualTable('hoststatus'); + $this->select->join( + array('ss' => $this->prefix . 'servicestatus'), + 'ss.service_object_id = so.object_id', + array() + ); + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + // Propagate that the "parent" query has to be filtered as well + $additionalFilter = clone $filter; + + $this->requireVirtualTable('members'); + + $query->joinVirtualTable('members'); + + return ['hgm.host_object_id', 'ho.object_id']; + } elseif ($name === 'servicegroup') { + $this->requireVirtualTable('members'); + + $query->joinVirtualTable('services'); + + return ['s.host_object_id', 'ho.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupsummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupsummaryQuery.php new file mode 100644 index 0000000..a1b7182 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupsummaryQuery.php @@ -0,0 +1,142 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Zend_Db_Expr; +use Zend_Db_Select; + +use Icinga\Data\Filter\Filter; + +/** + * Query for host group summary + */ +class HostgroupsummaryQuery extends IdoQuery +{ + protected $allowCustomVars = true; + + protected $columnMap = array( + 'hostgroupsummary' => array( + 'hostgroup_alias' => 'hostgroup_alias', + 'hostgroup_name' => 'hostgroup_name', + 'hosts_down' => 'SUM(CASE WHEN host_state = 1 THEN 1 ELSE 0 END)', + 'hosts_down_handled' => 'SUM(CASE WHEN host_state = 1 AND host_handled = 1 THEN 1 ELSE 0 END)', + 'hosts_down_unhandled' => 'SUM(CASE WHEN host_state = 1 AND host_handled = 0 THEN 1 ELSE 0 END)', + 'hosts_pending' => 'SUM(CASE WHEN host_state = 99 THEN 1 ELSE 0 END)', + 'hosts_severity' => 'MAX(host_severity)', + 'hosts_total' => 'SUM(CASE WHEN host_state IS NOT NULL THEN 1 ELSE 0 END)', + 'hosts_unreachable' => 'SUM(CASE WHEN host_state = 2 THEN 1 ELSE 0 END)', + 'hosts_unreachable_handled' => 'SUM(CASE WHEN host_state = 2 AND host_handled = 1 THEN 1 ELSE 0 END)', + 'hosts_unreachable_unhandled' => 'SUM(CASE WHEN host_state = 2 AND host_handled = 0 THEN 1 ELSE 0 END)', + 'hosts_up' => 'SUM(CASE WHEN host_state = 0 THEN 1 ELSE 0 END)', + 'services_critical' => 'SUM(CASE WHEN service_state = 2 THEN 1 ELSE 0 END)', + 'services_critical_handled' => 'SUM(CASE WHEN service_state = 2 AND service_handled = 1 THEN 1 ELSE 0 END)', + 'services_critical_unhandled' => 'SUM(CASE WHEN service_state = 2 AND service_handled = 0 THEN 1 ELSE 0 END)', + 'services_ok' => 'SUM(CASE WHEN service_state = 0 THEN 1 ELSE 0 END)', + 'services_pending' => 'SUM(CASE WHEN service_state = 99 THEN 1 ELSE 0 END)', + 'services_total' => 'SUM(CASE WHEN service_state IS NOT NULL THEN 1 ELSE 0 END)', + 'services_unknown' => 'SUM(CASE WHEN service_state = 3 THEN 1 ELSE 0 END)', + 'services_unknown_handled' => 'SUM(CASE WHEN service_state = 3 AND service_handled = 1 THEN 1 ELSE 0 END)', + 'services_unknown_unhandled' => 'SUM(CASE WHEN service_state = 3 AND service_handled = 0 THEN 1 ELSE 0 END)', + 'services_warning' => 'SUM(CASE WHEN service_state = 1 THEN 1 ELSE 0 END)', + 'services_warning_handled' => 'SUM(CASE WHEN service_state = 1 AND service_handled = 1 THEN 1 ELSE 0 END)', + 'services_warning_unhandled' => 'SUM(CASE WHEN service_state = 1 AND service_handled = 0 THEN 1 ELSE 0 END)', + ) + ); + + /** + * The union + * + * @var Zend_Db_Select + */ + protected $summaryQuery; + + /** + * Subqueries used for the summary query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * Count query + * + * @var IdoQuery + */ + protected $countQuery; + + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + $this->countQuery->applyFilter(clone $filter); + return $this; + } + + protected function joinBaseTables() + { + $this->countQuery = $this->createSubQuery( + 'Hostgroup', + array() + ); + $hosts = $this->createSubQuery( + 'Hostgroup', + array( + 'hostgroup_alias', + 'hostgroup_name', + 'host_handled', + 'host_severity', + 'host_state', + 'service_handled' => new Zend_Db_Expr('NULL'), + 'service_severity' => new Zend_Db_Expr('0'), + 'service_state' => new Zend_Db_Expr('NULL'), + ) + ); + $this->subQueries[] = $hosts; + $services = $this->createSubQuery( + 'Hostgroup', + array( + 'hostgroup_alias', + 'hostgroup_name', + 'host_handled' => new Zend_Db_Expr('NULL'), + 'host_severity' => new Zend_Db_Expr('0'), + 'host_state' => new Zend_Db_Expr('NULL'), + 'service_handled', + 'service_severity', + 'service_state' + ) + ); + $this->subQueries[] = $services; + $emptyGroups = $this->createSubQuery( + 'Emptyhostgroup', + [ + 'hostgroup_alias', + 'hostgroup_name', + 'host_handled' => new Zend_Db_Expr('NULL'), + 'host_severity' => new Zend_Db_Expr('0'), + 'host_state' => new Zend_Db_Expr('NULL'), + 'service_handled' => new Zend_Db_Expr('NULL'), + 'service_severity' => new Zend_Db_Expr('0'), + 'service_state' => new Zend_Db_Expr('NULL'), + ] + ); + $this->subQueries[] = $emptyGroups; + $this->summaryQuery = $this->db->select()->union( + [$hosts, $services, $emptyGroups], + Zend_Db_Select::SQL_UNION_ALL + ); + $this->select->from(array('hostgroupsummary' => $this->summaryQuery), array()); + $this->group(array('hostgroup_name', 'hostgroup_alias')); + $this->joinedVirtualTables['hostgroupsummary'] = true; + } + + public function getCountQuery() + { + $count = $this->countQuery->select(); + $this->countQuery->applyFilterSql($count); + $count->columns(array('hgo.object_id')); + $count->group(array('hgo.object_id')); + return $this->db->select()->from($count, array('cnt' => 'COUNT(*)')); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostnotificationQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostnotificationQuery.php new file mode 100644 index 0000000..f252a7e --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostnotificationQuery.php @@ -0,0 +1,284 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; + +/** + * Query for host notifications + */ +class HostnotificationQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'contactnotifications' => array( + 'notification_contact_name' => 'co.name1' + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci', + 'host_alias' => 'h.alias COLLATE latin1_general_ci', + ), + 'history' => array( + 'output' => null, + 'state' => 'hn.state', + 'timestamp' => 'UNIX_TIMESTAMP(hn.start_time)', + 'type' => ' + CASE hn.notification_reason + WHEN 1 THEN \'notification_ack\' + WHEN 2 THEN \'notification_flapping\' + WHEN 3 THEN \'notification_flapping_end\' + WHEN 5 THEN \'notification_dt_start\' + WHEN 6 THEN \'notification_dt_end\' + WHEN 7 THEN \'notification_dt_end\' + WHEN 8 THEN \'notification_custom\' + ELSE \'notification_state\' + END', + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'notifications' => array( + 'id' => 'hn.notification_id', + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1', + 'notification_output' => 'hn.output', + 'notification_reason' => 'hn.notification_reason', + 'notification_state' => 'hn.state', + 'notification_timestamp' => 'UNIX_TIMESTAMP(hn.start_time)', + 'object_type' => '(\'host\')' + ), + 'servicegroups' => array( + 'servicegroup_name' => 'sgo.name1', + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ) + ); + + protected function requireFilterColumns(Filter $filter) + { + if ($filter instanceof FilterExpression) { + switch ($filter->getColumn()) { + case 'output': + $this->requireColumn('output'); + $filter->setColumn('hn.output'); + return null; + case 'timestamp': + case 'notification_timestamp': + $this->requireColumn($filter->getColumn()); + $filter->setColumn('hn.start_time'); + $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression()))); + return null; + } + } + + return parent::requireFilterColumns($filter); + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $concattedContacts = null; + switch ($this->ds->getDbType()) { + case 'mysql': + $concattedContacts = "GROUP_CONCAT(" + . "DISTINCT co.name1 ORDER BY co.name1 SEPARATOR ', '" + . ") COLLATE latin1_general_ci"; + break; + case 'pgsql': + // TODO: Find a way to order the contact alias list: + $concattedContacts = "ARRAY_TO_STRING(ARRAY_AGG(DISTINCT co.name1), ', ')"; + break; + } + $this->columnMap['history']['output'] = "('[' || $concattedContacts || '] ' || hn.output)"; + + $this->select->from( + array('hn' => $this->prefix . 'notifications'), + array() + )->join( + array('ho' => $this->prefix . 'objects'), + 'ho.object_id = hn.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + ); + $this->joinedVirtualTables['notifications'] = true; + } + + /** + * Join virtual table history + */ + protected function joinHistory() + { + $this->requireVirtualTable('contactnotifications'); + } + + /** + * Join contact notifications + */ + protected function joinContactnotifications() + { + $this->select->joinLeft( + array('cn' => $this->prefix . 'contactnotifications'), + 'cn.notification_id = hn.notification_id', + array() + ); + $this->select->joinLeft( + array('co' => $this->prefix . 'objects'), + 'co.object_id = cn.contact_object_id AND co.is_active = 1 AND co.objecttype_id = 10', + array() + ); + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = ho.object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->joinLeft( + array('s' => $this->prefix . 'services'), + 's.host_object_id = ho.object_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = hn.instance_id', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + + if ($this->hasJoinedVirtualTable('history') + || $this->hasJoinedVirtualTable('services') + || $this->hasJoinedVirtualTable('hostgroups') + ) { + $group = array('hn.notification_id', 'ho.object_id'); + if ($this->hasJoinedVirtualTable('contactnotifications') && !$this->hasJoinedVirtualTable('history')) { + $group[] = 'co.object_id'; + } + } elseif ($this->hasJoinedVirtualTable('contactnotifications')) { + $group = array('hn.notification_id', 'co.object_id', 'ho.object_id'); + } + + if (! empty($group)) { + if ($this->hasJoinedVirtualTable('hosts')) { + $group[] = 'h.host_id'; + } + + if ($this->hasJoinedVirtualTable('instances')) { + $group[] = 'i.instance_id'; + } + } + + return $group; + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $query->joinVirtualTable('members'); + + return ['hgm.host_object_id', 'ho.object_id']; + } elseif ($name === 'servicegroup') { + $query->joinVirtualTable('services'); + + return ['s.host_object_id', 'ho.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatehistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatehistoryQuery.php new file mode 100644 index 0000000..ac85c1f --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatehistoryQuery.php @@ -0,0 +1,222 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; + +/** + * Query for host state history records + */ +class HoststatehistoryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + /** + * {@inheritdoc} + */ + protected $groupBase = array('statehistory' => array('hh.statehistory_id', 'ho.object_id')); + + /** + * {@inheritdoc} + */ + protected $groupOrigin = array('hostgroups', 'services'); + + /** + * Array to map type names to type ids for query optimization + * + * @var array + */ + protected $types = array( + 'soft_state' => 0, + 'hard_state' => 1 + ); + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ), + 'statehistory' => array( + 'id' => 'hh.statehistory_id', + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1', + 'object_id' => 'hh.object_id', + 'object_type' => '(\'host\')', + 'output' => '(CASE WHEN hh.state_type = 1 THEN hh.output ELSE \'[ \' || hh.current_check_attempt || \'/\' || hh.max_check_attempts || \' ] \' || hh.output END)', + 'state' => 'hh.state', + 'timestamp' => 'UNIX_TIMESTAMP(hh.state_time)', + 'type' => "(CASE WHEN hh.state_type = 1 THEN 'hard_state' ELSE 'soft_state' END)" + ), + ); + + protected function requireFilterColumns(Filter $filter) + { + if ($filter instanceof FilterExpression) { + switch ($filter->getColumn()) { + case 'timestamp': + $this->requireColumn('timestamp'); + $filter->setColumn('hh.state_time'); + $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression()))); + return null; + case 'type': + if (! is_array($filter->getExpression())) { + $this->requireColumn('type'); + $filter->setColumn('hh.state_type'); + if (isset($this->types[$filter->getExpression()])) { + $filter->setExpression($this->types[$filter->getExpression()]); + } else { + $filter->setExpression(-1); + } + + return null; + } + } + } + + return parent::requireFilterColumns($filter); + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('hh' => $this->prefix . 'statehistory'), + array() + )->join( + array('ho' => $this->prefix . 'objects'), + 'ho.object_id = hh.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + ); + $this->joinedVirtualTables['statehistory'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = ho.object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = hh.instance_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->joinLeft( + array('s' => $this->prefix . 'services'), + 's.host_object_id = ho.object_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $query->joinVirtualTable('members'); + + return ['hgm.host_object_id', 'ho.object_id']; + } elseif ($name === 'servicegroup') { + $query->joinVirtualTable('services'); + + return ['s.host_object_id', 'ho.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php new file mode 100644 index 0000000..e1b5480 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php @@ -0,0 +1,338 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +class HoststatusQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + /** + * {@inheritdoc} + */ + protected $groupBase = array('hosts' => array('ho.object_id', 'h.host_id')); + + /** + * {@inheritdoc} + */ + protected $groupOrigin = array('hostgroups', 'servicegroups', 'services'); + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'checktimeperiods' => array( + 'host_check_timeperiod' => 'ctp.alias COLLATE latin1_general_ci' + ), + 'contacts' => [ + 'host_contact' => 'hco.name1' + ], + 'contactgroups' => [ + 'host_contactgroup' => 'hcgo.name1' + ], + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_action_url' => 'h.action_url', + 'host_address' => 'h.address', + 'host_address6' => 'h.address6', + 'host_alias' => 'h.alias', + 'host_check_interval' => '(h.check_interval * 60)', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci', + 'host_icon_image' => 'h.icon_image', + 'host_icon_image_alt' => 'h.icon_image_alt', + 'host_ipv4' => 'INET_ATON(h.address)', + 'host_name' => 'ho.name1', + 'host_notes' => 'h.notes', + 'host_notes_url' => 'h.notes_url', + 'object_type' => '(\'host\')', + 'object_id' => 'ho.object_id' + ), + 'hoststatus' => array( + 'host_acknowledged' => 'hs.problem_has_been_acknowledged', + 'host_acknowledgement_type' => 'hs.acknowledgement_type', + 'host_active_checks_enabled' => 'hs.active_checks_enabled', + 'host_active_checks_enabled_changed' => 'CASE WHEN hs.active_checks_enabled = h.active_checks_enabled THEN 0 ELSE 1 END', + 'host_attempt' => 'hs.current_check_attempt || \'/\' || hs.max_check_attempts', + 'host_check_command' => 'hs.check_command', + 'host_check_execution_time' => 'hs.execution_time', + 'host_check_latency' => 'hs.latency', + 'host_check_source' => 'hs.check_source', + 'host_check_type' => 'hs.check_type', + 'host_current_check_attempt' => 'hs.current_check_attempt', + 'host_current_notification_number' => 'hs.current_notification_number', + 'host_event_handler' => 'hs.event_handler', + 'host_event_handler_enabled' => 'hs.event_handler_enabled', + 'host_event_handler_enabled_changed' => 'CASE WHEN hs.event_handler_enabled = h.event_handler_enabled THEN 0 ELSE 1 END', + 'host_failure_prediction_enabled' => 'hs.failure_prediction_enabled', + 'host_flap_detection_enabled' => 'hs.flap_detection_enabled', + 'host_flap_detection_enabled_changed' => 'CASE WHEN hs.flap_detection_enabled = h.flap_detection_enabled THEN 0 ELSE 1 END', + 'host_handled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END', + 'host_hard_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE CASE WHEN hs.state_type = 1 THEN hs.current_state ELSE hs.last_hard_state END END', + 'host_in_downtime' => 'CASE WHEN (hs.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END', + 'host_is_flapping' => 'hs.is_flapping', + 'host_is_passive_checked' => 'CASE WHEN hs.active_checks_enabled = 0 AND hs.passive_checks_enabled = 1 THEN 1 ELSE 0 END', + 'host_is_reachable' => 'hs.is_reachable', + 'host_last_check' => 'UNIX_TIMESTAMP(hs.last_check)', + 'host_last_hard_state' => 'hs.last_hard_state', + 'host_last_hard_state_change' => 'UNIX_TIMESTAMP(hs.last_hard_state_change)', + 'host_last_notification' => 'UNIX_TIMESTAMP(hs.last_notification)', + 'host_last_state_change' => 'UNIX_TIMESTAMP(hs.last_state_change)', + 'host_last_state_change_ts' => 'hs.last_state_change', + 'host_last_time_down' => 'UNIX_TIMESTAMP(hs.last_time_down)', + 'host_last_time_unreachable' => 'UNIX_TIMESTAMP(hs.last_time_unreachable)', + 'host_last_time_up' => 'UNIX_TIMESTAMP(hs.last_time_up)', + 'host_long_output' => 'hs.long_output', + 'host_max_check_attempts' => 'hs.max_check_attempts', + 'host_modified_host_attributes' => 'hs.modified_host_attributes', + 'host_next_check' => 'CASE hs.should_be_scheduled WHEN 1 THEN UNIX_TIMESTAMP(hs.next_check) ELSE NULL END', + 'host_next_notification' => 'UNIX_TIMESTAMP(hs.next_notification)', + 'host_next_update' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL + THEN + CASE hs.should_be_scheduled WHEN 1 THEN UNIX_TIMESTAMP(hs.next_check) + (hs.normal_check_interval * 60) ELSE NULL END + ELSE + UNIX_TIMESTAMP(hs.next_check) + + (CASE WHEN + COALESCE(hs.current_state, 0) > 0 AND hs.state_type = 0 + THEN + hs.retry_check_interval + ELSE + hs.normal_check_interval + END * 60) + + (CEIL(hs.execution_time + hs.latency) * 2) + END', + 'host_no_more_notifications' => 'hs.no_more_notifications', + 'host_normal_check_interval' => 'hs.normal_check_interval', + 'host_notifications_enabled' => 'hs.notifications_enabled', + 'host_notifications_enabled_changed' => 'CASE WHEN hs.notifications_enabled = h.notifications_enabled THEN 0 ELSE 1 END', + 'host_obsessing' => 'hs.obsess_over_host', + 'host_obsessing_changed' => 'CASE WHEN hs.obsess_over_host = h.obsess_over_host THEN 0 ELSE 1 END', + 'host_output' => 'hs.output', + 'host_passive_checks_enabled' => 'hs.passive_checks_enabled', + 'host_passive_checks_enabled_changed' => 'CASE WHEN hs.passive_checks_enabled = h.passive_checks_enabled THEN 0 ELSE 1 END', + 'host_percent_state_change' => 'hs.percent_state_change', + 'host_perfdata' => 'hs.perfdata', + 'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END', + 'host_problem_has_been_acknowledged' => 'hs.problem_has_been_acknowledged', + 'host_process_performance_data' => 'hs.process_performance_data', + 'host_retry_check_interval' => 'hs.retry_check_interval', + 'host_scheduled_downtime_depth' => 'hs.scheduled_downtime_depth', + 'host_severity' => ' + CASE + WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL + THEN 16 + ELSE + CASE + WHEN hs.current_state = 0 + THEN 1 + ELSE + CASE + WHEN hs.current_state = 1 THEN 64 + WHEN hs.current_state = 2 THEN 32 + ELSE 256 + END + + + CASE + WHEN hs.problem_has_been_acknowledged = 1 THEN 2 + WHEN hs.scheduled_downtime_depth > 0 THEN 1 + ELSE 256 + END + END + 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_state_type' => 'hs.state_type', + 'host_status_update_time' => 'hs.status_update_time', + 'host_unhandled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) = 0 THEN 1 ELSE 0 END', + 'problems' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END' + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + ) + ); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + if (version_compare($this->getIdoVersion(), '1.10.0', '<')) { + $this->columnMap['hoststatus']['host_check_source'] = '(NULL)'; + } + if (version_compare($this->getIdoVersion(), '1.13.0', '<')) { + $this->columnMap['hoststatus']['host_is_reachable'] = '(NULL)'; + } + + $this->select->from( + array('ho' => $this->prefix . 'objects'), + array() + )->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = ho.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + ); + $this->joinedVirtualTables['hosts'] = true; + } + + /** + * Join check time periods + */ + protected function joinChecktimeperiods() + { + $this->select->joinLeft( + array('ctp' => $this->prefix . 'timeperiods'), + 'ctp.timeperiod_object_id = h.check_timeperiod_object_id', + array() + ); + } + + /** + * Join contacts + */ + protected function joinContacts() + { + $this->select->joinLeft( + ['hc' => 'icinga_host_contacts'], + 'hc.host_id = h.host_id', + [] + )->joinLeft( + ['hco' => 'icinga_objects'], + 'hco.object_id = hc.contact_object_id AND hco.is_active = 1 AND hco.objecttype_id = 10', + [] + ); + } + + /** + * Join contact groups + */ + protected function joinContactgroups() + { + $this->select->joinLeft( + ['hcg' => 'icinga_host_contactgroups'], + 'hcg.host_id = h.host_id', + [] + )->joinLeft( + ['hcgo' => 'icinga_objects'], + 'hcgo.object_id = hcg.contactgroup_object_id AND hcgo.is_active = 1 AND hcgo.objecttype_id = 11', + [] + ); + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = ho.object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join host status + */ + protected function joinHoststatus() + { + $this->select->join( + array('hs' => $this->prefix . 'hoststatus'), + 'hs.host_object_id = ho.object_id', + array() + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = ho.instance_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = s.service_object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sgm.servicegroup_id = sg.' . $this->servicegroup_id, + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->requireVirtualTable('hosts'); + $this->select->joinLeft( + array('s' => $this->prefix . 'services'), + 's.host_object_id = h.host_object_id', + array() + )->joinLeft( + array('so' => $this->prefix . 'objects'), + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $query->joinVirtualTable('members'); + + return ['hgm.host_object_id', 'ho.object_id']; + } elseif ($name === 'servicegroup') { + $query->joinVirtualTable('services'); + + return ['s.host_object_id', 'ho.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatussummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatussummaryQuery.php new file mode 100644 index 0000000..b4a91f3 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatussummaryQuery.php @@ -0,0 +1,91 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; + +/** + * Query for host group summaries + */ +class HoststatussummaryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'hoststatussummary' => array( + 'hosts_down' => 'SUM(CASE WHEN state = 1 THEN 1 ELSE 0 END)', + 'hosts_down_handled' => 'SUM(CASE WHEN state = 1 AND handled = 1 THEN 1 ELSE 0 END)', + 'hosts_down_unhandled' => 'SUM(CASE WHEN state = 1 AND handled = 0 THEN 1 ELSE 0 END)', + 'hosts_pending' => 'SUM(CASE WHEN state = 99 THEN 1 ELSE 0 END)', + 'hosts_total' => 'SUM(1)', + 'hosts_unreachable' => 'SUM(CASE WHEN state = 2 THEN 1 ELSE 0 END)', + 'hosts_unreachable_handled' => 'SUM(CASE WHEN state = 2 AND handled = 1 THEN 1 ELSE 0 END)', + 'hosts_unreachable_unhandled' => 'SUM(CASE WHEN state = 2 AND handled = 0 THEN 1 ELSE 0 END)', + 'hosts_up' => 'SUM(CASE WHEN state = 0 THEN 1 ELSE 0 END)' + ) + ); + + /** + * The host status sub select + * + * @var HoststatusQuery + */ + protected $subSelect; + + /** + * {@inheritdoc} + */ + public function allowsCustomVars() + { + return $this->subSelect->allowsCustomVars(); + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + $this->subSelect->applyFilter(clone $filter); + return $this; + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + // TODO(el): Allow to switch between hard and soft states + $this->subSelect = $this->createSubQuery( + 'Hoststatus', + array( + 'handled' => 'host_handled', + 'state' => 'host_state', + 'state_change' => 'host_last_state_change' + ) + ); + $this->select->from( + array('hoststatussummary' => $this->subSelect->setIsSubQuery(true)), + array() + ); + $this->joinedVirtualTables['hoststatussummary'] = true; + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->subSelect->where($condition, $value); + return $this; + } + + public function whereEx(FilterExpression $ex) + { + $this->subSelect->whereEx($ex); + + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php new file mode 100644 index 0000000..5785092 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php @@ -0,0 +1,1599 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\FilterNot; +use Zend_Db_Expr; +use Icinga\Application\Icinga; +use Icinga\Application\Hook; +use Icinga\Application\Logger; +use Icinga\Data\Db\DbQuery; +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; +use Icinga\Exception\IcingaException; +use Icinga\Exception\NotImplementedError; +use Icinga\Exception\ProgrammingError; +use Icinga\Exception\QueryException; +use Icinga\Web\Session; +use Icinga\Module\Monitoring\Data\ColumnFilterIterator; +use Zend_Db_Select; + +/** + * Base class for Ido Queries + * + * This is the base class for all Ido queries and should be extended for new queries + * The starting point for implementations is the columnMap attribute. This is an asscociative array in the + * following form: + * + * <pre> + * <code> + * array( + * 'virtualTable' => array( + * 'fieldalias1' => 'queryColumn1', + * 'fieldalias2' => 'queryColumn2', + * .... + * ), + * 'virtualTable2' => array( + * 'host' => 'host_name1' + * ) + * ) + * </code> + * </pre> + * + * This allows you to select e.g. fieldalias1, which automatically calls the query code for joining 'virtualTable'. If + * you afterwards select 'host', 'virtualTable2' will be joined. The joining logic is up to you, in order to make the + * above example work you need to implement the joinVirtualTable() method which contain your + * custom (Zend_Db) logic for joining, filtering and querying the data you want. + * + */ +abstract class IdoQuery extends DbQuery +{ + /** + * The prefix to use + * + * @var string + */ + protected $prefix; + + /** + * An array to map aliases to column names + * + * @var array + */ + protected $idxAliasColumn; + + /** + * An array to map aliases to table names + * + * @var array + */ + protected $idxAliasTable; + + /** + * An array to map custom aliases to aliases + * + * @var array + */ + protected $idxCustomAliases; + + /** + * The column map containing all filterable columns + * + * This must be overwritten by child classes, in the format + * array( + * 'virtualTable' => array( + * 'fieldalias1' => 'queryColumn1', + * 'fieldalias2' => 'queryColumn2', + * .... + * ) + * ) + * + * @var array + */ + protected $columnMap = array(); + + /** + * Custom vars available for this query + * + * @var array + */ + protected $customVars = array(); + + /** + * Printf compatible string to joins custom vars + * + * - %1$s Source field, contain the object_id + * - %2$s Alias used for the relation + * - %3$s Name of the CustomVariable + * + * @var string + */ + private $customVarsJoinTemplate = '%1$s = %2$s.object_id AND %2$s.varname = %3$s'; + + /** + * An array with all 'virtual' tables that are already joined + * + * Virtual tables are the keys of the columnMap array and require a + * join%VirtualTableName%() method to be defined in the concrete + * query + * + * @var array + */ + protected $joinedVirtualTables = array(); + + /** + * A map of virtual table names and corresponding hook instances + * + * Joins for those tables will be delegated to them + * + * @var array + */ + protected $hookedVirtualTables = array(); + + /** + * List of column aliases used for sorting the result + * + * @var array + */ + protected $orderColumns = array(); + + /** + * Table to columns map which have to be added to the GROUP BY list if the query is grouped + * + * @var array + */ + protected $groupBase = array(); + + /** + * List of table names which initiate grouping if one of them is joined + * + * @var array + */ + protected $groupOrigin = array(); + + /** + * Map of table names to query names for which to create subquery filters + * + * @var array + */ + protected $subQueryTargets = array(); + + /** + * The primary key column for the instances table + * + * @var string + */ + protected $instance_id = 'instance_id'; + + /** + * The primary key column for the objects table + * + * @var string + */ + protected $object_id = 'object_id'; + + /** + * The primary key column for the acknowledgements table + * + * @var string + */ + protected $acknowledgement_id = 'acknowledgement_id'; + + /** + * The primary key column for the commenthistory table + * + * @var string + */ + protected $commenthistory_id = 'commenthistory_id'; + + /** + * The primary key column for the contactnotifications table + * + * @var string + */ + protected $contactnotification_id = 'contactnotification_id'; + + /** + * The primary key column for the downtimehistory table + * + * @var string + */ + protected $downtimehistory_id = 'downtimehistory_id'; + + /** + * The primary key column for the flappinghistory table + * + * @var string + */ + protected $flappinghistory_id = 'flappinghistory_id'; + + /** + * The primary key column for the notifications table + * + * @var string + */ + protected $notification_id = 'notification_id'; + + /** + * The primary key column for the statehistory table + * + * @var string + */ + protected $statehistory_id = 'statehistory_id'; + + /** + * The primary key column for the comments table + * + * @var string + */ + protected $comment_id = 'comment_id'; + + /** + * The primary key column for the customvariablestatus table + * + * @var string + */ + protected $customvariablestatus_id = 'customvariablestatus_id'; + + /** + * The primary key column for the hoststatus table + * + * @var string + */ + protected $hoststatus_id = 'hoststatus_id'; + + /** + * The primary key column for the programstatus table + * + * @var string + */ + protected $programstatus_id = 'programstatus_id'; + + /** + * The primary key column for the runtimevariables table + * + * @var string + */ + protected $runtimevariable_id = 'runtimevariable_id'; + + /** + * The primary key column for the scheduleddowntime table + * + * @var string + */ + protected $scheduleddowntime_id = 'scheduleddowntime_id'; + + /** + * The primary key column for the servicestatus table + * + * @var string + */ + protected $servicestatus_id = 'servicestatus_id'; + + /** + * The primary key column for the contactstatus table + * + * @var string + */ + protected $contactstatus_id = 'contactstatus_id'; + + /** + * The primary key column for the commands table + * + * @var string + */ + protected $command_id = 'command_id'; + + /** + * The primary key column for the contactgroup_members table + * + * @var string + */ + protected $contactgroup_member_id = 'contactgroup_member_id'; + + /** + * The primary key column for the contactgroups table + * + * @var string + */ + protected $contactgroup_id = 'contactgroup_id'; + + /** + * The primary key column for the contacts table + * + * @var string + */ + protected $contact_id = 'contact_id'; + + /** + * The primary key column for the customvariables table + * + * @var string + */ + protected $customvariable_id = 'customvariable_id'; + + /** + * The primary key column for the host_contactgroups table + * + * @var string + */ + protected $host_contactgroup_id = 'host_contactgroup_id'; + + /** + * The primary key column for the host_contacts table + * + * @var string + */ + protected $host_contact_id = 'host_contact_id'; + + /** + * The primary key column for the hostgroup_members table + * + * @var string + */ + protected $hostgroup_member_id = 'hostgroup_member_id'; + + /** + * The primary key column for the hostgroups table + * + * @var string + */ + protected $hostgroup_id = 'hostgroup_id'; + + /** + * The primary key column for the hosts table + * + * @var string + */ + protected $host_id = 'host_id'; + + /** + * The primary key column for the service_contactgroup table + * + * @var string + */ + protected $service_contactgroup_id = 'service_contactgroup_id'; + + /** + * The primary key column for the service_contact table + * + * @var string + */ + protected $service_contact_id = 'service_contact_id'; + + /** + * The primary key column for the servicegroup_members table + * + * @var string + */ + protected $servicegroup_member_id = 'servicegroup_member_id'; + + /** + * The primary key column for the servicegroups table + * + * @var string + */ + protected $servicegroup_id = 'servicegroup_id'; + + /** + * The primary key column for the services table + * + * @var string + */ + protected $service_id = 'service_id'; + + /** + * The primary key column for the timeperiods table + * + * @var string + */ + protected $timeperiod_id = 'timeperiod_id'; + + /** + * An array containing Column names that cause an aggregation of the query + * + * @var array + */ + protected $aggregateColumnIdx = array(); + + /** + * True to allow customvar filters and queries + * + * @var bool + */ + protected $allowCustomVars = false; + + /** + * Current IDO version. This is bullshit and needs to be moved somewhere + * else. As someone decided that we need no Backend-specific connection + * class unfortunately there is no better place right now. And as of the + * 'check_source' patch we need a quick fix immediately. So here you go. + * + * TODO: Fix this. + * + * @var string + */ + protected static $idoVersion; + + /** + * List of column aliases mapped to their table where the COLLATE SQL-instruction has been removed + * + * This list is being populated in case of a PostgreSQL backend only, + * to ensure case-insensitive string comparison in WHERE clauses. + * + * @var array + */ + protected $caseInsensitiveColumns; + + /** + * Return true when the column is an aggregate column + * + * @param String $column The column to test + * @return bool True when the column is an aggregate column + */ + public function isAggregateColumn($column) + { + return array_key_exists($column, $this->aggregateColumnIdx); + } + + /** + * Order the result by the given alias + * + * @param string $alias The column alias to order by + * @param int $dir The sort direction or null to use the default direction + * + * @return $this + */ + public function order($alias, $dir = null) + { + $this->requireColumn($alias); + + if ($this->isCustomVar($alias)) { + $column = $this->getCustomvarColumnName($alias); + } elseif ($this->hasAliasName($alias)) { + $column = $this->aliasToColumnName($alias); + $table = $this->aliasToTableName($alias); + if (isset($this->caseInsensitiveColumns[$table][$alias])) { + $column = 'LOWER(' . $column . ')'; + } + } else { + Logger::info('Can\'t order by column ' . $alias); + return $this; + } + + $this->orderColumns[] = $alias; + return parent::order($column, $dir); + } + + /** + * Return true when the given field can be used for filtering + * + * @param String $field The field to test + * @return bool True when the field can be used for querying, otherwise false + */ + public function isValidFilterTarget($field) + { + return $this->getMappedField($field) !== null; + } + + /** + * Return the resolved field for an alias + * + * @param String $field The alias to resolve + * @return String The resolved alias or null if unknown + */ + public function getMappedField($field) + { + foreach ($this->columnMap as $columnSource => $columnSet) { + if (isset($columnSet[$field])) { + return $columnSet[$field]; + } + } + if ($this->isCustomVar($field)) { + return $this->getCustomvarColumnName($field); + } + return null; + } + + public function distinct() + { + $this->select->distinct(); + return $this; + } + + /** + * Prepare the given query so that it can be linked to the parent + * + * @param IdoQuery $query + * @param string $name + * @param FilterExpression $filter The filter which initiated the sub query + * @param bool $and Whether it's an AND filter + * @param bool $negate Whether it's an != filter + * @param FilterExpression $additionalFilter Filters which should be applied to the "parent" query + * + * @return array The first value is their, the second our key column + * + * @throws NotImplementedError In case the given query is unknown + */ + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + throw new NotImplementedError('Query "%s" is unknown', $name); + } + + /** + * Create and return a sub-query filter for the given filter expression + * + * @param FilterExpression $filter + * @param string $queryName + * + * @return Filter + * + * @throws QueryException + */ + protected function createSubQueryFilter(FilterExpression $filter, $queryName) + { + $expr = $filter->getExpression(); + $op = $filter->getSign(); + + if ($op === '=' && ! is_array($expr) && $op !== '!=') { + // We're joining a subquery only if the filter is enclosed in parentheses or if it's a != filter, + // e.g. hostgroup_name=(linux...), hostgroup_name!=linux, hostgroup_name!=(linux...) + throw new NotImplementedError(''); + } + + $subQuery = $this->createSubQuery($queryName); + $subQuery->setIsSubQuery(); + + $subQueryFilter = clone $filter; + + if ($op === '!=') { + $negate = true; + if (! is_array($expr)) { + // We assume that expression is an array later on but we'll support subquery joins for != filters + // which are not enclosed in parentheses + $expr = [$expr]; + } + } else { + $negate = false; + } + + if (count($expr) === 1 && strpos($expr[0], '&') !== false) { + // Our current filter implementation does not specify & as a control character so the count of the + // expression array is always one in this case + $expr = array_unique(explode('&', $expr[0])); + $subQueryFilter->setExpression($expr); + $and = true; + } else { + // Or filters are respected by our filter implementation. No special handling needed here + $and = false; + } + + $alias = $filter->getColumn(); + $column = $subQuery->aliasToColumnName($alias); + if (isset($this->caseInsensitiveColumns[$subQuery->aliasToTableName($alias)][$alias])) { + $column = 'LOWER( ' . $column . ' )'; + $subQueryFilter->setColumn($column); + $subQueryFilter->setExpression(array_map('strtolower', (array) $subQueryFilter->getExpression())); + } else { + $subQueryFilter->setColumn($column); + } + + $additional = null; + + list($theirs, $ours) = $this->joinSubQuery($subQuery, $queryName, $subQueryFilter, $and, $negate, $additional); + + $zendSelect = $subQuery->select(); + $fromPart = $zendSelect->getPart($zendSelect::FROM); + $zendSelect->reset($zendSelect::FROM); + + foreach ($fromPart as $correlationName => $joinOptions) { + if (isset($joinOptions['joinCondition'])) { + $joinOptions['joinCondition'] = preg_replace( + '/(?<=^|\s)\w+(?=\.)/', + 'sub_$0', + $joinOptions['joinCondition'] + ); + } + + $name = ['sub_' . $correlationName => $joinOptions['tableName']]; + switch ($joinOptions['joinType']) { + case $zendSelect::FROM: + $zendSelect->from($name); + break; + case $zendSelect::INNER_JOIN: + $zendSelect->joinInner($name, $joinOptions['joinCondition'], null); + break; + case $zendSelect::LEFT_JOIN: + $zendSelect->joinLeft($name, $joinOptions['joinCondition'], null); + break; + default: + // TODO: Add support for other join types if required? + throw new QueryException( + 'Unsupported join type %s. Cannot create subquery filter.', + $joinOptions['joinType'] + ); + } + } + + if ($and || $negate) { + // Having is only required for AND and != filters, + // e.g. hostgroup_name=(ping&linux), hostgroup_name!=ping, hostgroup_name!=(ping|linux) + $groups = $subQuery->getGroup(); + if (! empty($groups)) { + $group = $groups[0]; + $group = preg_replace('/(?<=^|\s)\w+(?=\.)/', 'sub_$0', $group); + + $cnt = count($expr); + + $subQuery->select()->having("COUNT(DISTINCT $group) >= $cnt"); + } + } + + $subQueryFilter->setColumn(preg_replace( + '/(?<=^|\s)\w+(?=\.)/', + 'sub_$0', + $column + )); + + if ($negate) { + // != will be NOT EXISTS later + $subQueryFilter = $subQueryFilter->setSign('='); + } + + $subQueryFilter = $subQueryFilter->andFilter(Filter::where( + preg_replace('/(?<=^|\s)\w+(?=\.)/', 'sub_$0', $theirs), + new Zend_Db_Expr($ours) + )); + + $subQuery + ->setFilter($subQueryFilter) + ->clearGroupingRules() + ->select() + ->reset('columns') + ->columns([new Zend_Db_Expr('1')]); + + // EXISTS is the column name because without any column $this->isCustomVar() fails badly otherwise. + // Additionally it bypasses the non-required optimizations made by our filter rendering implementation. + $exists = new FilterExpression($negate ? 'NOT EXISTS' : 'EXISTS', '', new Zend_Db_Expr($subQuery)); + + if ($additional !== null) { + return Filter::matchAll($exists, $additional); + } + + return $exists; + } + + protected function requireFilterColumns(Filter $filter) + { + if ($filter instanceof FilterExpression) { + $alias = $filter->getColumn(); + + $virtualTable = $this->aliasToTableName($alias); + if (isset($this->subQueryTargets[$virtualTable])) { + try { + return $this->createSubQueryFilter($filter, $this->subQueryTargets[$virtualTable]); + } catch (NotImplementedError $e) { + // We don't want to create subquery filters in all cases + } + } + + $this->requireColumn($alias); + + if ($this->isCustomVar($alias)) { + $column = $this->getCustomvarColumnName($alias); + } else { + $column = $this->aliasToColumnName($alias); + if (isset($this->caseInsensitiveColumns[$this->aliasToTableName($alias)][$alias])) { + $column = 'LOWER(' . $column . ')'; + $expression = $filter->getExpression(); + if (is_array($expression)) { + $filter->setExpression(array_map('strtolower', $expression)); + } else { + $filter->setExpression(strtolower($expression)); + } + } + } + + $filter->setColumn($column); + } else { + if (! $filter instanceof FilterNot) { + // Allow subquery filters in a filter chain + $columns = $filter->listFilteredColumns(); + if (count($columns) === 1) { + $column = $columns[0]; + $virtualTable = $this->aliasToTableName($column); + if (isset($this->subQueryTargets[$virtualTable])) { + $lastSign = null; + $filters = []; + $expressions = []; + foreach ($filter->filters() as $child) { + switch (true) { + case $child instanceof FilterExpression: + $expression = $child->getExpression(); + if (! is_array($expression)) { + break; + } + // Move to default + default: + $filters[] = $child; + continue 2; + } + if ($lastSign === null) { + $lastSign = $child->getSign(); + } else { + $sign = $child->getSign(); + if ($sign !== $lastSign) { + $filters[] = new FilterExpression( + $column, + $lastSign, + $filter->getOperatorSymbol() === '&' + ? [implode('&', $expressions)] + : $expressions + ); + $expressions = []; + $lastSign = $sign; + } + } + $expressions[] = $expression; + } + if (! empty($expressions)) { + $filters[] = new FilterExpression( + $column, + $lastSign, + $filter->getOperatorSymbol() === '&' + ? [implode('&', $expressions)] + : $expressions + ); + } + $filter->setFilters($filters); + } + } + } + + foreach ($filter->filters() as $child) { + $replacement = $this->requireFilterColumns($child); + if ($replacement !== null) { + // setId($child->getId()) is performed because replaceById() doesn't already do it + $filter->replaceById($child->getId(), $replacement->setId($child->getId())); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + $filter = clone $filter; + return parent::addFilter($this->requireFilterColumns($filter) ?: $filter); + } + + public function where($condition, $value = null) + { + $this->requireColumn($condition); + $col = $this->getMappedField($condition); + if ($col === null) { + throw new IcingaException( + 'No such field: %s', + $condition + ); + } + return parent::where($col, $value); + } + + /** + * Add a filter expression, with as less validation as possible + * + * @param FilterExpression $ex + * + * @internal If you use this outside the monitoring module, it's your fault if something breaks + * @return $this + */ + public function whereEx(FilterExpression $ex) + { + $this->requireColumn($ex->getColumn()); + $col = $this->getMappedField($ex->getColumn()); + if ($col === null) { + throw new IcingaException( + 'No such field: %s', + $ex->getColumn() + ); + } + + parent::addFilter((clone $ex)->setColumn($col)); + + return $this; + } + + /** + * Return true if an field contains an explicit timestamp + * + * @param string $field The field to test for containing an timestamp + * + * @return bool True when the field represents an timestamp + */ + public function isTimestamp($field) + { + if ($this->isCustomVar($field)) { + return false; + } + + return stripos($this->getMappedField($field) ?: $field, 'UNIX_TIMESTAMP') !== false; + } + + /** + * Return whether the given alias provides case insensitive value comparison + * + * @param string $alias + * + * @return bool + */ + public function isCaseInsensitive($alias) + { + if ($this->isCustomVar($alias)) { + return false; + } + + $column = $this->getMappedField($alias); + if (! $column) { + return false; + } + + if (empty($this->caseInsensitiveColumns)) { + return preg_match('/ COLLATE .+$/', $column) === 1; + } + + if (strpos($column, 'LOWER') === 0) { + return true; + } + + $table = $this->aliasToTableName($alias); + if (! $table) { + return false; + } + + return isset($this->caseInsensitiveColumns[$table][$alias]); + } + + /** + * Return our column map + * + * Might be useful for hooks + * + * @return array + */ + public function getColumnMap() + { + return $this->columnMap; + } + + /** + * Apply oracle specific query initialization + */ + private function initializeForOracle() + { + // Oracle uses the reserved field 'id' for primary keys, so + // these must be used instead of the normally defined ids + $this->object_id = $this->host_id = $this->service_id + = $this->hostgroup_id = $this->servicegroup_id + = $this->contact_id = $this->contactgroup_id = 'id'; + $this->customVarsJoinTemplate = + '%1$s = %2$s.object_id AND LOWER(%2$s.varname) = %3$s'; + foreach ($this->columnMap as &$columns) { + foreach ($columns as &$value) { + $value = preg_replace('/UNIX_TIMESTAMP/', 'localts2unixts', $value); + $value = preg_replace('/ COLLATE .+$/', '', $value); + } + } + } + + /** + * Apply PostgreSQL specific query initialization + */ + private function initializeForPostgres() + { + $this->customVarsJoinTemplate = + '%1$s = %2$s.object_id AND LOWER(%2$s.varname) = %3$s'; + foreach ($this->columnMap as $table => & $columns) { + foreach ($columns as $alias => & $column) { + if ($column === null) { + continue; + } + + // Using a regex here because COLLATE may occur anywhere in the string + $column = preg_replace('/ COLLATE .+$/', '', $column, -1, $count); + if ($count > 0) { + $this->caseInsensitiveColumns[$table][$alias] = true; + } + + $column = preg_replace( + '/inet_aton\(([[:word:].]+)\)/i', + '(CASE WHEN $1 ~ \'(?:[0-9]{1,3}\\\\.){3}[0-9]{1,3}\' THEN $1::inet - \'0.0.0.0\' ELSE NULL END)', + $column + ); + if (version_compare($this->getIdoVersion(), '1.14.2', '>=')) { + $column = str_replace('NOW()', 'NOW() AT TIME ZONE \'UTC\'', $column); + } else { + $column = preg_replace( + '/UNIX_TIMESTAMP(\((?>[^()]|(?-1))*\))/i', + 'CASE WHEN ($1 < \'1970-01-03 00:00:00+00\'::timestamp with time zone) THEN 0 ELSE UNIX_TIMESTAMP($1) END', + $column + ); + } + } + } + } + + /** + * Set up this query and join the initial tables + * + * @see IdoQuery::initializeForPostgres For postgresql specific setup + */ + protected function init() + { + parent::init(); + $this->prefix = $this->ds->getTablePrefix(); + + foreach (Hook::all('monitoring/idoQueryExtension') as $hook) { + $extensions = $hook->extendColumnMap($this); + if (! is_array($extensions)) { + continue; + } + + foreach ($extensions as $vTable => $cols) { + if (! array_key_exists($vTable, $this->columnMap)) { + $this->hookedVirtualTables[$vTable] = $hook; + $this->columnMap[$vTable] = array(); + } + + foreach ($cols as $k => $v) { + $this->columnMap[$vTable][$k] = $v; + } + } + } + + $dbType = $this->ds->getDbType(); + if ($dbType === 'oracle') { + $this->initializeForOracle(); + } elseif ($dbType === 'pgsql') { + $this->initializeForPostgres(); + } else { + $charset = $this->ds->getConfig()->get('charset') ?: 'latin1'; + $this->customVarsJoinTemplate .= " COLLATE {$charset}_general_ci"; + } + $this->joinBaseTables(); + $this->select->columns($this->columns); + $this->prepareAliasIndexes(); + } + + /** + * Join the base tables for this query + */ + protected function joinBaseTables() + { + reset($this->columnMap); + $table = key($this->columnMap); + + $this->select->from( + array($table => $this->prefix . $table), + array() + ); + + $this->joinedVirtualTables = array($table => true); + } + + /** + * Populates the idxAliasTAble and idxAliasColumn properties + */ + protected function prepareAliasIndexes() + { + foreach ($this->columnMap as $tbl => & $cols) { + foreach ($cols as $alias => $col) { + $this->idxAliasTable[$alias] = $tbl; + $this->idxAliasColumn[$alias] = preg_replace('~\n\s*~', ' ', $col); + } + } + } + + /** + * Resolve columns aliases to their database field using the columnMap + * + * @param array $columns + * + * @return array + */ + public function resolveColumns($columns) + { + $resolvedColumns = array(); + + foreach ($columns as $alias => $col) { + if ($col instanceof Zend_Db_Expr) { + // Support selecting NULL as column for example + $resolvedColumns[$alias] = $col; + continue; + } + $this->requireColumn($col); + if ($this->isCustomVar($col)) { + $name = $this->getCustomvarColumnName($col); + } else { + $name = $this->aliasToColumnName($col); + } + if (is_int($alias)) { + $alias = $col; + } else { + $this->idxCustomAliases[$alias] = $col; + } + + $resolvedColumns[$alias] = preg_replace('|\n|', ' ', $name); + } + + return $resolvedColumns; + } + + /** + * Return all columns that will be selected when no columns are given in the constructor or from + * + * @return array An array of column aliases + */ + public function getDefaultColumns() + { + reset($this->columnMap); + $table = key($this->columnMap); + return array_keys($this->columnMap[$table]); + } + + /** + * Modify the query to the given alias can be used in the result set or queries + * + * This calls requireVirtualTable if needed + * + * @param string $alias The alias of the column to require + * + * @return $this Fluent interface + * @see IdoQuery::requireVirtualTable The method initializing required joins + * @throws \Icinga\Exception\ProgrammingError When an unknown column is requested + */ + public function requireColumn($alias) + { + if ($this->hasAliasName($alias)) { + $this->requireVirtualTable($this->aliasToTableName($alias)); + } elseif ($this->isCustomVar($alias)) { + $this->requireCustomvar($alias); + } else { + throw new ProgrammingError( + '%s : Got invalid column: %s', + get_called_class(), + $alias + ); + } + return $this; + } + + /** + * Return true if the given alias exists + * + * @param String $alias The alias to test for + * @return bool True when the alias exists, otherwise false + */ + protected function hasAliasName($alias) + { + return array_key_exists($alias, $this->idxAliasColumn); + } + + /** + * Require a virtual table for the given table name if not already required + * + * @param String $name The table name to require + * @return $this Fluent interface + */ + protected function requireVirtualTable($name) + { + if ($this->hasJoinedVirtualTable($name)) { + return $this; + } + + if ($this->virtualTableIsHooked($name)) { + return $this->joinHookedVirtualTable($name); + } else { + return $this->joinVirtualTable($name); + } + } + + /** + * Whether a given virtual table name has been provided by a hook + * + * @param string $name Virtual table name + * + * @return boolean + */ + protected function virtualTableIsHooked($name) + { + return array_key_exists($name, $this->hookedVirtualTables); + } + + protected function conflictsWithVirtualTable($name) + { + if ($this->hasJoinedVirtualTable($name)) { + throw new ProgrammingError( + 'IDO query virtual table conflict with "%s"', + $name + ); + } + return $this; + } + + /** + * Call the method for joining a virtual table + * + * This requires a join$Table() method to exist + * + * @param String $table The table to join by calling join$Table() in the concrete implementation + * @return $this Fluent interface + * + * @throws \Icinga\Exception\ProgrammingError If the join method for this table does not exist + */ + protected function joinVirtualTable($table) + { + $func = 'join' . ucfirst($table); + if (method_exists($this, $func)) { + $this->$func(); + } else { + throw new ProgrammingError( + 'Cannot join "%s", no such table found', + $table + ); + } + $this->joinedVirtualTables[$table] = true; + return $this; + } + + /** + * Tell a hook to join a virtual table + * + * @param String $table + * @return $this + */ + protected function joinHookedVirtualTable($table) + { + $this->hookedVirtualTables[$table]->joinVirtualTable($this, $table); + $this->joinedVirtualTables[$table] = true; + return $this; + } + + /** + * Get the table for a specific alias + * + * @param String $alias The alias to request the table for + * @return String The table for the alias or null if it doesn't exist + */ + protected function aliasToTableName($alias) + { + return isset($this->idxAliasTable[$alias]) ? $this->idxAliasTable[$alias] : null; + } + + /** + * Return whether this query allows to join custom variables + * + * @return bool + */ + public function allowsCustomVars() + { + return $this->allowCustomVars; + } + + /** + * Return true if the given alias denotes a custom variable + * + * @param String $alias The alias to test for being a customvariable + * @return bool True if the alias is a customvariable, otherwise false + */ + protected function isCustomVar($alias) + { + return $this->allowCustomVars && $alias[0] === '_'; + } + + protected function requireCustomvar($customvar) + { + if (! $this->hasCustomvar($customvar)) { + $this->joinCustomvar($customvar); + } + return $this; + } + + protected function hasCustomvar($customvar) + { + return array_key_exists(strtolower($customvar), $this->customVars); + } + + protected function joinCustomvar($customvar) + { + // TODO: This is not generic enough yet + list($type, $name) = $this->customvarNameToTypeName($customvar); + $alias = ($type === 'host' ? 'hcv_' : 'scv_') . preg_replace('~[^a-zA-Z0-9_]~', '_', $name); + + // We're replacing any problematic char with an underscore, which will lead to duplicates, this avoids them + $from = $this->select->getPart(Zend_Db_Select::FROM); + for ($i = 2; array_key_exists($alias, $from); $i++) { + $alias = $alias . '_' . $i; + } + + $this->customVars[strtolower($customvar)] = $alias; + + if ($type === 'host') { + if ($this instanceof ServicecommentQuery + || $this instanceof ServicedowntimeQuery + || $this instanceof ServicecommenthistoryQuery + || $this instanceof ServicedowntimestarthistoryQuery + || $this instanceof ServiceflappingstarthistoryQuery + || $this instanceof ServicegroupQuery + || $this instanceof ServicenotificationQuery + || $this instanceof ServicestatehistoryQuery + || $this instanceof ServicestatusQuery + ) { + $this->requireVirtualTable('services'); + $leftcol = 's.host_object_id'; + } else { + $leftcol = 'ho.object_id'; + if (! $this->hasJoinedTable('ho')) { + $this->requireVirtualTable('hosts'); + } + } + } else { // $type === 'service' + $leftcol = 'so.object_id'; + if (! $this->hasJoinedTable('so')) { + $this->requireVirtualTable('services'); + } + } + + $mapped = $this->getMappedField($leftcol); + if ($mapped !== null) { + $this->requireColumn($leftcol); + $leftcol = $mapped; + } + + $joinOn = sprintf( + $this->customVarsJoinTemplate, + $leftcol, + $alias, + $this->db->quote($name) + ); + + $this->select->joinLeft( + array($alias => $this->prefix . 'customvariablestatus'), + $joinOn, + array() + ); + + return $this; + } + + protected function customvarNameToTypeName($customvar) + { + $customvar = strtolower($customvar); + if (! preg_match('~^_(host|service)_(.+)$~', $customvar, $m)) { + throw new ProgrammingError( + 'Got invalid custom var: "%s"', + $customvar + ); + } + return array($m[1], $m[2]); + } + + protected function hasJoinedVirtualTable($name) + { + return array_key_exists($name, $this->joinedVirtualTables); + } + + /** + * Get the query column of a already joined custom variable + * + * @param string $customvar + * + * @return string + * @throws QueryException If the custom variable has not been joined + */ + protected function getCustomvarColumnName($customvar) + { + if (! isset($this->customVars[($customvar = strtolower($customvar))])) { + throw new QueryException('Custom variable %s has not been joined', $customvar); + } + return $this->customVars[$customvar] . '.varvalue'; + } + + public function aliasToColumnName($alias) + { + return $this->idxAliasColumn[$alias]; + } + + /** + * Get the alias of a column expression as defined in the {@link $columnMap} property. + * + * @param string $alias Potential custom alias + * + * @return string + */ + public function customAliasToAlias($alias) + { + if (isset($this->idxCustomAliases[$alias])) { + return $this->idxCustomAliases[$alias]; + } + return $alias; + } + + /** + * Create a sub query + * + * @param string $queryName + * @param array $columns + * + * @return static + */ + protected function createSubQuery($queryName, $columns = array()) + { + $class = '\\' + . substr(__CLASS__, 0, strrpos(__CLASS__, '\\') + 1) + . ucfirst($queryName) . 'Query'; + $query = new $class($this->ds, $columns); + return $query; + } + + /** + * Set columns to select + * + * @param array $columns + * + * @return $this + */ + public function columns(array $columns) + { + $this->idxCustomAliases = array(); + $this->columns = $this->resolveColumns($columns); + // TODO: we need to refresh our select! + // $this->select->columns($columns); + return $this; + } + + public function clearGroupingRules() + { + $this->groupBase = array(); + $this->groupOrigin = array(); + return $this; + } + + /** + * Register the GROUP BY columns required for the given alias + * + * @param string $alias The alias to register columns for + * @param string $table The table the given alias is associated with + * @param array $groupedColumns The grouping columns registered so far + * @param array $groupedTables The tables for which columns were registered so far + */ + protected function registerGroupColumns($alias, $table, array &$groupedColumns, array &$groupedTables) + { + switch ($table) { + case 'checktimeperiods': + $groupedColumns[] = 'ctp.timeperiod_id'; + break; + case 'contacts': + $groupedColumns[] = 'co.object_id'; + $groupedColumns[] = 'c.contact_id'; + break; + case 'hostobjects': + $groupedColumns[] = 'ho.object_id'; + break; + case 'hosts': + $groupedColumns[] = 'h.host_id'; + break; + case 'hostgroups': + $groupedColumns[] = 'hgo.object_id'; + $groupedColumns[] = 'hg.hostgroup_id'; + break; + case 'hoststatus': + $groupedColumns[] = 'hs.hoststatus_id'; + break; + case 'instances': + $groupedColumns[] = 'i.instance_id'; + break; + case 'servicegroups': + $groupedColumns[] = 'sgo.object_id'; + $groupedColumns[] = 'sg.servicegroup_id'; + break; + case 'serviceobjects': + $groupedColumns[] = 'so.object_id'; + break; + case 'serviceproblemsummary': + $groupedColumns[] = 'sps.unhandled_services_count'; + break; + case 'services': + $groupedColumns[] = 'so.object_id'; + $groupedColumns[] = 's.service_id'; + break; + case 'servicestatus': + $groupedColumns[] = 'ss.servicestatus_id'; + break; + default: + return; + } + + $groupedTables[$table] = true; + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = parent::getGroup() ?: array(); + if (! is_array($group)) { + $group = array($group); + } + + $joinedOrigins = array_filter($this->groupOrigin, array($this, 'hasJoinedVirtualTable')); + if (empty($joinedOrigins)) { + return $group; + } + + $groupedTables = array(); + foreach ($this->groupBase as $baseTable => $aliasedPks) { + if (! $this->hasJoinedVirtualTable($baseTable)) { + continue; + } + $groupedTables[$baseTable] = true; + foreach ($aliasedPks as $aliasedPk) { + $group[] = $aliasedPk; + } + } + + foreach (new ColumnFilterIterator($this->columns) as $desiredAlias => $desiredColumn) { + $alias = is_string($desiredAlias) ? $this->customAliasToAlias($desiredAlias) : $desiredColumn; + if ($this->isCustomVar($alias) && $this->getDatasource()->getDbType() === 'pgsql') { + $table = $this->customVars[$alias]; + if (! isset($groupedTables[$table])) { + $group[] = $this->getCustomvarColumnName($alias); + $groupedTables[$table] = true; + } + continue; + } + $table = $this->aliasToTableName($alias); + if ($table && !isset($groupedTables[$table]) && ( + in_array($table, $joinedOrigins, true) || $this->getDatasource()->getDbType() === 'pgsql') + ) { + $this->registerGroupColumns($alias, $table, $group, $groupedTables); + } + } + + if (! empty($group) && $this->getDatasource()->getDbType() === 'pgsql') { + foreach (new ColumnFilterIterator($this->orderColumns) as $alias) { + if ($this->isCustomVar($alias)) { + $table = $this->customVars[$alias]; + if (! isset($groupedTables[$table])) { + $group[] = $this->getCustomvarColumnName($alias); + $groupedTables[$table] = true; + } + continue; + } + $table = $this->aliasToTableName($alias); + if ($table && !isset($groupedTables[$table]) + && !in_array($this->getMappedField($alias), $this->columns, true) + ) { + $this->registerGroupColumns($alias, $table, $group, $groupedTables); + } + } + } + + return array_unique($group); + } + + // TODO: Move this away, see note related to $idoVersion var + protected function getIdoVersion() + { + if (self::$idoVersion === null) { + $dbconf = $this->db->getConfig(); + $id = $dbconf['host'] . '/' . $dbconf['dbname']; + $session = null; + if (Icinga::app()->isWeb()) { + // TODO: Once we have version per connection we should choose a + // namespace based on resource name + $session = Session::getSession()->getNamespace('monitoring/ido/' . $id); + if (isset($session->version)) { + self::$idoVersion = $session->version; + return self::$idoVersion; + } + } + self::$idoVersion = $this->db->fetchOne( + $this->db->select()->from($this->prefix . 'dbversion', 'version') + ); + if ($session !== null) { + $session->version = self::$idoVersion; + } + } + return self::$idoVersion; + } + + /** + * Return the name of the primary key column for the given table name + * + * @param string $table + * + * @return string + * + * @throws ProgrammingError In case $table is unknown + */ + protected function getPrimaryKeyColumn($table) + { + // TODO: For god's sake, make this being a mapping + // (instead of matching a ton of properties using a ridiculous long switch case) + switch ($table) { + case 'instances': + return $this->instance_id; + case 'objects': + return $this->object_id; + case 'acknowledgements': + return $this->acknowledgement_id; + case 'commenthistory': + return $this->commenthistory_id; + case 'contactnotifiations': + return $this->contactnotification_id; + case 'downtimehistory': + return $this->downtimehistory_id; + case 'flappinghistory': + return $this->flappinghistory_id; + case 'notifications': + return $this->notification_id; + case 'statehistory': + return $this->statehistory_id; + case 'comments': + return $this->comment_id; + case 'customvariablestatus': + return $this->customvariablestatus_id; + case 'hoststatus': + return $this->hoststatus_id; + case 'programstatus': + return $this->programstatus_id; + case 'runtimevariables': + return $this->runtimevariable_id; + case 'scheduleddowntime': + return $this->scheduleddowntime_id; + case 'servicestatus': + return $this->servicestatus_id; + case 'contactstatus': + return $this->contactstatus_id; + case 'commands': + return $this->command_id; + case 'contactgroup_members': + return $this->contactgroup_member_id; + case 'contactgroups': + return $this->contactgroup_id; + case 'contacts': + return $this->contact_id; + case 'customvariables': + return $this->customvariable_id; + case 'host_contactgroups': + return $this->host_contactgroup_id; + case 'host_contacts': + return $this->host_contact_id; + case 'hostgroup_members': + return $this->hostgroup_member_id; + case 'hostgroups': + return $this->hostgroup_id; + case 'hosts': + return $this->host_id; + case 'service_contactgroups': + return $this->service_contactgroup_id; + case 'service_contacts': + return $this->service_contact_id; + case 'servicegroup_members': + return $this->servicegroup_member_id; + case 'servicegroups': + return $this->servicegroup_id; + case 'services': + return $this->service_id; + case 'timeperiods': + return $this->timeperiod_id; + default: + throw new ProgrammingError('Cannot provide a primary key column. Table "%s" is unknown', $table); + } + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/InstanceQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/InstanceQuery.php new file mode 100644 index 0000000..ac538ec --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/InstanceQuery.php @@ -0,0 +1,26 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +class InstanceQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'instances' => array( + 'instance_id' => 'i.instance_id', + 'instance_name' => 'i.instance_name' + ) + ); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select()->from(array('i' => $this->prefix . 'instances'), array()); + $this->joinedVirtualTables['instances'] = true; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationQuery.php new file mode 100644 index 0000000..8bfb725 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationQuery.php @@ -0,0 +1,144 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\FilterExpression; +use Zend_Db_Expr; +use Zend_Db_Select; +use Icinga\Data\Filter\Filter; + +/** + * Query for host and service notifications + */ +class NotificationQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'notifications' => array( + 'id' => 'n.id', + 'instance_name' => 'n.instance_name', + 'notification_contact_name' => 'n.notification_contact_name', + 'notification_output' => 'n.notification_output', + 'notification_reason' => 'n.notification_reason', + 'notification_state' => 'n.notification_state', + 'notification_timestamp' => 'n.notification_timestamp' + ), + 'hosts' => array( + 'host_display_name' => 'n.host_display_name', + 'host_name' => 'n.host_name' + ), + 'services' => array( + 'service_description' => 'n.service_description', + 'service_display_name' => 'n.service_display_name', + 'service_host_name' => 'n.service_host_name' + ) + ); + + /** + * The union + * + * @var Zend_Db_Select + */ + protected $notificationQuery; + + /** + * Subqueries used for the notification query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->notificationQuery = $this->db->select(); + $this->select->from( + array('n' => $this->notificationQuery), + array() + ); + $this->joinedVirtualTables['notifications'] = true; + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $columns = $this->desiredColumns; + $columns = array_combine($columns, $columns); + foreach ($this->columnMap['services'] as $column => $_) { + if (isset($columns[$column])) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + } + $hosts = $this->createSubQuery('hostnotification', $columns); + $hosts->setIsSubQuery(true); + $this->subQueries[] = $hosts; + $this->notificationQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * Join services + */ + protected function joinServices() + { + $services = $this->createSubQuery('servicenotification', $this->desiredColumns); + $services->setIsSubQuery(true); + $this->subQueries[] = $services; + $this->notificationQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + public function whereEx(FilterExpression $ex) + { + $this->requireColumn($ex->getColumn()); + foreach ($this->subQueries as $sub) { + $sub->whereEx($ex); + } + + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationeventQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationeventQuery.php new file mode 100644 index 0000000..87a71f6 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationeventQuery.php @@ -0,0 +1,52 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Query for host and service notification events + */ +class NotificationeventQuery extends IdoQuery +{ + protected $columnMap = array( + 'notificationevent' => array( + 'notificationevent_id' => 'n.notification_id', + 'notificationevent_reason' => <<<EOF +(CASE n.notification_reason + WHEN 0 THEN 'normal_notification' + WHEN 1 THEN 'ack' + WHEN 2 THEN 'flapping_started' + WHEN 3 THEN 'flapping_stopped' + WHEN 4 THEN 'flapping_disabled' + WHEN 5 THEN 'dt_start' + WHEN 6 THEN 'dt_end' + WHEN 7 THEN 'dt_cancel' + WHEN 99 THEN 'custom_notification' + ELSE NULL +END) +EOF + , + 'notificationevent_start_time' => 'UNIX_TIMESTAMP(n.start_time)', + 'notificationevent_end_time' => 'UNIX_TIMESTAMP(n.end_time)', + 'notificationevent_state' => 'n.state', + 'notificationevent_output' => 'n.output', + 'notificationevent_long_output' => 'n.long_output', + 'notificationevent_escalated' => 'n.escalated', + 'notificationevent_contacts_notified' => 'n.contacts_notified' + ), + 'object' => array( + 'host_name' => 'o.name1', + 'service_description' => 'o.name2' + ) + ); + + protected function joinBaseTables() + { + $this->select() + ->from(array('n' => $this->prefix . 'notifications'), array()) + ->join(array('o' => $this->prefix . 'objects'), 'n.object_id = o.object_id', array()); + + $this->joinedVirtualTables['notificationevent'] = true; + $this->joinedVirtualTables['object'] = true; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php new file mode 100644 index 0000000..f629115 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php @@ -0,0 +1,142 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\FilterExpression; +use Zend_Db_Expr; +use Zend_Db_Select; +use Icinga\Data\Filter\Filter; + +/** + * Query for host and service notification history + */ +class NotificationhistoryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'history' => array( + 'id' => 'n.id', + 'object_type' => 'n.object_type', + 'output' => 'n.output', + 'state' => 'n.state', + 'timestamp' => 'n.timestamp', + 'type' => 'n.type' + ), + 'hosts' => array( + 'host_display_name' => 'n.host_display_name', + 'host_name' => 'n.host_name' + ), + 'services' => array( + 'service_description' => 'n.service_description', + 'service_display_name' => 'n.service_display_name', + 'service_host_name' => 'n.service_host_name' + ) + ); + + /** + * The union + * + * @var Zend_Db_Select + */ + protected $notificationQuery; + + /** + * Subqueries used for the notification query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->notificationQuery = $this->db->select(); + $this->select->from( + array('n' => $this->notificationQuery), + array() + ); + $this->joinedVirtualTables['history'] = true; + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $columns = $this->desiredColumns; + $columns = array_combine($columns, $columns); + foreach ($this->columnMap['services'] as $column => $_) { + if (isset($columns[$column])) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + } + $hosts = $this->createSubQuery('hostnotification', $columns); + $this->subQueries[] = $hosts; + $this->notificationQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * Join services + */ + protected function joinServices() + { + $columns = array_flip($this->desiredColumns); + $services = $this->createSubQuery('servicenotification', array_flip($columns)); + $this->subQueries[] = $services; + $this->notificationQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + public function whereEx(FilterExpression $ex) + { + $this->requireColumn($ex->getColumn()); + foreach ($this->subQueries as $sub) { + $sub->whereEx($ex); + } + + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ProgramstatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ProgramstatusQuery.php new file mode 100644 index 0000000..9e9f5f6 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ProgramstatusQuery.php @@ -0,0 +1,68 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Program status query + */ +class ProgramstatusQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'programstatus' => array( + 'id' => 'programstatus_id', + 'status_update_time' => 'UNIX_TIMESTAMP(programstatus.status_update_time)', + 'program_version' => 'program_version', + 'program_start_time' => 'UNIX_TIMESTAMP(programstatus.program_start_time)', + 'program_end_time' => 'UNIX_TIMESTAMP(programstatus.program_end_time)', + 'is_currently_running' => 'CASE WHEN (UNIX_TIMESTAMP(programstatus.status_update_time) + 60 > UNIX_TIMESTAMP(NOW())) + THEN + 1 + ELSE + 0 + END', + 'process_id' => 'process_id', + 'endpoint_name' => 'endpoint_name', + 'daemon_mode' => 'daemon_mode', + 'last_command_check' => 'UNIX_TIMESTAMP(programstatus.last_command_check)', + 'last_log_rotation' => 'UNIX_TIMESTAMP(programstatus.last_log_rotation)', + 'notifications_enabled' => 'notifications_enabled', + 'disable_notif_expire_time' => 'UNIX_TIMESTAMP(programstatus.disable_notif_expire_time)', + 'active_service_checks_enabled' => 'active_service_checks_enabled', + 'passive_service_checks_enabled' => 'passive_service_checks_enabled', + 'active_host_checks_enabled' => 'active_host_checks_enabled', + 'passive_host_checks_enabled' => 'passive_host_checks_enabled', + 'event_handlers_enabled' => 'event_handlers_enabled', + 'flap_detection_enabled' => 'flap_detection_enabled', + 'failure_prediction_enabled' => 'failure_prediction_enabled', + 'process_performance_data' => 'process_performance_data', + 'obsess_over_hosts' => 'obsess_over_hosts', + 'obsess_over_services' => 'obsess_over_services', + 'modified_host_attributes' => 'modified_host_attributes', + 'modified_service_attributes' => 'modified_service_attributes', + 'global_host_event_handler' => 'global_host_event_handler', + 'global_service_event_handler' => 'global_service_event_handler', + ) + ); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + parent::joinBaseTables(); + + if (version_compare($this->getIdoVersion(), '1.11.7', '<')) { + $this->columnMap['programstatus']['endpoint_name'] = '(0)'; + } + if (version_compare($this->getIdoVersion(), '1.11.8', '<')) { + $this->columnMap['programstatus']['program_version'] = '(NULL)'; + } + if (version_compare($this->getIdoVersion(), '1.8', '<')) { + $this->columnMap['programstatus']['disable_notif_expire_time'] = '(NULL)'; + } + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimesummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimesummaryQuery.php new file mode 100644 index 0000000..1aa2257 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimesummaryQuery.php @@ -0,0 +1,80 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Zend_Db_Select; + +/** + * Query check summaries out of database + */ +class RuntimesummaryQuery extends IdoQuery +{ + protected $columnMap = array( + 'runtimesummary' => array( + 'check_type' => 'check_type', + 'active_checks_enabled' => 'active_checks_enabled', + 'passive_checks_enabled' => 'passive_checks_enabled', + 'execution_time' => 'execution_time', + 'latency' => 'latency', + 'object_count' => 'object_count', + 'object_type' => 'object_type' + ) + ); + + protected function joinBaseTables() + { + $hosts = $this->db->select()->from( + array('ho' => $this->prefix . 'objects'), + array() + )->join( + array('hs' => $this->prefix . 'hoststatus'), + 'ho.object_id = hs.host_object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + array() + )->columns( + array( + 'check_type' => 'CASE ' + . 'WHEN hs.active_checks_enabled = 0 AND hs.passive_checks_enabled = 1 THEN \'passive\' ' + . 'WHEN hs.active_checks_enabled = 1 THEN \'active\' ' + . 'END', + 'active_checks_enabled' => 'hs.active_checks_enabled', + 'passive_checks_enabled' => 'hs.passive_checks_enabled', + 'execution_time' => 'SUM(hs.execution_time)', + 'latency' => 'SUM(hs.latency)', + 'object_count' => 'COUNT(*)', + 'object_type' => "('host')" + ) + )->group('check_type')->group('active_checks_enabled')->group('passive_checks_enabled'); + + $services = $this->db->select()->from( + array('so' => $this->prefix . 'objects'), + array() + )->join( + array('ss' => $this->prefix . 'servicestatus'), + 'so.object_id = ss.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + )->columns( + array( + 'check_type' => 'CASE ' + . 'WHEN ss.active_checks_enabled = 0 AND ss.passive_checks_enabled = 1 THEN \'passive\' ' + . 'WHEN ss.active_checks_enabled = 1 THEN \'active\' ' + . 'END', + 'active_checks_enabled' => 'ss.active_checks_enabled', + 'passive_checks_enabled' => 'ss.passive_checks_enabled', + 'execution_time' => 'SUM(ss.execution_time)', + 'latency' => 'SUM(ss.latency)', + 'object_count' => 'COUNT(*)', + 'object_type' => "('service')" + ) + )->group('check_type')->group('active_checks_enabled')->group('passive_checks_enabled'); + + $union = $this->db->select()->union( + array('s' => $services, 'h' => $hosts), + Zend_Db_Select::SQL_UNION_ALL + ); + + $this->select->from(array('hs' => $union)); + + $this->joinedVirtualTables = array('runtimesummary' => true); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimevariablesQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimevariablesQuery.php new file mode 100644 index 0000000..494744a --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimevariablesQuery.php @@ -0,0 +1,18 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Query for runtimevariables table + */ +class RuntimevariablesQuery extends IdoQuery +{ + protected $columnMap = array( + 'runtimevariables' => array( + 'id' => 'runtimevariable_id', + 'varname' => 'varname', + 'varvalue' => 'varvalue' + ) + ); +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentQuery.php new file mode 100644 index 0000000..cae11bc --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentQuery.php @@ -0,0 +1,218 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Query for service comments + */ +class ServicecommentQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + /** + * {@inheritdoc} + */ + protected $groupBase = array('comments' => array('c.comment_id', 'so.object_id')); + + /** + * {@inheritdoc} + */ + protected $groupOrigin = array('hostgroups', 'servicegroups'); + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'comments' => array( + 'comment_author' => 'c.author_name COLLATE latin1_general_ci', + 'comment_author_name' => 'c.author_name', + 'comment_data' => 'c.comment_data', + 'comment_expiration' => 'CASE c.expires WHEN 1 THEN UNIX_TIMESTAMP(c.expiration_time) ELSE NULL END', + 'comment_internal_id' => 'c.internal_comment_id', + 'comment_is_persistent' => 'c.is_persistent', + 'comment_name' => 'c.name', + 'comment_timestamp' => 'UNIX_TIMESTAMP(c.comment_time)', + 'comment_type' => "CASE c.entry_type WHEN 1 THEN 'comment' WHEN 2 THEN 'downtime' WHEN 3 THEN 'flapping' WHEN 4 THEN 'ack' END", + 'host' => 'so.name1 COLLATE latin1_general_ci', + 'host_name' => 'so.name1', + 'object_type' => '(\'service\')', + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_host' => 'so.name1 COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'hoststatus' => array( + 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service_display_name' => 's.display_name COLLATE latin1_general_ci' + ), + 'servicestatus' => array( + 'service_state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE ss.current_state END' + ) + ); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + if (version_compare($this->getIdoVersion(), '1.14.0', '<')) { + $this->columnMap['comments']['comment_name'] = '(NULL)'; + } + $this->select->from( + array('c' => $this->prefix . 'comments'), + array() + )->join( + array('so' => $this->prefix . 'objects'), + 'so.object_id = c.object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + $this->joinedVirtualTables['comments'] = true; + } + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = s.host_object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->requireVirtualTable('services'); + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join host status + */ + protected function joinHoststatus() + { + $this->requireVirtualTable('services'); + $this->select->join( + array('hs' => $this->prefix . 'hoststatus'), + 'hs.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = so.object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sgm.servicegroup_id = sg.' . $this->servicegroup_id, + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = c.instance_id', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->join( + array('s' => $this->prefix . 'services'), + 's.service_object_id = so.object_id', + array() + ); + } + + /** + * Join service status + */ + protected function joinServicestatus() + { + $this->select->join( + array('ss' => $this->prefix . 'servicestatus'), + 'ss.service_object_id = so.object_id', + array() + ); + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $this->requireVirtualTable('services'); + + $query->joinVirtualTable('members'); + + return ['hgm.host_object_id', 's.host_object_id']; + } elseif ($name === 'servicegroup') { + $query->joinVirtualTable('members'); + + return ['sgm.service_object_id', 'so.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentdeletionhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentdeletionhistoryQuery.php new file mode 100644 index 0000000..33aaa25 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentdeletionhistoryQuery.php @@ -0,0 +1,44 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; + +/** + * Query for service comment removal records + */ +class ServicecommentdeletionhistoryQuery extends ServicecommenthistoryQuery +{ + protected function requireFilterColumns(Filter $filter) + { + if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') { + $this->requireColumn('timestamp'); + $filter->setColumn('sch.deletion_time'); + $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression()))); + return null; + } + + return parent::requireFilterColumns($filter); + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + parent::joinBaseTables(); + $this->select->where("sch.deletion_time > '1970-01-02 00:00:00'"); + $this->columnMap['commenthistory']['timestamp'] = str_replace( + 'comment_time', + 'deletion_time', + $this->columnMap['commenthistory']['timestamp'] + ); + $this->columnMap['commenthistory']['type'] = str_replace( + 'END)', + "END || '_deleted')", + $this->columnMap['commenthistory']['type'] + ); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommenthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommenthistoryQuery.php new file mode 100644 index 0000000..b3e9c16 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommenthistoryQuery.php @@ -0,0 +1,195 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; + +/** + * Query for service comment history records + */ +class ServicecommenthistoryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + /** + * {@inheritdoc} + */ + protected $groupBase = array('commenthistory' => array('sch.commenthistory_id', 'so.object_id')); + + /** + * {@inheritdoc} + */ + protected $groupOrigin = array('hostgroups', 'servicegroups', 'services'); + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'commenthistory' => array( + 'id' => 'sch.commenthistory_id', + 'host' => 'so.name1 COLLATE latin1_general_ci', + 'host_name' => 'so.name1', + 'object_id' => 'sch.object_id', + 'object_type' => '(\'service\')', + 'output' => "('[' || sch.author_name || '] ' || sch.comment_data)", + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_host' => 'so.name1 COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1', + 'state' => '(-1)', + 'timestamp' => 'UNIX_TIMESTAMP(sch.comment_time)', + 'type' => "(CASE sch.entry_type WHEN 1 THEN 'comment' WHEN 2 THEN 'dt_comment' WHEN 3 THEN 'flapping' WHEN 4 THEN 'ack' END)" + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service_display_name' => 's.display_name COLLATE latin1_general_ci' + ) + ); + + protected function requireFilterColumns(Filter $filter) + { + if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') { + $this->requireColumn('timestamp'); + $filter->setColumn('sch.comment_time'); + $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression()))); + return null; + } + + return parent::requireFilterColumns($filter); + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('sch' => $this->prefix . 'commenthistory'), + array() + )->join( + array('so' => $this->prefix . 'objects'), + 'so.object_id = sch.object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + $this->joinedVirtualTables['commenthistory'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = s.host_object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->requireVirtualTable('services'); + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = sch.instance_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = so.object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->join( + array('s' => $this->prefix . 'services'), + 's.service_object_id = so.object_id', + array() + ); + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $query->joinVirtualTable('services'); + + return ['so.object_id', 'so.object_id']; + } elseif ($name === 'servicegroup') { + $query->joinVirtualTable('members'); + + return ['sgm.service_object_id', 'so.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecontactQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecontactQuery.php new file mode 100644 index 0000000..0a46709 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecontactQuery.php @@ -0,0 +1,235 @@ +<?php +/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Query for service contacts + */ +class ServicecontactQuery extends IdoQuery +{ + protected $allowCustomVars = true; + + protected $groupBase = [ + 'contacts' => ['co.object_id', 'c.contact_id'], + 'timeperiods' => ['ht.timeperiod_id', 'st.timeperiod_id'] + ]; + + protected $groupOrigin = ['contactgroups', 'hosts', 'services']; + + protected $subQueryTargets = [ + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ]; + + protected $columnMap = [ + 'contactgroups' => [ + 'contactgroup' => 'cgo.name1 COLLATE latin1_general_ci', + 'contactgroup_name' => 'cgo.name1', + 'contactgroup_alias' => 'cg.alias COLLATE latin1_general_ci' + ], + 'contacts' => [ + 'contact_id' => 'c.contact_id', + 'contact' => 'co.name1 COLLATE latin1_general_ci', + 'contact_name' => 'co.name1', + 'contact_alias' => 'c.alias COLLATE latin1_general_ci', + 'contact_email' => 'c.email_address COLLATE latin1_general_ci', + 'contact_pager' => 'c.pager_address', + 'contact_object_id' => 'c.contact_object_id', + 'contact_has_host_notfications' => 'c.host_notifications_enabled', + 'contact_has_service_notfications' => 'c.service_notifications_enabled', + 'contact_can_submit_commands' => 'c.can_submit_commands', + 'contact_notify_service_recovery' => 'c.notify_service_recovery', + 'contact_notify_service_warning' => 'c.notify_service_warning', + 'contact_notify_service_critical' => 'c.notify_service_critical', + 'contact_notify_service_unknown' => 'c.notify_service_unknown', + 'contact_notify_service_flapping' => 'c.notify_service_flapping', + 'contact_notify_service_downtime' => 'c.notify_service_downtime', + 'contact_notify_host_recovery' => 'c.notify_host_recovery', + 'contact_notify_host_down' => 'c.notify_host_down', + 'contact_notify_host_unreachable' => 'c.notify_host_unreachable', + 'contact_notify_host_flapping' => 'c.notify_host_flapping', + 'contact_notify_host_downtime' => 'c.notify_host_downtime' + ], + 'hostgroups' => [ + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ], + 'hosts' => [ + 'host' => 'ho.name1 COLLATE latin1_general_ci', + 'host_name' => 'ho.name1', + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ], + 'instances' => [ + 'instance_name' => 'i.instance_name' + ], + 'servicegroups' => [ + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ], + 'services' => [ + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ], + 'timeperiods' => [ + 'contact_notify_host_timeperiod' => 'ht.alias COLLATE latin1_general_ci', + 'contact_notify_service_timeperiod' => 'st.alias COLLATE latin1_general_ci' + ] + ]; + + protected function joinBaseTables() + { + $this->select->from( + ['c' => $this->prefix . 'contacts'], + [] + )->join( + ['co' => $this->prefix . 'objects'], + 'co.object_id = c.contact_object_id AND co.is_active = 1 AND co.objecttype_id = 10', + [] + ); + + $this->select->joinLeft( + ['sc' => $this->prefix . 'service_contacts'], + 'sc.contact_object_id = c.contact_object_id', + [] + )->joinLeft( + ['s' => $this->prefix . 'services'], + 's.service_id = sc.service_id', + [] + )->joinLeft( + ['so' => $this->prefix . 'objects'], + 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2', + [] + ); + + $this->joinedVirtualTables['contacts'] = true; + $this->joinedVirtualTables['services'] = true; + } + + /** + * Join contact groups + */ + protected function joinContactgroups() + { + $this->select->joinLeft( + ['cgm' => $this->prefix . 'contactgroup_members'], + 'co.object_id = cgm.contact_object_id', + [] + )->joinLeft( + ['cg' => $this->prefix . 'contactgroups'], + 'cgm.contactgroup_id = cg.contactgroup_id', + [] + )->joinLeft( + ['cgo' => $this->prefix . 'objects'], + 'cg.contactgroup_object_id = cgo.object_id AND cgo.is_active = 1 AND cgo.objecttype_id = 11', + [] + ); + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('hosts'); + $this->select->joinLeft( + ['hgm' => $this->prefix . 'hostgroup_members'], + 'hgm.host_object_id = ho.object_id', + [] + )->joinLeft( + ['hg' => $this->prefix . 'hostgroups'], + 'hg.hostgroup_id = hgm.hostgroup_id', + [] + )->joinLeft( + ['hgo' => $this->prefix . 'objects'], + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + [] + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->joinLeft( + ['h' => $this->prefix . 'hosts'], + 'h.host_object_id = s.host_object_id', + [] + )->joinLeft( + ['ho' => $this->prefix . 'objects'], + 'ho.object_id = h.host_object_id AND ho.is_active = 1 AND ho.objecttype_id = 1', + [] + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + ['i' => $this->prefix . 'instances'], + 'i.instance_id = c.instance_id', + [] + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + ['sgm' => $this->prefix . 'servicegroup_members'], + 'sgm.service_object_id = s.service_object_id', + [] + )->joinLeft( + ['sg' => $this->prefix . 'servicegroups'], + 'sg.servicegroup_id = sgm.servicegroup_id', + [] + )->joinLeft( + ['sgo' => $this->prefix . 'objects'], + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + [] + ); + } + + /** + * Join time periods + */ + protected function joinTimeperiods() + { + $this->select->joinLeft( + ['ht' => $this->prefix . 'timeperiods'], + 'ht.timeperiod_object_id = c.host_timeperiod_object_id', + [] + ); + $this->select->joinLeft( + ['st' => $this->prefix . 'timeperiods'], + 'st.timeperiod_object_id = c.service_timeperiod_object_id', + [] + ); + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $query->joinVirtualTable('members'); + + return ['hgm.host_object_id', 's.host_object_id']; + } elseif ($name === 'servicegroup') { + $query->joinVirtualTable('members'); + + return ['sgm.service_object_id', 'so.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeQuery.php new file mode 100644 index 0000000..feea061 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeQuery.php @@ -0,0 +1,222 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Query for service downtimes + */ +class ServicedowntimeQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + /** + * {@inheritdoc} + */ + protected $groupBase = array('downtimes' => array('sd.scheduleddowntime_id', 'so.object_id')); + + /** + * {@inheritdoc} + */ + protected $groupOrigin = array('hostgroups', 'servicegroups'); + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'downtimes' => array( + 'downtime_author' => 'sd.author_name COLLATE latin1_general_ci', + 'downtime_author_name' => 'sd.author_name', + 'downtime_comment' => 'sd.comment_data', + 'downtime_duration' => 'sd.duration', + '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_entry_time' => 'UNIX_TIMESTAMP(sd.entry_time)', + 'downtime_internal_id' => 'sd.internal_downtime_id', + 'downtime_is_fixed' => 'sd.is_fixed', + 'downtime_is_flexible' => 'CASE WHEN sd.is_fixed = 0 THEN 1 ELSE 0 END', + 'downtime_is_in_effect' => 'sd.is_in_effect', + 'downtime_name' => 'sd.name', + 'downtime_scheduled_end' => 'UNIX_TIMESTAMP(sd.scheduled_end_time)', + 'downtime_scheduled_start' => 'UNIX_TIMESTAMP(sd.scheduled_start_time)', + 'downtime_start' => 'UNIX_TIMESTAMP(CASE WHEN UNIX_TIMESTAMP(sd.trigger_time) > 0 then sd.trigger_time ELSE sd.scheduled_start_time END)', + 'downtime_triggered_by_id' => 'sd.triggered_by_id', + 'host' => 'so.name1 COLLATE latin1_general_ci', + 'host_name' => 'so.name1', + 'object_type' => '(\'service\')', + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_host' => 'so.name1 COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1' + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'hoststatus' => array( + 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END' + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service_display_name' => 's.display_name COLLATE latin1_general_ci' + ), + 'servicestatus' => array( + 'service_state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE ss.current_state END' + ) + ); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + if (version_compare($this->getIdoVersion(), '1.14.0', '<')) { + $this->columnMap['downtimes']['downtime_name'] = '(NULL)'; + } + $this->select->from( + array('sd' => $this->prefix . 'scheduleddowntime'), + array() + )->join( + array('so' => $this->prefix . 'objects'), + 'sd.object_id = so.object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + $this->joinedVirtualTables['downtimes'] = true; + } + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = s.host_object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->requireVirtualTable('services'); + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join host status + */ + protected function joinHoststatus() + { + $this->requireVirtualTable('services'); + $this->select->join( + array('hs' => $this->prefix . 'hoststatus'), + 'hs.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = sd.instance_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = so.object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sgm.servicegroup_id = sg.' . $this->servicegroup_id, + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->join( + array('s' => $this->prefix . 'services'), + 's.service_object_id = so.object_id', + array() + ); + } + + /** + * Join service status + */ + protected function joinServicestatus() + { + $this->select->join( + array('ss' => $this->prefix . 'servicestatus'), + 'ss.service_object_id = so.object_id', + array() + ); + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $query->joinVirtualTable('services'); + + return ['so.object_id', 'so.object_id']; + } elseif ($name === 'servicegroup') { + $query->joinVirtualTable('members'); + + return ['sgm.service_object_id', 'so.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeendhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeendhistoryQuery.php new file mode 100644 index 0000000..6243829 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeendhistoryQuery.php @@ -0,0 +1,42 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; + +/** + * Query for host downtime end history records + */ +class ServicedowntimeendhistoryQuery extends ServicedowntimestarthistoryQuery +{ + protected function requireFilterColumns(Filter $filter) + { + if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') { + $this->requireColumn('timestamp'); + $filter->setColumn('sdh.actual_end_time'); + $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression()))); + return null; + } + + return parent::requireFilterColumns($filter); + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + parent::joinBaseTables(true); + $this->select->where( + "sdh.actual_end_time > '1970-01-02 00:00:00' AND sdh.was_started = 1 AND sdh.was_cancelled = 0" + ); + $this->columnMap['downtimehistory']['type'] = "('dt_end')"; + $this->columnMap['downtimehistory']['timestamp'] = str_replace( + 'actual_start_time', + 'actual_end_time', + $this->columnMap['downtimehistory']['timestamp'] + ); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimestarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimestarthistoryQuery.php new file mode 100644 index 0000000..b8805fe --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimestarthistoryQuery.php @@ -0,0 +1,205 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; + +/** + * Query for service downtime start history records + */ +class ServicedowntimestarthistoryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + /** + * {@inheritdoc} + */ + protected $groupBase = array('downtimehistory' => array('sdh.downtimehistory_id', 'so.object_id')); + + /** + * {@inheritdoc} + */ + protected $groupOrigin = array('hostgroups', 'servicegroups'); + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'downtimehistory' => array( + 'id' => 'sdh.downtimehistory_id', + 'host' => 'so.name1 COLLATE latin1_general_ci', + 'host_name' => 'so.name1', + 'object_id' => 'sdh.object_id', + 'object_type' => '(\'service\')', + 'output' => "('[' || sdh.author_name || '] ' || sdh.comment_data)", + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_host' => 'so.name1 COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1', + 'state' => '(-1)', + 'timestamp' => 'UNIX_TIMESTAMP(sdh.actual_start_time)', + 'type' => "('dt_start')" + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service_display_name' => 's.display_name COLLATE latin1_general_ci' + ) + ); + + protected function requireFilterColumns(Filter $filter) + { + if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') { + $this->requireColumn('timestamp'); + $filter->setColumn('sdh.actual_start_time'); + $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression()))); + return null; + } + + return parent::requireFilterColumns($filter); + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('sdh' => $this->prefix . 'downtimehistory'), + array() + )->join( + array('so' => $this->prefix . 'objects'), + 'so.object_id = sdh.object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + + if (func_num_args() === 0 || func_get_arg(0) === false) { + $this->select->where( + "sdh.actual_start_time > '1970-01-02 00:00:00'" + ); + } + $this->select->where( + "sdh.was_started = 1 AND sdh.was_cancelled = 0" + ); + + $this->joinedVirtualTables['downtimehistory'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = s.host_object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->requireVirtualTable('services'); + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = sdh.instance_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = so.object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->join( + array('s' => $this->prefix . 'services'), + 's.service_object_id = so.object_id', + array() + ); + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $query->joinVirtualTable('services'); + + return ['so.object_id', 'so.object_id']; + } elseif ($name === 'servicegroup') { + $query->joinVirtualTable('members'); + + return ['sgm.service_object_id', 'so.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServiceflappingendhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServiceflappingendhistoryQuery.php new file mode 100644 index 0000000..48fb0bc --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServiceflappingendhistoryQuery.php @@ -0,0 +1,31 @@ +<?php +/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Query for service flapping end history records + */ +class ServiceflappingendhistoryQuery extends ServiceflappingstarthistoryQuery +{ + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('sfh' => $this->prefix . 'flappinghistory'), + array() + )->join( + array('so' => $this->prefix . 'objects'), + 'so.object_id = sfh.object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + + $this->select->where('sfh.event_type = 1001'); + + $this->joinedVirtualTables['flappinghistory'] = true; + + $this->columnMap['flappinghistory']['type'] = '(\'flapping_deleted\')'; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServiceflappingstarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServiceflappingstarthistoryQuery.php new file mode 100644 index 0000000..f068681 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServiceflappingstarthistoryQuery.php @@ -0,0 +1,197 @@ +<?php +/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; + +/** + * Query for service flapping start history records + */ +class ServiceflappingstarthistoryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + /** + * {@inheritdoc} + */ + protected $groupBase = array('flappinghistory' => array('sfh.flappinghistory_id', 'so.object_id')); + + /** + * {@inheritdoc} + */ + protected $groupOrigin = array('hostgroups', 'servicegroups'); + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'flappinghistory' => array( + 'id' => 'sfh.flappinghistory_id', + 'host' => 'so.name1 COLLATE latin1_general_ci', + 'host_name' => 'so.name1', + 'object_id' => 'sfh.object_id', + 'object_type' => '(\'service\')', + 'output' => '(sfh.percent_state_change || \'\')', + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_host_name' => 'so.name1', + 'state' => '(-1)', + 'timestamp' => 'UNIX_TIMESTAMP(sfh.event_time)', + 'type' => "('flapping')" + ), + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'servicegroups' => array( + 'servicegroup_name' => 'sgo.name1', + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service_display_name' => 's.display_name COLLATE latin1_general_ci' + ) + ); + + protected function requireFilterColumns(Filter $filter) + { + if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') { + $this->requireColumn('timestamp'); + $filter->setColumn('sfh.event_time'); + $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression()))); + return null; + } + + return parent::requireFilterColumns($filter); + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('sfh' => $this->prefix . 'flappinghistory'), + array() + )->join( + array('so' => $this->prefix . 'objects'), + 'so.object_id = sfh.object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + + $this->select->where('sfh.event_type = 1000'); + + $this->joinedVirtualTables['flappinghistory'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = s.host_object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->requireVirtualTable('services'); + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = sfh.instance_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = so.object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->join( + array('s' => $this->prefix . 'services'), + 's.service_object_id = so.object_id', + array() + ); + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $query->joinVirtualTable('services'); + + return ['so.object_id', 'so.object_id']; + } elseif ($name === 'servicegroup') { + $query->joinVirtualTable('members'); + + return ['sgm.service_object_id', 'so.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupQuery.php new file mode 100644 index 0000000..7f7be50 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupQuery.php @@ -0,0 +1,303 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +class ServicegroupQuery extends IdoQuery +{ + protected $groupBase = array( + 'servicegroups' => array('sgo.object_id', 'sg.servicegroup_id'), + 'servicestatus' => array('ss.servicestatus_id', 'hs.hoststatus_id') + ); + + protected $groupOrigin = array('members'); + + protected $allowCustomVars = true; + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + protected $columnMap = array( + 'contacts' => [ + 'service_contact' => 'sco.name1' + ], + 'contactgroups' => [ + 'service_contactgroup' => 'scgo.name1' + ], + 'hostcontacts' => [ + 'host_contact' => 'hco.name1' + ], + 'hostcontactgroups' => [ + 'host_contactgroup' => 'hcgo.name1' + ], + 'hostgroups' => array( + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'h.host_object_id' => 's.host_object_id' + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'members' => array( + 'host_name' => 'so.name1', + 'service_description' => 'so.name2' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1' + ), + 'servicestatus' => array( + 'service_handled' => 'CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) > 0 THEN 1 ELSE 0 END', + 'service_severity' => ' + CASE WHEN ss.current_state = 0 + THEN + CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL + THEN 16 + ELSE 0 + END + + + CASE WHEN ss.problem_has_been_acknowledged = 1 + THEN 2 + ELSE + CASE WHEN ss.scheduled_downtime_depth > 0 + THEN 1 + ELSE 4 + END + END + ELSE + CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 16 + WHEN ss.current_state = 1 THEN 32 + WHEN ss.current_state = 2 THEN 128 + WHEN ss.current_state = 3 THEN 64 + ELSE 256 + END + + + CASE WHEN hs.current_state > 0 + THEN 1024 + ELSE + CASE WHEN ss.problem_has_been_acknowledged = 1 + THEN 512 + ELSE + CASE WHEN ss.scheduled_downtime_depth > 0 + THEN 256 + ELSE 2048 + END + END + END + END', + 'service_state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE ss.current_state END' + ) + ); + + protected function joinBaseTables() + { + $this->select->from( + array('sgo' => $this->prefix . 'objects'), + array() + )->join( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.servicegroup_object_id = sgo.object_id AND sgo.objecttype_id = 4 AND sgo.is_active = 1', + array() + ); + $this->joinedVirtualTables = array('servicegroups' => true); + } + + /** + * Join contacts + */ + protected function joinContacts() + { + $this->requireVirtualTable('services'); + + $this->select->joinLeft( + ['sc' => 'icinga_service_contacts'], + 'sc.service_id = s.service_id', + [] + )->joinLeft( + ['sco' => 'icinga_objects'], + 'sco.object_id = sc.contact_object_id AND sco.is_active = 1 AND sco.objecttype_id = 10', + [] + ); + } + + /** + * Join contact groups + */ + protected function joinContactgroups() + { + $this->requireVirtualTable('services'); + + $this->select->joinLeft( + ['scg' => 'icinga_service_contactgroups'], + 'scg.service_id = s.service_id', + [] + )->joinLeft( + ['scgo' => 'icinga_objects'], + 'scgo.object_id = scg.contactgroup_object_id AND scgo.is_active = 1 AND scgo.objecttype_id = 11', + [] + ); + } + + /** + * Join host contacts + */ + protected function joinHostcontacts() + { + $this->requireVirtualTable('services'); + + $this->select->joinLeft( + ['h' => 'icinga_hosts'], + 'h.host_object_id = s.host_object_id', + [] + )->joinLeft( + ['hc' => 'icinga_host_contacts'], + 'hc.host_id = h.host_id', + [] + )->joinLeft( + ['hco' => 'icinga_objects'], + 'hco.object_id = hc.contact_object_id AND hco.is_active = 1 AND hco.objecttype_id = 10', + [] + ); + } + + /** + * Join host contact groups + */ + protected function joinHostcontactgroups() + { + $this->requireVirtualTable('services'); + + $this->select->joinLeft( + ['h' => 'icinga_hosts'], + 'h.host_object_id = s.host_object_id', + [] + )->joinLeft( + ['hcg' => 'icinga_host_contactgroups'], + 'hcg.host_id = h.host_id', + [] + )->joinLeft( + ['hcgo' => 'icinga_objects'], + 'hcgo.object_id = hcg.contactgroup_object_id AND hcgo.is_active = 1 AND hcgo.objecttype_id = 11', + [] + ); + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = s.host_object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.objecttype_id = 3 AND hgo.is_active = 1', + array() + ); + } + + /** + * Join hosts + * + * This is required to make filters work which filter by host custom variables. + */ + protected function joinHosts() + { + $this->requireVirtualTable('services'); + + // Host custom var filters work w/o any host related table. If a host table join is necessary here some day, + // please adjust `joinHostcontact*()` where we explicitly do this already + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = sg.instance_id', + array() + ); + } + + /** + * Join service objects + */ + protected function joinMembers() + { + $this->select->join( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.servicegroup_id = sg.servicegroup_id', + array() + )->join( + array('so' => $this->prefix . 'objects'), + 'so.object_id = sgm.service_object_id AND so.objecttype_id = 2 AND so.is_active = 1', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->requireVirtualTable('members'); + $this->select->join( + array('s' => $this->prefix . 'services'), + 's.service_object_id = so.object_id', + array() + ); + } + + /** + * Join service status + */ + protected function joinServicestatus() + { + $this->requireVirtualTable('services'); + $this->select->join( + array('hs' => $this->prefix . 'hoststatus'), + 'hs.host_object_id = s.host_object_id', + array() + ); + $this->select->join( + array('ss' => $this->prefix . 'servicestatus'), + 'ss.service_object_id = so.object_id', + array() + ); + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $this->requireVirtualTable('members'); + + $query->joinVirtualTable('services'); + + return ['so.object_id', 'so.object_id']; + } elseif ($name === 'servicegroup') { + // Propagate that the "parent" query has to be filtered as well + $additionalFilter = clone $filter; + + $this->requireVirtualTable('members'); + + $query->joinVirtualTable('members'); + + return ['sgm.service_object_id', 'so.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupsummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupsummaryQuery.php new file mode 100644 index 0000000..11b62d0 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupsummaryQuery.php @@ -0,0 +1,113 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; +use Zend_Db_Expr; +use Zend_Db_Select; + +/** + * Query for service group summary + */ +class ServicegroupsummaryQuery extends IdoQuery +{ + + protected $allowCustomVars = true; + + protected $columnMap = array( + 'servicegroupsummary' => array( + 'servicegroup_alias' => 'servicegroup_alias', + 'servicegroup_name' => 'servicegroup_name', + 'services_critical' => 'SUM(CASE WHEN service_state = 2 THEN 1 ELSE 0 END)', + 'services_critical_handled' => 'SUM(CASE WHEN service_state = 2 AND service_handled = 1 THEN 1 ELSE 0 END)', + 'services_critical_unhandled' => 'SUM(CASE WHEN service_state = 2 AND service_handled = 0 THEN 1 ELSE 0 END)', + 'services_ok' => 'SUM(CASE WHEN service_state = 0 THEN 1 ELSE 0 END)', + 'services_pending' => 'SUM(CASE WHEN service_state = 99 THEN 1 ELSE 0 END)', + 'services_severity' => 'MAX(service_severity)', + 'services_total' => 'SUM(1)', + 'services_unknown' => 'SUM(CASE WHEN service_state = 3 THEN 1 ELSE 0 END)', + 'services_unknown_handled' => 'SUM(CASE WHEN service_state = 3 AND service_handled = 1 THEN 1 ELSE 0 END)', + 'services_unknown_unhandled' => 'SUM(CASE WHEN service_state = 3 AND service_handled = 0 THEN 1 ELSE 0 END)', + 'services_warning' => 'SUM(CASE WHEN service_state = 1 THEN 1 ELSE 0 END)', + 'services_warning_handled' => 'SUM(CASE WHEN service_state = 1 AND service_handled = 1 THEN 1 ELSE 0 END)', + 'services_warning_unhandled' => 'SUM(CASE WHEN service_state = 1 AND service_handled = 0 THEN 1 ELSE 0 END)', + ) + ); + + /** + * The union + * + * @var Zend_Db_Select + */ + protected $summaryQuery; + + /** + * Subqueries used for the summary query + * + * @var IdoQuery[] + */ + protected $subQueries = []; + + /** + * Count query + * + * @var IdoQuery + */ + protected $countQuery; + + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + $this->countQuery->applyFilter(clone $filter); + return $this; + } + + protected function joinBaseTables() + { + $this->countQuery = $this->createSubQuery( + 'Servicegroup', + array() + ); + $subQuery = $this->createSubQuery( + 'Servicegroup', + array( + 'servicegroup_alias', + 'servicegroup_name', + 'service_handled', + 'service_severity', + 'service_state' + ) + ); + $this->subQueries[] = $subQuery; + $emptyGroups = $this->createSubQuery( + 'Emptyservicegroup', + [ + 'servicegroup_alias', + 'servicegroup_name', + 'service_handled' => new Zend_Db_Expr('NULL'), + 'service_severity' => new Zend_Db_Expr('0'), + 'service_state' => new Zend_Db_Expr('NULL'), + ] + ); + $this->subQueries[] = $emptyGroups; + $this->summaryQuery = $this->db->select()->union( + [$subQuery, $emptyGroups], + Zend_Db_Select::SQL_UNION_ALL + ); + $this->select->from(['servicesgroupsummary' => $this->summaryQuery], []); + $this->group(['servicegroup_name', 'servicegroup_alias']); + $this->joinedVirtualTables['servicegroupsummary'] = true; + } + + public function getCountQuery() + { + $count = $this->countQuery->select(); + $this->countQuery->applyFilterSql($count); + $count->columns(array('sgo.object_id')); + $count->group(array('sgo.object_id')); + return $this->db->select()->from($count, array('cnt' => 'COUNT(*)')); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicenotificationQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicenotificationQuery.php new file mode 100644 index 0000000..1159e0c --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicenotificationQuery.php @@ -0,0 +1,287 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; + +/** + * Query for service notifications + */ +class ServicenotificationQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'contactnotifications' => array( + 'notification_contact_name' => 'co.name1' + ), + 'history' => array( + 'output' => null, + 'state' => 'sn.state', + 'timestamp' => 'UNIX_TIMESTAMP(sn.start_time)', + 'type' => ' + CASE sn.notification_reason + WHEN 1 THEN \'notification_ack\' + WHEN 2 THEN \'notification_flapping\' + WHEN 3 THEN \'notification_flapping_end\' + WHEN 5 THEN \'notification_dt_start\' + WHEN 6 THEN \'notification_dt_end\' + WHEN 7 THEN \'notification_dt_end\' + WHEN 8 THEN \'notification_custom\' + ELSE \'notification_state\' + END', + ), + 'hostgroups' => array( + 'hostgroup_name' => 'hgo.name1', + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + ), + 'hosts' => array( + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci', + 'host_alias' => 'h.alias COLLATE latin1_general_ci', + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'notifications' => array( + 'id' => 'sn.notification_id', + 'host' => 'so.name1 COLLATE latin1_general_ci', + 'host_name' => 'so.name1', + 'notification_output' => 'sn.output', + 'notification_reason' => 'sn.notification_reason', + 'notification_state' => 'sn.state', + 'notification_timestamp' => 'UNIX_TIMESTAMP(sn.start_time)', + 'object_type' => '(\'service\')', + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_host_name' => 'so.name1' + ), + 'servicegroups' => array( + 'servicegroup_name' => 'sgo.name1', + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service_display_name' => 's.display_name COLLATE latin1_general_ci' + ) + ); + + protected function requireFilterColumns(Filter $filter) + { + if ($filter instanceof FilterExpression) { + switch ($filter->getColumn()) { + case 'output': + $this->requireColumn('output'); + $filter->setColumn('sn.output'); + return null; + case 'timestamp': + case 'notification_timestamp': + $this->requireColumn($filter->getColumn()); + $filter->setColumn('sn.start_time'); + $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression()))); + return null; + } + } + + return parent::requireFilterColumns($filter); + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $concattedContacts = null; + switch ($this->ds->getDbType()) { + case 'mysql': + $concattedContacts = "GROUP_CONCAT(" + . "DISTINCT co.name1 ORDER BY co.name1 SEPARATOR ', '" + . ") COLLATE latin1_general_ci"; + break; + case 'pgsql': + // TODO: Find a way to order the contact alias list: + $concattedContacts = "ARRAY_TO_STRING(ARRAY_AGG(DISTINCT co.name1), ', ')"; + break; + } + $this->columnMap['history']['output'] = "('[' || $concattedContacts || '] ' || sn.output)"; + + $this->select->from( + array('sn' => $this->prefix . 'notifications'), + array() + )->join( + array('so' => $this->prefix . 'objects'), + 'so.object_id = sn.object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + $this->joinedVirtualTables['notifications'] = true; + } + + /** + * Join virtual table history + */ + protected function joinHistory() + { + $this->requireVirtualTable('contactnotifications'); + } + + /** + * Join contact notifications + */ + protected function joinContactnotifications() + { + $this->select->joinLeft( + array('cn' => $this->prefix . 'contactnotifications'), + 'cn.notification_id = sn.notification_id', + array() + ); + $this->select->joinLeft( + array('co' => $this->prefix . 'objects'), + 'co.object_id = cn.contact_object_id AND co.is_active = 1 AND co.objecttype_id = 10', + array() + ); + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = s.host_object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->requireVirtualTable('services'); + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = so.object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->join( + array('s' => $this->prefix . 'services'), + 's.service_object_id = so.object_id', + array() + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = sn.instance_id', + array() + ); + } + + /** + * {@inheritdoc} + */ + public function getGroup() + { + $group = array(); + + if ($this->hasJoinedVirtualTable('history') + || $this->hasJoinedVirtualTable('hostgroups') + || $this->hasJoinedVirtualTable('servicegroups') + ) { + $group = array('sn.notification_id', 'so.object_id'); + if ($this->hasJoinedVirtualTable('contactnotifications') && !$this->hasJoinedVirtualTable('history')) { + $group[] = 'co.object_id'; + } + } elseif ($this->hasJoinedVirtualTable('contactnotifications')) { + $group = array('sn.notification_id', 'co.object_id', 'so.object_id'); + } + + if (! empty($group)) { + if ($this->hasJoinedVirtualTable('hosts')) { + $group[] = 'h.host_id'; + } + + if ($this->hasJoinedVirtualTable('services')) { + $group[] = 's.service_id'; + } + + if ($this->hasJoinedVirtualTable('instances')) { + $group[] = 'i.instance_id'; + } + } + + return $group; + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $this->requireVirtualTable('services'); + + $query->joinVirtualTable('members'); + + return ['hgm.host_object_id', 's.host_object_id']; + } elseif ($name === 'servicegroup') { + $query->joinVirtualTable('members'); + + return ['sgm.service_object_id', 'so.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatehistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatehistoryQuery.php new file mode 100644 index 0000000..f93ca8a --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatehistoryQuery.php @@ -0,0 +1,220 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; + +/** + * Query for service state history records + */ +class ServicestatehistoryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + /** + * {@inheritdoc} + */ + protected $groupBase = array('statehistory' => array('sh.statehistory_id', 'so.object_id')); + + /** + * {@inheritdoc} + */ + protected $groupOrigin = array('hostgroups', 'servicegroups'); + + /** + * Array to map type names to type ids for query optimization + * + * @var array + */ + protected $types = array( + 'soft_state' => 0, + 'hard_state' => 1 + ); + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_alias' => 'h.alias', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci' + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'services' => array( + 'service_display_name' => 's.display_name COLLATE latin1_general_ci' + ), + 'statehistory' => array( + 'id' => 'sh.statehistory_id', + 'host' => 'so.name1 COLLATE latin1_general_ci', + 'host_name' => 'so.name1', + 'object_id' => 'sh.object_id', + 'object_type' => '(\'service\')', + 'output' => '(CASE WHEN sh.state_type = 1 THEN sh.output ELSE \'[ \' || sh.current_check_attempt || \'/\' || sh.max_check_attempts || \' ] \' || sh.output END)', + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_description' => 'so.name2', + 'service_host' => 'so.name1 COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1', + 'state' => 'sh.state', + 'timestamp' => 'UNIX_TIMESTAMP(sh.state_time)', + 'type' => "(CASE WHEN sh.state_type = 1 THEN 'hard_state' ELSE 'soft_state' END)" + ), + ); + + protected function requireFilterColumns(Filter $filter) + { + if ($filter instanceof FilterExpression) { + switch ($filter->getColumn()) { + case 'timestamp': + $this->requireColumn('timestamp'); + $filter->setColumn('sh.state_time'); + $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression()))); + return null; + case 'type': + if (! is_array($filter->getExpression())) { + $this->requireColumn('type'); + $filter->setColumn('sh.state_type'); + if (isset($this->types[$filter->getExpression()])) { + $filter->setExpression($this->types[$filter->getExpression()]); + } else { + $filter->setExpression(-1); + } + + return null; + } + } + } + + return parent::requireFilterColumns($filter); + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->select->from( + array('sh' => $this->prefix . 'statehistory'), + array() + )->join( + array('so' => $this->prefix . 'objects'), + 'so.object_id = sh.object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + $this->joinedVirtualTables['statehistory'] = true; + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->requireVirtualTable('services'); + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = s.host_object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->requireVirtualTable('services'); + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = sh.instance_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = so.object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join services + */ + protected function joinServices() + { + $this->select->join( + array('s' => $this->prefix . 'services'), + 's.service_object_id = so.object_id', + array() + ); + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $query->joinVirtualTable('services'); + + return ['so.object_id', 'so.object_id']; + } elseif ($name === 'servicegroup') { + $query->joinVirtualTable('members'); + + return ['sgm.service_object_id', 'so.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatusQuery.php new file mode 100644 index 0000000..fafa03b --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatusQuery.php @@ -0,0 +1,524 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Query for service status + */ +class ServicestatusQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $allowCustomVars = true; + + /** + * {@inheritdoc} + */ + protected $groupBase = array('services' => array('so.object_id', 's.service_id')); + + /** + * {@inheritdoc} + */ + protected $groupOrigin = array('hostgroups', 'servicegroups', 'contacts', 'contactgroups'); + + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup' + ); + + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'checktimeperiods' => array( + 'service_check_timeperiod' => 'ctp.alias COLLATE latin1_general_ci' + ), + 'contacts' => [ + 'service_contact' => 'sco.name1' + ], + 'contactgroups' => [ + 'service_contactgroup' => 'scgo.name1' + ], + 'hostcontacts' => [ + 'host_contact' => 'hco.name1' + ], + 'hostcontactgroups' => [ + 'host_contactgroup' => 'hcgo.name1' + ], + 'hostgroups' => array( + 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hgo.name1' + ), + 'hosts' => array( + 'host_action_url' => 'h.action_url', + 'host_address' => 'h.address', + 'host_address6' => 'h.address6', + 'host_alias' => 'h.alias COLLATE latin1_general_ci', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci', + 'host_icon_image' => 'h.icon_image', + 'host_icon_image_alt' => 'h.icon_image_alt', + 'host_ipv4' => 'INET_ATON(h.address)', + 'host_notes' => 'h.notes', + 'host_notes_url' => 'h.notes_url' + ), + 'hoststatus' => array( + 'host_acknowledged' => 'hs.problem_has_been_acknowledged', + 'host_acknowledgement_type' => 'hs.acknowledgement_type', + 'host_active_checks_enabled' => 'hs.active_checks_enabled', + 'host_active_checks_enabled_changed' => 'CASE WHEN hs.active_checks_enabled = h.active_checks_enabled THEN 0 ELSE 1 END', + 'host_attempt' => 'hs.current_check_attempt || \'/\' || hs.max_check_attempts', + 'host_check_command' => 'hs.check_command', + 'host_check_execution_time' => 'hs.execution_time', + 'host_check_latency' => 'hs.latency', + 'host_check_source' => 'hs.check_source', + 'host_check_timeperiod_object_id' => 'hs.check_timeperiod_object_id', + 'host_check_type' => 'hs.check_type', + 'host_current_check_attempt' => 'hs.current_check_attempt', + 'host_current_notification_number' => 'hs.current_notification_number', + 'host_event_handler' => 'hs.event_handler', + 'host_event_handler_enabled' => 'hs.event_handler_enabled', + 'host_event_handler_enabled_changed' => 'CASE WHEN hs.event_handler_enabled = h.event_handler_enabled THEN 0 ELSE 1 END', + 'host_failure_prediction_enabled' => 'hs.failure_prediction_enabled', + 'host_flap_detection_enabled' => 'hs.flap_detection_enabled', + 'host_flap_detection_enabled_changed' => 'CASE WHEN hs.flap_detection_enabled = h.flap_detection_enabled THEN 0 ELSE 1 END', + 'host_handled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END', + 'host_hard_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE CASE WHEN hs.state_type = 1 THEN hs.current_state ELSE hs.last_hard_state END END', + 'host_in_downtime' => 'CASE WHEN (hs.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END', + 'host_is_flapping' => 'hs.is_flapping', + 'host_is_reachable' => 'hs.is_reachable', + 'host_last_check' => 'UNIX_TIMESTAMP(hs.last_check)', + 'host_last_hard_state' => 'hs.last_hard_state', + 'host_last_hard_state_change' => 'UNIX_TIMESTAMP(hs.last_hard_state_change)', + 'host_last_notification' => 'UNIX_TIMESTAMP(hs.last_notification)', + 'host_last_state_change' => 'UNIX_TIMESTAMP(hs.last_state_change)', + 'host_last_time_down' => 'UNIX_TIMESTAMP(hs.last_time_down)', + 'host_last_time_unreachable' => 'UNIX_TIMESTAMP(hs.last_time_unreachable)', + 'host_last_time_up' => 'UNIX_TIMESTAMP(hs.last_time_up)', + 'host_long_output' => 'hs.long_output', + 'host_max_check_attempts' => 'hs.max_check_attempts', + 'host_modified_host_attributes' => 'hs.modified_host_attributes', + 'host_next_check' => 'CASE hs.should_be_scheduled WHEN 1 THEN UNIX_TIMESTAMP(hs.next_check) ELSE NULL END', + 'host_next_notification' => 'UNIX_TIMESTAMP(hs.next_notification)', + 'host_no_more_notifications' => 'hs.no_more_notifications', + 'host_normal_check_interval' => 'hs.normal_check_interval', + 'host_notifications_enabled' => 'hs.notifications_enabled', + 'host_notifications_enabled_changed' => 'CASE WHEN hs.notifications_enabled = h.notifications_enabled THEN 0 ELSE 1 END', + 'host_obsessing' => 'hs.obsess_over_host', + 'host_obsessing_changed' => 'CASE WHEN hs.obsess_over_host = h.obsess_over_host THEN 0 ELSE 1 END', + 'host_output' => 'hs.output', + 'host_passive_checks_enabled' => 'hs.passive_checks_enabled', + 'host_passive_checks_enabled_changed' => 'CASE WHEN hs.passive_checks_enabled = h.passive_checks_enabled THEN 0 ELSE 1 END', + 'host_percent_state_change' => 'hs.percent_state_change', + 'host_perfdata' => 'hs.perfdata', + 'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END', + 'host_problem_has_been_acknowledged' => 'hs.problem_has_been_acknowledged', + 'host_process_performance_data' => 'hs.process_performance_data', + 'host_retry_check_interval' => 'hs.retry_check_interval', + 'host_scheduled_downtime_depth' => 'hs.scheduled_downtime_depth', + 'host_severity' => ' + CASE + WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL + THEN 16 + ELSE + CASE + WHEN hs.current_state = 0 + THEN 1 + ELSE + CASE + WHEN hs.current_state = 1 THEN 64 + WHEN hs.current_state = 2 THEN 32 + ELSE 256 + END + + + CASE + WHEN hs.problem_has_been_acknowledged = 1 THEN 2 + WHEN hs.scheduled_downtime_depth > 0 THEN 1 + ELSE 256 + END + END + 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_state_type' => 'hs.state_type', + 'host_status_update_time' => 'hs.status_update_time', + 'host_unhandled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) = 0 THEN 1 ELSE 0 END' + + ), + 'instances' => array( + 'instance_name' => 'i.instance_name' + ), + 'services' => array( + 'host' => 'so.name1 COLLATE latin1_general_ci', + 'host_name' => 'so.name1', + 'object_type' => '(\'service\')', + 'service' => 'so.name2 COLLATE latin1_general_ci', + 'service_action_url' => 's.action_url', + 'service_check_interval' => '(s.check_interval * 60)', + 'service_description' => 'so.name2', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', + 'service_host' => 'so.name1 COLLATE latin1_general_ci', + 'service_host_name' => 'so.name1', + 'service_icon_image' => 's.icon_image', + 'service_icon_image_alt' => 's.icon_image_alt', + 'service_notes_url' => 's.notes_url', + 'service_notes' => 's.notes' + ), + 'servicegroups' => array( + 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', + 'servicegroup_name' => 'sgo.name1', + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' + ), + 'servicestatus' => array( + 'service_acknowledged' => 'ss.problem_has_been_acknowledged', + 'service_acknowledgement_type' => 'ss.acknowledgement_type', + 'service_active_checks_enabled' => 'ss.active_checks_enabled', + 'service_active_checks_enabled_changed' => 'CASE WHEN ss.active_checks_enabled=s.active_checks_enabled THEN 0 ELSE 1 END', + 'service_attempt' => 'ss.current_check_attempt || \'/\' || ss.max_check_attempts', + 'service_check_command' => 'ss.check_command', + 'service_check_execution_time' => 'ss.execution_time', + 'service_check_latency' => 'ss.latency', + 'service_check_source' => 'ss.check_source', + 'service_check_timeperiod_object_id' => 'ss.check_timeperiod_object_id', + 'service_check_type' => 'ss.check_type', + 'service_current_check_attempt' => 'ss.current_check_attempt', + 'service_current_notification_number' => 'ss.current_notification_number', + 'service_event_handler' => 'ss.event_handler', + 'service_event_handler_enabled' => 'ss.event_handler_enabled', + 'service_event_handler_enabled_changed' => 'CASE WHEN ss.event_handler_enabled=s.event_handler_enabled THEN 0 ELSE 1 END', + 'service_failure_prediction_enabled' => 'ss.failure_prediction_enabled', + 'service_flap_detection_enabled' => 'ss.flap_detection_enabled', + 'service_flap_detection_enabled_changed' => 'CASE WHEN ss.flap_detection_enabled=s.flap_detection_enabled THEN 0 ELSE 1 END', + 'service_handled' => 'CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) > 0 THEN 1 ELSE 0 END', + 'service_hard_state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE CASE WHEN ss.state_type = 1 THEN ss.current_state ELSE ss.last_hard_state END END', + 'service_in_downtime' => 'CASE WHEN (ss.scheduled_downtime_depth = 0 OR ss.scheduled_downtime_depth IS NULL) THEN 0 ELSE 1 END', + 'service_is_flapping' => 'ss.is_flapping', + 'service_is_passive_checked' => 'CASE WHEN ss.active_checks_enabled = 0 AND ss.passive_checks_enabled = 1 THEN 1 ELSE 0 END', + 'service_is_reachable' => 'ss.is_reachable', + 'service_last_check' => 'UNIX_TIMESTAMP(ss.last_check)', + 'service_last_hard_state' => 'ss.last_hard_state', + 'service_last_hard_state_change' => 'UNIX_TIMESTAMP(ss.last_hard_state_change)', + 'service_last_notification' => 'UNIX_TIMESTAMP(ss.last_notification)', + 'service_last_state_change' => 'UNIX_TIMESTAMP(ss.last_state_change)', + 'service_last_state_change_ts' => 'ss.last_state_change', + 'service_last_time_critical' => 'ss.last_time_critical', + 'service_last_time_ok' => 'ss.last_time_ok', + 'service_last_time_unknown' => 'ss.last_time_unknown', + 'service_last_time_warning' => 'ss.last_time_warning', + 'service_long_output' => 'ss.long_output', + 'service_max_check_attempts' => 'ss.max_check_attempts', + 'service_modified_service_attributes' => 'ss.modified_service_attributes', + 'service_next_check' => 'UNIX_TIMESTAMP(ss.next_check)', + 'service_next_notification' => 'UNIX_TIMESTAMP(ss.next_notification)', + 'service_next_update' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL + THEN + CASE ss.should_be_scheduled WHEN 1 THEN UNIX_TIMESTAMP(ss.next_check) + (ss.normal_check_interval * 60) ELSE NULL END + ELSE + UNIX_TIMESTAMP(ss.next_check) + + (CASE WHEN + COALESCE(ss.current_state, 0) > 0 AND ss.state_type = 0 + THEN + ss.retry_check_interval + ELSE + ss.normal_check_interval + END * 60) + + (CEIL(ss.execution_time + ss.latency) * 2) + END', + 'service_no_more_notifications' => 'ss.no_more_notifications', + 'service_normal_check_interval' => 'ss.normal_check_interval', + 'service_notifications_enabled' => 'ss.notifications_enabled', + 'service_notifications_enabled_changed' => 'CASE WHEN ss.notifications_enabled=s.notifications_enabled THEN 0 ELSE 1 END', + 'service_obsessing' => 'ss.obsess_over_service', + 'service_obsessing_changed' => 'CASE WHEN ss.obsess_over_service=s.obsess_over_service THEN 0 ELSE 1 END', + 'service_output' => 'ss.output', + 'service_passive_checks_enabled' => 'ss.passive_checks_enabled', + 'service_passive_checks_enabled_changed' => 'CASE WHEN ss.passive_checks_enabled=s.passive_checks_enabled THEN 0 ELSE 1 END', + 'service_percent_state_change' => 'ss.percent_state_change', + 'service_perfdata' => 'ss.perfdata', + 'service_problem' => 'CASE WHEN COALESCE(ss.current_state, 0) = 0 THEN 0 ELSE 1 END', + 'service_problem_has_been_acknowledged' => 'ss.problem_has_been_acknowledged', + 'service_process_performance_data' => 'ss.process_performance_data', + 'service_retry_check_interval' => 'ss.retry_check_interval', + 'service_scheduled_downtime_depth' => 'ss.scheduled_downtime_depth', + 'service_severity' => 'CASE WHEN ss.current_state = 0 + THEN + CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL + THEN 16 + ELSE 0 + END + + + CASE WHEN ss.problem_has_been_acknowledged = 1 + THEN 2 + ELSE + CASE WHEN ss.scheduled_downtime_depth > 0 + THEN 1 + ELSE 4 + END + END + ELSE + CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 16 + WHEN ss.current_state = 1 THEN 32 + WHEN ss.current_state = 2 THEN 128 + WHEN ss.current_state = 3 THEN 64 + ELSE 256 + END + + + CASE WHEN hs.current_state > 0 + THEN 1024 + ELSE + CASE WHEN ss.problem_has_been_acknowledged = 1 + THEN 512 + ELSE + CASE WHEN ss.scheduled_downtime_depth > 0 + THEN 256 + ELSE 2048 + END + END + END + END', + 'service_state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE ss.current_state END', + 'service_state_type' => 'ss.state_type', + 'service_status_update_time' => 'ss.status_update_time', + 'service_unhandled' => 'CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) = 0 THEN 1 ELSE 0 END', + 'problems' => 'CASE WHEN COALESCE(ss.current_state, 0) = 0 THEN 0 ELSE 1 END' + ) + ); + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + if (version_compare($this->getIdoVersion(), '1.10.0', '<')) { + $this->columnMap['hoststatus']['host_check_source'] = '(NULL)'; + $this->columnMap['servicestatus']['service_check_source'] = '(NULL)'; + } + if (version_compare($this->getIdoVersion(), '1.13.0', '<')) { + $this->columnMap['hoststatus']['host_is_reachable'] = '(NULL)'; + $this->columnMap['servicestatus']['service_is_reachable'] = '(NULL)'; + } + + $this->select->from( + array('so' => $this->prefix . 'objects'), + array() + )->join( + array('s' => $this->prefix . 'services'), + 's.service_object_id = so.object_id AND so.is_active = 1 AND so.objecttype_id = 2', + array() + ); + $this->joinedVirtualTables['services'] = true; + } + + /** + * Join check time periods + */ + protected function joinChecktimeperiods() + { + $this->select->joinLeft( + array('ctp' => $this->prefix . 'timeperiods'), + 'ctp.timeperiod_object_id = s.check_timeperiod_object_id', + array() + ); + } + + /** + * Join contacts + */ + protected function joinContacts() + { + $this->select->joinLeft( + ['sc' => 'icinga_service_contacts'], + 'sc.service_id = s.service_id', + [] + )->joinLeft( + ['sco' => 'icinga_objects'], + 'sco.object_id = sc.contact_object_id AND sco.is_active = 1 AND sco.objecttype_id = 10', + [] + ); + } + + /** + * Join contact groups + */ + protected function joinContactgroups() + { + $this->select->joinLeft( + ['scg' => 'icinga_service_contactgroups'], + 'scg.service_id = s.service_id', + [] + )->joinLeft( + ['scgo' => 'icinga_objects'], + 'scgo.object_id = scg.contactgroup_object_id AND scgo.is_active = 1 AND scgo.objecttype_id = 11', + [] + ); + } + + /** + * Join host contacts + */ + protected function joinHostcontacts() + { + $this->requireVirtualTable('hosts'); + + $this->select->joinLeft( + ['hc' => 'icinga_host_contacts'], + 'hc.host_id = h.host_id', + [] + )->joinLeft( + ['hco' => 'icinga_objects'], + 'hco.object_id = hc.contact_object_id AND hco.is_active = 1 AND hco.objecttype_id = 10', + [] + ); + } + + /** + * Join host contact groups + */ + protected function joinHostcontactgroups() + { + $this->requireVirtualTable('hosts'); + + $this->select->joinLeft( + ['hcg' => 'icinga_host_contactgroups'], + 'hcg.host_id = h.host_id', + [] + )->joinLeft( + ['hcgo' => 'icinga_objects'], + 'hcgo.object_id = hcg.contactgroup_object_id AND hcgo.is_active = 1 AND hcgo.objecttype_id = 11', + [] + ); + } + + /** + * Join host groups + */ + protected function joinHostgroups() + { + $this->select->joinLeft( + array('hgm' => $this->prefix . 'hostgroup_members'), + 'hgm.host_object_id = s.host_object_id', + array() + )->joinLeft( + array('hg' => $this->prefix . 'hostgroups'), + 'hg.hostgroup_id = hgm.hostgroup_id', + array() + )->joinLeft( + array('hgo' => $this->prefix . 'objects'), + 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3', + array() + ); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $this->select->join( + array('h' => $this->prefix . 'hosts'), + 'h.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join host status + */ + protected function joinHoststatus() + { + $this->select->join( + array('hs' => $this->prefix . 'hoststatus'), + 'hs.host_object_id = s.host_object_id', + array() + ); + } + + /** + * Join instances + */ + protected function joinInstances() + { + $this->select->join( + array('i' => $this->prefix . 'instances'), + 'i.instance_id = so.instance_id', + array() + ); + } + + /** + * Join service groups + */ + protected function joinServicegroups() + { + $this->select->joinLeft( + array('sgm' => $this->prefix . 'servicegroup_members'), + 'sgm.service_object_id = so.object_id', + array() + )->joinLeft( + array('sg' => $this->prefix . 'servicegroups'), + 'sg.servicegroup_id = sgm.servicegroup_id', + array() + )->joinLeft( + array('sgo' => $this->prefix . 'objects'), + 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4', + array() + ); + } + + /** + * Join service status + */ + protected function joinServicestatus() + { + $this->requireVirtualTable('hoststatus'); + $this->select->join( + array('ss' => $this->prefix . 'servicestatus'), + 'ss.service_object_id = so.object_id', + array() + ); + } + + /** + * {@inheritdoc} + */ + protected function registerGroupColumns($alias, $table, array &$groupedColumns, array &$groupedTables) + { + if ($alias === 'service_handled' || $alias === 'service_severity' || $alias === 'service_unhandled') { + if (! isset($groupedTables['hoststatus'])) { + $groupedColumns[] = 'hs.hoststatus_id'; + $groupedTables['hoststatus'] = true; + } + + if (! isset($groupedTables['servicestatus'])) { + $groupedColumns[] = 'ss.servicestatus_id'; + $groupedTables['servicestatus'] = true; + } + } elseif ($table === 'contacts') { + $groupedColumns[] = 'sc.service_contact_id'; + $groupedColumns[] = 'sco.object_id'; + $groupedTables[$table] = true; + } elseif ($table === 'contactgroups') { + $groupedColumns[] = 'scg.service_contactgroup_id'; + $groupedColumns[] = 'scgo.object_id'; + $groupedTables[$table] = true; + } else { + parent::registerGroupColumns($alias, $table, $groupedColumns, $groupedTables); + } + } + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'hostgroup') { + $query->joinVirtualTable('members'); + + return ['hgm.host_object_id', 's.host_object_id']; + } elseif ($name === 'servicegroup') { + $query->joinVirtualTable('members'); + + return ['sgm.service_object_id', 'so.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatussummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatussummaryQuery.php new file mode 100644 index 0000000..4455c3f --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatussummaryQuery.php @@ -0,0 +1,104 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; + +/** + * Query for service status summary + * + * TODO(el): Allow to switch between hard and soft states + */ +class ServicestatussummaryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'servicestatussummary' => array( + 'services_critical' => 'SUM(CASE WHEN state = 2 THEN 1 ELSE 0 END)', + 'services_critical_handled' => 'SUM(CASE WHEN state = 2 AND handled = 1 THEN 1 ELSE 0 END)', +// 'services_critical_handled_last_state_change' => 'MAX(CASE WHEN state = 2 AND handled = 1 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)', + 'services_critical_unhandled' => 'SUM(CASE WHEN state = 2 AND handled = 0 THEN 1 ELSE 0 END)', +// 'services_critical_unhandled_last_state_change' => 'MAX(CASE WHEN state = 2 AND handled = 0 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)', + 'services_ok' => 'SUM(CASE WHEN state = 0 THEN 1 ELSE 0 END)', +// 'services_ok_last_state_change' => 'MAX(CASE WHEN state = 0 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)', + 'services_pending' => 'SUM(CASE WHEN state = 99 THEN 1 ELSE 0 END)', +// 'services_pending_last_state_change' => 'MAX(CASE WHEN state = 99 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)', + 'services_total' => 'SUM(1)', + 'services_unknown' => 'SUM(CASE WHEN state = 3 THEN 1 ELSE 0 END)', + 'services_unknown_handled' => 'SUM(CASE WHEN state = 3 AND handled = 1 THEN 1 ELSE 0 END)', +// 'services_unknown_handled_last_state_change' => 'MAX(CASE WHEN state = 3 AND handled = 1 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)', + 'services_unknown_unhandled' => 'SUM(CASE WHEN state = 3 AND handled = 0 THEN 1 ELSE 0 END)', +// 'services_unknown_unhandled_last_state_change' => 'MAX(CASE WHEN state = 3 AND handled = 0 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)', + 'services_warning' => 'SUM(CASE WHEN state = 1 THEN 1 ELSE 0 END)', + 'services_warning_handled' => 'SUM(CASE WHEN state = 1 AND handled = 1 THEN 1 ELSE 0 END)', +// 'services_warning_handled_last_state_change' => 'MAX(CASE WHEN state = 1 AND handled = 1 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)', + 'services_warning_unhandled' => 'SUM(CASE WHEN state = 1 AND handled = 0 THEN 1 ELSE 0 END)', +// 'services_warning_unhandled_last_state_change' => 'MAX(CASE WHEN state = 1 AND handled = 0 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)' + ) + ); + + /** + * The service status sub select + * + * @var ServicestatusQuery + */ + protected $subSelect; + + /** + * {@inheritdoc} + */ + public function allowsCustomVars() + { + return $this->subSelect->allowsCustomVars(); + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + $this->subSelect->applyFilter(clone $filter); + return $this; + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + // TODO(el): Allow to switch between hard and soft states + $this->subSelect = $this->createSubQuery( + 'servicestatus', + array( + 'handled' => 'service_handled', + 'state' => 'service_state', + 'state_change' => 'service_last_state_change' + ) + ); + $this->select->from( + array('servicestatussummary' => $this->subSelect->setIsSubQuery(true)), + array() + ); + $this->joinedVirtualTables['servicestatussummary'] = true; + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->subSelect->where($condition, $value); + return $this; + } + + public function whereEx(FilterExpression $ex) + { + $this->subSelect->whereEx($ex); + + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatechangeeventQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatechangeeventQuery.php new file mode 100644 index 0000000..18d893f --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatechangeeventQuery.php @@ -0,0 +1,41 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +/** + * Query for host and service state change events + */ +class StatechangeeventQuery extends IdoQuery +{ + protected $columnMap = array( + 'statechangeevent' => array( + 'statechangeevent_id' => 'sh.statehistory_id', + 'statechangeevent_state_time' => 'UNIX_TIMESTAMP(sh.state_time)', + 'statechangeevent_state_change' => 'sh.state_change', + 'statechangeevent_state' => 'sh.state', + 'statechangeevent_state_type' => "(CASE sh.state_type WHEN 0 THEN 'soft_state' WHEN 1 THEN 'hard_state' ELSE NULL END)", + 'statechangeevent_current_check_attempt' => 'sh.current_check_attempt', + 'statechangeevent_max_check_attempts' => 'sh.max_check_attempts', + 'statechangeevent_last_state' => 'sh.last_state', + 'statechangeevent_last_hard_state' => 'sh.last_hard_state', + 'statechangeevent_output' => 'sh.output', + 'statechangeevent_long_output' => 'sh.long_output', + 'statechangeevent_check_source' => 'sh.check_source' + ), + 'object' => array( + 'host_name' => 'o.name1', + 'service_description' => 'o.name2' + ) + ); + + protected function joinBaseTables() + { + $this->select() + ->from(array('sh' => $this->prefix . 'statehistory'), array()) + ->join(array('o' => $this->prefix . 'objects'), 'sh.object_id = o.object_id', array()); + + $this->joinedVirtualTables['statechangeevent'] = true; + $this->joinedVirtualTables['object'] = true; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatehistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatehistoryQuery.php new file mode 100644 index 0000000..56d1e3b --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatehistoryQuery.php @@ -0,0 +1,179 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\FilterExpression; +use Zend_Db_Expr; +use Zend_Db_Select; +use Icinga\Data\Filter\Filter; + +/** + * Query for host and service state history records + */ +class StatehistoryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'statehistory' => array( + 'id' => 'sth.id', + 'object_type' => 'sth.object_type' + ), + 'history' => array( + 'type' => 'sth.type', + 'timestamp' => 'sth.timestamp', + 'object_id' => 'sth.object_id', + 'state' => 'sth.state', + 'output' => 'sth.output' + ), + 'hosts' => array( + 'host_display_name' => 'sth.host_display_name', + 'host_name' => 'sth.host_name' + ), + 'services' => array( + 'service_description' => 'sth.service_description', + 'service_display_name' => 'sth.service_display_name', + 'service_host_name' => 'sth.service_host_name' + ) + ); + + /** + * The union + * + * @var Zend_Db_Select + */ + protected $stateHistoryQuery; + + /** + * Subqueries used for the state history query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * Whether to additionally select all history columns + * + * @var bool + */ + protected $fetchHistoryColumns = false; + + /** + * {@inheritdoc} + */ + public function allowsCustomVars() + { + foreach ($this->subQueries as $query) { + if (! $query->allowsCustomVars()) { + return false; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + $this->stateHistoryQuery = $this->db->select(); + $this->select->from( + array('sth' => $this->stateHistoryQuery), + array() + ); + $this->joinedVirtualTables['statehistory'] = true; + } + + /** + * Join history related columns and tables + */ + protected function joinHistory() + { + // TODO: Ensure that one is selecting the history columns first... + $this->fetchHistoryColumns = true; + $this->requireVirtualTable('hosts'); + $this->requireVirtualTable('services'); + } + + /** + * Join hosts + */ + protected function joinHosts() + { + $columns = array_keys( + $this->columnMap['statehistory'] + $this->columnMap['hosts'] + ); + foreach ($this->columnMap['services'] as $column => $_) { + $columns[$column] = new Zend_Db_Expr('NULL'); + } + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $hosts = $this->createSubQuery('Hoststatehistory', $columns); + $this->subQueries[] = $hosts; + $this->stateHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * Join services + */ + protected function joinServices() + { + $columns = array_keys( + $this->columnMap['statehistory'] + $this->columnMap['hosts'] + $this->columnMap['services'] + ); + if ($this->fetchHistoryColumns) { + $columns = array_merge($columns, array_keys($this->columnMap['history'])); + } + $services = $this->createSubQuery('Servicestatehistory', $columns); + $this->subQueries[] = $services; + $this->stateHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL); + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } + + public function whereEx(FilterExpression $ex) + { + $this->requireColumn($ex->getColumn()); + foreach ($this->subQueries as $sub) { + $sub->whereEx($ex); + } + + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatussummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatussummaryQuery.php new file mode 100644 index 0000000..b1ee9e2 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatussummaryQuery.php @@ -0,0 +1,243 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\FilterExpression; +use Zend_Db_Expr; +use Zend_Db_Select; +use Icinga\Data\Filter\Filter; + +/** + * Query for host and service status summary + */ +class StatussummaryQuery extends IdoQuery +{ + /** + * {@inheritdoc} + */ + protected $columnMap = array( + 'hoststatussummary' => array( + 'hosts_total' => 'SUM(CASE WHEN object_type = \'host\' THEN 1 ELSE 0 END)', + 'hosts_up' => 'SUM(CASE WHEN object_type = \'host\' AND state = 0 THEN 1 ELSE 0 END)', + 'hosts_up_not_checked' => 'SUM(CASE WHEN object_type = \'host\' AND state = 0 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', + 'hosts_pending' => 'SUM(CASE WHEN object_type = \'host\' AND state = 99 THEN 1 ELSE 0 END)', + 'hosts_pending_not_checked' => 'SUM(CASE WHEN object_type = \'host\' AND state = 99 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', + 'hosts_down' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 THEN 1 ELSE 0 END)', + 'hosts_down_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND handled > 0 THEN 1 ELSE 0 END)', + 'hosts_down_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND handled = 0 THEN 1 ELSE 0 END)', + 'hosts_down_passive' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', + 'hosts_down_not_checked' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', + 'hosts_unreachable' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 THEN 1 ELSE 0 END)', + 'hosts_unreachable_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND handled > 0 THEN 1 ELSE 0 END)', + 'hosts_unreachable_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND handled = 0 THEN 1 ELSE 0 END)', + 'hosts_unreachable_passive' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', + 'hosts_unreachable_not_checked' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', + 'hosts_active' => 'SUM(CASE WHEN object_type = \'host\' AND is_active_checked = 1 THEN 1 ELSE 0 END)', + 'hosts_passive' => 'SUM(CASE WHEN object_type = \'host\' AND is_passive_checked = 1 THEN 1 ELSE 0 END)', + 'hosts_not_checked' => 'SUM(CASE WHEN object_type = \'host\' AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', + 'hosts_not_processing_event_handlers' => 'SUM(CASE WHEN object_type = \'host\' AND is_processing_events = 0 THEN 1 ELSE 0 END)', + 'hosts_not_triggering_notifications' => 'SUM(CASE WHEN object_type = \'host\' AND is_triggering_notifications = 0 THEN 1 ELSE 0 END)', + 'hosts_without_flap_detection' => 'SUM(CASE WHEN object_type = \'host\' AND is_allowed_to_flap = 0 THEN 1 ELSE 0 END)', + '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 handled + host_problem > 0 THEN 1 ELSE 0 END)', + 'services_problem_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 AND handled + 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 handled + host_problem > 0 THEN 1 ELSE 0 END)', + 'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND handled + 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 handled + host_problem > 0 THEN 1 ELSE 0 END)', + 'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND handled + 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 handled + host_problem > 0 THEN 1 ELSE 0 END)', + 'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND handled + 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)', + 'services_passive' => 'SUM(CASE WHEN object_type = \'service\' AND is_passive_checked = 1 THEN 1 ELSE 0 END)', + 'services_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', + 'services_not_processing_event_handlers' => 'SUM(CASE WHEN object_type = \'service\' AND is_processing_events = 0 THEN 1 ELSE 0 END)', + 'services_not_triggering_notifications' => 'SUM(CASE WHEN object_type = \'service\' AND is_triggering_notifications = 0 THEN 1 ELSE 0 END)', + 'services_without_flap_detection' => 'SUM(CASE WHEN object_type = \'service\' AND is_allowed_to_flap = 0 THEN 1 ELSE 0 END)', + 'services_flapping' => 'SUM(CASE WHEN object_type = \'service\' AND is_flapping = 1 THEN 1 ELSE 0 END)', + +/* +NOTE: in case you might wonder, please see #7303. As a quickfix I did: + +:%s/(host_state = 0 OR host_state = 99)/host_state != 1 AND host_state != 2/g +:%s/(host_state = 1 OR host_state = 2)/host_state != 0 AND host_state != 99/g + +We have to find a better solution here. + +*/ + 'services_ok_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 0 THEN 1 ELSE 0 END)', + 'services_ok_not_checked_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 0 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', + 'services_pending_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 99 THEN 1 ELSE 0 END)', + 'services_pending_not_checked_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 99 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', + 'services_warning_handled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 1 AND handled > 0 THEN 1 ELSE 0 END)', + 'services_warning_unhandled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 1 AND handled = 0 THEN 1 ELSE 0 END)', + 'services_warning_passive_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 1 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', + 'services_warning_not_checked_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 1 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', + 'services_critical_handled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 2 AND handled > 0 THEN 1 ELSE 0 END)', + 'services_critical_unhandled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 2 AND handled = 0 THEN 1 ELSE 0 END)', + 'services_critical_passive_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 2 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', + 'services_critical_not_checked_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 2 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', + 'services_unknown_handled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 3 AND handled > 0 THEN 1 ELSE 0 END)', + 'services_unknown_unhandled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 3 AND handled = 0 THEN 1 ELSE 0 END)', + 'services_unknown_passive_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 3 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', + 'services_unknown_not_checked_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 3 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', + 'services_ok_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 0 THEN 1 ELSE 0 END)', + 'services_ok_not_checked_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 0 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', + 'services_pending_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 99 THEN 1 ELSE 0 END)', + 'services_pending_not_checked_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 99 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', + 'services_warning_handled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 1 AND handled > 0 THEN 1 ELSE 0 END)', + 'services_warning_unhandled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 1 AND handled = 0 THEN 1 ELSE 0 END)', + 'services_warning_passive_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 1 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', + 'services_warning_not_checked_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 1 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', + 'services_critical_handled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 2 AND handled > 0 THEN 1 ELSE 0 END)', + 'services_critical_unhandled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 2 AND handled = 0 THEN 1 ELSE 0 END)', + 'services_critical_passive_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 2 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', + 'services_critical_not_checked_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 2 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', + 'services_unknown_handled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 3 AND handled > 0 THEN 1 ELSE 0 END)', + 'services_unknown_unhandled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 3 AND handled = 0 THEN 1 ELSE 0 END)', + 'services_unknown_passive_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 3 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', + 'services_unknown_not_checked_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 3 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)' + ) + ); + + /** + * The union + * + * @var Zend_Db_Select + */ + protected $summaryQuery; + + /** + * Subqueries used for the summary query + * + * @var IdoQuery[] + */ + protected $subQueries = array(); + + /** + * {@inheritdoc} + */ + public function allowsCustomVars() + { + foreach ($this->subQueries as $query) { + if (! $query->allowsCustomVars()) { + return false; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + foreach ($this->subQueries as $sub) { + $sub->applyFilter(clone $filter); + } + return $this; + } + + /** + * {@inheritdoc} + */ + protected function joinBaseTables() + { + // TODO(el): Allow to switch between hard and soft states + $hosts = $this->createSubQuery( + 'Hoststatus', + array( + 'handled' => 'host_handled', + 'host_problem', + 'host_state' => new Zend_Db_Expr('NULL'), + 'is_active_checked' => 'host_active_checks_enabled', + 'is_allowed_to_flap' => 'host_flap_detection_enabled', + 'is_flapping' => 'host_is_flapping', + 'is_passive_checked' => 'host_is_passive_checked', + 'is_processing_events' => 'host_event_handler_enabled', + 'is_triggering_notifications' => 'host_notifications_enabled', + 'object_type', + 'severity' => 'host_severity', + 'state_change' => 'host_last_state_change', + 'state' => 'host_state' + ) + ); + $this->subQueries[] = $hosts; + $services = $this->createSubQuery( + 'Servicestatus', + array( + 'handled' => 'service_handled', + 'host_problem', + 'host_state' => 'host_hard_state', + 'is_active_checked' => 'service_active_checks_enabled', + 'is_allowed_to_flap' => 'service_flap_detection_enabled', + 'is_flapping' => 'service_is_flapping', + 'is_passive_checked' => 'service_is_passive_checked', + 'is_processing_events' => 'service_event_handler_enabled', + 'is_triggering_notifications' => 'service_notifications_enabled', + 'object_type', + 'severity' => 'service_severity', + 'state_change' => 'service_last_state_change', + 'state' => 'service_state' + ) + ); + $this->subQueries[] = $services; + $this->summaryQuery = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL); + $this->select->from(array('statussummary' => $this->summaryQuery), array()); + $this->joinedVirtualTables['hoststatussummary'] = true; + $this->joinedVirtualTables['servicestatussummary'] = true; + } + + /** + * {@inheritdoc} + */ + public function order($columnOrAlias, $dir = null) + { + if (! $this->hasAliasName($columnOrAlias)) { + foreach ($this->subQueries as $sub) { + $sub->requireColumn($columnOrAlias); + } + } + return parent::order($columnOrAlias, $dir); + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + $this->requireColumn($condition); + foreach ($this->subQueries as $sub) { + $sub->where($condition, $value); + } + return $this; + } + + public function whereEx(FilterExpression $ex) + { + $this->requireColumn($ex->getColumn()); + foreach ($this->subQueries as $sub) { + $sub->whereEx($ex); + } + + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/UnhandledhostproblemsQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/UnhandledhostproblemsQuery.php new file mode 100644 index 0000000..f4c4e07 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/UnhandledhostproblemsQuery.php @@ -0,0 +1,48 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; + +/** + * Query for unhandled host problems + */ +class UnhandledhostproblemsQuery extends IdoQuery +{ + protected $allowCustomVars = true; + + protected $columnMap = array( + 'problems' => array( + 'hosts_down_unhandled' => 'COUNT(*)', + ) + ); + + /** + * The service status sub select + * + * @var HoststatusQuery + */ + protected $subSelect; + + public function addFilter(Filter $filter) + { + $this->subSelect->applyFilter(clone $filter); + return $this; + } + + protected function joinBaseTables() + { + $this->subSelect = $this->createSubQuery( + 'Hoststatus', + array('host_name') + ); + $this->subSelect->where('host_handled', 0); + $this->subSelect->where('host_state', 1); + $this->select->from( + array('problems' => $this->subSelect->setIsSubQuery(true)), + array() + ); + $this->joinedVirtualTables['problems'] = true; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/UnhandledserviceproblemsQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/UnhandledserviceproblemsQuery.php new file mode 100644 index 0000000..a218caf --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/UnhandledserviceproblemsQuery.php @@ -0,0 +1,48 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend\Ido\Query; + +use Icinga\Data\Filter\Filter; + +/** + * Query for unhandled service problems + */ +class UnhandledserviceproblemsQuery extends IdoQuery +{ + protected $allowCustomVars = true; + + protected $columnMap = array( + 'problems' => array( + 'services_critical_unhandled' => 'COUNT(*)', + ) + ); + + /** + * The service status sub select + * + * @var ServicestatusQuery + */ + protected $subSelect; + + public function addFilter(Filter $filter) + { + $this->subSelect->applyFilter(clone $filter); + return $this; + } + + protected function joinBaseTables() + { + $this->subSelect = $this->createSubQuery( + 'Servicestatus', + array('service_description') + ); + $this->subSelect->where('service_handled', 0); + $this->subSelect->where('service_state', 2); + $this->select->from( + array('problems' => $this->subSelect->setIsSubQuery(true)), + array() + ); + $this->joinedVirtualTables['problems'] = true; + } +} diff --git a/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php b/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php new file mode 100644 index 0000000..440ffa4 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php @@ -0,0 +1,349 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Backend; + +use Icinga\Application\Config; +use Icinga\Data\ConfigObject; +use Icinga\Data\QueryInterface; +use Icinga\Data\ResourceFactory; +use Icinga\Data\ConnectionInterface; +use Icinga\Data\Queryable; +use Icinga\Data\Selectable; +use Icinga\Exception\ConfigurationError; +use Icinga\Exception\ProgrammingError; + +class MonitoringBackend implements Selectable, Queryable, ConnectionInterface +{ + /** + * Backend configuration + * + * @var ConfigObject + */ + protected $config; + + /** + * Resource + * + * @var mixed + */ + protected $resource; + + /** + * Type + * + * @var string + */ + protected $type; + + /** + * The configured name of this backend + * + * @var string + */ + protected $name; + + /** + * Already created instances + * + * @var array + */ + protected static $instances = array(); + + /** + * Create a new backend + * + * @param string $name + * @param ConfigObject $config + */ + protected function __construct($name, ConfigObject $config) + { + $this->name = $name; + $this->config = $config; + } + + /** + * Get a backend instance + * + * You may ask for a specific backend name or get the default one otherwise + * + * @param string $name Backend name + * + * @return MonitoringBackend + */ + public static function instance($name = null) + { + if (! array_key_exists($name, self::$instances)) { + list($foundName, $config) = static::loadConfig($name); + $type = $config->get('type'); + $class = implode( + '\\', + array( + __NAMESPACE__, + ucfirst($type), + ucfirst($type) . 'Backend' + ) + ); + + if (!class_exists($class)) { + throw new ConfigurationError( + mt('monitoring', 'There is no "%s" monitoring backend'), + $class + ); + } + + self::$instances[$name] = new $class($foundName, $config); + if ($name === null) { + self::$instances[$foundName] = self::$instances[$name]; + } + } + + return self::$instances[$name]; + } + + /** + * Clear all cached instances. Mostly for testing purposes. + */ + public static function clearInstances() + { + self::$instances = array(); + } + + /** + * Whether this backend is of a specific type + * + * @param string $type Backend type + * + * @return boolean + */ + public function is($type) + { + return $this->getType() === $type; + } + + /** + * Get the configured name of this backend + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Get the backend type name + * + * @return string + */ + public function getType() + { + if ($this->type === null) { + $parts = preg_split('~\\\~', get_class($this)); + $class = array_pop($parts); + if (substr($class, -7) === 'Backend') { + $this->type = lcfirst(substr($class, 0, -7)); + } else { + throw new ProgrammingError( + '%s is not a valid monitoring backend class name', + $class + ); + } + } + return $this->type; + } + + /** + * Return the configuration for the first enabled or the given backend + */ + protected static function loadConfig($name = null) + { + $backends = Config::module('monitoring', 'backends'); + + if ($name === null) { + $count = 0; + + foreach ($backends as $name => $config) { + $count++; + if ((bool) $config->get('disabled', false) === false) { + return array($name, $config); + } + } + + if ($count === 0) { + $message = mt('monitoring', 'No backend has been configured'); + } else { + $message = mt('monitoring', 'All backends are disabled'); + } + + throw new ConfigurationError($message); + } else { + $config = $backends->getSection($name); + + if ($config->isEmpty()) { + throw new ConfigurationError( + mt('monitoring', 'No configuration for backend %s'), + $name + ); + } + + if ((bool) $config->get('disabled', false) === true) { + throw new ConfigurationError( + mt('monitoring', 'Configuration for backend %s is disabled'), + $name + ); + } + + return array($name, $config); + } + } + + /** + * Get this backend's internal resource + * + * @return mixed + */ + public function getResource() + { + if ($this->resource === null) { + $config = ResourceFactory::getResourceConfig($this->config->get('resource')); + if ($this->is('ido') && $config->type === 'db' && $config->db === 'mysql' && $config->charset === null) { + $config->charset = 'latin1'; + } + $this->resource = ResourceFactory::createResource($config); + if ($this->is('ido') && $this->resource->getDbType() !== 'oracle') { + // TODO(el): The resource should set the table prefix + $this->resource->setTablePrefix('icinga_'); + } + } + return $this->resource; + } + + /** + * Backend entry point + * + * @return $this + */ + public function select() + { + return $this; + } + + /** + * Create a data view to fetch data from + * + * @param string $name + * @param array $columns + * + * @return \Icinga\Module\Monitoring\DataView\DataView + */ + public function from($name, array $columns = null) + { + $class = $this->buildViewClassName($name); + return new $class($this, $columns); + } + + /** + * View name to class name resolution + * + * @param string $view + * + * @return string + * + * @throws ProgrammingError In case the view does not exist + */ + protected function buildViewClassName($view) + { + $class = ucfirst(strtolower($view)); + $classPath = '\\Icinga\\Module\\Monitoring\\DataView\\' . $class; + if (! class_exists($classPath)) { + throw new ProgrammingError('DataView %s does not exist', $class); + } + + return $classPath; + } + + /** + * Get a specific query class instance + * + * @param string $name Query name + * @param array $columns Optional column list + * + * @return QueryInterface + * + * @throws ProgrammingError When the query does not exist for this backend + */ + public function query($name, $columns = null) + { + $class = $this->buildQueryClassName($name); + + if (!class_exists($class)) { + throw new ProgrammingError( + 'Query "%s" does not exist for backend %s', + $name, + $this->getType() + ); + } + + return new $class($this->getResource(), $columns); + } + + /** + * Whether this backend supports the given query + * + * @param string $name Query name to check for + * + * @return bool + */ + public function hasQuery($name) + { + return class_exists($this->buildQueryClassName($name)); + } + + /** + * Query name to class name resolution + * + * @param string $query + * + * @return string + */ + protected function buildQueryClassName($query) + { + $parts = preg_split('~\\\~', get_class($this)); + array_pop($parts); + array_push($parts, 'Query', ucfirst(strtolower($query)) . 'Query'); + return implode('\\', $parts); + } + + /** + * Fetch and return the program version of the current instance + * + * @return string + */ + public function getProgramVersion() + { + return preg_replace( + '/^[vr]/', + '', + $this->select()->from('programstatus', array('program_version'))->fetchOne() + ); + } + + /** + * Get whether the backend is Icinga 2 + * + * @param string $programVersion + * + * @return bool + */ + public function isIcinga2($programVersion = null) + { + if ($programVersion === null) { + $programVersion = $this->select()->from('programstatus', array('program_version'))->fetchOne(); + } + return (bool) preg_match( + '/^[vr]?2\.\d+\.\d+.*$/', + $programVersion + ); + } +} diff --git a/modules/monitoring/library/Monitoring/BackendStep.php b/modules/monitoring/library/Monitoring/BackendStep.php new file mode 100644 index 0000000..9683392 --- /dev/null +++ b/modules/monitoring/library/Monitoring/BackendStep.php @@ -0,0 +1,208 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring; + +use Exception; +use Icinga\Module\Setup\Step; +use Icinga\Application\Config; +use Icinga\Exception\IcingaException; + +class BackendStep extends Step +{ + protected $data; + + protected $backendIniError; + + protected $resourcesIniError; + + public function __construct(array $data) + { + $this->data = $data; + } + + public function apply() + { + $success = $this->createBackendsIni(); + $success &= $this->createResourcesIni(); + return $success; + } + + protected function createBackendsIni() + { + $config = array(); + $config[$this->data['backendConfig']['name']] = array( + 'type' => $this->data['backendConfig']['type'], + 'resource' => $this->data['resourceConfig']['name'] + ); + + try { + Config::fromArray($config) + ->setConfigFile(Config::resolvePath('modules/monitoring/backends.ini')) + ->saveIni(); + } catch (Exception $e) { + $this->backendIniError = $e; + return false; + } + + $this->backendIniError = false; + return true; + } + + protected function createResourcesIni() + { + $resourceConfig = $this->data['resourceConfig']; + $resourceName = $resourceConfig['name']; + unset($resourceConfig['name']); + + try { + $config = Config::app('resources', true); + $config->setSection($resourceName, $resourceConfig); + $config->saveIni(); + } catch (Exception $e) { + $this->resourcesIniError = $e; + return false; + } + + $this->resourcesIniError = false; + return true; + } + + public function getSummary() + { + $pageTitle = '<h2>' . mt('monitoring', 'Monitoring Backend', 'setup.page.title') . '</h2>'; + $backendDescription = '<p>' . sprintf( + mt( + 'monitoring', + 'Icinga Web 2 will retrieve information from your monitoring environment' + . ' using a backend called "%s" and the specified resource below:' + ), + $this->data['backendConfig']['name'] + ) . '</p>'; + + $resourceTitle = null; + $resourceHtml = null; + if ($this->data['resourceConfig']['type'] === 'db') { + $resourceTitle = '<h3>' . mt('monitoring', 'Database Resource') . '</h3>'; + $resourceHtml = '' + . '<table>' + . '<tbody>' + . '<tr>' + . '<td><strong>' . t('Resource Name') . '</strong></td>' + . '<td>' . $this->data['resourceConfig']['name'] . '</td>' + . '</tr>' + . '<tr>' + . '<td><strong>' . t('Database Type') . '</strong></td>' + . '<td>' . $this->data['resourceConfig']['db'] . '</td>' + . '</tr>' + . '<tr>' + . '<td><strong>' . t('Host') . '</strong></td>' + . '<td>' . $this->data['resourceConfig']['host'] . '</td>' + . '</tr>' + . '<tr>' + . '<td><strong>' . t('Port') . '</strong></td>' + . '<td>' . $this->data['resourceConfig']['port'] . '</td>' + . '</tr>' + . '<tr>' + . '<td><strong>' . t('Database Name') . '</strong></td>' + . '<td>' . $this->data['resourceConfig']['dbname'] . '</td>' + . '</tr>' + . '<tr>' + . '<td><strong>' . t('Username') . '</strong></td>' + . '<td>' . $this->data['resourceConfig']['username'] . '</td>' + . '</tr>' + . '<tr>' + . '<td><strong>' . t('Password') . '</strong></td>' + . '<td>' . str_repeat('*', strlen($this->data['resourceConfig']['password'])) . '</td>' + . '</tr>'; + + if (defined('\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT') + && isset($this->data['resourceConfig']['ssl_do_not_verify_server_cert']) + && $this->data['resourceConfig']['ssl_do_not_verify_server_cert'] + ) { + $resourceHtml .= '' + . '<tr>' + . '<td><strong>' . t('SSL Do Not Verify Server Certificate') . '</strong></td>' + . '<td>' . $this->data['resourceConfig']['ssl_do_not_verify_server_cert'] . '</td>' + . '</tr>'; + } + if (isset($this->data['resourceConfig']['ssl_key']) && $this->data['resourceConfig']['ssl_key']) { + $resourceHtml .= '' + .'<tr>' + . '<td><strong>' . t('SSL Key') . '</strong></td>' + . '<td>' . $this->data['resourceConfig']['ssl_key'] . '</td>' + . '</tr>'; + } + if (isset($this->data['resourceConfig']['ssl_cert']) && $this->data['resourceConfig']['ssl_cert']) { + $resourceHtml .= '' + . '<tr>' + . '<td><strong>' . t('SSL Cert') . '</strong></td>' + . '<td>' . $this->data['resourceConfig']['ssl_cert'] . '</td>' + . '</tr>'; + } + if (isset($this->data['resourceConfig']['ssl_ca']) && $this->data['resourceConfig']['ssl_ca']) { + $resourceHtml .= '' + . '<tr>' + . '<td><strong>' . t('CA') . '</strong></td>' + . '<td>' . $this->data['resourceConfig']['ssl_ca'] . '</td>' + . '</tr>'; + } + if (isset($this->data['resourceConfig']['ssl_capath']) && $this->data['resourceConfig']['ssl_capath']) { + $resourceHtml .= '' + . '<tr>' + . '<td><strong>' . t('CA Path') . '</strong></td>' + . '<td>' . $this->data['resourceConfig']['ssl_capath'] . '</td>' + . '</tr>'; + } + if (isset($this->data['resourceConfig']['ssl_cipher']) && $this->data['resourceConfig']['ssl_cipher']) { + $resourceHtml .= '' + . '<tr>' + . '<td><strong>' . t('Cipher') . '</strong></td>' + . '<td>' . $this->data['resourceConfig']['ssl_cipher'] . '</td>' + . '</tr>'; + } + + $resourceHtml .= '' + . '</tbody>' + . '</table>'; + } + + return $pageTitle . '<div class="topic">' . $backendDescription . $resourceTitle . $resourceHtml . '</div>'; + } + + public function getReport() + { + $report = array(); + + if ($this->backendIniError === false) { + $report[] = sprintf( + mt('monitoring', 'Monitoring backend configuration has been successfully written to: %s'), + Config::resolvePath('modules/monitoring/backends.ini') + ); + } elseif ($this->backendIniError !== null) { + $report[] = sprintf( + mt( + 'monitoring', + 'Monitoring backend configuration could not be written to: %s. An error occured:' + ), + Config::resolvePath('modules/monitoring/backends.ini') + ); + $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->backendIniError)); + } + + if ($this->resourcesIniError === false) { + $report[] = sprintf( + mt('monitoring', 'Resource configuration has been successfully updated: %s'), + Config::resolvePath('resources.ini') + ); + } elseif ($this->resourcesIniError !== null) { + $report[] = sprintf( + mt('monitoring', 'Resource configuration could not be udpated: %s. An error occured:'), + Config::resolvePath('resources.ini') + ); + $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->resourcesIniError)); + } + + return $report; + } +} diff --git a/modules/monitoring/library/Monitoring/Cli/CliUtils.php b/modules/monitoring/library/Monitoring/Cli/CliUtils.php new file mode 100644 index 0000000..3d7d3ee --- /dev/null +++ b/modules/monitoring/library/Monitoring/Cli/CliUtils.php @@ -0,0 +1,122 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Cli; + +use Icinga\Cli\Screen; + +class CliUtils +{ + protected $hostColors = array( + 0 => array('black', 'lightgreen'), + 1 => array('black', 'lightred'), + 2 => array('black', 'brown'), + 99 => array('black', 'lightgray'), + ); + protected $serviceColors = array( + 0 => array('black', 'lightgreen'), + 1 => array('black', 'yellow'), + 2 => array('black', 'lightred'), + 3 => array('black', 'lightpurple'), + 99 => array('black', 'lightgray'), + ); + protected $hostStates = array( + 0 => 'UP', + 1 => 'DOWN', + 2 => 'UNREACHABLE', + 99 => 'PENDING', + ); + + protected $serviceStates = array( + 0 => 'OK', + 1 => 'WARNING', + 2 => 'CRITICAL', + 3 => 'UNKNOWN', + 99 => 'PENDING', + ); + + protected $screen; + protected $hostState; + protected $serviceState; + + public function __construct(Screen $screen) + { + $this->screen = $screen; + } + + public function setHostState($state) + { + $this->hostState = $state; + } + + public function setServiceState($state) + { + $this->serviceState = $state; + } + + public function shortHostState($state = null) + { + if ($state === null) { + $state = $this->hostState; + } + return sprintf('%-4s', substr($this->hostStates[$state], 0, 4)); + } + + public function shortServiceState($state = null) + { + if ($state === null) { + $state = $this->serviceState; + } + return sprintf('%-4s', substr($this->serviceStates[$state], 0, 4)); + } + + public function hostStateBackground($text, $state = null) + { + if ($state === null) { + $state = $this->hostState; + } + return $this->screen->colorize( + $text, + $this->hostColors[$state][0], + $this->hostColors[$state][1] + ); + } + + public function serviceStateBackground($text, $state = null) + { + if ($state === null) { + $state = $this->serviceState; + } + return $this->screen->colorize( + $text, + $this->serviceColors[$state][0], + $this->serviceColors[$state][1] + ); + } + + public function objectStateFlags($type, &$row) + { + $extra = array(); + if ($row->{$type . '_in_downtime'}) { + if ($this->screen->hasUtf8()) { + $extra[] = 'DOWNTIME ⌚'; + } else { + $extra[] = 'DOWNTIME'; + } + } + if ($row->{$type . '_acknowledged'}) { + if ($this->screen->hasUtf8()) { + $extra[] = 'ACK ✓'; + } else { + $extra[] = 'ACK'; + } + } + + if (empty($extra)) { + $extra = ''; + } else { + $extra = sprintf(' [ %s ]', implode(', ', $extra)); + } + return $extra; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/IcingaApiCommand.php b/modules/monitoring/library/Monitoring/Command/IcingaApiCommand.php new file mode 100644 index 0000000..c33157f --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/IcingaApiCommand.php @@ -0,0 +1,126 @@ +<?php +/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command; + +class IcingaApiCommand +{ + /** + * Command data + * + * @var array + */ + protected $data; + + /** + * Name of the endpoint + * + * @var string + */ + protected $endpoint; + + /** + * Next Icinga API command to be sent, if any + * + * @var static + */ + protected $next; + + /** + * Create a new Icinga 2 API command + * + * @param string $endpoint + * @param array $data + * + * @return static + */ + public static function create($endpoint, array $data) + { + $command = new static(); + $command + ->setEndpoint($endpoint) + ->setData($data); + return $command; + } + + /** + * Get the command data + * + * @return array + */ + public function getData() + { + return $this->data; + } + + /** + * Set the command data + * + * @param array $data + * + * @return $this + */ + public function setData($data) + { + $this->data = $data; + + return $this; + } + + /** + * Get the name of the endpoint + * + * @return string + */ + public function getEndpoint() + { + return $this->endpoint; + } + + /** + * Set the name of the endpoint + * + * @param string $endpoint + * + * @return $this + */ + public function setEndpoint($endpoint) + { + $this->endpoint = $endpoint; + + return $this; + } + + /** + * Get whether another Icinga API command should be sent after this one + * + * @return bool + */ + public function hasNext() + { + return $this->next !== null; + } + + /** + * Get the next Icinga API command + * + * @return IcingaApiCommand + */ + public function getNext() + { + return $this->next; + } + + /** + * Set the next Icinga API command + * + * @param IcingaApiCommand $next + * + * @return IcingaApiCommand + */ + public function setNext(IcingaApiCommand $next) + { + $this->next = $next; + return $next; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/IcingaCommand.php b/modules/monitoring/library/Monitoring/Command/IcingaCommand.php new file mode 100644 index 0000000..49ce586 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/IcingaCommand.php @@ -0,0 +1,21 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command; + +/** + * Base class for commands sent to an Icinga instance + */ +abstract class IcingaCommand +{ + /** + * Get the name of the command + * + * @return string + */ + public function getName() + { + $nsParts = explode('\\', get_called_class()); + return substr_replace(end($nsParts), '', -7); // Remove 'Command' Suffix + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Instance/DisableNotificationsExpireCommand.php b/modules/monitoring/library/Monitoring/Command/Instance/DisableNotificationsExpireCommand.php new file mode 100644 index 0000000..1d3ce9d --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Instance/DisableNotificationsExpireCommand.php @@ -0,0 +1,42 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Instance; + +use Icinga\Module\Monitoring\Command\IcingaCommand; + +/** + * Disable host and service notifications w/ expire time on an Icinga instance + */ +class DisableNotificationsExpireCommand extends IcingaCommand +{ + /** + * The time when notifications should be re-enabled after disabling + * + * @var int|null Unix timestamp + */ + protected $expireTime; + + /** + * Set time when notifications should be re-enabled after disabling + * + * @param $expireTime int Unix timestamp + * + * @return $this + */ + public function setExpireTime($expireTime) + { + $this->expireTime = (int) $expireTime; + return $this; + } + + /** + * Get the date and time when notifications should be re-enabled after disabling + * + * @return int|null Unix timestamp + */ + public function getExpireTime() + { + return $this->expireTime; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Instance/ToggleInstanceFeatureCommand.php b/modules/monitoring/library/Monitoring/Command/Instance/ToggleInstanceFeatureCommand.php new file mode 100644 index 0000000..8a8a8ca --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Instance/ToggleInstanceFeatureCommand.php @@ -0,0 +1,122 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Instance; + +use Icinga\Module\Monitoring\Command\IcingaCommand; + +/** + * Enable or disable a feature of an Icinga instance + */ +class ToggleInstanceFeatureCommand extends IcingaCommand +{ + /** + * Feature for enabling or disabling active host checks on an Icinga instance + */ + const FEATURE_ACTIVE_HOST_CHECKS = 'active_host_checks_enabled'; + + /** + * Feature for enabling or disabling active service checks on an Icinga instance + */ + const FEATURE_ACTIVE_SERVICE_CHECKS = 'active_service_checks_enabled'; + + /** + * Feature for enabling or disabling host and service event handlers on an Icinga instance + */ + const FEATURE_EVENT_HANDLERS = 'event_handlers_enabled'; + + /** + * Feature for enabling or disabling host and service flap detection on an Icinga instance + */ + const FEATURE_FLAP_DETECTION = 'flap_detection_enabled'; + + /** + * Feature for enabling or disabling host and service notifications on an Icinga instance + */ + const FEATURE_NOTIFICATIONS = 'notifications_enabled'; + + /** + * Feature for enabling or disabling processing of host checks via the OCHP command on an Icinga instance + */ + const FEATURE_HOST_OBSESSING = 'obsess_over_hosts'; + + /** + * Feature for enabling or disabling processing of service checks via the OCHP command on an Icinga instance + */ + const FEATURE_SERVICE_OBSESSING = 'obsess_over_services'; + + /** + * Feature for enabling or disabling passive host checks on an Icinga instance + */ + const FEATURE_PASSIVE_HOST_CHECKS = 'passive_host_checks_enabled'; + + /** + * Feature for enabling or disabling passive service checks on an Icinga instance + */ + const FEATURE_PASSIVE_SERVICE_CHECKS = 'passive_service_checks_enabled'; + + /** + * Feature for enabling or disabling the processing of host and service performance data on an Icinga instance + */ + const FEATURE_PERFORMANCE_DATA = 'process_performance_data'; + + /** + * Feature that is to be enabled or disabled + * + * @var string + */ + protected $feature; + + /** + * Whether the feature should be enabled or disabled + * + * @var bool + */ + protected $enabled; + + /** + * Set the feature that is to be enabled or disabled + * + * @param string $feature + * + * @return $this + */ + public function setFeature($feature) + { + $this->feature = (string) $feature; + return $this; + } + + /** + * Get the feature that is to be enabled or disabled + * + * @return string + */ + public function getFeature() + { + return $this->feature; + } + + /** + * Set whether the feature should be enabled or disabled + * + * @param bool $enabled + * + * @return $this + */ + public function setEnabled($enabled = true) + { + $this->enabled = (bool) $enabled; + return $this; + } + + /** + * Get whether the feature should be enabled or disabled + * + * @return bool + */ + public function getEnabled() + { + return $this->enabled; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Object/AcknowledgeProblemCommand.php b/modules/monitoring/library/Monitoring/Command/Object/AcknowledgeProblemCommand.php new file mode 100644 index 0000000..2001e78 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Object/AcknowledgeProblemCommand.php @@ -0,0 +1,144 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Object; + +/** + * Acknowledge a host or service problem + */ +class AcknowledgeProblemCommand extends WithCommentCommand +{ + /** + * (non-PHPDoc) + * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation. + */ + protected $allowedObjects = array( + self::TYPE_HOST, + self::TYPE_SERVICE + ); + + /** + * Whether the acknowledgement is sticky + * + * Sticky acknowledgements remain until the host or service recovers. Non-sticky acknowledgements will be + * automatically removed when the host or service state changes. + * + * @var bool + */ + protected $sticky = false; + + /** + * Whether to send a notification about the acknowledgement + + * @var bool + */ + protected $notify = false; + + /** + * Whether the comment associated with the acknowledgement is persistent + * + * Persistent comments are not lost the next time the monitoring host restarts. + * + * @var bool + */ + protected $persistent = false; + + /** + * Optional time when the acknowledgement should expire + * + * @var int|null + */ + protected $expireTime; + + /** + * Set whether the acknowledgement is sticky + * + * @param bool $sticky + * + * @return $this + */ + public function setSticky($sticky = true) + { + $this->sticky = (bool) $sticky; + return $this; + } + + /** + * Is the acknowledgement sticky? + * + * @return bool + */ + public function getSticky() + { + return $this->sticky; + } + + /** + * Set whether to send a notification about the acknowledgement + * + * @param bool $notify + * + * @return $this + */ + public function setNotify($notify = true) + { + $this->notify = (bool) $notify; + return $this; + } + + /** + * Get whether to send a notification about the acknowledgement + * + * @return bool + */ + public function getNotify() + { + return $this->notify; + } + + /** + * Set whether the comment associated with the acknowledgement is persistent + * + * @param bool $persistent + * + * @return $this + */ + public function setPersistent($persistent = true) + { + $this->persistent = (bool) $persistent; + return $this; + } + + /** + * Is the comment associated with the acknowledgement is persistent? + * + * @return bool + */ + public function getPersistent() + { + return $this->persistent; + } + + /** + * Set the time when the acknowledgement should expire + * + * @param int $expireTime + * + * @return $this + */ + public function setExpireTime($expireTime) + { + $this->expireTime = (int) $expireTime; + return $this; + } + + /** + * Get the time when the acknowledgement should expire + * + * @return int|null + */ + public function getExpireTime() + { + return $this->expireTime; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Object/AddCommentCommand.php b/modules/monitoring/library/Monitoring/Command/Object/AddCommentCommand.php new file mode 100644 index 0000000..9e3151f --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Object/AddCommentCommand.php @@ -0,0 +1,80 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Object; + +/** + * Add a comment to a host or service + */ +class AddCommentCommand extends WithCommentCommand +{ + /** + * (non-PHPDoc) + * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation. + */ + protected $allowedObjects = array( + self::TYPE_HOST, + self::TYPE_SERVICE + ); + + /** + * Whether the comment is persistent + * + * Persistent comments are not lost the next time the monitoring host restarts. + */ + protected $persistent; + + /** + * Optional time when the acknowledgement should expire + * + * @var int|null + */ + protected $expireTime; + + /** + * Set whether the comment is persistent + * + * @param bool $persistent + * + * @return $this + */ + public function setPersistent($persistent = true) + { + $this->persistent = $persistent; + return $this; + } + + /** + * Is the comment persistent? + * + * @return bool + */ + public function getPersistent() + { + return $this->persistent; + } + + /** + * Set the time when the acknowledgement should expire + * + * @param int $expireTime + * + * @return $this + */ + public function setExpireTime($expireTime) + { + $this->expireTime = (int) $expireTime; + + return $this; + } + + /** + * Get the time when the acknowledgement should expire + * + * @return int|null + */ + public function getExpireTime() + { + return $this->expireTime; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Object/ApiScheduleHostDowntimeCommand.php b/modules/monitoring/library/Monitoring/Command/Object/ApiScheduleHostDowntimeCommand.php new file mode 100644 index 0000000..6495375 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Object/ApiScheduleHostDowntimeCommand.php @@ -0,0 +1,40 @@ +<?php +/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Object; + +/** + * Schedule host downtime command for API command transport and Icinga >= 2.11.0 that + * sends all_services and child_options in a single request + */ +class ApiScheduleHostDowntimeCommand extends ScheduleHostDowntimeCommand +{ + /** @var int Whether no, triggered, or non-triggered child downtimes should be scheduled */ + protected $childOptions; + + protected $forAllServicesNative = true; + + /** + * Get child options, i.e. whether no, triggered, or non-triggered child downtimes should be scheduled + * + * @return int + */ + public function getChildOptions() + { + return $this->childOptions; + } + + /** + * Set child options, i.e. whether no, triggered, or non-triggered child downtimes should be scheduled + * + * @param int $childOptions + * + * @return $this + */ + public function setChildOptions($childOptions) + { + $this->childOptions = $childOptions; + + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Object/CommandAuthor.php b/modules/monitoring/library/Monitoring/Command/Object/CommandAuthor.php new file mode 100644 index 0000000..577e3df --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Object/CommandAuthor.php @@ -0,0 +1,38 @@ +<?php + +/* Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Object; + +trait CommandAuthor +{ + /** + * Author of the command + * + * @var string + */ + protected $author; + + /** + * Set the author + * + * @param string $author + * + * @return $this + */ + public function setAuthor($author) + { + $this->author = (string) $author; + return $this; + } + + /** + * Get the author + * + * @return string + */ + public function getAuthor() + { + return $this->author; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Object/DeleteCommentCommand.php b/modules/monitoring/library/Monitoring/Command/Object/DeleteCommentCommand.php new file mode 100644 index 0000000..348175a --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Object/DeleteCommentCommand.php @@ -0,0 +1,110 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Object; + +use Icinga\Module\Monitoring\Command\IcingaCommand; + +/** + * Delete a host or service comment + */ +class DeleteCommentCommand extends IcingaCommand +{ + use CommandAuthor; + + /** + * ID of the comment that is to be deleted + * + * @var int + */ + protected $commentId; + + /** + * Name of the comment (Icinga 2.4+) + * + * Required for removing the comment via Icinga 2's API. + * + * @var string + */ + protected $commentName; + + /** + * Whether the command affects a service comment + * + * @var boolean + */ + protected $isService = false; + + /** + * Get the ID of the comment that is to be deleted + * + * @return int + */ + public function getCommentId() + { + return $this->commentId; + } + + /** + * Set the ID of the comment that is to be deleted + * + * @param int $commentId + * + * @return $this + */ + public function setCommentId($commentId) + { + $this->commentId = (int) $commentId; + return $this; + } + + /** + * Get the name of the comment (Icinga 2.4+) + * + * Required for removing the comment via Icinga 2's API. + * + * @return string + */ + public function getCommentName() + { + return $this->commentName; + } + + /** + * Set the name of the comment (Icinga 2.4+) + * + * Required for removing the comment via Icinga 2's API. + * + * @param string $commentName + * + * @return $this + */ + public function setCommentName($commentName) + { + $this->commentName = $commentName; + return $this; + } + + /** + * Get whether the command affects a service comment + * + * @return boolean + */ + public function getIsService() + { + return $this->isService; + } + + /** + * Set whether the command affects a service comment + * + * @param bool $isService + * + * @return $this + */ + public function setIsService($isService = true) + { + $this->isService = (bool) $isService; + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Object/DeleteDowntimeCommand.php b/modules/monitoring/library/Monitoring/Command/Object/DeleteDowntimeCommand.php new file mode 100644 index 0000000..a314864 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Object/DeleteDowntimeCommand.php @@ -0,0 +1,110 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Object; + +use Icinga\Module\Monitoring\Command\IcingaCommand; + +/** + * Delete a host or service downtime + */ +class DeleteDowntimeCommand extends IcingaCommand +{ + use CommandAuthor; + + /** + * ID of the downtime that is to be deleted + * + * @var int + */ + protected $downtimeId; + + /** + * Name of the downtime (Icinga 2.4+) + * + * Required for removing the downtime via Icinga 2's API. + * + * @var string + */ + protected $downtimeName; + + /** + * Whether the command affects a service downtime + * + * @var boolean + */ + protected $isService = false; + + /** + * Get the ID of the downtime that is to be deleted + * + * @return int + */ + public function getDowntimeId() + { + return $this->downtimeId; + } + + /** + * Set the ID of the downtime that is to be deleted + * + * @param int $downtimeId + * + * @return $this + */ + public function setDowntimeId($downtimeId) + { + $this->downtimeId = (int) $downtimeId; + return $this; + } + + /** + * Get the name of the downtime (Icinga 2.4+) + * + * Required for removing the downtime via Icinga 2's API. + * + * @return string + */ + public function getDowntimeName() + { + return $this->downtimeName; + } + + /** + * Set the name of the downtime (Icinga 2.4+) + * + * Required for removing the downtime via Icinga 2's API. + * + * @param string $downtimeName + * + * @return $this + */ + public function setDowntimeName($downtimeName) + { + $this->downtimeName = $downtimeName; + return $this; + } + + /** + * Get whether the command affects a service + * + * @return bool + */ + public function getIsService() + { + return $this->isService; + } + + /** + * Set whether the command affects a service + * + * @param bool $isService + * + * @return $this + */ + public function setIsService($isService = true) + { + $this->isService = (bool) $isService; + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Object/ObjectCommand.php b/modules/monitoring/library/Monitoring/Command/Object/ObjectCommand.php new file mode 100644 index 0000000..43ab645 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Object/ObjectCommand.php @@ -0,0 +1,61 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Object; + +use Icinga\Module\Monitoring\Command\IcingaCommand; +use Icinga\Module\Monitoring\Object\MonitoredObject; + +/** + * Base class for commands that involve a monitored object, i.e. a host or service + */ +abstract class ObjectCommand extends IcingaCommand +{ + /** + * Type host + */ + const TYPE_HOST = MonitoredObject::TYPE_HOST; + + /** + * Type service + */ + const TYPE_SERVICE = MonitoredObject::TYPE_SERVICE; + + /** + * Allowed Icinga object types for the command + * + * @var string[] + */ + protected $allowedObjects = array(); + + /** + * Involved object + * + * @var MonitoredObject + */ + protected $object; + + /** + * Set the involved object + * + * @param MonitoredObject $object + * + * @return $this + */ + public function setObject(MonitoredObject $object) + { + $object->assertOneOf($this->allowedObjects); + $this->object = $object; + return $this; + } + + /** + * Get the involved object + * + * @return MonitoredObject + */ + public function getObject() + { + return $this->object; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Object/ProcessCheckResultCommand.php b/modules/monitoring/library/Monitoring/Command/Object/ProcessCheckResultCommand.php new file mode 100644 index 0000000..cd2db33 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Object/ProcessCheckResultCommand.php @@ -0,0 +1,176 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Object; + +use InvalidArgumentException; +use LogicException; + +/** + * Submit a passive check result for a host or service + */ +class ProcessCheckResultCommand extends ObjectCommand +{ + /** + * (non-PHPDoc) + * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation. + */ + protected $allowedObjects = array( + self::TYPE_HOST, + self::TYPE_SERVICE + ); + + /** + * Host up + */ + const HOST_UP = 0; + + /** + * Host down + */ + const HOST_DOWN = 1; + + /** + * Host unreachable + */ + const HOST_UNREACHABLE = 2; // TODO: Icinga 2.x does not support submitting results with this state, yet + + /** + * Service ok + */ + const SERVICE_OK = 0; + + /** + * Service warning + */ + const SERVICE_WARNING = 1; + + /** + * Service critical + */ + const SERVICE_CRITICAL = 2; + + /** + * Service unknown + */ + const SERVICE_UNKNOWN = 3; + + /** + * Possible status codes for passive host and service checks + * + * @var array + */ + public static $statusCodes = array( + self::TYPE_HOST => array( + self::HOST_UP, self::HOST_DOWN, self::HOST_UNREACHABLE + ), + self::TYPE_SERVICE => array( + self::SERVICE_OK, self::SERVICE_WARNING, self::SERVICE_CRITICAL, self::SERVICE_UNKNOWN + ) + ); + + /** + * Status code of the host or service check result + * + * @var int + */ + protected $status; + + /** + * Text output of the host or service check result + * + * @var string + */ + protected $output; + + /** + * Optional performance data of the host or service check result + * + * @var string + */ + protected $performanceData; + + + /** + * Set the status code of the host or service check result + * + * @param int $status + * + * @return $this + * + * @throws LogicException If the object is null + * @throws InvalidArgumentException If status is not one of the valid status codes for the object's type + */ + public function setStatus($status) + { + if ($this->object === null) { + throw new LogicException('You\'re required to call setObject() before calling setStatus()'); + } + $status = (int) $status; + if (! in_array($status, self::$statusCodes[$this->object->getType()])) { + throw new InvalidArgumentException(sprintf( + 'The status code %u you provided is not one of the valid status codes for type %s', + $status, + $this->object->getType() + )); + } + $this->status = $status; + return $this; + } + + /** + * Get the status code of the host or service check result + * + * @return int + */ + public function getStatus() + { + return $this->status; + } + + /** + * Set the text output of the host or service check result + * + * @param string $output + * + * @return $this + */ + public function setOutput($output) + { + $this->output = (string) $output; + return $this; + } + + /** + * Get the text output of the host or service check result + * + * @return string + */ + public function getOutput() + { + return $this->output; + } + + /** + * Set the performance data of the host or service check result + * + * @param string $performanceData + * + * @return $this + */ + public function setPerformanceData($performanceData) + { + $this->performanceData = (string) $performanceData; + return $this; + } + + /** + * Get the performance data of the host or service check result + * + * @return string + */ + public function getPerformanceData() + { + return $this->performanceData; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Object/PropagateHostDowntimeCommand.php b/modules/monitoring/library/Monitoring/Command/Object/PropagateHostDowntimeCommand.php new file mode 100644 index 0000000..3fd350c --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Object/PropagateHostDowntimeCommand.php @@ -0,0 +1,48 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Object; + +/** + * Schedule and propagate host downtime + */ +class PropagateHostDowntimeCommand extends ScheduleServiceDowntimeCommand +{ + /** + * (non-PHPDoc) + * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation. + */ + protected $allowedObjects = array( + self::TYPE_HOST + ); + + /** + * Whether the downtime for child hosts are all set to be triggered by this' host downtime + * + * @var bool + */ + protected $triggered = false; + + /** + * Set whether the downtime for child hosts are all set to be triggered by this' host downtime + * + * @param bool $triggered + * + * @return $this + */ + public function setTriggered($triggered = true) + { + $this->triggered = (bool) $triggered; + return $this; + } + + /** + * Get whether the downtime for child hosts are all set to be triggered by this' host downtime + * + * @return bool + */ + public function getTriggered() + { + return $this->triggered; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Object/RemoveAcknowledgementCommand.php b/modules/monitoring/library/Monitoring/Command/Object/RemoveAcknowledgementCommand.php new file mode 100644 index 0000000..31c8180 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Object/RemoveAcknowledgementCommand.php @@ -0,0 +1,21 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Object; + +/** + * Remove a problem acknowledgement from a host or service + */ +class RemoveAcknowledgementCommand extends ObjectCommand +{ + use CommandAuthor; + + /** + * (non-PHPDoc) + * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation. + */ + protected $allowedObjects = array( + self::TYPE_HOST, + self::TYPE_SERVICE + ); +} diff --git a/modules/monitoring/library/Monitoring/Command/Object/ScheduleHostCheckCommand.php b/modules/monitoring/library/Monitoring/Command/Object/ScheduleHostCheckCommand.php new file mode 100644 index 0000000..8a0a2cb --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Object/ScheduleHostCheckCommand.php @@ -0,0 +1,48 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Object; + +/** + * Schedule a host check + */ +class ScheduleHostCheckCommand extends ScheduleServiceCheckCommand +{ + /** + * (non-PHPDoc) + * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation. + */ + protected $allowedObjects = array( + self::TYPE_HOST + ); + + /** + * Whether to schedule a check of all services associated with a particular host + * + * @var bool + */ + protected $ofAllServices = false; + + /** + * Set whether to schedule a check of all services associated with a particular host + * + * @param bool $ofAllServices + * + * @return $this + */ + public function setOfAllServices($ofAllServices = true) + { + $this->ofAllServices = (bool) $ofAllServices; + return $this; + } + + /** + * Get whether to schedule a check of all services associated with a particular host + * + * @return bool + */ + public function getOfAllServices() + { + return $this->ofAllServices; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Object/ScheduleHostDowntimeCommand.php b/modules/monitoring/library/Monitoring/Command/Object/ScheduleHostDowntimeCommand.php new file mode 100644 index 0000000..3ac37d3 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Object/ScheduleHostDowntimeCommand.php @@ -0,0 +1,75 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Object; + +/** + * Schedule a host downtime + */ +class ScheduleHostDowntimeCommand extends ScheduleServiceDowntimeCommand +{ + /** + * (non-PHPDoc) + * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation. + */ + protected $allowedObjects = array( + self::TYPE_HOST + ); + + /** + * Whether to schedule a downtime for all services associated with a particular host + * + * @var bool + */ + protected $forAllServices = false; + + /** @var bool Whether to send the all_services API parameter */ + protected $forAllServicesNative; + + /** + * Set whether to schedule a downtime for all services associated with a particular host + * + * @param bool $forAllServices + * + * @return $this + */ + public function setForAllServices($forAllServices = true) + { + $this->forAllServices = (bool) $forAllServices; + return $this; + } + + /** + * Get whether to schedule a downtime for all services associated with a particular host + * + * @return bool + */ + public function getForAllServices() + { + return $this->forAllServices; + } + + /** + * Get whether to send the all_services API parameter + * + * @return bool + */ + public function isForAllServicesNative() + { + return $this->forAllServicesNative; + } + + /** + * Get whether to send the all_services API parameter + * + * @param bool $forAllServicesNative + * + * @return $this + */ + public function setForAllServicesNative($forAllServicesNative = true) + { + $this->forAllServicesNative = (bool) $forAllServicesNative; + + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Object/ScheduleServiceCheckCommand.php b/modules/monitoring/library/Monitoring/Command/Object/ScheduleServiceCheckCommand.php new file mode 100644 index 0000000..8880984 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Object/ScheduleServiceCheckCommand.php @@ -0,0 +1,92 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Object; + +/** + * Schedule a service check + */ +class ScheduleServiceCheckCommand extends ObjectCommand +{ + /** + * {@inheritdoc} + */ + protected $allowedObjects = array( + self::TYPE_SERVICE + ); + + /** + * Time when the next check of a host or service is to be scheduled + * + * If active checks are disabled on a host- or service-specific or program-wide basis or the host or service is + * already scheduled to be checked at an earlier time, etc. The check may not actually be scheduled at the time + * specified. This behaviour can be overridden by setting `ScheduledCheck::$forced' to true. + * + * @var int Unix timestamp + */ + protected $checkTime; + + /** + * Whether the check is forced + * + * Forced checks are performed regardless of what time it is (e.g. time period restrictions are ignored) and whether + * or not active checks are enabled on a host- or service-specific or program-wide basis. + * + * @var bool + */ + protected $forced = false; + + /** + * Set the time when the next check of a host or service is to be scheduled + * + * @param int $checkTime Unix timestamp + * + * @return $this + */ + public function setCheckTime($checkTime) + { + $this->checkTime = (int) $checkTime; + return $this; + } + + /** + * Get the time when the next check of a host or service is to be scheduled + * + * @return int Unix timestamp + */ + public function getCheckTime() + { + return $this->checkTime; + } + + /** + * Set whether the check is forced + * + * @param bool $forced + * + * @return $this + */ + public function setForced($forced = true) + { + $this->forced = (bool) $forced; + return $this; + } + + /** + * Get whether the check is forced + * + * @return bool + */ + public function getForced() + { + return $this->forced; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ScheduleCheck'; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Object/ScheduleServiceDowntimeCommand.php b/modules/monitoring/library/Monitoring/Command/Object/ScheduleServiceDowntimeCommand.php new file mode 100644 index 0000000..a023ab5 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Object/ScheduleServiceDowntimeCommand.php @@ -0,0 +1,190 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Object; + +/** + * Schedule a service downtime + */ +class ScheduleServiceDowntimeCommand extends AddCommentCommand +{ + /** + * (non-PHPDoc) + * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation. + */ + protected $allowedObjects = array( + self::TYPE_SERVICE + ); + + /** + * Downtime starts at the exact time specified + * + * If `Downtime::$fixed' is set to false, the time between `Downtime::$start' and `Downtime::$end' at which a + * host or service transitions to a problem state determines the time at which the downtime actually starts. + * The downtime will then last for `Downtime::$duration' seconds. + * + * @var int Unix timestamp + */ + protected $start; + + /** + * Downtime ends at the exact time specified + * + * If `Downtime::$fixed' is set to false, the time between `Downtime::$start' and `Downtime::$end' at which a + * host or service transitions to a problem state determines the time at which the downtime actually starts. + * The downtime will then last for `Downtime::$duration' seconds. + * + * @var int Unix timestamp + */ + protected $end; + + /** + * Whether it's a fixed or flexible downtime + * + * @var bool + */ + protected $fixed = true; + + /** + * ID of the downtime which triggers this downtime + * + * The start of this downtime is triggered by the start of the other scheduled host or service downtime. + * + * @var int|null + */ + protected $triggerId; + + /** + * The duration in seconds the downtime must last if it's a flexible downtime + * + * If `Downtime::$fixed' is set to false, the downtime will last for the duration in seconds specified, even + * if the host or service recovers before the downtime expires. + * + * @var int|null + */ + protected $duration; + + /** + * Set the time when the downtime should start + * + * @param int $start Unix timestamp + * + * @return $this + */ + public function setStart($start) + { + $this->start = (int) $start; + return $this; + } + + /** + * Get the time when the downtime should start + * + * @return int Unix timestamp + */ + public function getStart() + { + return $this->start; + } + + /** + * Set the time when the downtime should end + * + * @param int $end Unix timestamp + * + * @return $this + */ + public function setEnd($end) + { + $this->end = (int) $end; + return $this; + } + + /** + * Get the time when the downtime should end + * + * @return int Unix timestamp + */ + public function getEnd() + { + return $this->end; + } + + /** + * Set whether it's a fixed or flexible downtime + * + * @param boolean $fixed + * + * @return $this + */ + public function setFixed($fixed = true) + { + $this->fixed = (bool) $fixed; + return $this; + } + + /** + * Is the downtime fixed? + * + * @return boolean + */ + public function getFixed() + { + return $this->fixed; + } + + /** + * Set the ID of the downtime which triggers this downtime + * + * @param int $triggerId + * + * @return $this + */ + public function setTriggerId($triggerId) + { + $this->triggerId = (int) $triggerId; + return $this; + } + + /** + * Get the ID of the downtime which triggers this downtime + * + * @return int|null + */ + public function getTriggerId() + { + return $this->triggerId; + } + + /** + * Set the duration in seconds the downtime must last if it's a flexible downtime + * + * @param int $duration + * + * @return $this + */ + public function setDuration($duration) + { + $this->duration = (int) $duration; + return $this; + } + + /** + * Get the duration in seconds the downtime must last if it's a flexible downtime + * + * @return int|null + */ + public function getDuration() + { + return $this->duration; + } + + /** + * (non-PHPDoc) + * @see \Icinga\Module\Monitoring\Command\Object\IcingaCommand::getName() For the method documentation. + */ + public function getName() + { + return 'ScheduleDowntime'; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Object/SendCustomNotificationCommand.php b/modules/monitoring/library/Monitoring/Command/Object/SendCustomNotificationCommand.php new file mode 100644 index 0000000..ac8889c --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Object/SendCustomNotificationCommand.php @@ -0,0 +1,82 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Object; + +/** + * Send custom notifications for a host or service + */ +class SendCustomNotificationCommand extends WithCommentCommand +{ + /** + * {@inheritdoc} + */ + protected $allowedObjects = array( + self::TYPE_HOST, + self::TYPE_SERVICE + ); + + /** + * Whether the notification is forced + * + * Forced notifications are sent out regardless of time restrictions and whether or not notifications are enabled. + * + * @var bool + */ + protected $forced; + + /** + * Whether to broadcast the notification + * + * Broadcast notifications are sent out to all normal and escalated contacts. + * + * @var bool + */ + protected $broadcast; + + /** + * Get whether to force the notification + * + * @return bool + */ + public function getForced() + { + return $this->forced; + } + + /** + * Set whether to force the notification + * + * @param bool $forced + * + * @return $this + */ + public function setForced($forced = true) + { + $this->forced = $forced; + return $this; + } + + /** + * Get whether to broadcast the notification + * + * @return bool + */ + public function getBroadcast() + { + return $this->broadcast; + } + + /** + * Set whether to broadcast the notification + * + * @param bool $broadcast + * + * @return $this + */ + public function setBroadcast($broadcast = true) + { + $this->broadcast = $broadcast; + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Object/ToggleObjectFeatureCommand.php b/modules/monitoring/library/Monitoring/Command/Object/ToggleObjectFeatureCommand.php new file mode 100644 index 0000000..e3ba8a2 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Object/ToggleObjectFeatureCommand.php @@ -0,0 +1,113 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Object; + +/** + * Enable or disable a feature of an Icinga object, i.e. host or service + */ +class ToggleObjectFeatureCommand extends ObjectCommand +{ + /** + * (non-PHPDoc) + * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation. + */ + protected $allowedObjects = array( + self::TYPE_HOST, + self::TYPE_SERVICE + ); + + /** + * Feature for enabling or disabling active checks of a host or service + */ + const FEATURE_ACTIVE_CHECKS = 'active_checks_enabled'; + + /** + * Feature for enabling or disabling passive checks of a host or service + */ + const FEATURE_PASSIVE_CHECKS = 'passive_checks_enabled'; + + /** + * Feature for enabling or disabling processing of host or service checks via the OCHP command for a host or service + */ + const FEATURE_OBSESSING = 'obsessing'; + + /** + * Feature for enabling or disabling notifications for a host or service + * + * Notifications will be sent out only if notifications are enabled on a program-wide basis as well. + */ + const FEATURE_NOTIFICATIONS = 'notifications_enabled'; + + /** + * Feature for enabling or disabling event handler for a host or service + */ + const FEATURE_EVENT_HANDLER = 'event_handler_enabled'; + + /** + * Feature for enabling or disabling flap detection for a host or service. + * + * In order to enable flap detection flap detection must be enabled on a program-wide basis as well. + */ + const FEATURE_FLAP_DETECTION = 'flap_detection_enabled'; + + /** + * Feature that is to be enabled or disabled + * + * @var string + */ + protected $feature; + + /** + * Whether the feature should be enabled or disabled + * + * @var bool + */ + protected $enabled; + + /** + * Set the feature that is to be enabled or disabled + * + * @param string $feature + * + * @return $this + */ + public function setFeature($feature) + { + $this->feature = (string) $feature; + return $this; + } + + /** + * Get the feature that is to be enabled or disabled + * + * @return string + */ + public function getFeature() + { + return $this->feature; + } + + /** + * Set whether the feature should be enabled or disabled + * + * @param bool $enabled + * + * @return $this + */ + public function setEnabled($enabled = true) + { + $this->enabled = (bool) $enabled; + return $this; + } + + /** + * Get whether the feature should be enabled or disabled + * + * @return bool + */ + public function getEnabled() + { + return $this->enabled; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Object/WithCommentCommand.php b/modules/monitoring/library/Monitoring/Command/Object/WithCommentCommand.php new file mode 100644 index 0000000..aa2e439 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Object/WithCommentCommand.php @@ -0,0 +1,42 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Object; + +/** + * Base class for commands adding comments + */ +abstract class WithCommentCommand extends ObjectCommand +{ + use CommandAuthor; + + /** + * Comment + * + * @var string + */ + protected $comment; + + /** + * Set the comment + * + * @param string $comment + * + * @return $this + */ + public function setComment($comment) + { + $this->comment = (string) $comment; + return $this; + } + + /** + * Get the comment + * + * @return string + */ + public function getComment() + { + return $this->comment; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Renderer/IcingaApiCommandRenderer.php b/modules/monitoring/library/Monitoring/Command/Renderer/IcingaApiCommandRenderer.php new file mode 100644 index 0000000..3fcda6d --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Renderer/IcingaApiCommandRenderer.php @@ -0,0 +1,322 @@ +<?php +/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Renderer; + +use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use Icinga\Module\Monitoring\Command\IcingaApiCommand; +use Icinga\Module\Monitoring\Command\Instance\ToggleInstanceFeatureCommand; +use Icinga\Module\Monitoring\Command\Object\AcknowledgeProblemCommand; +use Icinga\Module\Monitoring\Command\Object\AddCommentCommand; +use Icinga\Module\Monitoring\Command\Object\ApiScheduleHostDowntimeCommand; +use Icinga\Module\Monitoring\Command\Object\DeleteCommentCommand; +use Icinga\Module\Monitoring\Command\Object\DeleteDowntimeCommand; +use Icinga\Module\Monitoring\Command\Object\ProcessCheckResultCommand; +use Icinga\Module\Monitoring\Command\Object\PropagateHostDowntimeCommand; +use Icinga\Module\Monitoring\Command\Object\RemoveAcknowledgementCommand; +use Icinga\Module\Monitoring\Command\Object\ScheduleHostDowntimeCommand; +use Icinga\Module\Monitoring\Command\Object\ScheduleServiceCheckCommand; +use Icinga\Module\Monitoring\Command\Object\ScheduleServiceDowntimeCommand; +use Icinga\Module\Monitoring\Command\Object\SendCustomNotificationCommand; +use Icinga\Module\Monitoring\Command\Object\ToggleObjectFeatureCommand; +use Icinga\Module\Monitoring\Command\IcingaCommand; +use Icinga\Module\Monitoring\Object\MonitoredObject; +use InvalidArgumentException; + +/** + * Icinga command renderer for the Icinga command file + */ +class IcingaApiCommandRenderer implements IcingaCommandRendererInterface +{ + /** + * Name of the Icinga application object + * + * @var string + */ + protected $app = 'app'; + + /** + * Get the name of the Icinga application object + * + * @return string + */ + public function getApp() + { + return $this->app; + } + + /** + * Set the name of the Icinga application object + * + * @param string $app + * + * @return $this + */ + public function setApp($app) + { + $this->app = $app; + + return $this; + } + + /** + * Apply filter to query data + * + * @param array $data + * @param MonitoredObject $object + */ + protected function applyFilter(array &$data, MonitoredObject $object) + { + if ($object->getType() === $object::TYPE_HOST) { + /** @var \Icinga\Module\Monitoring\Object\Host $object */ + $data['host'] = $object->getName(); + } else { + /** @var \Icinga\Module\Monitoring\Object\Service $object */ + $data['service'] = sprintf('%s!%s', $object->getHost()->getName(), $object->getName()); + } + } + + /** + * Render a command + * + * @param IcingaCommand $command + * + * @return IcingaApiCommand + */ + public function render(IcingaCommand $command) + { + $renderMethod = 'render' . $command->getName(); + if (! method_exists($this, $renderMethod)) { + die($renderMethod); + } + return $this->$renderMethod($command); + } + + public function renderAddComment(AddCommentCommand $command) + { + $endpoint = 'actions/add-comment'; + $data = array( + 'author' => $command->getAuthor(), + 'comment' => $command->getComment() + ); + + if ($command->getExpireTime() !== null) { + $data['expiry'] = $command->getExpireTime(); + } + + $this->applyFilter($data, $command->getObject()); + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderSendCustomNotification(SendCustomNotificationCommand $command) + { + $endpoint = 'actions/send-custom-notification'; + $data = array( + 'author' => $command->getAuthor(), + 'comment' => $command->getComment(), + 'force' => $command->getForced() + ); + $this->applyFilter($data, $command->getObject()); + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderProcessCheckResult(ProcessCheckResultCommand $command) + { + $endpoint = 'actions/process-check-result'; + $data = array( + 'exit_status' => $command->getStatus(), + 'plugin_output' => $command->getOutput(), + 'performance_data' => $command->getPerformanceData() + ); + $this->applyFilter($data, $command->getObject()); + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderScheduleCheck(ScheduleServiceCheckCommand $command) + { + $endpoint = 'actions/reschedule-check'; + $data = array( + 'next_check' => $command->getCheckTime(), + 'force' => $command->getForced() + ); + $this->applyFilter($data, $command->getObject()); + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderScheduleDowntime(ScheduleServiceDowntimeCommand $command) + { + $endpoint = 'actions/schedule-downtime'; + $data = array( + 'author' => $command->getAuthor(), + 'comment' => $command->getComment(), + 'start_time' => $command->getStart(), + 'end_time' => $command->getEnd(), + 'duration' => $command->getDuration(), + 'fixed' => $command->getFixed(), + 'trigger_name' => $command->getTriggerId() + ); + $commandData = $data; + + if ($command instanceof PropagateHostDowntimeCommand) { + $commandData['child_options'] = $command->getTriggered() ? 1 : 2; + } elseif ($command instanceof ApiScheduleHostDowntimeCommand) { + // We assume that it has previously been verified that the Icinga version is + // equal to or greater than 2.11.0 + $commandData['child_options'] = $command->getChildOptions(); + } + + $allServicesCompat = false; + if ($command instanceof ScheduleHostDowntimeCommand) { + if ($command->isForAllServicesNative()) { + // We assume that it has previously been verified that the Icinga version is + // equal to or greater than 2.11.0 + $commandData['all_services'] = $command->getForAllServices(); + } else { + $allServicesCompat = $command->getForAllServices(); + } + } + + $this->applyFilter($commandData, $command->getObject()); + $apiCommand = IcingaApiCommand::create($endpoint, $commandData); + + if ($allServicesCompat) { + $commandData = $data + [ + 'type' => 'Service', + 'filter' => 'host.name == host_name', + 'filter_vars' => [ + 'host_name' => $command->getObject()->getName() + ] + ]; + $apiCommand->setNext(IcingaApiCommand::create($endpoint, $commandData)); + } + + return $apiCommand; + } + + public function renderAcknowledgeProblem(AcknowledgeProblemCommand $command) + { + $endpoint = 'actions/acknowledge-problem'; + $data = array( + 'author' => $command->getAuthor(), + 'comment' => $command->getComment(), + 'sticky' => $command->getSticky(), + 'notify' => $command->getNotify(), + 'persistent' => $command->getPersistent() + ); + if ($command->getExpireTime() !== null) { + $data['expiry'] = $command->getExpireTime(); + } + $this->applyFilter($data, $command->getObject()); + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderToggleObjectFeature(ToggleObjectFeatureCommand $command) + { + if ($command->getEnabled() === true) { + $enabled = true; + } else { + $enabled = false; + } + switch ($command->getFeature()) { + case ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS: + $attr = 'enable_active_checks'; + break; + case ToggleObjectFeatureCommand::FEATURE_PASSIVE_CHECKS: + $attr = 'enable_passive_checks'; + break; + case ToggleObjectFeatureCommand::FEATURE_NOTIFICATIONS: + $attr = 'enable_notifications'; + break; + case ToggleObjectFeatureCommand::FEATURE_EVENT_HANDLER: + $attr = 'enable_event_handler'; + break; + case ToggleObjectFeatureCommand::FEATURE_FLAP_DETECTION: + $attr = 'enable_flapping'; + break; + default: + throw new InvalidArgumentException($command->getFeature()); + } + $endpoint = 'objects/'; + $object = $command->getObject(); + if ($object->getType() === ToggleObjectFeatureCommand::TYPE_HOST) { + /** @var \Icinga\Module\Monitoring\Object\Host $object */ + $endpoint .= 'hosts'; + } else { + /** @var \Icinga\Module\Monitoring\Object\Service $object */ + $endpoint .= 'services'; + } + $data = array( + 'attrs' => array( + $attr => $enabled + ) + ); + $this->applyFilter($data, $object); + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderDeleteComment(DeleteCommentCommand $command) + { + $endpoint = 'actions/remove-comment'; + $data = [ + 'author' => $command->getAuthor(), + 'comment' => $command->getCommentName() + ]; + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderDeleteDowntime(DeleteDowntimeCommand $command) + { + $endpoint = 'actions/remove-downtime'; + $data = [ + 'author' => $command->getAuthor(), + 'downtime' => $command->getDowntimeName() + ]; + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderRemoveAcknowledgement(RemoveAcknowledgementCommand $command) + { + $endpoint = 'actions/remove-acknowledgement'; + $data = ['author' => $command->getAuthor()]; + $this->applyFilter($data, $command->getObject()); + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderToggleInstanceFeature(ToggleInstanceFeatureCommand $command) + { + $endpoint = 'objects/icingaapplications/' . $this->getApp(); + if ($command->getEnabled() === true) { + $enabled = true; + } else { + $enabled = false; + } + switch ($command->getFeature()) { + case ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS: + $attr = 'enable_host_checks'; + break; + case ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS: + $attr = 'enable_service_checks'; + break; + case ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS: + $attr = 'enable_event_handlers'; + break; + case ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION: + $attr = 'enable_flapping'; + break; + case ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS: + $attr = 'enable_notifications'; + break; + case ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA: + $attr = 'enable_perfdata'; + break; + default: + throw new InvalidArgumentException($command->getFeature()); + } + $data = array( + 'attrs' => array( + $attr => $enabled + ) + ); + return IcingaApiCommand::create($endpoint, $data); + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandFileCommandRenderer.php b/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandFileCommandRenderer.php new file mode 100644 index 0000000..97d1314 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandFileCommandRenderer.php @@ -0,0 +1,478 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Renderer; + +use Icinga\Module\Monitoring\Command\Instance\DisableNotificationsExpireCommand; +use Icinga\Module\Monitoring\Command\Instance\ToggleInstanceFeatureCommand; +use Icinga\Module\Monitoring\Command\Object\AcknowledgeProblemCommand; +use Icinga\Module\Monitoring\Command\Object\AddCommentCommand; +use Icinga\Module\Monitoring\Command\Object\DeleteCommentCommand; +use Icinga\Module\Monitoring\Command\Object\DeleteDowntimeCommand; +use Icinga\Module\Monitoring\Command\Object\ProcessCheckResultCommand; +use Icinga\Module\Monitoring\Command\Object\PropagateHostDowntimeCommand; +use Icinga\Module\Monitoring\Command\Object\RemoveAcknowledgementCommand; +use Icinga\Module\Monitoring\Command\Object\ScheduleServiceCheckCommand; +use Icinga\Module\Monitoring\Command\Object\ScheduleServiceDowntimeCommand; +use Icinga\Module\Monitoring\Command\Object\SendCustomNotificationCommand; +use Icinga\Module\Monitoring\Command\Object\ToggleObjectFeatureCommand; +use Icinga\Module\Monitoring\Command\IcingaCommand; +use InvalidArgumentException; + +/** + * Icinga command renderer for the Icinga command file + */ +class IcingaCommandFileCommandRenderer implements IcingaCommandRendererInterface +{ + /** + * Escape a command string + * + * @param string $commandString + * + * @return string + */ + protected function escape($commandString) + { + return str_replace(array("\r", "\n"), array('\r', '\n'), $commandString); + } + + /** + * Render a command + * + * @param IcingaCommand $command + * @param int|null $now + * + * @return string + */ + public function render(IcingaCommand $command, $now = null) + { + $renderMethod = 'render' . $command->getName(); + if (! method_exists($this, $renderMethod)) { + die($renderMethod); + } + if ($now === null) { + $now = time(); + } + return sprintf('[%u] %s', $now, $this->escape($this->$renderMethod($command))); + } + + public function renderAddComment(AddCommentCommand $command) + { + $object = $command->getObject(); + if ($command->getObject()->getType() === $command::TYPE_HOST) { + /** @var \Icinga\Module\Monitoring\Object\Host $object */ + $commandString = sprintf( + 'ADD_HOST_COMMENT;%s', + $object->getName() + ); + } else { + /** @var \Icinga\Module\Monitoring\Object\Service $object */ + $commandString = sprintf( + 'ADD_SVC_COMMENT;%s;%s', + $object->getHost()->getName(), + $object->getName() + ); + } + return sprintf( + '%s;%u;%s;%s', + $commandString, + $command->getPersistent(), + $command->getAuthor(), + $command->getComment() + ); + } + + public function renderSendCustomNotification(SendCustomNotificationCommand $command) + { + $object = $command->getObject(); + if ($command->getObject()->getType() === $command::TYPE_HOST) { + /** @var \Icinga\Module\Monitoring\Object\Host $object */ + $commandString = sprintf( + 'SEND_CUSTOM_HOST_NOTIFICATION;%s', + $object->getName() + ); + } else { + /** @var \Icinga\Module\Monitoring\Object\Service $object */ + $commandString = sprintf( + 'SEND_CUSTOM_SVC_NOTIFICATION;%s;%s', + $object->getHost()->getName(), + $object->getName() + ); + } + $options = 0; // 0 for no options + if ($command->getBroadcast() === true) { + $options |= 1; + } + if ($command->getForced() === true) { + $options |= 2; + } + return sprintf( + '%s;%u;%s;%s', + $commandString, + $options, + $command->getAuthor(), + $command->getComment() + ); + } + + public function renderProcessCheckResult(ProcessCheckResultCommand $command) + { + $object = $command->getObject(); + if ($command->getObject()->getType() === $command::TYPE_HOST) { + /** @var \Icinga\Module\Monitoring\Object\Host $object */ + $commandString = sprintf( + 'PROCESS_HOST_CHECK_RESULT;%s', + $object->getName() + ); + } else { + /** @var \Icinga\Module\Monitoring\Object\Service $object */ + $commandString = sprintf( + 'PROCESS_SERVICE_CHECK_RESULT;%s;%s', + $object->getHost()->getName(), + $object->getName() + ); + } + $output = $command->getOutput(); + if ($command->getPerformanceData() !== null) { + $output .= '|' . $command->getPerformanceData(); + } + return sprintf( + '%s;%u;%s', + $commandString, + $command->getStatus(), + $output + ); + } + + public function renderScheduleCheck(ScheduleServiceCheckCommand $command) + { + $object = $command->getObject(); + if ($command->getObject()->getType() === $command::TYPE_HOST) { + /** @var \Icinga\Module\Monitoring\Object\Host $object */ + /** @var \Icinga\Module\Monitoring\Command\Object\ScheduleHostCheckCommand $command */ + if ($command->getOfAllServices() === true) { + if ($command->getForced() === true) { + $commandName = 'SCHEDULE_FORCED_HOST_SVC_CHECKS'; + } else { + $commandName = 'SCHEDULE_HOST_SVC_CHECKS'; + } + } else { + if ($command->getForced() === true) { + $commandName = 'SCHEDULE_FORCED_HOST_CHECK'; + } else { + $commandName = 'SCHEDULE_HOST_CHECK'; + } + } + $commandString = sprintf( + '%s;%s', + $commandName, + $object->getName() + ); + } else { + /** @var \Icinga\Module\Monitoring\Object\Service $object */ + $commandString = sprintf( + '%s;%s;%s', + $command->getForced() === true ? 'SCHEDULE_FORCED_SVC_CHECK' : 'SCHEDULE_SVC_CHECK', + $object->getHost()->getName(), + $object->getName() + ); + } + return sprintf( + '%s;%u', + $commandString, + $command->getCheckTime() + ); + } + + public function renderScheduleDowntime(ScheduleServiceDowntimeCommand $command) + { + $object = $command->getObject(); + if ($command->getObject()->getType() === $command::TYPE_HOST) { + /** @var \Icinga\Module\Monitoring\Object\Host $object */ + /** @var \Icinga\Module\Monitoring\Command\Object\ScheduleHostDowntimeCommand $command */ + if ($command instanceof PropagateHostDowntimeCommand) { + /** @var \Icinga\Module\Monitoring\Command\Object\PropagateHostDowntimeCommand $command */ + $commandName = $command->getTriggered() === true ? 'SCHEDULE_AND_PROPAGATE_TRIGGERED_HOST_DOWNTIME' + : 'SCHEDULE_AND_PROPAGATE_HOST_DOWNTIME'; + } elseif ($command->getForAllServices() === true) { + $commandName = 'SCHEDULE_HOST_SVC_DOWNTIME'; + } else { + $commandName = 'SCHEDULE_HOST_DOWNTIME'; + } + $commandString = sprintf( + '%s;%s', + $commandName, + $object->getName() + ); + } else { + /** @var \Icinga\Module\Monitoring\Object\Service $object */ + $commandString = sprintf( + '%s;%s;%s', + 'SCHEDULE_SVC_DOWNTIME', + $object->getHost()->getName(), + $object->getName() + ); + } + return sprintf( + '%s;%u;%u;%u;%u;%u;%s;%s', + $commandString, + $command->getStart(), + $command->getEnd(), + $command->getFixed(), + $command->getTriggerId(), + $command->getDuration(), + $command->getAuthor(), + $command->getComment() + ); + } + + public function renderAcknowledgeProblem(AcknowledgeProblemCommand $command) + { + $object = $command->getObject(); + if ($command->getObject()->getType() === $command::TYPE_HOST) { + /** @var \Icinga\Module\Monitoring\Object\Host $object */ + $commandString = sprintf( + '%s;%s', + $command->getExpireTime() !== null ? 'ACKNOWLEDGE_HOST_PROBLEM_EXPIRE' : 'ACKNOWLEDGE_HOST_PROBLEM', + $object->getName() + ); + } else { + /** @var \Icinga\Module\Monitoring\Object\Service $object */ + $commandString = sprintf( + '%s;%s;%s', + $command->getExpireTime() !== null ? 'ACKNOWLEDGE_SVC_PROBLEM_EXPIRE' : 'ACKNOWLEDGE_SVC_PROBLEM', + $object->getHost()->getName(), + $object->getName() + ); + } + $commandString = sprintf( + '%s;%u;%u;%u', + $commandString, + $command->getSticky() ? 2 : 0, + $command->getNotify(), + $command->getPersistent() + ); + if ($command->getExpireTime() !== null) { + $commandString = sprintf( + '%s;%u', + $commandString, + $command->getExpireTime() + ); + } + return sprintf( + '%s;%s;%s', + $commandString, + $command->getAuthor(), + $command->getComment() + ); + } + + public function renderToggleObjectFeature(ToggleObjectFeatureCommand $command) + { + if ($command->getEnabled() === true) { + $commandPrefix = 'ENABLE'; + } else { + $commandPrefix = 'DISABLE'; + } + switch ($command->getFeature()) { + case ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS: + $commandFormat = sprintf('%s_%%s_CHECK', $commandPrefix); + break; + case ToggleObjectFeatureCommand::FEATURE_PASSIVE_CHECKS: + $commandFormat = sprintf('%s_PASSIVE_%%s_CHECKS', $commandPrefix); + break; + case ToggleObjectFeatureCommand::FEATURE_OBSESSING: + if ($command->getEnabled() === true) { + $commandPrefix = 'START'; + } else { + $commandPrefix = 'STOP'; + } + $commandFormat = sprintf('%s_OBSESSING_OVER_%%s', $commandPrefix); + break; + case ToggleObjectFeatureCommand::FEATURE_NOTIFICATIONS: + $commandFormat = sprintf('%s_%%s_NOTIFICATIONS', $commandPrefix); + break; + case ToggleObjectFeatureCommand::FEATURE_EVENT_HANDLER: + $commandFormat = sprintf('%s_%%s_EVENT_HANDLER', $commandPrefix); + break; + case ToggleObjectFeatureCommand::FEATURE_FLAP_DETECTION: + $commandFormat = sprintf('%s_%%s_FLAP_DETECTION', $commandPrefix); + break; + default: + throw new InvalidArgumentException($command->getFeature()); + } + $object = $command->getObject(); + if ($object->getType() === ToggleObjectFeatureCommand::TYPE_HOST) { + /** @var \Icinga\Module\Monitoring\Object\Host $object */ + $commandString = sprintf( + $commandFormat . ';%s', + 'HOST', + $object->getName() + ); + } else { + /** @var \Icinga\Module\Monitoring\Object\Service $object */ + $commandString = sprintf( + $commandFormat . ';%s;%s', + 'SVC', + $object->getHost()->getName(), + $object->getName() + ); + } + return $commandString; + } + + public function renderDeleteComment(DeleteCommentCommand $command) + { + return sprintf( + '%s;%u', + $command->getIsService() ? 'DEL_SVC_COMMENT' : 'DEL_HOST_COMMENT', + $command->getCommentId() + ); + } + + public function renderDeleteDowntime(DeleteDowntimeCommand $command) + { + return sprintf( + '%s;%u', + $command->getIsService() ? 'DEL_SVC_DOWNTIME' : 'DEL_HOST_DOWNTIME', + $command->getDowntimeId() + ); + } + + public function renderRemoveAcknowledgement(RemoveAcknowledgementCommand $command) + { + $object = $command->getObject(); + if ($command->getObject()->getType() === $command::TYPE_HOST) { + /** @var \Icinga\Module\Monitoring\Object\Host $object */ + $commandString = sprintf( + '%s;%s', + 'REMOVE_HOST_ACKNOWLEDGEMENT', + $object->getName() + ); + } else { + /** @var \Icinga\Module\Monitoring\Object\Service $object */ + $commandString = sprintf( + '%s;%s;%s', + 'REMOVE_SVC_ACKNOWLEDGEMENT', + $object->getHost()->getName(), + $object->getName() + ); + } + return $commandString; + } + + public function renderDisableNotificationsExpire(DisableNotificationsExpireCommand $command) + { + return sprintf( + '%s;%u;%u', + 'DISABLE_NOTIFICATIONS_EXPIRE_TIME', + time(), + $command->getExpireTime() + ); + } + + public function renderToggleInstanceFeature(ToggleInstanceFeatureCommand $command) + { + switch ($command->getFeature()) { + case ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS: + case ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS: + case ToggleInstanceFeatureCommand::FEATURE_HOST_OBSESSING: + case ToggleInstanceFeatureCommand::FEATURE_SERVICE_OBSESSING: + case ToggleInstanceFeatureCommand::FEATURE_PASSIVE_HOST_CHECKS: + case ToggleInstanceFeatureCommand::FEATURE_PASSIVE_SERVICE_CHECKS: + if ($command->getEnabled() === true) { + $commandPrefix = 'START'; + } else { + $commandPrefix = 'STOP'; + } + break; + case ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS: + case ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION: + case ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS: + case ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA: + if ($command->getEnabled() === true) { + $commandPrefix = 'ENABLE'; + } else { + $commandPrefix = 'DISABLE'; + } + break; + default: + throw new InvalidArgumentException($command->getFeature()); + } + switch ($command->getFeature()) { + case ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS: + $commandString = sprintf( + '%s_%s', + $commandPrefix, + 'EXECUTING_HOST_CHECKS' + ); + break; + case ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS: + $commandString = sprintf( + '%s_%s', + $commandPrefix, + 'EXECUTING_SVC_CHECKS' + ); + break; + case ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS: + $commandString = sprintf( + '%s_%s', + $commandPrefix, + 'EVENT_HANDLERS' + ); + break; + case ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION: + $commandString = sprintf( + '%s_%s', + $commandPrefix, + 'FLAP_DETECTION' + ); + break; + case ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS: + $commandString = sprintf( + '%s_%s', + $commandPrefix, + 'NOTIFICATIONS' + ); + break; + case ToggleInstanceFeatureCommand::FEATURE_HOST_OBSESSING: + $commandString = sprintf( + '%s_%s', + $commandPrefix, + 'OBSESSING_OVER_HOST_CHECKS' + ); + break; + case ToggleInstanceFeatureCommand::FEATURE_SERVICE_OBSESSING: + $commandString = sprintf( + '%s_%s', + $commandPrefix, + 'OBSESSING_OVER_SVC_CHECKS' + ); + break; + case ToggleInstanceFeatureCommand::FEATURE_PASSIVE_HOST_CHECKS: + $commandString = sprintf( + '%s_%s', + $commandPrefix, + 'ACCEPTING_PASSIVE_HOST_CHECKS' + ); + break; + case ToggleInstanceFeatureCommand::FEATURE_PASSIVE_SERVICE_CHECKS: + $commandString = sprintf( + '%s_%s', + $commandPrefix, + 'ACCEPTING_PASSIVE_SVC_CHECKS' + ); + break; + case ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA: + $commandString = sprintf( + '%s_%s', + $commandPrefix, + 'PERFORMANCE_DATA' + ); + break; + default: + throw new InvalidArgumentException($command->getFeature()); + } + return $commandString; + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandRendererInterface.php b/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandRendererInterface.php new file mode 100644 index 0000000..e3ef6ba --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandRendererInterface.php @@ -0,0 +1,11 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Renderer; + +/** + * Interface for Icinga command renderer + */ +interface IcingaCommandRendererInterface +{ +} diff --git a/modules/monitoring/library/Monitoring/Command/Transport/ApiCommandTransport.php b/modules/monitoring/library/Monitoring/Command/Transport/ApiCommandTransport.php new file mode 100644 index 0000000..06e6afd --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Transport/ApiCommandTransport.php @@ -0,0 +1,291 @@ +<?php +/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Transport; + +use Icinga\Application\Hook\AuditHook; +use Icinga\Application\Logger; +use Icinga\Exception\Json\JsonDecodeException; +use Icinga\Module\Monitoring\Command\IcingaApiCommand; +use Icinga\Module\Monitoring\Command\IcingaCommand; +use Icinga\Module\Monitoring\Command\Renderer\IcingaApiCommandRenderer; +use Icinga\Module\Monitoring\Exception\CommandTransportException; +use Icinga\Module\Monitoring\Exception\CurlException; +use Icinga\Module\Monitoring\Web\Rest\RestRequest; +use Icinga\Util\Json; + +/** + * Command transport over Icinga 2's REST API + */ +class ApiCommandTransport implements CommandTransportInterface +{ + /** + * Transport identifier + */ + const TRANSPORT = 'api'; + + /** + * API host + * + * @var string + */ + protected $host; + + /** + * API password + * + * @var string + */ + protected $password; + + /** + * API port + * + * @var int + */ + protected $port = 5665; + + /** + * Command renderer + * + * @var IcingaApiCommandRenderer + */ + protected $renderer; + + /** + * API username + * + * @var string + */ + protected $username; + + /** + * Create a new API command transport + */ + public function __construct() + { + $this->renderer = new IcingaApiCommandRenderer(); + } + + /** + * Set the name of the Icinga application object + * + * @param string $app + * + * @return $this + */ + public function setApp($app) + { + $this->renderer->setApp($app); + + return $this; + } + + /** + * Get the API host + * + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * Set the API host + * + * @param string $host + * + * @return $this + */ + public function setHost($host) + { + $this->host = $host; + + return $this; + } + + /** + * Get the API password + * + * @return string + */ + public function getPassword() + { + return $this->password; + } + + /** + * Set the API password + * + * @param string $password + * + * @return $this + */ + public function setPassword($password) + { + $this->password = $password; + + return $this; + } + + /** + * Get the API port + * + * @return int + */ + public function getPort() + { + return $this->port; + } + + /** + * Set the API port + * + * @param int $port + * + * @return $this + */ + public function setPort($port) + { + $this->port = (int) $port; + + return $this; + } + + /** + * Get the API username + * + * @return string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Set the API username + * + * @param string $username + * + * @return $this + */ + public function setUsername($username) + { + $this->username = $username; + + return $this; + } + + /** + * Get URI for endpoint + * + * @param string $endpoint + * + * @return string + */ + protected function getUriFor($endpoint) + { + return sprintf('https://%s:%u/v1/%s', $this->getHost(), $this->getPort(), $endpoint); + } + + protected function sendCommand(IcingaApiCommand $command) + { + Logger::debug( + 'Sending Icinga command "%s" to the API "%s:%u"', + $command->getEndpoint(), + $this->getHost(), + $this->getPort() + ); + + $data = $command->getData(); + $payload = Json::encode($data); + AuditHook::logActivity( + 'monitoring/command', + "Issued command {$command->getEndpoint()} with the following payload: $payload", + $data + ); + + try { + $response = RestRequest::post($this->getUriFor($command->getEndpoint())) + ->authenticateWith($this->getUsername(), $this->getPassword()) + ->sendJson() + ->noStrictSsl() + ->setPayload($command->getData()) + ->send(); + } catch (JsonDecodeException $e) { + throw new CommandTransportException( + 'Got invalid JSON response from the Icinga 2 API: %s', + $e->getMessage() + ); + } + + if (isset($response['error'])) { + throw new CommandTransportException( + 'Can\'t send external Icinga command: %u %s', + $response['error'], + $response['status'] + ); + } + $result = array_pop($response['results']); + if (! empty($result) + && ($result['code'] < 200 || $result['code'] >= 300) + ) { + throw new CommandTransportException( + 'Can\'t send external Icinga command: %u %s', + $result['code'], + $result['status'] + ); + } + if ($command->hasNext()) { + $this->sendCommand($command->getNext()); + } + } + + /** + * Send the Icinga command over the Icinga 2 API + * + * @param IcingaCommand $command + * @param int|null $now + * + * @throws CommandTransportException + */ + public function send(IcingaCommand $command, $now = null) + { + $this->sendCommand($this->renderer->render($command)); + } + + /** + * Try to connect to the API + * + * @throws CommandTransportException In case of failure + */ + public function probe() + { + $request = RestRequest::get($this->getUriFor(null)) + ->authenticateWith($this->getUsername(), $this->getPassword()) + ->noStrictSsl(); + + try { + $response = $request->send(); + } catch (CurlException $e) { + throw new CommandTransportException( + 'Couldn\'t connect to the Icinga 2 API: %s', + $e->getMessage() + ); + } catch (JsonDecodeException $e) { + throw new CommandTransportException( + 'Got invalid JSON response from the Icinga 2 API: %s', + $e->getMessage() + ); + } + + if (isset($response['error'])) { + throw new CommandTransportException( + 'Can\'t connect to the Icinga 2 API: %u %s', + $response['error'], + $response['status'] + ); + } + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Transport/CommandTransport.php b/modules/monitoring/library/Monitoring/Command/Transport/CommandTransport.php new file mode 100644 index 0000000..4086dec --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Transport/CommandTransport.php @@ -0,0 +1,170 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Transport; + +use Exception; +use Icinga\Application\Config; +use Icinga\Application\Logger; +use Icinga\Data\ConfigObject; +use Icinga\Exception\ConfigurationError; +use Icinga\Module\Monitoring\Command\IcingaCommand; +use Icinga\Module\Monitoring\Command\Object\ObjectCommand; +use Icinga\Module\Monitoring\Exception\CommandTransportException; + +/** + * Command transport + * + * This class is subject to change as we do not have environments yet (#4471). + */ +class CommandTransport implements CommandTransportInterface +{ + /** + * Transport configuration + * + * @var Config + */ + protected static $config; + + /** + * Get transport configuration + * + * @return Config + * + * @throws ConfigurationError + */ + public static function getConfig() + { + if (static::$config === null) { + $config = Config::module('monitoring', 'commandtransports'); + if ($config->isEmpty()) { + throw new ConfigurationError( + mt('monitoring', 'No command transports have been configured in "%s".'), + $config->getConfigFile() + ); + } + + static::$config = $config; + } + + return static::$config; + } + + /** + * Create a transport from config + * + * @param ConfigObject $config + * + * @return LocalCommandFile|RemoteCommandFile|ApiCommandTransport + * + * @throws ConfigurationError + */ + public static function createTransport(ConfigObject $config) + { + $config = clone $config; + switch (strtolower($config->transport)) { + case RemoteCommandFile::TRANSPORT: + $transport = new RemoteCommandFile(); + break; + case ApiCommandTransport::TRANSPORT: + $transport = new ApiCommandTransport(); + break; + case LocalCommandFile::TRANSPORT: + case '': // Casting null to string is the empty string + $transport = new LocalCommandFile(); + break; + default: + throw new ConfigurationError( + mt( + 'monitoring', + 'Cannot create command transport "%s". Invalid transport' + . ' defined in "%s". Use one of "%s", "%s" or "%s".' + ), + $config->transport, + static::getConfig()->getConfigFile(), + LocalCommandFile::TRANSPORT, + RemoteCommandFile::TRANSPORT, + ApiCommandTransport::TRANSPORT + ); + } + + unset($config->transport); + foreach ($config as $key => $value) { + $method = 'set' . ucfirst($key); + if (! method_exists($transport, $method)) { + // Ignore settings from config that don't have a setter on the transport instead of throwing an + // exception here because the transport should throw an exception if it's not fully set up + // when being about to send a command + continue; + } + + $transport->$method($value); + } + + return $transport; + } + + /** + * Send the given command over an appropriate Icinga command transport + * + * This will try one configured transport after another until the command has been successfully sent. + * + * @param IcingaCommand $command The command to send + * @param int|null $now Timestamp of the command or null for now + * + * @throws CommandTransportException If sending the Icinga command failed + */ + public function send(IcingaCommand $command, $now = null) + { + $errors = array(); + + foreach (static::getConfig() as $name => $transportConfig) { + $transport = static::createTransport($transportConfig); + if ($this->transferPossible($command, $transport)) { + try { + $transport->send($command, $now); + } catch (Exception $e) { + Logger::error($e); + $errors[] = sprintf('%s: %s.', $name, rtrim($e->getMessage(), '.')); + continue; // Try the next transport + } + + return; // The command was successfully sent + } + } + + if (! empty($errors)) { + throw new CommandTransportException(implode("\n", $errors)); + } + + throw new CommandTransportException( + mt( + 'monitoring', + 'Failed to send external Icinga command. No transport has been configured' + . ' for this instance. Please contact your Icinga Web administrator.' + ) + ); + } + + /** + * Return whether it is possible to send the given command using the given transport + * + * @param IcingaCommand $command + * @param CommandTransportInterface $transport + * + * @return bool + */ + protected function transferPossible($command, $transport) + { + if (! method_exists($transport, 'getInstance') || !$command instanceof ObjectCommand) { + return true; + } + + $transportInstance = $transport->getInstance(); + if (! $transportInstance || $transportInstance === 'none') { + return true; + } + + return strtolower($transportInstance) === strtolower($command->getObject()->instance_name); + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Transport/CommandTransportInterface.php b/modules/monitoring/library/Monitoring/Command/Transport/CommandTransportInterface.php new file mode 100644 index 0000000..e9cb086 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Transport/CommandTransportInterface.php @@ -0,0 +1,22 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Transport; + +use Icinga\Module\Monitoring\Command\IcingaCommand; + +/** + * Interface for Icinga command transports + */ +interface CommandTransportInterface +{ + /** + * Send an Icinga command over the Icinga command transport + * + * @param IcingaCommand $command The command to send + * @param int|null $now Timestamp of the command or null for now + * + * @throws \Icinga\Module\Monitoring\Exception\CommandTransportException If sending the Icinga command failed + */ + public function send(IcingaCommand $command, $now = null); +} diff --git a/modules/monitoring/library/Monitoring/Command/Transport/LocalCommandFile.php b/modules/monitoring/library/Monitoring/Command/Transport/LocalCommandFile.php new file mode 100644 index 0000000..891a46f --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Transport/LocalCommandFile.php @@ -0,0 +1,168 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Transport; + +use Exception; +use RuntimeException; +use Icinga\Application\Logger; +use Icinga\Exception\ConfigurationError; +use Icinga\Module\Monitoring\Command\IcingaCommand; +use Icinga\Module\Monitoring\Command\Renderer\IcingaCommandFileCommandRenderer; +use Icinga\Module\Monitoring\Exception\CommandTransportException; +use Icinga\Util\File; + +/** + * A local Icinga command file + */ +class LocalCommandFile implements CommandTransportInterface +{ + /** + * Transport identifier + */ + const TRANSPORT = 'local'; + + /** + * The name of the Icinga instance this transport will transfer commands to + * + * @var string + */ + protected $instanceName; + + /** + * Path to the icinga command file + * + * @var String + */ + protected $path; + + /** + * Mode used to open the icinga command file + * + * @var string + */ + protected $openMode = 'wn'; + + /** + * Command renderer + * + * @var IcingaCommandFileCommandRenderer + */ + protected $renderer; + + /** + * Create a new local command file command transport + */ + public function __construct() + { + $this->renderer = new IcingaCommandFileCommandRenderer(); + } + + /** + * Set the name of the Icinga instance this transport will transfer commands to + * + * @param string $name + * + * @return $this + */ + public function setInstance($name) + { + $this->instanceName = $name; + return $this; + } + + /** + * Return the name of the Icinga instance this transport will transfer commands to + * + * @return string + */ + public function getInstance() + { + return $this->instanceName; + } + + /** + * Set the path to the local Icinga command file + * + * @param string $path + * + * @return $this + */ + public function setPath($path) + { + $this->path = (string) $path; + return $this; + } + + /** + * Get the path to the local Icinga command file + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Set the mode used to open the icinga command file + * + * @param string $openMode + * + * @return $this + */ + public function setOpenMode($openMode) + { + $this->openMode = (string) $openMode; + return $this; + } + + /** + * Get the mode used to open the icinga command file + * + * @return string + */ + public function getOpenMode() + { + return $this->openMode; + } + + /** + * Write the command to the local Icinga command file + * + * @param IcingaCommand $command + * @param int|null $now + * + * @throws ConfigurationError + * @throws CommandTransportException + */ + public function send(IcingaCommand $command, $now = null) + { + if (! isset($this->path)) { + throw new ConfigurationError( + 'Can\'t send external Icinga Command. Path to the local command file is missing' + ); + } + $commandString = $this->renderer->render($command, $now); + Logger::debug( + 'Sending external Icinga command "%s" to the local command file "%s"', + $commandString, + $this->path + ); + try { + $file = new File($this->path, $this->openMode); + $file->fwrite($commandString . "\n"); + } catch (Exception $e) { + $message = $e->getMessage(); + if ($e instanceof RuntimeException && ($pos = strrpos($message, ':')) !== false) { + // Assume RuntimeException thrown by SplFileObject in the format: __METHOD__ . "({$filename}): Message" + $message = substr($message, $pos + 1); + } + throw new CommandTransportException( + 'Can\'t send external Icinga command to the local command file "%s": %s', + $this->path, + $message + ); + } + } +} diff --git a/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php b/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php new file mode 100644 index 0000000..8619e87 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php @@ -0,0 +1,465 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Command\Transport; + +use Icinga\Application\Logger; +use Icinga\Data\ResourceFactory; +use Icinga\Exception\ConfigurationError; +use Icinga\Module\Monitoring\Command\IcingaCommand; +use Icinga\Module\Monitoring\Command\Renderer\IcingaCommandFileCommandRenderer; +use Icinga\Module\Monitoring\Exception\CommandTransportException; + +/** + * A remote Icinga command file + * + * Key-based SSH login must be possible for the user to log in as on the remote host + */ +class RemoteCommandFile implements CommandTransportInterface +{ + /** + * Transport identifier + */ + const TRANSPORT = 'remote'; + + /** + * The name of the Icinga instance this transport will transfer commands to + * + * @var string + */ + protected $instanceName; + + /** + * Remote host + * + * @var string + */ + protected $host; + + /** + * Port to connect to on the remote host + * + * @var int + */ + protected $port = 22; + + /** + * User to log in as on the remote host + * + * Defaults to current PHP process' user + * + * @var string + */ + protected $user; + + /** + * Path to the private key file for the key-based authentication + * + * @var string + */ + protected $privateKey; + + /** + * Path to the Icinga command file on the remote host + * + * @var string + */ + protected $path; + + /** + * Command renderer + * + * @var IcingaCommandFileCommandRenderer + */ + protected $renderer; + + /** + * SSH subprocess pipes + * + * @var array + */ + protected $sshPipes; + + /** + * SSH subprocess + * + * @var resource + */ + protected $sshProcess; + + /** + * Create a new remote command file command transport + */ + public function __construct() + { + $this->renderer = new IcingaCommandFileCommandRenderer(); + } + + /** + * Set the name of the Icinga instance this transport will transfer commands to + * + * @param string $name + * + * @return $this + */ + public function setInstance($name) + { + $this->instanceName = $name; + return $this; + } + + /** + * Return the name of the Icinga instance this transport will transfer commands to + * + * @return string + */ + public function getInstance() + { + return $this->instanceName; + } + + /** + * Set the remote host + * + * @param string $host + * + * @return $this + */ + public function setHost($host) + { + $this->host = (string) $host; + return $this; + } + + /** + * Get the remote host + * + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * Set the port to connect to on the remote host + * + * @param int $port + * + * @return $this + */ + public function setPort($port) + { + $this->port = (int) $port; + return $this; + } + + /** + * Get the port to connect on the remote host + * + * @return int + */ + public function getPort() + { + return $this->port; + } + + /** + * Set the user to log in as on the remote host + * + * @param string $user + * + * @return $this + */ + public function setUser($user) + { + $this->user = (string) $user; + return $this; + } + + /** + * Get the user to log in as on the remote host + * + * Defaults to current PHP process' user + * + * @return string|null + */ + public function getUser() + { + return $this->user; + } + + /** + * Set the path to the private key file + * + * @param string $privateKey + * + * @return $this + */ + public function setPrivateKey($privateKey) + { + $this->privateKey = (string) $privateKey; + return $this; + } + + /** + * Get the path to the private key + * + * @return string + */ + public function getPrivateKey() + { + return $this->privateKey; + } + + /** + * Use a given resource to set the user and the key + * + * @param ?string $resource + * + * @throws ConfigurationError + */ + public function setResource($resource = null) + { + $config = ResourceFactory::getResourceConfig($resource); + + if (! isset($config->user)) { + throw new ConfigurationError( + t("Can't send external Icinga Command. Remote user is missing") + ); + } + if (! isset($config->private_key)) { + throw new ConfigurationError( + t("Can't send external Icinga Command. The private key for the remote user is missing") + ); + } + + $this->setUser($config->user); + $this->setPrivateKey($config->private_key); + } + + /** + * Set the path to the Icinga command file on the remote host + * + * @param string $path + * + * @return $this + */ + public function setPath($path) + { + $this->path = (string) $path; + return $this; + } + + /** + * Get the path to the Icinga command file on the remote host + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Write the command to the Icinga command file on the remote host + * + * @param IcingaCommand $command + * @param int|null $now + * + * @throws ConfigurationError + * @throws CommandTransportException + */ + public function send(IcingaCommand $command, $now = null) + { + if (! isset($this->path)) { + throw new ConfigurationError( + 'Can\'t send external Icinga Command. Path to the remote command file is missing' + ); + } + if (! isset($this->host)) { + throw new ConfigurationError('Can\'t send external Icinga Command. Remote host is missing'); + } + $commandString = $this->renderer->render($command, $now); + Logger::debug( + 'Sending external Icinga command "%s" to the remote command file "%s:%u%s"', + $commandString, + $this->host, + $this->port, + $this->path + ); + return $this->sendCommandString($commandString); + } + + /** + * Get the SSH command + * + * @return string + */ + protected function sshCommand() + { + $cmd = sprintf( + 'exec ssh -o BatchMode=yes -p %u', + $this->port + ); + // -o BatchMode=yes for disabling interactive authentication methods + + if (isset($this->user)) { + $cmd .= ' -l ' . escapeshellarg($this->user); + } + + if (isset($this->privateKey)) { + // TODO: StrictHostKeyChecking=no for compat only, must be removed + $cmd .= ' -o StrictHostKeyChecking=no' + . ' -i ' . escapeshellarg($this->privateKey); + } + + $cmd .= sprintf( + ' %s "cat > %s"', + escapeshellarg($this->host), + escapeshellarg($this->path) + ); + + return $cmd; + } + + /** + * Send the command over SSH + * + * @param string $commandString + * + * @throws CommandTransportException + */ + protected function sendCommandString($commandString) + { + if ($this->isSshAlive()) { + $ret = fwrite($this->sshPipes[0], $commandString . "\n"); + if ($ret === false) { + $this->throwSshFailure('Cannot write to the remote command pipe'); + } elseif ($ret !== strlen($commandString) + 1) { + $this->throwSshFailure( + 'Failed to write the whole command to the remote command pipe' + ); + } + } else { + $this->throwSshFailure(); + } + } + + /** + * Get the pipes of the SSH subprocess + * + * @return array + */ + protected function getSshPipes() + { + if ($this->sshPipes === null) { + $this->forkSsh(); + } + + return $this->sshPipes; + } + + /** + * Get the SSH subprocess + * + * @return resource + */ + protected function getSshProcess() + { + if ($this->sshProcess === null) { + $this->forkSsh(); + } + + return $this->sshProcess; + } + + /** + * Get the status of the SSH subprocess + * + * @param string $what + * + * @return mixed + */ + protected function getSshProcessStatus($what = null) + { + $status = proc_get_status($this->getSshProcess()); + if ($what === null) { + return $status; + } else { + return $status[$what]; + } + } + + /** + * Get whether the SSH subprocess is alive + * + * @return bool + */ + protected function isSshAlive() + { + return $this->getSshProcessStatus('running'); + } + + /** + * Fork SSH subprocess + * + * @throws CommandTransportException If fork fails + */ + protected function forkSsh() + { + $descriptors = array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w') + ); + + $this->sshProcess = proc_open($this->sshCommand(), $descriptors, $this->sshPipes); + + if (! is_resource($this->sshProcess)) { + throw new CommandTransportException( + 'Can\'t send external Icinga command: Failed to fork SSH' + ); + } + } + + /** + * Read from STDERR + * + * @return string + */ + protected function readStderr() + { + return stream_get_contents($this->sshPipes[2]); + } + + /** + * Throw SSH failure + * + * @param string $msg + * + * @throws CommandTransportException + */ + protected function throwSshFailure($msg = 'Can\'t send external Icinga command') + { + throw new CommandTransportException( + '%s: %s', + $msg, + $this->readStderr() . var_export($this->getSshProcessStatus(), true) + ); + } + + /** + * Close SSH pipes and SSH subprocess + */ + public function __destruct() + { + if (is_resource($this->sshProcess)) { + fclose($this->sshPipes[0]); + fclose($this->sshPipes[1]); + fclose($this->sshPipes[2]); + + proc_close($this->sshProcess); + } + } +} diff --git a/modules/monitoring/library/Monitoring/Controller.php b/modules/monitoring/library/Monitoring/Controller.php new file mode 100644 index 0000000..2628935 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Controller.php @@ -0,0 +1,159 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring; + +use ArrayIterator; +use Icinga\Exception\ConfigurationError; +use Icinga\Exception\QueryException; +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filterable; +use Icinga\File\Csv; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use Icinga\Module\Monitoring\Data\CustomvarProtectionIterator; +use Icinga\Util\Json; +use Icinga\Web\Controller as IcingaWebController; +use Icinga\Web\Url; + +/** + * Base class for all monitoring action controller + */ +class Controller extends IcingaWebController +{ + /** + * The backend used for this controller + * + * @var MonitoringBackend + */ + protected $backend; + + protected function moduleInit() + { + $this->backend = MonitoringBackend::instance($this->_getParam('backend')); + $this->view->url = Url::fromRequest(); + } + + protected function handleFormatRequest($query) + { + $desiredContentType = $this->getRequest()->getHeader('Accept'); + if ($desiredContentType === 'application/json') { + $desiredFormat = 'json'; + } elseif ($desiredContentType === 'text/csv') { + $desiredFormat = 'csv'; + } else { + $desiredFormat = strtolower($this->params->get('format', 'html')); + } + + if ($desiredFormat !== 'html' && ! $this->params->has('limit')) { + $query->limit(); // Resets any default limit and offset + } + + switch ($desiredFormat) { + case 'sql': + echo '<pre>' + . htmlspecialchars(wordwrap($query->dump())) + . '</pre>'; + exit; + case 'json': + $response = $this->getResponse(); + $response + ->setHeader('Content-Type', 'application/json') + ->setHeader('Cache-Control', 'no-store') + ->setHeader( + 'Content-Disposition', + 'inline; filename=' . $this->getRequest()->getActionName() . '.json' + ) + ->appendBody( + Json::sanitize( + iterator_to_array( + new CustomvarProtectionIterator( + new ArrayIterator($query->fetchAll()) + ) + ) + ) + ) + ->sendResponse(); + exit; + case 'csv': + $response = $this->getResponse(); + $response + ->setHeader('Content-Type', 'text/csv') + ->setHeader('Cache-Control', 'no-store') + ->setHeader( + 'Content-Disposition', + 'attachment; filename=' . $this->getRequest()->getActionName() . '.csv' + ) + ->appendBody((string) Csv::fromQuery(new CustomvarProtectionIterator($query))) + ->sendResponse(); + exit; + } + } + + /** + * Apply a restriction of the authenticated on the given filterable + * + * @param string $name Name of the restriction + * @param Filterable $filterable Filterable to restrict + * + * @return Filterable The filterable having the restriction applied + */ + protected function applyRestriction($name, Filterable $filterable) + { + $filterable->applyFilter($this->getRestriction($name)); + return $filterable; + } + + /** + * Get a restriction of the authenticated + * + * @param string $name Name of the restriction + * + * @return Filter Filter object + * @throws ConfigurationError If the restriction contains invalid filter columns + */ + protected function getRestriction($name) + { + $restriction = Filter::matchAny(); + $restriction->setAllowedFilterColumns(array( + 'host_name', + 'hostgroup_name', + 'instance_name', + 'service_description', + 'servicegroup_name', + function ($c) { + return preg_match('/^_(?:host|service)_/i', $c); + } + )); + foreach ($this->getRestrictions($name) as $filter) { + if ($filter === '*') { + return Filter::matchAll(); + } + try { + $restriction->addFilter(Filter::fromQueryString($filter)); + } catch (QueryException $e) { + throw new ConfigurationError( + $this->translate( + 'Cannot apply restriction %s using the filter %s. You can only use the following columns: %s' + ), + $name, + $filter, + implode(', ', array( + 'instance_name', + 'host_name', + 'hostgroup_name', + 'service_description', + 'servicegroup_name', + '_(host|service)_<customvar-name>' + )), + $e + ); + } + } + + if ($restriction->isEmpty()) { + return Filter::matchAll(); + } + + return $restriction; + } +} diff --git a/modules/monitoring/library/Monitoring/Data/ColumnFilterIterator.php b/modules/monitoring/library/Monitoring/Data/ColumnFilterIterator.php new file mode 100644 index 0000000..0ad051b --- /dev/null +++ b/modules/monitoring/library/Monitoring/Data/ColumnFilterIterator.php @@ -0,0 +1,30 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Data; + +use ArrayIterator; +use FilterIterator; +use Zend_Db_Expr; + +/** + * Iterator over non-pseudo monitoring query columns + */ +class ColumnFilterIterator extends FilterIterator +{ + /** + * Create a new ColumnFilterIterator + * + * @param array $columns + */ + public function __construct(array $columns) + { + parent::__construct(new ArrayIterator($columns)); + } + + public function accept(): bool + { + $column = $this->current(); + return ! ($column instanceof Zend_Db_Expr || $column === '(NULL)'); + } +} diff --git a/modules/monitoring/library/Monitoring/Data/CustomvarProtectionIterator.php b/modules/monitoring/library/Monitoring/Data/CustomvarProtectionIterator.php new file mode 100644 index 0000000..c3cc01a --- /dev/null +++ b/modules/monitoring/library/Monitoring/Data/CustomvarProtectionIterator.php @@ -0,0 +1,25 @@ +<?php +/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Data; + +use Icinga\Module\Monitoring\Object\MonitoredObject; +use IteratorIterator; + +class CustomvarProtectionIterator extends IteratorIterator +{ + const IS_CV_RE = '~^_(host|service)_([a-zA-Z0-9_]+)$~'; + + public function current(): object + { + $row = parent::current(); + + foreach ($row as $col => $val) { + if (preg_match(self::IS_CV_RE, $col, $m)) { + $row->$col = MonitoredObject::protectCustomVars([$m[2] => $val])[$m[2]]; + } + } + + return $row; + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Command.php b/modules/monitoring/library/Monitoring/DataView/Command.php new file mode 100644 index 0000000..6beb8bc --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Command.php @@ -0,0 +1,24 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +/** + * View representation for commands + */ +class Command extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'command_id', + 'command_instance_id', + 'command_config_type', + 'command_line', + 'command_name' + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Comment.php b/modules/monitoring/library/Monitoring/DataView/Comment.php new file mode 100644 index 0000000..3a035bc --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Comment.php @@ -0,0 +1,82 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +/** + * Host and service comments view + */ +class Comment extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'comment_author_name', + 'comment_data', + 'comment_expiration', + 'comment_internal_id', + 'comment_is_persistent', + 'comment_name', + 'comment_timestamp', + 'comment_type', + 'host_display_name', + 'host_name', + 'object_type', + 'service_description', + 'service_display_name', + 'service_host_name' + ); + } + + /** + * {@inheritdoc} + */ + public function getStaticFilterColumns() + { + return array( + 'comment_author', + 'host', 'host_alias', + 'hostgroup', 'hostgroup_alias', 'hostgroup_name', + 'instance_name', + 'service', + 'servicegroup', 'servicegroup_alias', 'servicegroup_name' + ); + } + + /** + * {@inheritdoc} + */ + public function getSearchColumns() + { + return array('host_display_name', 'service_display_name'); + } + + /** + * {@inheritdoc} + */ + public function getSortRules() + { + return array( + 'comment_timestamp' => array( + 'order' => self::SORT_DESC + ), + 'host_display_name' => array( + 'columns' => array( + 'host_display_name', + 'service_display_name' + ), + 'order' => self::SORT_ASC + ), + 'service_display_name' => array( + 'columns' => array( + 'service_display_name', + 'host_display_name' + ), + 'order' => self::SORT_ASC + ) + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Commentevent.php b/modules/monitoring/library/Monitoring/DataView/Commentevent.php new file mode 100644 index 0000000..316700a --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Commentevent.php @@ -0,0 +1,30 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +class Commentevent extends DataView +{ + public function getColumns() + { + return array( + 'commentevent_id', + 'commentevent_entry_type', + 'commentevent_comment_time', + 'commentevent_author_name', + 'commentevent_comment_data', + 'commentevent_is_persistent', + 'commentevent_comment_source', + 'commentevent_expires', + 'commentevent_expiration_time', + 'commentevent_deletion_time', + 'host_name', + 'service_description' + ); + } + + public function getStaticFilterColumns() + { + return array('commentevent_id'); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Contact.php b/modules/monitoring/library/Monitoring/DataView/Contact.php new file mode 100644 index 0000000..986acab --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Contact.php @@ -0,0 +1,73 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +class Contact extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'contact_object_id', + 'contact_id', + 'contact_name', + 'contact_alias', + 'contact_email', + 'contact_pager', + 'contact_has_host_notfications', + 'contact_has_service_notfications', + 'contact_can_submit_commands', + 'contact_notify_service_recovery', + 'contact_notify_service_warning', + 'contact_notify_service_critical', + 'contact_notify_service_unknown', + 'contact_notify_service_flapping', + 'contact_notify_service_downtime', + 'contact_notify_host_recovery', + 'contact_notify_host_down', + 'contact_notify_host_unreachable', + 'contact_notify_host_flapping', + 'contact_notify_host_downtime', + 'contact_notify_host_timeperiod', + 'contact_notify_service_timeperiod' + ); + } + + /** + * {@inheritdoc} + */ + public function getSortRules() + { + return array( + 'contact_name' => array( + 'order' => self::SORT_ASC + ) + ); + } + + /** + * {@inheritdoc} + */ + public function getStaticFilterColumns() + { + return array( + 'contact', 'instance_name', + 'contactgroup', 'contactgroup_name', 'contactgroup_alias', + 'host', 'host_name', 'host_display_name', 'host_alias', + 'hostgroup', 'hostgroup_alias', 'hostgroup_name', + 'service', 'service_description', 'service_display_name', + 'servicegroup', 'servicegroup_alias', 'servicegroup_name' + ); + } + + /** + * {@inheritdoc} + */ + public function getSearchColumns() + { + return array('contact_alias'); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Contactgroup.php b/modules/monitoring/library/Monitoring/DataView/Contactgroup.php new file mode 100644 index 0000000..84eecd1 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Contactgroup.php @@ -0,0 +1,57 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +class Contactgroup extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'contactgroup_name', + 'contactgroup_alias', + 'contact_count' + ); + } + + /** + * {@inheritdoc} + */ + public function getSortRules() + { + return array( + 'contactgroup_name' => array( + 'order' => self::SORT_ASC + ), + 'contactgroup_alias' => array( + 'order' => self::SORT_ASC + ) + ); + } + + /** + * {@inheritdoc} + */ + public function getStaticFilterColumns() + { + return array( + 'contactgroup', + 'host', 'host_name', 'host_display_name', 'host_alias', + 'hostgroup', 'hostgroup_alias', 'hostgroup_name', + 'instance_name', + 'service', 'service_description', 'service_display_name', + 'servicegroup', 'servicegroup_alias', 'servicegroup_name' + ); + } + + /** + * {@inheritdoc} + */ + public function getSearchColumns() + { + return array('contactgroup_alias'); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Customvar.php b/modules/monitoring/library/Monitoring/DataView/Customvar.php new file mode 100644 index 0000000..c02d52f --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Customvar.php @@ -0,0 +1,47 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +class Customvar extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'varname', + 'varvalue', + 'is_json', + 'host_name', + 'service_description', + 'contact_name', + 'object_type', + 'object_type_id' + ); + } + + /** + * {@inheritdoc} + */ + public function getSortRules() + { + return array( + 'varname' => array( + 'columns' => array( + 'varname', + 'varvalue' + ) + ) + ); + } + + /** + * {@inheritdoc} + */ + public function getStaticFilterColumns() + { + return array('host', 'service', 'contact'); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/DataView.php b/modules/monitoring/library/Monitoring/DataView/DataView.php new file mode 100644 index 0000000..5b16e28 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/DataView.php @@ -0,0 +1,608 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +use Icinga\Data\Filter\FilterExpression; +use Icinga\Data\Filter\FilterMatch; +use IteratorAggregate; +use Icinga\Application\Hook; +use Icinga\Data\ConnectionInterface; +use Icinga\Data\Filter\Filter; +use Icinga\Data\FilterColumns; +use Icinga\Data\PivotTable; +use Icinga\Data\QueryInterface; +use Icinga\Data\SortRules; +use Icinga\Exception\QueryException; +use Icinga\Module\Monitoring\Backend\Ido\Query\IdoQuery; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use Icinga\Web\Request; +use Icinga\Web\Url; +use Traversable; + +/** + * A read-only view of an underlying query + */ +abstract class DataView implements QueryInterface, SortRules, FilterColumns, IteratorAggregate +{ + /** + * The query used to populate the view + * + * @var IdoQuery + */ + protected $query; + + protected $connection; + + protected $isSorted = false; + + /** + * The cache for all filter columns + * + * @var array + */ + protected $filterColumns; + + /** + * Create a new view + * + * @param ConnectionInterface $connection + * @param array $columns + */ + public function __construct(ConnectionInterface $connection, array $columns = null) + { + $this->connection = $connection; + $this->query = $connection->query($this->getQueryName(), $columns); + } + + /** + * Return a iterator for all rows of the result set + * + * @return IdoQuery + */ + public function getIterator(): Traversable + { + return $this->getQuery(); + } + + /** + * Return the current position of the result set's iterator + * + * @return int + */ + public function getIteratorPosition() + { + return $this->query->getIteratorPosition(); + } + + /** + * Get the query name this data view relies on + * + * By default this is this class' name without its namespace + * + * @return string + */ + public static function getQueryName() + { + $tableName = explode('\\', get_called_class()); + $tableName = end($tableName); + return $tableName; + } + + public function where($condition, $value = null) + { + $this->query->where($condition, $value); + return $this; + } + + /** + * Add a filter expression, with as less validation as possible + * + * @param FilterExpression $ex + * + * @internal If you use this outside the monitoring module, it's your fault if something breaks + * @return $this + */ + public function whereEx(FilterExpression $ex) + { + $this->query->whereEx($ex); + return $this; + } + + public function dump() + { + if (! $this->isSorted) { + $this->order(); + } + return $this->query->dump(); + } + + /** + * Retrieve columns provided by this view + * + * @return array + */ + abstract public function getColumns(); + + protected function getHookedColumns() + { + $columns = array(); + foreach (Hook::all('monitoring/dataviewExtension') as $hook) { + foreach ($hook->getAdditionalQueryColumns($this->getQueryName()) as $col) { + $columns[] = $col; + } + } + + return $columns; + } + + /** + * Create view from params + * + * @param array $params + * @param array $columns + * + * @return static + */ + public static function fromParams(array $params, array $columns = null) + { + $view = new static(MonitoringBackend::instance($params['backend']), $columns); + + foreach ($params as $key => $value) { + if ($view->isValidFilterTarget($key)) { + $view->where($key, $value); + } + } + + 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->order($params['sort'], $order); + } + return $view; + } + + /** + * Check whether the given column is a valid filter column + * + * @param string $column + * + * @return bool + */ + public function isValidFilterTarget($column) + { + // Customvar + if ($column[0] === '_' && preg_match('/^_(?:host|service)_/i', $column)) { + return true; + } + return in_array($column, $this->getColumns()) || in_array($column, $this->getStaticFilterColumns()); + } + + /** + * Return all filter columns with their optional label as key + * + * This will merge the results of self::getColumns(), self::getStaticFilterColumns() and + * self::getDynamicFilterColumns() *once*. (i.e. subsequent calls of this function will + * return the same result.) + * + * @return array + */ + public function getFilterColumns() + { + if ($this->filterColumns === null) { + $columns = array_merge( + $this->getColumns(), + $this->getStaticFilterColumns(), + $this->getDynamicFilterColumns() + ); + + $this->filterColumns = array(); + foreach ($columns as $label => $column) { + if (is_int($label)) { + $label = ucwords(str_replace('_', ' ', $column)); + } + + if ($this->query->isCaseInsensitive($column)) { + $label .= ' ' . t('(Case insensitive)'); + } + + $this->filterColumns[$label] = $column; + } + } + + return $this->filterColumns; + } + + /** + * Return all static filter columns + * + * @return array + */ + public function getStaticFilterColumns() + { + return array(); + } + + /** + * Return all dynamic filter columns such as custom variables + * + * @return array + */ + public function getDynamicFilterColumns() + { + $columns = array(); + if (! $this->query->allowsCustomVars()) { + return $columns; + } + + $query = MonitoringBackend::instance() + ->select() + ->from('customvar', array('varname', 'object_type')) + ->where('is_json', 0) + ->where('object_type_id', array(1, 2)) + ->getQuery()->group(array('varname', 'object_type')); + foreach ($query as $row) { + if ($row->object_type === 'host') { + $label = t('Host') . ' ' . ucwords(str_replace('_', ' ', $row->varname)); + $columns[$label] = '_host_' . $row->varname; + } else { // $row->object_type === 'service' + $label = t('Service') . ' ' . ucwords(str_replace('_', ' ', $row->varname)); + $columns[$label] = '_service_' . $row->varname; + } + } + + return $columns; + } + + /** + * Return the current filter + * + * @return Filter + */ + public function getFilter() + { + return $this->query->getFilter(); + } + + /** + * Return a pivot table for the given columns based on the current query + * + * @param string $xAxisColumn The column to use for the x axis + * @param string $yAxisColumn The column to use for the y axis + * @param Filter $xAxisFilter The filter to apply on a query for the x axis + * @param Filter $yAxisFilter The filter to apply on a query for the y axis + * + * @return PivotTable + */ + public function pivot($xAxisColumn, $yAxisColumn, Filter $xAxisFilter = null, Filter $yAxisFilter = null) + { + $pivot = new PivotTable($this->query, $xAxisColumn, $yAxisColumn); + return $pivot->setXAxisFilter($xAxisFilter)->setYAxisFilter($yAxisFilter); + } + + /** + * Sort result set either by the given column (and direction) or the sort defaults + * + * @param string $column + * @param string $direction + * + * @return $this + */ + public function order($column = null, $direction = null) + { + $sortRules = $this->getSortRules(); + if ($column === null) { + // Use first available sort rule as default + if (empty($sortRules)) { + return $this; + } + $sortColumns = reset($sortRules); + if (! isset($sortColumns['columns'])) { + $sortColumns['columns'] = array(key($sortRules)); + } + } else { + if (isset($sortRules[$column])) { + $sortColumns = $sortRules[$column]; + if (! isset($sortColumns['columns'])) { + $sortColumns['columns'] = array($column); + } + } else { + $sortColumns = array( + 'columns' => array($column), + 'order' => $direction + ); + }; + } + + $direction = $direction === null ? ($sortColumns['order'] ?? static::SORT_ASC) : $direction; + $direction = (strtoupper($direction) === static::SORT_ASC) ? 'ASC' : 'DESC'; + + foreach ($sortColumns['columns'] as $column) { + list($column, $order) = $this->query->splitOrder($column); + if (! $this->isValidFilterTarget($column)) { + throw new QueryException( + mt('monitoring', 'The sort column "%s" is not allowed in "%s".'), + $column, + get_class($this) + ); + } + $this->query->order($column, $order !== null ? $order : $direction); + } + $this->isSorted = true; + return $this; + } + + /** + * Retrieve default sorting rules for particular columns. These involve sort order and potential additional to sort + * + * @return array + */ + public function getSortRules() + { + return array(); + } + + /** + * Whether an order is set + * + * @return bool + */ + public function hasOrder() + { + return $this->query->hasOrder(); + } + + /** + * Get the order if any + * + * @return array|null + */ + public function getOrder() + { + return $this->query->getOrder(); + } + + public function getMappedField($field) + { + return $this->query->getMappedField($field); + } + + /** + * Return the query which was created in the constructor + * + * @return \Icinga\Data\SimpleQuery + */ + public function getQuery() + { + if (! $this->isSorted) { + $this->order(); + } + return $this->query; + } + + public function applyFilter(Filter $filter) + { + $this->validateFilterColumns($filter); + + return $this->addFilter($filter); + } + + /** + * Validates recursive the Filter columns against the isValidFilterTarget() method + * + * @param Filter $filter + * + * @throws \Icinga\Data\Filter\FilterException + */ + public function validateFilterColumns(Filter $filter) + { + if ($filter instanceof FilterMatch) { + if (! $this->isValidFilterTarget($filter->getColumn())) { + throw new QueryException( + mt('monitoring', 'The filter column "%s" is not allowed here.'), + $filter->getColumn() + ); + } + } + + if (method_exists($filter, 'filters')) { + foreach ($filter->filters() as $filter) { + $this->validateFilterColumns($filter); + } + } + } + + public function clearFilter() + { + $this->query->clearFilter(); + return $this; + } + + /** + * @deprecated(EL): Only use DataView::applyFilter() for applying filter because all other functions are missing + * column validation. Filter::matchAny() for the IdoQuery (or the DbQuery or the SimpleQuery I didn't have a look) + * is required for the filter to work properly. + */ + public function setFilter(Filter $filter) + { + $this->query->setFilter($filter); + return $this; + } + + /** + * Get the view's search columns + * + * @return string[] + */ + public function getSearchColumns() + { + return array(); + } + + /** + * @deprecated(EL): Only use DataView::applyFilter() for applying filter because all other functions are missing + * column validation. + */ + public function addFilter(Filter $filter) + { + $this->query->addFilter($filter); + return $this; + } + + /** + * Count result set + * + * @return int + */ + public function count(): int + { + return $this->query->count(); + } + + /** + * Set whether the query should peek ahead for more results + * + * Enabling this causes the current query limit to be increased by one. The potential extra row being yielded will + * be removed from the result set. Note that this only applies when fetching multiple results of limited queries. + * + * @return $this + */ + public function peekAhead($state = true) + { + $this->query->peekAhead($state); + return $this; + } + + /** + * Return whether the query did not yield all available results + * + * @return bool + */ + public function hasMore() + { + return $this->query->hasMore(); + } + + /** + * Return whether this query will or has yielded any result + * + * @return bool + */ + public function hasResult() + { + return $this->query->hasResult(); + } + + /** + * Set a limit count and offset + * + * @param int $count Number of rows to return + * @param int $offset Start returning after this many rows + * + * @return self + */ + public function limit($count = null, $offset = null) + { + $this->query->limit($count, $offset); + return $this; + } + + /** + * Whether a limit is set + * + * @return bool + */ + public function hasLimit() + { + return $this->query->hasLimit(); + } + + /** + * Get the limit if any + * + * @return int|null + */ + public function getLimit() + { + return $this->query->getLimit(); + } + + /** + * Whether an offset is set + * + * @return bool + */ + public function hasOffset() + { + return $this->query->hasOffset(); + } + + /** + * Get the offset if any + * + * @return int|null + */ + public function getOffset() + { + return $this->query->getOffset(); + } + + /** + * Retrieve an array containing all rows of the result set + * + * @return array + */ + public function fetchAll() + { + return $this->getQuery()->fetchAll(); + } + + /** + * Fetch the first row of the result set + * + * @return mixed + */ + public function fetchRow() + { + return $this->getQuery()->fetchRow(); + } + + /** + * Fetch the first column of all rows of the result set as an array + * + * @return array + */ + public function fetchColumn() + { + return $this->getQuery()->fetchColumn(); + } + + /** + * Fetch the first column of the first row of the result set + * + * @return string + */ + public function fetchOne() + { + return $this->getQuery()->fetchOne(); + } + + /** + * Fetch all rows of the result set as an array of key-value pairs + * + * The first column is the key, the second column is the value. + * + * @return array + */ + public function fetchPairs() + { + return $this->getQuery()->fetchPairs(); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Downtime.php b/modules/monitoring/library/Monitoring/DataView/Downtime.php new file mode 100644 index 0000000..ca42e2d --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Downtime.php @@ -0,0 +1,96 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +/** + * Host and service downtimes view + */ +class Downtime extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'downtime_author_name', + 'downtime_comment', + 'downtime_duration', + 'downtime_end', + 'downtime_entry_time', + 'downtime_internal_id', + 'downtime_is_fixed', + 'downtime_is_flexible', + 'downtime_is_in_effect', + 'downtime_name', + 'downtime_scheduled_end', + 'downtime_scheduled_start', + 'downtime_start', + 'host_display_name', + 'host_name', + 'host_state', + 'object_type', + 'service_description', + 'service_display_name', + 'service_host_name', + 'service_state' + ); + } + + /** + * {@inheritdoc} + */ + public function getStaticFilterColumns() + { + return array( + 'downtime_author', + 'host', 'host_alias', + 'hostgroup', 'hostgroup_alias', 'hostgroup_name', + 'instance_name', + 'service', + 'servicegroup', 'servicegroup_alias', 'servicegroup_name' + ); + } + + /** + * {@inheritdoc} + */ + public function getSearchColumns() + { + return array('host_display_name', 'service_display_name'); + } + + /** + * {@inheritdoc} + */ + public function getSortRules() + { + return array( + 'downtime_is_in_effect' => array( + 'columns' => array( + 'downtime_is_in_effect', + 'downtime_scheduled_start' + ), + 'order' => self::SORT_DESC + ), + 'downtime_start' => array( + 'order' => self::SORT_DESC + ), + 'host_display_name' => array( + 'columns' => array( + 'host_display_name', + 'service_display_name' + ), + 'order' => self::SORT_ASC + ), + 'service_display_name' => array( + 'columns' => array( + 'service_display_name', + 'host_display_name' + ), + 'order' => self::SORT_ASC + ) + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Downtimeevent.php b/modules/monitoring/library/Monitoring/DataView/Downtimeevent.php new file mode 100644 index 0000000..a1fc0f6 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Downtimeevent.php @@ -0,0 +1,33 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +class Downtimeevent extends DataView +{ + public function getColumns() + { + return array( + 'downtimeevent_id', + 'downtimeevent_entry_time', + 'downtimeevent_author_name', + 'downtimeevent_comment_data', + 'downtimeevent_is_fixed', + 'downtimeevent_scheduled_start_time', + 'downtimeevent_scheduled_end_time', + 'downtimeevent_was_started', + 'downtimeevent_actual_start_time', + 'downtimeevent_actual_end_time', + 'downtimeevent_was_cancelled', + 'downtimeevent_is_in_effect', + 'downtimeevent_trigger_time', + 'host_name', + 'service_description' + ); + } + + public function getStaticFilterColumns() + { + return array('downtimeevent_id'); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Eventgrid.php b/modules/monitoring/library/Monitoring/DataView/Eventgrid.php new file mode 100644 index 0000000..1639e6b --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Eventgrid.php @@ -0,0 +1,60 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +class Eventgrid extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'day', + 'cnt_up', + 'cnt_down_hard', + 'cnt_down', + 'cnt_unreachable_hard', + 'cnt_unreachable', + 'cnt_unknown_hard', + 'cnt_unknown', + 'cnt_critical', + 'cnt_critical_hard', + 'cnt_warning', + 'cnt_warning_hard', + 'cnt_ok', + 'host_name', + 'host_display_name', + 'service_description', + 'service_display_name', + 'timestamp' + ); + } + + /** + * {@inheritdoc} + */ + public function getSortRules() + { + return array( + 'day' => array( + 'order' => self::SORT_DESC + ) + ); + } + + /** + * {@inheritdoc} + */ + public function getStaticFilterColumns() + { + return array( + 'instance_name', + 'host', 'host_alias', + 'hostgroup', 'hostgroup_alias', 'hostgroup_name', + 'service', 'service_host_name', + 'servicegroup', 'servicegroup_alias', 'servicegroup_name' + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Eventgridhosts.php b/modules/monitoring/library/Monitoring/DataView/Eventgridhosts.php new file mode 100644 index 0000000..9d9acc9 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Eventgridhosts.php @@ -0,0 +1,7 @@ +<?php + +namespace Icinga\Module\Monitoring\DataView; + +class Eventgridhosts extends Eventgrid +{ +} diff --git a/modules/monitoring/library/Monitoring/DataView/Eventgridservices.php b/modules/monitoring/library/Monitoring/DataView/Eventgridservices.php new file mode 100644 index 0000000..faa1065 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Eventgridservices.php @@ -0,0 +1,7 @@ +<?php + +namespace Icinga\Module\Monitoring\DataView; + +class Eventgridservices extends Eventgrid +{ +} diff --git a/modules/monitoring/library/Monitoring/DataView/Eventhistory.php b/modules/monitoring/library/Monitoring/DataView/Eventhistory.php new file mode 100644 index 0000000..cd947f5 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Eventhistory.php @@ -0,0 +1,60 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +class EventHistory extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'id', + 'instance_name', + 'host_name', + 'host_display_name', + 'service_description', + 'service_display_name', + 'object_type', + 'timestamp', + 'state', + 'output', + 'type' + ); + } + + /** + * {@inheritdoc} + */ + public function getSortRules() + { + return array( + 'timestamp' => array( + 'order' => self::SORT_DESC + ) + ); + } + + /** + * {@inheritdoc} + */ + public function getStaticFilterColumns() + { + return array( + 'host', 'host_alias', + 'hostgroup', 'hostgroup_alias', 'hostgroup_name', + 'service', + 'servicegroup', 'servicegroup_alias', 'servicegroup_name' + ); + } + + /** + * {@inheritdoc} + */ + public function getSearchColumns() + { + return array('host_display_name', 'service_display_name'); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Flappingevent.php b/modules/monitoring/library/Monitoring/DataView/Flappingevent.php new file mode 100644 index 0000000..bc79497 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Flappingevent.php @@ -0,0 +1,27 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +class Flappingevent extends DataView +{ + public function getColumns() + { + return array( + 'flappingevent_id', + 'flappingevent_event_time', + 'flappingevent_event_type', + 'flappingevent_reason_type', + 'flappingevent_percent_state_change', + 'flappingevent_low_threshold', + 'flappingevent_high_threshold', + 'host_name', + 'service_description' + ); + } + + public function getStaticFilterColumns() + { + return array('flappingevent_id'); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Hostcomment.php b/modules/monitoring/library/Monitoring/DataView/Hostcomment.php new file mode 100644 index 0000000..74fc2ef --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Hostcomment.php @@ -0,0 +1,45 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +/** + * Host comment view + */ +class Hostcomment extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'comment_author', + 'comment_author_name', + 'comment_data', + 'comment_expiration', + 'comment_internal_id', + 'comment_is_persistent', + 'comment_name', + 'comment_timestamp', + 'comment_type', + 'host_display_name', + 'host_name', + 'object_type' + ); + } + + /** + * {@inheritdoc} + */ + public function getStaticFilterColumns() + { + return array( + 'host', 'host_alias', + 'hostgroup', 'hostgroup_alias', 'hostgroup_name', + 'instance_name', + 'service', 'service_description', 'service_display_name', + 'servicegroup', 'servicegroup_alias', 'servicegroup_name' + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Hostcontact.php b/modules/monitoring/library/Monitoring/DataView/Hostcontact.php new file mode 100644 index 0000000..ecfed2f --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Hostcontact.php @@ -0,0 +1,17 @@ +<?php +/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +class Hostcontact extends Contact +{ + public function getColumns() + { + return [ + 'contact_name', + 'contact_alias', + 'contact_email', + 'contact_pager' + ]; + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Hostdowntime.php b/modules/monitoring/library/Monitoring/DataView/Hostdowntime.php new file mode 100644 index 0000000..f5e4e80 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Hostdowntime.php @@ -0,0 +1,50 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +/** + * Host downtime view + */ +class Hostdowntime extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'downtime_author', + 'downtime_author_name', + 'downtime_comment', + 'downtime_duration', + 'downtime_end', + 'downtime_entry_time', + 'downtime_internal_id', + 'downtime_is_fixed', + 'downtime_is_flexible', + 'downtime_is_in_effect', + 'downtime_name', + 'downtime_scheduled_end', + 'downtime_scheduled_start', + 'downtime_start', + 'host_display_name', + 'host_name', + 'object_type' + ); + } + + /** + * {@inheritdoc} + */ + public function getStaticFilterColumns() + { + return array( + 'host', 'host_alias', + 'hostgroup', 'hostgroup_alias', 'hostgroup_name', + 'instance_name', + 'service', 'service_description', 'service_display_name', + 'servicegroup', 'servicegroup_alias', 'servicegroup_name' + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Hostgroup.php b/modules/monitoring/library/Monitoring/DataView/Hostgroup.php new file mode 100644 index 0000000..b204fcd --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Hostgroup.php @@ -0,0 +1,34 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +/** + * Host group data view + */ +class Hostgroup extends DataView +{ + public function getColumns() + { + return array( + 'hostgroup_alias', + 'hostgroup_name' + ); + } + + public function getSortRules() + { + return array( + 'hostgroup_alias' => array( + 'order' => self::SORT_ASC + ) + ); + } + + public function getStaticFilterColumns() + { + return array( + 'instance_name', 'host_name', 'service_description', 'servicegroup_name' + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Hostgroupsummary.php b/modules/monitoring/library/Monitoring/DataView/Hostgroupsummary.php new file mode 100644 index 0000000..9ed2eb9 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Hostgroupsummary.php @@ -0,0 +1,81 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +/** + * Data view for the host group summary + */ +class Hostgroupsummary extends DataView +{ + public function getColumns() + { + return array( + 'hostgroup_alias', + 'hostgroup_name', + 'hosts_down_handled', + 'hosts_down_unhandled', + 'hosts_pending', + 'hosts_severity', + 'hosts_total', + 'hosts_unreachable_handled', + 'hosts_unreachable_unhandled', + 'hosts_up', + 'services_critical_handled', + 'services_critical_unhandled', + 'services_ok', + 'services_pending', + 'services_total', + 'services_unknown_handled', + 'services_unknown_unhandled', + 'services_warning_handled', + 'services_warning_unhandled' + ); + } + + public function getSearchColumns() + { + return array('hostgroup', 'hostgroup_alias'); + } + + public function getSortRules() + { + return array( + 'hostgroup_alias' => array( + 'order' => self::SORT_ASC + ), + 'hosts_severity' => array( + 'columns' => array( + 'hosts_severity', + 'hostgroup_alias ASC' + ), + 'order' => self::SORT_DESC + ) + ); + } + + public function getStaticFilterColumns() + { + return array( + 'instance_name', + 'host_contact', 'host_contactgroup', 'host_name', + 'hostgroup', + 'service_description', + 'servicegroup_name' + ); + } + + public function getFilterColumns() + { + if ($this->filterColumns === null) { + $filterColumns = parent::getFilterColumns(); + $diff = array_diff($filterColumns, $this->getColumns()); + $this->filterColumns = array_merge($diff, [ + 'Hostgroup Name' => 'hostgroup_name', + 'Hostgroup Alias' => 'hostgroup_alias' + ]); + } + + return $this->filterColumns; + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Hoststatus.php b/modules/monitoring/library/Monitoring/DataView/Hoststatus.php new file mode 100644 index 0000000..cd9b31f --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Hoststatus.php @@ -0,0 +1,129 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +class Hoststatus extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array_merge($this->getHookedColumns(), array( + 'host_acknowledged', + 'host_acknowledgement_type', + 'host_action_url', + 'host_active_checks_enabled', + 'host_active_checks_enabled_changed', + 'host_address', + 'host_address6', + 'host_alias', + 'host_check_command', + 'host_check_execution_time', + 'host_check_latency', + 'host_check_source', + 'host_check_timeperiod', + 'host_current_check_attempt', + 'host_current_notification_number', + 'host_display_name', + 'host_event_handler_enabled', + 'host_event_handler_enabled_changed', + 'host_flap_detection_enabled', + 'host_flap_detection_enabled_changed', + 'host_handled', + 'host_hard_state', + 'host_in_downtime', + 'host_ipv4', + 'host_is_flapping', + 'host_is_reachable', + 'host_last_check', + 'host_last_notification', + 'host_last_state_change', + 'host_last_state_change_ts', + 'host_long_output', + 'host_max_check_attempts', + 'host_modified_host_attributes', + 'host_name', + 'host_next_check', + 'host_notes_url', + 'host_notifications_enabled', + 'host_notifications_enabled_changed', + 'host_obsessing', + 'host_obsessing_changed', + 'host_output', + 'host_passive_checks_enabled', + 'host_passive_checks_enabled_changed', + 'host_percent_state_change', + 'host_perfdata', + 'host_problem', + 'host_severity', + 'host_state', + 'host_state_type', + 'host_unhandled', + 'instance_name' + )); + } + + /** + * {@inheritdoc} + */ + public function getStaticFilterColumns() + { + return array( + 'host', 'host_contact', 'host_contactgroup', + 'hostgroup', 'hostgroup_alias', 'hostgroup_name', + 'service', 'service_description', 'service_display_name', + 'servicegroup', 'servicegroup_alias', 'servicegroup_name' + ); + } + + /** + * {@inheritdoc} + */ + public function getSearchColumns($search = null) + { + if ($search !== null + && (@inet_pton($search) !== false || preg_match('/^\d{1,3}\.\d{1,3}\./', $search)) + ) { + return array('host', 'host_address', 'host_address6'); + } else { + if ($this->connection->isIcinga2()) { + return array('host', 'host_display_name'); + } else { + return array('host', 'host_display_name', 'host_alias'); + } + } + } + + /** + * {@inheritdoc} + */ + public function getSortRules() + { + return array( + 'host_display_name' => array( + 'order' => self::SORT_ASC + ), + 'host_severity' => array( + 'columns' => array( + 'host_severity', + 'host_last_state_change_ts DESC' + ), + 'order' => self::SORT_DESC + ), + 'host_address' => array( + 'columns' => array( + 'host_ipv4' + ), + 'order' => self::SORT_ASC + ), + 'host_last_state_change' => array( + 'columns' => array( + 'host_last_state_change_ts' + ), + 'order' => self::SORT_DESC + ) + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Hoststatussummary.php b/modules/monitoring/library/Monitoring/DataView/Hoststatussummary.php new file mode 100644 index 0000000..a857466 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Hoststatussummary.php @@ -0,0 +1,40 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +/** + * Data view for host status summaries + */ +class Hoststatussummary extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'hosts_down_handled', + 'hosts_down_unhandled', + 'hosts_pending', + 'hosts_total', + 'hosts_unreachable_handled', + 'hosts_unreachable_unhandled', + 'hosts_up', + ); + } + + /** + * {@inheritdoc} + */ + public function getStaticFilterColumns() + { + return array( + 'instance_name', + 'host', 'host_alias', 'host_display_name', 'host_name', + 'hostgroup', 'hostgroup_alias', 'hostgroup_name', + 'service', 'service_description', 'service_display_name', + 'servicegroup', 'servicegroup_alias', 'servicegroup_name' + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Instance.php b/modules/monitoring/library/Monitoring/DataView/Instance.php new file mode 100644 index 0000000..98ef1d6 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Instance.php @@ -0,0 +1,33 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +/** + * View representation for instances + */ +class Instance extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'instance_id', + 'instance_name' + ); + } + + /** + * {@inheritdoc} + */ + public function getSortRules() + { + return array( + 'instance_name' => array( + 'order' => self::SORT_ASC + ) + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Notification.php b/modules/monitoring/library/Monitoring/DataView/Notification.php new file mode 100644 index 0000000..90755de --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Notification.php @@ -0,0 +1,59 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +class Notification extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'host_display_name', + 'host_name', + 'notification_contact_name', + 'notification_output', + 'notification_reason', + 'notification_state', + 'notification_timestamp', + 'object_type', + 'service_description', + 'service_display_name', + 'service_host_name' + ); + } + + /** + * {@inheritdoc} + */ + public function getSortRules() + { + return array( + 'notification_timestamp' => array( + 'order' => self::SORT_DESC + ) + ); + } + + /** + * {@inheritdoc} + */ + public function getStaticFilterColumns() + { + return array( + 'hostgroup_name', + 'instance_name', + 'servicegroup_name' + ); + } + + /** + * {@inheritdoc} + */ + public function getSearchColumns() + { + return array('host_display_name', 'service_display_name'); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Notificationevent.php b/modules/monitoring/library/Monitoring/DataView/Notificationevent.php new file mode 100644 index 0000000..82dd212 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Notificationevent.php @@ -0,0 +1,29 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +class Notificationevent extends DataView +{ + public function getColumns() + { + return array( + 'notificationevent_id', + 'notificationevent_reason', + 'notificationevent_start_time', + 'notificationevent_end_time', + 'notificationevent_state', + 'notificationevent_output', + 'notificationevent_long_output', + 'notificationevent_escalated', + 'notificationevent_contacts_notified', + 'host_name', + 'service_description' + ); + } + + public function getStaticFilterColumns() + { + return array('notificationevent_id'); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Programstatus.php b/modules/monitoring/library/Monitoring/DataView/Programstatus.php new file mode 100644 index 0000000..d611c72 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Programstatus.php @@ -0,0 +1,44 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +/** + * View for programstatus query + */ +class Programstatus extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'id', + 'status_update_time', + 'program_start_time', + 'program_end_time', + 'is_currently_running', + 'process_id', + 'daemon_mode', + 'last_command_check', + 'last_log_rotation', + 'notifications_enabled', + 'disable_notif_expire_time', + 'active_service_checks_enabled', + 'passive_service_checks_enabled', + 'active_host_checks_enabled', + 'passive_host_checks_enabled', + 'event_handlers_enabled', + 'flap_detection_enabled', + 'failure_prediction_enabled', + 'process_performance_data', + 'obsess_over_hosts', + 'obsess_over_services', + 'modified_host_attributes', + 'modified_service_attributes', + 'global_host_event_handler', + 'global_service_event_handler', + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Runtimesummary.php b/modules/monitoring/library/Monitoring/DataView/Runtimesummary.php new file mode 100644 index 0000000..bf80226 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Runtimesummary.php @@ -0,0 +1,38 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +/** + * View for runtimesummary query + */ +class Runtimesummary extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'check_type', + 'active_checks_enabled', + 'passive_checks_enabled', + 'execution_time', + 'latency', + 'object_count', + 'object_type' + ); + } + + /** + * {@inheritdoc} + */ + public function getSortRules() + { + return array( + 'active_checks_enabled' => array( + 'order' => self::SORT_ASC + ) + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Runtimevariables.php b/modules/monitoring/library/Monitoring/DataView/Runtimevariables.php new file mode 100644 index 0000000..b3624b7 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Runtimevariables.php @@ -0,0 +1,34 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +/** + * View for runtimevariables query + */ +class Runtimevariables extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'id', + 'varname', + 'varvalue' + ); + } + + /** + * {@inheritdoc} + */ + public function getSortRules() + { + return array( + 'id' => array( + 'order' => self::SORT_ASC + ) + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Servicecomment.php b/modules/monitoring/library/Monitoring/DataView/Servicecomment.php new file mode 100644 index 0000000..78c1333 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Servicecomment.php @@ -0,0 +1,48 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +/** + * Service comment view + */ +class Servicecomment extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'comment_author', + 'comment_author_name', + 'comment_data', + 'comment_expiration', + 'comment_internal_id', + 'comment_is_persistent', + 'comment_name', + 'comment_timestamp', + 'comment_type', + 'host_display_name', + 'host_name', + 'object_type', + 'service_description', + 'service_display_name', + 'service_host_name' + ); + } + + /** + * {@inheritdoc} + */ + public function getStaticFilterColumns() + { + return array( + 'host', 'host_alias', + 'hostgroup', 'hostgroup_alias', 'hostgroup_name', + 'instance_name', + 'service', + 'servicegroup', 'servicegroup_alias', 'servicegroup_name' + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Servicecontact.php b/modules/monitoring/library/Monitoring/DataView/Servicecontact.php new file mode 100644 index 0000000..55c9950 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Servicecontact.php @@ -0,0 +1,8 @@ +<?php +/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +class Servicecontact extends Hostcontact +{ +} diff --git a/modules/monitoring/library/Monitoring/DataView/Servicedowntime.php b/modules/monitoring/library/Monitoring/DataView/Servicedowntime.php new file mode 100644 index 0000000..43d895e --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Servicedowntime.php @@ -0,0 +1,50 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +class Servicedowntime extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'downtime_author', + 'downtime_author_name', + 'downtime_comment', + 'downtime_duration', + 'downtime_end', + 'downtime_entry_time', + 'downtime_internal_id', + 'downtime_is_fixed', + 'downtime_is_flexible', + 'downtime_is_in_effect', + 'downtime_name', + 'downtime_scheduled_end', + 'downtime_scheduled_start', + 'downtime_start', + 'host_display_name', + 'host_name', + 'object_type', + 'service_description', + 'service_display_name', + 'service_host_name' + ); + } + + /** + * {@inheritdoc} + */ + public function getStaticFilterColumns() + { + return array( + 'host', 'host_alias', + 'hostgroup', 'hostgroup_alias', 'hostgroup_name', + 'instance_name', + 'service', + 'servicegroup', 'servicegroup_alias', 'servicegroup_name' + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Servicegroup.php b/modules/monitoring/library/Monitoring/DataView/Servicegroup.php new file mode 100644 index 0000000..9909a68 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Servicegroup.php @@ -0,0 +1,31 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +class Servicegroup extends DataView +{ + public function getColumns() + { + return array( + 'servicegroup_alias', + 'servicegroup_name' + ); + } + + public function getSortRules() + { + return array( + 'servicegroup_alias' => array( + 'order' => self::SORT_ASC + ) + ); + } + + public function getStaticFilterColumns() + { + return array( + 'instance_name', 'host_name', 'hostgroup_name', 'service_description' + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Servicegroupsummary.php b/modules/monitoring/library/Monitoring/DataView/Servicegroupsummary.php new file mode 100644 index 0000000..9dc3ee0 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Servicegroupsummary.php @@ -0,0 +1,75 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +/** + * Data view for service group summaries + */ +class Servicegroupsummary extends DataView +{ + public function getColumns() + { + return array( + 'servicegroup_alias', + 'servicegroup_name', + 'services_critical_handled', + 'services_critical_unhandled', + 'services_ok', + 'services_pending', + 'services_severity', + 'services_total', + 'services_unknown_handled', + 'services_unknown_unhandled', + 'services_warning_handled', + 'services_warning_unhandled' + ); + } + + public function getSearchColumns() + { + return array('servicegroup', 'servicegroup_alias'); + } + + public function getSortRules() + { + return array( + 'servicegroup_alias' => array( + 'order' => self::SORT_ASC + ), + 'services_severity' => array( + 'columns' => array( + 'services_severity', + 'servicegroup_alias ASC' + ), + 'order' => self::SORT_DESC + ) + ); + } + + public function getStaticFilterColumns() + { + return array( + 'instance_name', + 'services_severity', + 'host_contact', 'host_contactgroup', 'host_name', + 'hostgroup_name', + 'service_contact', 'service_contactgroup', 'service_description', + 'servicegroup' + ); + } + + public function getFilterColumns() + { + if ($this->filterColumns === null) { + $filterColumns = parent::getFilterColumns(); + $diff = array_diff($filterColumns, $this->getColumns()); + $this->filterColumns = array_merge($diff, [ + 'Servicegroup Name' => 'servicegroup_name', + 'Servicegroup Alias' => 'servicegroup_alias' + ]); + } + + return $this->filterColumns; + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Servicestatus.php b/modules/monitoring/library/Monitoring/DataView/Servicestatus.php new file mode 100644 index 0000000..86da272 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Servicestatus.php @@ -0,0 +1,180 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +class Servicestatus extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array_merge($this->getHookedColumns(), array( + 'host_acknowledged', + 'host_action_url', + 'host_active_checks_enabled', + 'host_address', + 'host_address6', + 'host_alias', + 'host_check_source', + 'host_display_name', + 'host_handled', + 'host_hard_state', + 'host_in_downtime', + 'host_ipv4', + 'host_is_flapping', + 'host_last_check', + 'host_last_hard_state', + 'host_last_hard_state_change', + 'host_last_state_change', + 'host_last_time_down', + 'host_last_time_unreachable', + 'host_last_time_up', + 'host_long_output', + 'host_modified_host_attributes', + 'host_name', + 'host_notes_url', + 'host_notifications_enabled', + 'host_output', + 'host_passive_checks_enabled', + 'host_perfdata', + 'host_problem', + 'host_severity', + 'host_state', + 'host_state_type', + 'host_unhandled_service_count', + 'instance_name', + 'service_acknowledged', + 'service_acknowledgement_type', + 'service_action_url', + 'service_active_checks_enabled', + 'service_active_checks_enabled_changed', + 'service_attempt', + 'service_check_command', + 'service_check_source', + 'service_check_timeperiod', + 'service_current_check_attempt', + 'service_current_notification_number', + 'service_description', + 'service_display_name', + 'service_event_handler_enabled', + 'service_event_handler_enabled_changed', + 'service_flap_detection_enabled', + 'service_flap_detection_enabled_changed', + 'service_handled', + 'service_hard_state', + 'service_host_name', + 'service_in_downtime', + 'service_is_flapping', + 'service_is_reachable', + 'service_last_check', + 'service_last_hard_state', + 'service_last_hard_state_change', + 'service_last_notification', + 'service_last_state_change', + 'service_last_state_change_ts', + 'service_last_time_critical', + 'service_last_time_ok', + 'service_last_time_unknown', + 'service_last_time_warning', + 'service_long_output', + 'service_max_check_attempts', + 'service_modified_service_attributes', + 'service_next_check', + 'service_notes', + 'service_notes_url', + 'service_notifications_enabled', + 'service_notifications_enabled_changed', + 'service_obsessing', + 'service_obsessing_changed', + 'service_output', + 'service_passive_checks_enabled', + 'service_passive_checks_enabled_changed', + 'service_perfdata', + 'service_problem', + 'service_severity', + 'service_state', + 'service_state_type', + 'service_unhandled' + )); + } + + /** + * {@inheritdoc} + */ + public function getSortRules() + { + return array( + 'service_display_name' => array( + 'order' => self::SORT_ASC + ), + 'service_severity' => array( + 'columns' => array( + 'service_severity', + 'service_last_state_change_ts DESC' + ), + 'order' => self::SORT_DESC + ), + 'service_last_state_change' => array( + 'columns' => array( + 'service_last_state_change_ts' + ), + 'order' => self::SORT_DESC + ), + 'host_severity' => array( + 'columns' => array( + 'host_severity', + 'host_last_state_change DESC', + 'host_display_name ASC', + 'service_display_name ASC' + ), + 'order' => self::SORT_DESC + ), + 'host_display_name' => array( + 'columns' => array( + 'host_display_name', + 'service_display_name' + ), + 'order' => self::SORT_ASC + ), + 'host_address' => array( + 'columns' => array( + 'host_ipv4', + 'service_display_name' + ), + 'order' => self::SORT_ASC + ) + ); + } + + /** + * {@inheritdoc} + */ + public function getStaticFilterColumns() + { + return array( + 'host', + 'host_contact', + 'host_contactgroup', + 'hostgroup', + 'hostgroup_alias', + 'hostgroup_name', + 'service', + 'service_contact', + 'service_contactgroup', + 'service_host', + 'servicegroup', + 'servicegroup_alias', + 'servicegroup_name' + ); + } + + /** + * {@inheritdoc} + */ + public function getSearchColumns() + { + return array('service', 'service_display_name'); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Servicestatussummary.php b/modules/monitoring/library/Monitoring/DataView/Servicestatussummary.php new file mode 100644 index 0000000..abd3593 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Servicestatussummary.php @@ -0,0 +1,45 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +/** + * Data view for service status summaries + */ +class Servicestatussummary extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'services_critical', + 'services_critical_handled', + 'services_critical_unhandled', + 'services_ok', + 'services_pending', + 'services_total', + 'services_unknown', + 'services_unknown_handled', + 'services_unknown_unhandled', + 'services_warning', + 'services_warning_handled', + 'services_warning_unhandled' + ); + } + + /** + * {@inheritdoc} + */ + public function getStaticFilterColumns() + { + return array( + 'instance_name', + 'host', 'host_alias', 'host_display_name', 'host_name', + 'hostgroup', 'hostgroup_alias', 'hostgroup_name', + 'service', 'service_description', 'service_display_name', + 'servicegroup', 'servicegroup_alias', 'servicegroup_name' + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Statechangeevent.php b/modules/monitoring/library/Monitoring/DataView/Statechangeevent.php new file mode 100644 index 0000000..0b01aff --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Statechangeevent.php @@ -0,0 +1,32 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +class Statechangeevent extends DataView +{ + public function getColumns() + { + return array( + 'statechangeevent_id', + 'statechangeevent_state_time', + 'statechangeevent_state_change', + 'statechangeevent_state', + 'statechangeevent_state_type', + 'statechangeevent_current_check_attempt', + 'statechangeevent_max_check_attempts', + 'statechangeevent_last_state', + 'statechangeevent_last_hard_state', + 'statechangeevent_output', + 'statechangeevent_long_output', + 'statechangeevent_check_source', + 'host_name', + 'service_description' + ); + } + + public function getStaticFilterColumns() + { + return array('statechangeevent_id'); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Statussummary.php b/modules/monitoring/library/Monitoring/DataView/Statussummary.php new file mode 100644 index 0000000..36efccb --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Statussummary.php @@ -0,0 +1,111 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +class StatusSummary extends DataView +{ + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array( + 'hosts_up', + 'hosts_up_not_checked', + 'hosts_pending', + 'hosts_pending_not_checked', + 'hosts_down', + 'hosts_down_handled', + 'hosts_down_unhandled', + 'hosts_down_passive', + 'hosts_down_not_checked', + 'hosts_unreachable', + 'hosts_unreachable_handled', + 'hosts_unreachable_unhandled', + 'hosts_unreachable_passive', + 'hosts_unreachable_not_checked', + 'hosts_active', + 'hosts_passive', + 'hosts_not_checked', + 'hosts_not_processing_event_handlers', + 'hosts_not_triggering_notifications', + 'hosts_without_flap_detection', + 'hosts_flapping', + 'services_ok', + 'services_ok_not_checked', + 'services_pending', + 'services_pending_not_checked', + 'services_warning', + 'services_warning_handled', + 'services_warning_unhandled', + 'services_warning_passive', + 'services_warning_not_checked', + 'services_critical', + 'services_critical_handled', + 'services_critical_unhandled', + 'services_critical_passive', + 'services_critical_not_checked', + 'services_unknown', + 'services_unknown_handled', + 'services_unknown_unhandled', + 'services_unknown_passive', + 'services_unknown_not_checked', + 'services_active', + 'services_passive', + 'services_not_checked', + 'services_not_processing_event_handlers', + 'services_not_triggering_notifications', + 'services_without_flap_detection', + 'services_flapping', + + + 'services_ok_on_ok_hosts', + 'services_ok_not_checked_on_ok_hosts', + 'services_pending_on_ok_hosts', + 'services_pending_not_checked_on_ok_hosts', + 'services_warning_handled_on_ok_hosts', + 'services_warning_unhandled_on_ok_hosts', + 'services_warning_passive_on_ok_hosts', + 'services_warning_not_checked_on_ok_hosts', + 'services_critical_handled_on_ok_hosts', + 'services_critical_unhandled_on_ok_hosts', + 'services_critical_passive_on_ok_hosts', + 'services_critical_not_checked_on_ok_hosts', + 'services_unknown_handled_on_ok_hosts', + 'services_unknown_unhandled_on_ok_hosts', + 'services_unknown_passive_on_ok_hosts', + 'services_unknown_not_checked_on_ok_hosts', + 'services_ok_on_problem_hosts', + 'services_ok_not_checked_on_problem_hosts', + 'services_pending_on_problem_hosts', + 'services_pending_not_checked_on_problem_hosts', + 'services_warning_handled_on_problem_hosts', + 'services_warning_unhandled_on_problem_hosts', + 'services_warning_passive_on_problem_hosts', + 'services_warning_not_checked_on_problem_hosts', + 'services_critical_handled_on_problem_hosts', + 'services_critical_unhandled_on_problem_hosts', + 'services_critical_passive_on_problem_hosts', + 'services_critical_not_checked_on_problem_hosts', + 'services_unknown_handled_on_problem_hosts', + 'services_unknown_unhandled_on_problem_hosts', + 'services_unknown_passive_on_problem_hosts', + 'services_unknown_not_checked_on_problem_hosts' + ); + } + + /** + * {@inheritdoc} + */ + public function getStaticFilterColumns() + { + return array( + 'instance_name', + 'host', 'host_alias', 'host_display_name', 'host_name', + 'hostgroup', 'hostgroup_alias', 'hostgroup_name', + 'service', 'service_description', 'service_display_name', + 'servicegroup', 'servicegroup_alias', 'servicegroup_name' + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Unhandledhostproblems.php b/modules/monitoring/library/Monitoring/DataView/Unhandledhostproblems.php new file mode 100644 index 0000000..4f5f392 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Unhandledhostproblems.php @@ -0,0 +1,28 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +/** + * Data view for unhandled host problems + */ +class Unhandledhostproblems extends DataView +{ + public function getColumns() + { + return array( + 'hosts_down_unhandled' + ); + } + + public function getStaticFilterColumns() + { + return array( + 'instance_name', + 'host', 'host_alias', 'host_display_name', 'host_name', + 'hostgroup', 'hostgroup_alias', 'hostgroup_name', + 'service', 'service_description', 'service_display_name', + 'servicegroup', 'servicegroup_alias', 'servicegroup_name' + ); + } +} diff --git a/modules/monitoring/library/Monitoring/DataView/Unhandledserviceproblems.php b/modules/monitoring/library/Monitoring/DataView/Unhandledserviceproblems.php new file mode 100644 index 0000000..3af4502 --- /dev/null +++ b/modules/monitoring/library/Monitoring/DataView/Unhandledserviceproblems.php @@ -0,0 +1,28 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\DataView; + +/** + * Data view for unhandled service problems + */ +class Unhandledserviceproblems extends DataView +{ + public function getColumns() + { + return array( + 'services_critical_unhandled' + ); + } + + public function getStaticFilterColumns() + { + return array( + 'instance_name', + 'host', 'host_alias', 'host_display_name', 'host_name', + 'hostgroup', 'hostgroup_alias', 'hostgroup_name', + 'service', 'service_description', 'service_display_name', + 'servicegroup', 'servicegroup_alias', 'servicegroup_name' + ); + } +} diff --git a/modules/monitoring/library/Monitoring/Exception/CommandTransportException.php b/modules/monitoring/library/Monitoring/Exception/CommandTransportException.php new file mode 100644 index 0000000..5c08351 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Exception/CommandTransportException.php @@ -0,0 +1,13 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Exception; + +use Icinga\Exception\IcingaException; + +/** + * Exception thrown if a command was not sent + */ +class CommandTransportException extends IcingaException +{ +} diff --git a/modules/monitoring/library/Monitoring/Exception/CurlException.php b/modules/monitoring/library/Monitoring/Exception/CurlException.php new file mode 100644 index 0000000..01757af --- /dev/null +++ b/modules/monitoring/library/Monitoring/Exception/CurlException.php @@ -0,0 +1,13 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Exception; + +use Icinga\Exception\IcingaException; + +/** + * Exception thrown if {@link curl_exec()} fails + */ +class CurlException extends IcingaException +{ +} diff --git a/modules/monitoring/library/Monitoring/Exception/UnsupportedBackendException.php b/modules/monitoring/library/Monitoring/Exception/UnsupportedBackendException.php new file mode 100644 index 0000000..94d1af2 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Exception/UnsupportedBackendException.php @@ -0,0 +1,11 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + + +namespace Icinga\Module\Monitoring\Exception; + +use Icinga\Exception\IcingaException; + +class UnsupportedBackendException extends IcingaException +{ +} diff --git a/modules/monitoring/library/Monitoring/Hook/CustomVarRendererHook.php b/modules/monitoring/library/Monitoring/Hook/CustomVarRendererHook.php new file mode 100644 index 0000000..6895337 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Hook/CustomVarRendererHook.php @@ -0,0 +1,98 @@ +<?php +/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Hook; + +use Closure; +use Exception; +use Icinga\Application\Hook; +use Icinga\Application\Logger; +use Icinga\Module\Monitoring\Object\MonitoredObject; + +abstract class CustomVarRendererHook +{ + /** + * Prefetch the data the hook needs to render custom variables + * + * @param MonitoredObject $object The object for which they'll be rendered + * + * @return bool Return true if the hook can render variables for the given object, false otherwise + */ + abstract public function prefetchForObject(MonitoredObject $object); + + /** + * Render the given variable name + * + * @param string $key + * + * @return ?mixed + */ + abstract public function renderCustomVarKey($key); + + /** + * Render the given variable value + * + * @param string $key + * @param mixed $value + * + * @return ?mixed + */ + abstract public function renderCustomVarValue($key, $value); + + /** + * Return a group name for the given variable name + * + * @param string $key + * + * @return ?string + */ + abstract public function identifyCustomVarGroup($key); + + /** + * Prepare available hooks to render custom variables of the given object + * + * @param MonitoredObject $object + * + * @return Closure A callback ($key, $value) which returns an array [$newKey, $newValue, $group] + */ + final public static function prepareForObject(MonitoredObject $object) + { + $hooks = []; + foreach (Hook::all('Monitoring/CustomVarRenderer') as $hook) { + /** @var self $hook */ + try { + if ($hook->prefetchForObject($object)) { + $hooks[] = $hook; + } + } catch (Exception $e) { + Logger::error('Failed to load hook %s:', get_class($hook), $e); + } + } + + return function ($key, $value) use ($hooks) { + $newKey = $key; + $newValue = $value; + $group = null; + foreach ($hooks as $hook) { + /** @var self $hook */ + + try { + $renderedKey = $hook->renderCustomVarKey($key); + $renderedValue = $hook->renderCustomVarValue($key, $value); + $group = $hook->identifyCustomVarGroup($key); + } catch (Exception $e) { + Logger::error('Failed to use hook %s:', get_class($hook), $e); + continue; + } + + if ($renderedKey !== null || $renderedValue !== null) { + $newKey = $renderedKey !== null ? $renderedKey : $key; + $newValue = $renderedValue !== null ? $renderedValue : $value; + break; + } + } + + return [$newKey, $newValue, $group]; + }; + } +} diff --git a/modules/monitoring/library/Monitoring/Hook/DataviewExtensionHook.php b/modules/monitoring/library/Monitoring/Hook/DataviewExtensionHook.php new file mode 100644 index 0000000..24b97c5 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Hook/DataviewExtensionHook.php @@ -0,0 +1,20 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Hook; + +abstract class DataviewExtensionHook +{ + public function getAdditionalQueryColumns($queryName) + { + $cols = $this->provideAdditionalQueryColumns($queryName); + + if (! is_array($cols)) { + return array(); + } + + return $cols; + } + + abstract public function provideAdditionalQueryColumns($queryName); +} diff --git a/modules/monitoring/library/Monitoring/Hook/DetailviewExtensionHook.php b/modules/monitoring/library/Monitoring/Hook/DetailviewExtensionHook.php new file mode 100644 index 0000000..9eb5ca3 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Hook/DetailviewExtensionHook.php @@ -0,0 +1,126 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Hook; + +use Icinga\Application\ClassLoader; +use Icinga\Application\Icinga; +use Icinga\Application\Modules\Module; +use Icinga\Module\Monitoring\Object\MonitoredObject; +use Icinga\Module\Monitoring\Object\ObjectList; +use Icinga\Web\View; + +/** + * Base class for hooks extending the detail view of monitored objects + * + * Extend this class if you want to extend the detail view of monitored objects with custom HTML. + */ +abstract class DetailviewExtensionHook +{ + /** + * The view the generated HTML will be included in + * + * @var View + */ + private $view; + + /** + * The module of the derived class + * + * @var Module + */ + private $module; + + /** + * Create a new hook + * + * @see init() For hook initialization. + */ + final public function __construct() + { + $this->init(); + } + + /** + * Overwrite this function for hook initialization, e.g. loading the hook's config + */ + protected function init() + { + } + + /** + * Shall return valid HTML to include in the detail view + * + * @param MonitoredObject $object The object to generate HTML for + * + * @return string + */ + abstract public function getHtmlForObject(MonitoredObject $object); + + /** + * Shall return valid HTML to include in the detail view of a multi-select view + * + * @param ObjectList $objects A list of objects shown in the multi-select view + * + * @return string + */ + public function getHtmlForObjects($objects) + { + // For compatibility empty by default + return ''; + } + + /** + * Get {@link view} + * + * @return View + */ + public function getView() + { + return $this->view; + } + + /** + * Set {@link view} + * + * @param View $view + * + * @return $this + */ + public function setView($view) + { + $this->view = $view; + return $this; + } + + /** + * Get the module of the derived class + * + * @return Module + */ + public function getModule() + { + if ($this->module === null) { + $class = get_class($this); + if (ClassLoader::classBelongsToModule($class)) { + $this->module = Icinga::app()->getModuleManager()->getModule(ClassLoader::extractModuleName($class)); + } + } + + return $this->module; + } + + /** + * Set the module of the derived class + * + * @param Module $module + * + * @return $this + */ + public function setModule(Module $module) + { + $this->module = $module; + + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Hook/EventDetailsExtensionHook.php b/modules/monitoring/library/Monitoring/Hook/EventDetailsExtensionHook.php new file mode 100644 index 0000000..e0375d5 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Hook/EventDetailsExtensionHook.php @@ -0,0 +1,79 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Hook; + +use Icinga\Application\ClassLoader; +use Icinga\Application\Icinga; +use Icinga\Application\Modules\Module; + +/** + * Base class for hooks extending the event view of monitored objects + * + * Extend this class if you want to extend the event view of monitored objects with custom HTML. + */ +abstract class EventDetailsExtensionHook +{ + /** + * The module of the derived class + * + * @var Module + */ + private $module; + + /** + * Create a new hook + * + * @see init() For hook initialization. + */ + final public function __construct() + { + $this->init(); + } + /** + * Overwrite this function for hook initialization, e.g. loading the hook's config + */ + protected function init() + { + } + + + /** + * Shall return valid HTML to include in the detail view + * + * @param object $event The object to generate HTML for + * + * @return string + */ + abstract public function getHtmlForEvent($event); + + /** + * Get the module of the derived class + * + * @return Module + * @throws \Icinga\Exception\ProgrammingError + */ + public function getModule() + { + if ($this->module === null) { + $class = get_class($this); + if (ClassLoader::classBelongsToModule($class)) { + $this->module = Icinga::app()->getModuleManager()->getModule(ClassLoader::extractModuleName($class)); + } + } + return $this->module; + } + + /** + * Set the module of the derived class + * + * @param Module $module + * + * @return $this + */ + public function setModule(Module $module) + { + $this->module = $module; + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Hook/HostActionsHook.php b/modules/monitoring/library/Monitoring/Hook/HostActionsHook.php new file mode 100644 index 0000000..def0090 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Hook/HostActionsHook.php @@ -0,0 +1,52 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Hook; + +use Icinga\Module\Monitoring\Object\Host; +use Icinga\Module\Monitoring\Object\MonitoredObject; + +/** + * Base class for host action hooks + */ +abstract class HostActionsHook extends ObjectActionsHook +{ + /** + * Implementors of this method should return an array containing + * additional action links for a specific host. You get a full Host + * object, which allows you to return specific links only for nodes + * with specific properties. + * + * The result array should be in the form title => url, where title will + * be used as link caption. Url should be an Icinga\Web\Url object when + * the link should point to an Icinga Web url - otherwise a string would + * be fine. + * + * Mixed example: + * <code> + * return array( + * 'Wiki' => 'http://my.wiki/host=' . rawurlencode($host->host_name), + * 'Logstash' => Url::fromPath( + * 'logstash/search/syslog', + * array('host' => $host->host_name) + * ) + * ); + * </code> + * + * One might also provide ssh:// or rdp:// urls if equipped with fitting + * (safe) URL handlers for his browser(s). + * + * TODO: I'd love to see some kind of a Link/LinkSet object implemented + * for this and similar hooks. + * + * @param Host $host Monitoring host object + * + * @return array An array containing a list of host action links + */ + abstract public function getActionsForHost(Host $host); + + public function getActionsForObject(MonitoredObject $object) + { + return $this->getActionsForHost($object); + } +} diff --git a/modules/monitoring/library/Monitoring/Hook/IdoQueryExtensionHook.php b/modules/monitoring/library/Monitoring/Hook/IdoQueryExtensionHook.php new file mode 100644 index 0000000..64ac65c --- /dev/null +++ b/modules/monitoring/library/Monitoring/Hook/IdoQueryExtensionHook.php @@ -0,0 +1,15 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Hook; + +use Icinga\Module\Monitoring\Backend\Ido\Query\IdoQuery; + +abstract class IdoQueryExtensionHook +{ + abstract public function extendColumnMap(IdoQuery $query); + + public function joinVirtualTable(IdoQuery $query, $virtualTable) + { + } +} diff --git a/modules/monitoring/library/Monitoring/Hook/ObjectActionsHook.php b/modules/monitoring/library/Monitoring/Hook/ObjectActionsHook.php new file mode 100644 index 0000000..eb2d910 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Hook/ObjectActionsHook.php @@ -0,0 +1,47 @@ +<?php +/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Hook; + +use Icinga\Web\Navigation\Navigation; +use Icinga\Module\Monitoring\Object\MonitoredObject; + +/** + * Base class for object action hooks + */ +abstract class ObjectActionsHook +{ + /** + * Return the action navigation for the given object + * + * @return Navigation + */ + public function getNavigation(MonitoredObject $object) + { + $urls = $this->getActionsForObject($object); + if (is_array($urls)) { + $navigation = new Navigation(); + foreach ($urls as $label => $url) { + $navigation->addItem($label, array('url' => $url)); + } + } else { + $navigation = $urls; + } + + return $navigation; + } + + /** + * Create and return a new Navigation object + * + * @param array $actions Optional array of actions to add to the returned object + * + * @return Navigation + */ + protected function createNavigation(array $actions = null) + { + return empty($actions) ? new Navigation() : Navigation::fromArray($actions); + } + + abstract public function getActionsForObject(MonitoredObject $object); +} diff --git a/modules/monitoring/library/Monitoring/Hook/ObjectDetailsTabHook.php b/modules/monitoring/library/Monitoring/Hook/ObjectDetailsTabHook.php new file mode 100644 index 0000000..15fa9bb --- /dev/null +++ b/modules/monitoring/library/Monitoring/Hook/ObjectDetailsTabHook.php @@ -0,0 +1,60 @@ +<?php +/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Hook; + +use Icinga\Authentication\Auth; +use Icinga\Module\Monitoring\Object\MonitoredObject; +use Icinga\Web\Request; + +/** + * Base class for object host details custom tab hooks + */ +abstract class ObjectDetailsTabHook +{ + /** + * Return the tab name - it must be unique + * + * @return string + */ + abstract public function getName(); + + /** + * Return the tab label + * + * @return string + */ + abstract public function getLabel(); + + /** + * Return the tab header + * + * @param MonitoredObject $monitoredObject The monitored object related to that page + * @param Request $request + * @return string/bool The HTML string that compose the tab header, + * bool True if the default header should be shown, False to display nothing + */ + public function getHeader(MonitoredObject $monitoredObject, Request $request) + { + return true; + } + + /** + * Return the tab content + * + * @param MonitoredObject $monitoredObject The monitored object related to that page + * @param Request $request + * @return string The HTML string that compose the tab content + */ + abstract public function getContent(MonitoredObject $monitoredObject, Request $request); + + /** + * This method returns true if the tab is visible for the logged user, otherwise false + * + * @return bool True if the tab is visible for the logged user, otherwise false + */ + public function shouldBeShown(MonitoredObject $monitoredObject, Auth $auth) + { + return true; + } +} diff --git a/modules/monitoring/library/Monitoring/Hook/PluginOutputHook.php b/modules/monitoring/library/Monitoring/Hook/PluginOutputHook.php new file mode 100644 index 0000000..52ecd09 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Hook/PluginOutputHook.php @@ -0,0 +1,46 @@ +<?php +/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Hook; + +/** + * Base class for plugin output hooks + * + * The Plugin Output Hook allows you to rewrite the plugin output based on check commands. + * You have to implement the following methods: + * * {@link getCommands()} + * * and {@link render()} + */ +abstract class PluginOutputHook +{ + /** + * Get the command or list of commands the hook is responsible for + * + * With this method you specify for which commands the provided hook is responsible for. You may return a single + * command as string or a list of commands as array. + * If you want your hook to be responsible for every command, you have to return the asterisk `'*'`. + * + * @return string|array + */ + abstract public function getCommands(); + + /** + * Render the given plugin output based on the specified check command + * + * With this method you rewrite the plugin output based on check commands. The parameter `$command` specifies the + * check command of the host or service and `$output` specifies the plugin output. The parameter `$detail` tells you + * whether the output is requested from the detail area of the host or service. + * + * Do not use complex logic for rewriting plugin output in list views because of the performance impact! + * + * You have to return the rewritten plugin output as string. It is also possible to return a HTML string here. + * Please refer to {@link \Icinga\Module\Monitoring\Web\Helper\PluginOutputPurifier} for a list of allowed tags. + * + * @param string $command Check command + * @param string $output Plugin output + * @param bool $detail Whether the output is requested from the detail area + * + * @return string Rewritten plugin output + */ + abstract public function render($command, $output, $detail); +} diff --git a/modules/monitoring/library/Monitoring/Hook/ServiceActionsHook.php b/modules/monitoring/library/Monitoring/Hook/ServiceActionsHook.php new file mode 100644 index 0000000..c6cf5f5 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Hook/ServiceActionsHook.php @@ -0,0 +1,52 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Hook; + +use Icinga\Module\Monitoring\Object\Service; +use Icinga\Module\Monitoring\Object\MonitoredObject; + +/** + * Base class for host action hooks + */ +abstract class ServiceActionsHook extends ObjectActionsHook +{ + /** + * Implementors of this method should return an array containing + * additional action links for a specific host. You get a full Service + * object, which allows you to return specific links only for nodes + * with specific properties. + * + * The result array should be in the form title => url, where title will + * be used as link caption. Url should be an Icinga\Web\Url object when + * the link should point to an Icinga Web url - otherwise a string would + * be fine. + * + * Mixed example: + * <code> + * return array( + * 'Wiki' => 'http://my.wiki/host=' . rawurlencode($service->service_name), + * 'Logstash' => Url::fromPath( + * 'logstash/search/syslog', + * array('service' => $service->host_name) + * ) + * ); + * </code> + * + * One might also provide ssh:// or rdp:// urls if equipped with fitting + * (safe) URL handlers for his browser(s). + * + * TODO: I'd love to see some kind of a Link/LinkSet object implemented + * for this and similar hooks. + * + * @param Service $service Monitoring service object + * + * @return array An array containing a list of service action links + */ + abstract public function getActionsForService(Service $service); + + public function getActionsForObject(MonitoredObject $object) + { + return $this->getActionsForService($object); + } +} diff --git a/modules/monitoring/library/Monitoring/Hook/TimelineProviderHook.php b/modules/monitoring/library/Monitoring/Hook/TimelineProviderHook.php new file mode 100644 index 0000000..d302d12 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Hook/TimelineProviderHook.php @@ -0,0 +1,37 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Hook; + +use Icinga\Module\Monitoring\Timeline\TimeRange; + +/** + * Base class for TimeLine providers + */ +abstract class TimelineProviderHook +{ + /** + * Return the names by which to group entries + * + * @return array An array with the names as keys and their attribute-lists as values + */ + abstract public function getIdentifiers(); + + /** + * Return the visible entries supposed to be shown on the timeline + * + * @param TimeRange $range The range of time for which to fetch entries + * + * @return array The entries to display on the timeline + */ + abstract public function fetchEntries(TimeRange $range); + + /** + * Return the entries supposed to be used to calculate forecasts + * + * @param TimeRange $range The range of time for which to fetch forecasts + * + * @return array The entries to calculate forecasts with + */ + abstract public function fetchForecasts(TimeRange $range); +} diff --git a/modules/monitoring/library/Monitoring/MonitoringWizard.php b/modules/monitoring/library/Monitoring/MonitoringWizard.php new file mode 100644 index 0000000..d19650a --- /dev/null +++ b/modules/monitoring/library/Monitoring/MonitoringWizard.php @@ -0,0 +1,160 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring; + +use Icinga\Module\Setup\Forms\RequirementsPage; +use Icinga\Web\Form; +use Icinga\Web\Wizard; +use Icinga\Web\Request; +use Icinga\Module\Setup\Setup; +use Icinga\Module\Setup\SetupWizard; +use Icinga\Module\Setup\RequirementSet; +use Icinga\Module\Setup\Forms\SummaryPage; +use Icinga\Module\Monitoring\Forms\Setup\WelcomePage; +use Icinga\Module\Monitoring\Forms\Setup\SecurityPage; +use Icinga\Module\Monitoring\Forms\Setup\TransportPage; +use Icinga\Module\Monitoring\Forms\Setup\IdoResourcePage; +use Icinga\Module\Setup\Requirement\PhpModuleRequirement; + +/** + * Monitoring Module Setup Wizard + */ +class MonitoringWizard extends Wizard implements SetupWizard +{ + /** + * Register all pages for this wizard + */ + public function init() + { + $this->addPage(new WelcomePage()); + $this->addPage(new IdoResourcePage()); + $this->addPage(new TransportPage()); + $this->addPage(new SecurityPage()); + $this->addPage(new SummaryPage(array('name' => 'setup_monitoring_summary'))); + } + + /** + * Setup the given page that is either going to be displayed or validated + * + * @param Form|RequirementsPage|SummaryPage $page The page to setup + * @param Request $request The current request + */ + public function setupPage(Form $page, Request $request) + { + if ($page->getName() === 'setup_requirements') { + $page->setRequirements($this->getRequirements()); + } elseif ($page->getName() === 'setup_monitoring_summary') { + $page->setSummary($this->getSetup()->getSummary()); + $page->setSubjectTitle(mt('monitoring', 'the monitoring module', 'setup.summary.subject')); + } elseif ($this->getDirection() === static::FORWARD + && ($page->getName() === 'setup_monitoring_ido') + ) { + if ((($authDbResourceData = $this->getPageData('setup_auth_db_resource')) !== null + && $authDbResourceData['name'] === $request->getPost('name')) + || (($configDbResourceData = $this->getPageData('setup_config_db_resource')) !== null + && $configDbResourceData['name'] === $request->getPost('name')) + || (($ldapResourceData = $this->getPageData('setup_ldap_resource')) !== null + && $ldapResourceData['name'] === $request->getPost('name')) + ) { + $page->error(mt('monitoring', 'The given resource name is already in use.')); + } + } + } + + /** + * Add buttons to the given page based on its position in the page-chain + * + * @param Form $page The page to add the buttons to + * + * @todo This is never called, because its a sub-wizard only + * @todo This is missing the ´transport_validation´ case + * @see WebWizard::addButtons which does some of the needed work + */ + protected function addButtons(Form $page) + { + parent::addButtons($page); + + $pages = $this->getPages(); + $index = array_search($page, $pages, true); + if ($index === 0) { + // Used t() here as "Start" is too generic and already translated in the icinga domain + $page->getElement(static::BTN_NEXT)->setLabel(t('Start', 'setup.welcome.btn.next')); + } elseif ($index === count($pages) - 1) { + $page->getElement(static::BTN_NEXT)->setLabel( + mt('monitoring', 'Setup the monitoring module for Icinga Web 2', 'setup.summary.btn.finish') + ); + } + + if ($page->getName() === 'setup_monitoring_ido') { + $page->addElement( + 'submit', + 'backend_validation', + array( + 'ignore' => true, + 'label' => t('Validate Configuration'), + 'data-progress-label' => t('Validation In Progress'), + 'decorators' => array('ViewHelper'), + 'formnovalidate' => 'formnovalidate' + ) + ); + $page->getDisplayGroup('buttons')->addElement($page->getElement('backend_validation')); + } + } + + /** + * Return the setup for this wizard + * + * @return Setup + */ + public function getSetup() + { + $pageData = $this->getPageData(); + $setup = new Setup(); + + $setup->addStep( + new BackendStep(array( + 'backendConfig' => ['name' => 'icinga', 'type' => 'ido'], + 'resourceConfig' => array_diff_key( + $pageData['setup_monitoring_ido'], //TODO: Prefer a new backend once implemented. + array('skip_validation' => null) + ) + )) + ); + + $setup->addStep( + new TransportStep(array( + 'transportConfig' => $pageData['setup_command_transport'] + )) + ); + + $setup->addStep( + new SecurityStep(array( + 'securityConfig' => $pageData['setup_monitoring_security'] + )) + ); + + return $setup; + } + + /** + * Return the requirements of this wizard + * + * @return RequirementSet + */ + public function getRequirements() + { + $set = new RequirementSet(); + $set->add(new PhpModuleRequirement(array( + 'optional' => true, + 'condition' => 'curl', + 'alias' => 'cURL', + 'description' => mt( + 'monitoring', + 'To send external commands over Icinga 2\'s API the cURL module for PHP is required.' + ) + ))); + + return $set; + } +} diff --git a/modules/monitoring/library/Monitoring/Object/Acknowledgement.php b/modules/monitoring/library/Monitoring/Object/Acknowledgement.php new file mode 100644 index 0000000..3cd0d20 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Object/Acknowledgement.php @@ -0,0 +1,215 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Object; + +use InvalidArgumentException; +use Traversable; +use Icinga\Util\StringHelper; + +/** + * Acknowledgement of a host or service incident + */ +class Acknowledgement +{ + /** + * Author of the acknowledgement + * + * @var string + */ + protected $author; + + /** + * Comment of the acknowledgement + * + * @var string + */ + protected $comment; + + /** + * Entry time of the acknowledgement + * + * @var int + */ + protected $entryTime; + + /** + * Expiration time of the acknowledgment + * + * @var int|null + */ + protected $expirationTime; + + /** + * Whether the acknowledgement is sticky + * + * Sticky acknowledgements suppress notifications until the host or service recovers + * + * @var bool + */ + protected $sticky = false; + + /** + * Create a new acknowledgement of a host or service incident + * + * @param array|object|Traversable $properties + * + * @throws InvalidArgumentException If the type of the given properties is invalid + */ + public function __construct($properties = null) + { + if ($properties !== null) { + $this->setProperties($properties); + } + } + + /** + * Get the author of the acknowledgement + * + * @return string + */ + public function getAuthor() + { + return $this->author; + } + + /** + * Set the author of the acknowledgement + * + * @param string $author + * + * @return $this + */ + public function setAuthor($author) + { + $this->author = (string) $author; + return $this; + } + + /** + * Get the comment of the acknowledgement + * + * @return string + */ + public function getComment() + { + return $this->comment; + } + + /** + * Set the comment of the acknowledgement + * + * @param string $comment + * + * @return $this + */ + public function setComment($comment) + { + $this->comment = (string) $comment; + + return $this; + } + + /** + * Get the entry time of the acknowledgement + * + * @return int + */ + public function getEntryTime() + { + return $this->entryTime; + } + + /** + * Set the entry time of the acknowledgement + * + * @param int $entryTime + * + * @return $this + */ + public function setEntryTime($entryTime) + { + $this->entryTime = (int) $entryTime; + + return $this; + } + + /** + * Get the expiration time of the acknowledgement + * + * @return int|null + */ + public function getExpirationTime() + { + return $this->expirationTime; + } + + /** + * Set the expiration time of the acknowledgement + * + * @param int|null $expirationTime Unix timestamp + * + * @return $this + */ + public function setExpirationTime($expirationTime = null) + { + $this->expirationTime = $expirationTime !== null ? (int) $expirationTime : null; + + return $this; + } + + /** + * Get whether the acknowledgement is sticky + * + * @return bool + */ + public function getSticky() + { + return $this->sticky; + } + + /** + * Set whether the acknowledgement is sticky + * + * @param bool $sticky + * + * @return $this + */ + public function setSticky($sticky = true) + { + $this->sticky = (bool) $sticky; + return $this; + } + + /** + * Get whether the acknowledgement expires + * + * @return bool + */ + public function expires() + { + return $this->expirationTime !== null; + } + + /** + * Set the properties of the acknowledgement + * + * @param array|object|Traversable $properties + * + * @return $this + * @throws InvalidArgumentException If the type of the given properties is invalid + */ + public function setProperties($properties) + { + if (! is_array($properties) && ! is_object($properties) && ! $properties instanceof Traversable) { + throw new InvalidArgumentException('Properties must be either an array or an instance of Traversable'); + } + foreach ($properties as $name => $value) { + $setter = 'set' . ucfirst(StringHelper::cname($name)); + if (method_exists($this, $setter)) { + $this->$setter($value); + } + } + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Object/Host.php b/modules/monitoring/library/Monitoring/Object/Host.php new file mode 100644 index 0000000..dd8538b --- /dev/null +++ b/modules/monitoring/library/Monitoring/Object/Host.php @@ -0,0 +1,205 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Object; + +use Icinga\Data\Filter\FilterEqual; +use Icinga\Module\Monitoring\DataView\Hoststatus; +use InvalidArgumentException; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; + +/** + * An Icinga host + */ +class Host extends MonitoredObject +{ + /** + * Host state 'UP' + */ + const STATE_UP = 0; + + /** + * Host state 'DOWN' + */ + const STATE_DOWN = 1; + + /** + * Host state 'UNREACHABLE' + */ + const STATE_UNREACHABLE = 2; + + /** + * Host state 'PENDING' + */ + const STATE_PENDING = 99; + + /** + * Type of the Icinga host + * + * @var string + */ + public $type = self::TYPE_HOST; + + /** + * Prefix of the Icinga host + * + * @var string + */ + public $prefix = 'host_'; + + /** + * Hostname + * + * @var string + */ + protected $host; + + /** + * The services running on the hosts + * + * @var \Icinga\Module\Monitoring\Object\Service[] + */ + protected $services; + + /** + * Create a new host + * + * @param MonitoringBackend $backend Backend to fetch host information from + * @param string $host Hostname + */ + public function __construct(MonitoringBackend $backend, $host) + { + parent::__construct($backend); + $this->host = $host; + } + + /** + * Get the hostname + * + * @return string + */ + public function getName() + { + return $this->host; + } + + /** + * Get the data view to fetch the host information from + * + * @return Hoststatus + */ + protected function getDataView() + { + $columns = array( + 'host_acknowledged', + 'host_acknowledgement_type', + 'host_action_url', + 'host_active_checks_enabled', + 'host_active_checks_enabled_changed', + 'host_address', + 'host_address6', + 'host_alias', + 'host_attempt', + 'host_check_command', + 'host_check_execution_time', + 'host_check_interval', + 'host_check_latency', + 'host_check_source', + 'host_check_timeperiod', + 'host_current_check_attempt', + 'host_current_notification_number', + 'host_display_name', + 'host_event_handler_enabled', + 'host_event_handler_enabled_changed', + 'host_flap_detection_enabled', + 'host_flap_detection_enabled_changed', + 'host_handled', + 'host_icon_image', + 'host_icon_image_alt', + 'host_in_downtime', + 'host_is_flapping', + 'host_is_reachable', + 'host_last_check', + 'host_last_notification', + 'host_last_state_change', + 'host_long_output', + 'host_max_check_attempts', + 'host_name', + 'host_next_check', + 'host_next_update', + 'host_notes', + 'host_notes_url', + 'host_notifications_enabled', + 'host_notifications_enabled_changed', + 'host_obsessing', + 'host_obsessing_changed', + 'host_output', + 'host_passive_checks_enabled', + 'host_passive_checks_enabled_changed', + 'host_percent_state_change', + 'host_perfdata', + 'host_process_perfdata' => 'host_process_performance_data', + 'host_state', + 'host_state_type', + 'instance_name' + ); + return $this->backend->select()->from('hoststatus', $columns) + ->whereEx(new FilterEqual('host_name', '=', $this->host)); + } + + /** + * Fetch the services running on the host + * + * @return $this + */ + public function fetchServices() + { + $services = array(); + foreach ($this->backend->select()->from('servicestatus', array('service_description')) + ->where('host_name', $this->host) + ->applyFilter($this->getFilter()) + ->getQuery() as $service) { + $services[] = new Service($this->backend, $this->host, $service->service_description); + } + $this->services = $services; + return $this; + } + + /** + * Get the optional translated textual representation of a host state + * + * @param int $state + * @param bool $translate + * + * @return string + * @throws InvalidArgumentException If the host state is not valid + */ + public static function getStateText($state, $translate = false) + { + $translate = (bool) $translate; + switch ((int) $state) { + case self::STATE_UP: + $text = $translate ? mt('monitoring', 'UP') : 'up'; + break; + case self::STATE_DOWN: + $text = $translate ? mt('monitoring', 'DOWN') : 'down'; + break; + case self::STATE_UNREACHABLE: + $text = $translate ? mt('monitoring', 'UNREACHABLE') : 'unreachable'; + break; + case self::STATE_PENDING: + $text = $translate ? mt('monitoring', 'PENDING') : 'pending'; + break; + default: + throw new InvalidArgumentException(sprintf('Invalid host state \'%s\'', $state)); + } + return $text; + } + + public function getNotesUrls() + { + return $this->resolveAllStrings( + MonitoredObject::parseAttributeUrls($this->host_notes_url) + ); + } +} diff --git a/modules/monitoring/library/Monitoring/Object/HostList.php b/modules/monitoring/library/Monitoring/Object/HostList.php new file mode 100644 index 0000000..8b1947d --- /dev/null +++ b/modules/monitoring/library/Monitoring/Object/HostList.php @@ -0,0 +1,133 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Object; + +use Icinga\Data\DataArray\ArrayDatasource; +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterOr; +use Icinga\Data\SimpleQuery; +use Icinga\Util\StringHelper; + +/** + * A host list + */ +class HostList extends ObjectList +{ + protected $dataViewName = 'hoststatus'; + + protected $columns = array('host_name'); + + protected function fetchObjects() + { + $hosts = array(); + $query = $this->backend->select()->from($this->dataViewName, $this->columns)->applyFilter($this->filter) + ->getQuery()->getSelectQuery()->query(); + foreach ($query as $row) { + /** @var object $row */ + $host = new Host($this->backend, $row->host_name); + $host->setProperties($row); + $hosts[] = $host; + } + return $hosts; + } + + /** + * Create a state summary of all hosts that can be consumed by hostssummary.phtml + * + * @return SimpleQuery + */ + public function getStateSummary() + { + $hostStates = array_fill_keys(self::getHostStatesSummaryEmpty(), 0); + foreach ($this as $host) { + $unhandled = (bool) $host->problem === true && (bool) $host->handled === false; + + $stateName = 'hosts_' . $host::getStateText($host->state); + ++$hostStates[$stateName]; + ++$hostStates[$stateName. ($unhandled ? '_unhandled' : '_handled')]; + } + + $hostStates['hosts_total'] = count($this); + + $ds = new ArrayDatasource(array((object) $hostStates)); + return $ds->select(); + } + + /** + * Return an empty array with all possible host state names + * + * @return array An array containing all possible host states as keys and 0 as values. + */ + public static function getHostStatesSummaryEmpty() + { + return StringHelper::cartesianProduct( + array( + array('hosts'), + array( + Host::getStateText(Host::STATE_UP), + Host::getStateText(Host::STATE_DOWN), + Host::getStateText(Host::STATE_UNREACHABLE), + Host::getStateText(Host::STATE_PENDING) + ), + array(null, 'handled', 'unhandled') + ), + '_' + ); + } + + /** + * Returns a Filter that matches all hosts in this list + * + * @return Filter + */ + public function objectsFilter($columns = array('host' => 'host')) + { + $filterExpression = array(); + foreach ($this as $host) { + $filterExpression[] = Filter::where($columns['host'], $host->getName()); + } + return FilterOr::matchAny($filterExpression); + } + + /** + * Get the comments + * + * @return \Icinga\Module\Monitoring\DataView\Hostcomment + */ + public function getComments() + { + return $this->backend + ->select() + ->from('hostcomment', array('host_name')) + ->applyFilter(clone $this->filter); + } + + /** + * Get the scheduled downtimes + * + * @return \Icinga\Module\Monitoring\DataView\Hostdowntime + */ + public function getScheduledDowntimes() + { + return $this->backend + ->select() + ->from('hostdowntime', array('host_name')) + ->applyFilter(clone $this->filter); + } + + /** + * @return ObjectList + */ + public function getUnacknowledgedObjects() + { + $unhandledObjects = array(); + foreach ($this as $object) { + if (! in_array((int) $object->state, array(0, 99)) && + (bool) $object->host_acknowledged === false) { + $unhandledObjects[] = $object; + } + } + return $this->newFromArray($unhandledObjects); + } +} diff --git a/modules/monitoring/library/Monitoring/Object/Macro.php b/modules/monitoring/library/Monitoring/Object/Macro.php new file mode 100644 index 0000000..18a1855 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Object/Macro.php @@ -0,0 +1,83 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Object; + +use Exception; +use Icinga\Application\Logger; +use stdClass; + +/** + * Expand macros in string in the context of MonitoredObjects + */ +class Macro +{ + /** + * Known icinga macros + * + * @var array + */ + private static $icingaMacros = array( + 'HOSTNAME' => 'host_name', + 'HOSTADDRESS' => 'host_address', + 'HOSTADDRESS6' => 'host_address6', + 'SERVICEDESC' => 'service_description', + 'host.name' => 'host_name', + 'host.address' => 'host_address', + 'host.address6' => 'host_address6', + 'service.description' => 'service_description', + 'service.name' => 'service_description' + ); + + /** + * Return the given string with macros being resolved + * + * @param string $input The string in which to look for macros + * @param MonitoredObject|stdClass $object The host or service used to resolve macros + * + * @return string The substituted or unchanged string + */ + public static function resolveMacros($input, $object) + { + $matches = array(); + if (preg_match_all('@\$([^\$\s]+)\$@', $input, $matches)) { + foreach ($matches[1] as $key => $value) { + $newValue = self::resolveMacro($value, $object); + if ($newValue !== $value) { + $input = str_replace($matches[0][$key], $newValue, $input); + } + } + } + + return $input; + } + + /** + * Resolve a macro based on the given object + * + * @param string $macro The macro to resolve + * @param MonitoredObject|stdClass $object The object used to resolve the macro + * + * @return string The new value or the macro if it cannot be resolved + */ + public static function resolveMacro($macro, $object) + { + if (isset(self::$icingaMacros[$macro]) && isset($object->{self::$icingaMacros[$macro]})) { + return $object->{self::$icingaMacros[$macro]}; + } + + try { + $value = $object->$macro; + } catch (Exception $e) { + $objectName = $object->getName(); + if ($object instanceof Service) { + $objectName = $object->getHost()->getName() . '!' . $objectName; + } + + $value = null; + Logger::debug('Unable to resolve macro "%s" on object "%s". An error occured: %s', $macro, $objectName, $e); + } + + return $value !== null ? $value : $macro; + } +} diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php new file mode 100644 index 0000000..91fd9e7 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php @@ -0,0 +1,930 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Object; + +use Icinga\Data\Filter\FilterEqual; +use stdClass; +use InvalidArgumentException; +use Icinga\Authentication\Auth; +use Icinga\Application\Config; +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filterable; +use Icinga\Exception\InvalidPropertyException; +use Icinga\Exception\ProgrammingError; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use Icinga\Util\GlobFilter; +use Icinga\Web\UrlParams; + +/** + * A monitored Icinga object, i.e. host or service + */ +abstract class MonitoredObject implements Filterable +{ + /** + * Type host + */ + const TYPE_HOST = 'host'; + + /** + * Type service + */ + const TYPE_SERVICE = 'service'; + + /** + * Acknowledgement of the host or service if any + * + * @var object + */ + protected $acknowledgement; + + /** + * Backend to fetch object information from + * + * @var MonitoringBackend + */ + protected $backend; + + /** + * Comments + * + * @var array + */ + protected $comments; + + /** + * This object's obfuscated custom variables + * + * @var array + */ + protected $customvars; + + /** + * This object's obfuscated custom variables, names not lower case + * + * @var array + */ + protected $customvarsWithOriginalNames; + + /** + * The host custom variables + * + * @var array + */ + protected $hostVariables; + + /** + * The service custom variables + * + * @var array + */ + protected $serviceVariables; + + /** + * Contact groups + * + * @var array + */ + protected $contactgroups; + + /** + * Contacts + * + * @var array + */ + protected $contacts; + + /** + * Downtimes + * + * @var array + */ + protected $downtimes; + + /** + * Event history + * + * @var \Icinga\Module\Monitoring\DataView\EventHistory + */ + protected $eventhistory; + + /** + * Filter + * + * @var Filter + */ + protected $filter; + + /** + * Host groups + * + * @var array + */ + protected $hostgroups; + + /** + * Prefix of the Icinga object, i.e. 'host_' or 'service_' + * + * @var string + */ + protected $prefix; + + /** + * Properties + * + * @var object + */ + protected $properties; + + /** + * Service groups + * + * @var array + */ + protected $servicegroups; + + /** + * Type of the Icinga object, i.e. 'host' or 'service' + * + * @var string + */ + protected $type; + + /** + * Stats + * + * @var object + */ + protected $stats; + + /** + * The properties to hide from the user + * + * @var GlobFilter + */ + protected $blacklistedProperties = null; + + /** + * Create a monitored object, i.e. host or service + * + * @param MonitoringBackend $backend Backend to fetch object information from + */ + public function __construct(MonitoringBackend $backend) + { + $this->backend = $backend; + } + + /** + * Get the object's data view + * + * @return \Icinga\Module\Monitoring\DataView\DataView + */ + abstract protected function getDataView(); + + /** + * Get all note urls configured for this monitored object + * + * @return array All note urls as a string + */ + abstract public function getNotesUrls(); + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + // Left out on purpose. Interface is deprecated. + } + + /** + * {@inheritdoc} + */ + public function applyFilter(Filter $filter) + { + $this->getFilter()->addFilter($filter); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFilter() + { + if ($this->filter === null) { + $this->filter = Filter::matchAll(); + } + + return $this->filter; + } + + /** + * {@inheritdoc} + */ + public function setFilter(Filter $filter) + { + // Left out on purpose. Interface is deprecated. + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + // Left out on purpose. Interface is deprecated. + } + + /** + * Require the object's type to be one of the given types + * + * @param array $oneOf + * + * @return bool + * @throws InvalidArgumentException If the object's type is not one of the given types. + */ + public function assertOneOf(array $oneOf) + { + if (! in_array($this->type, $oneOf)) { + throw new InvalidArgumentException; + } + return true; + } + + /** + * Fetch the object's properties + * + * @return bool + */ + public function fetch() + { + $properties = $this->getDataView()->applyFilter($this->getFilter())->getQuery()->fetchRow(); + + if ($properties === false) { + return false; + } + + if (isset($properties->host_contacts)) { + $this->contacts = array(); + foreach (preg_split('~,~', $properties->host_contacts) as $contact) { + $this->contacts[] = (object) array( + 'contact_name' => $contact, + 'contact_alias' => $contact, + 'contact_email' => null, + 'contact_pager' => null, + ); + } + } + + $this->properties = $properties; + + return true; + } + + /** + * Fetch the object's acknowledgement + */ + public function fetchAcknowledgement() + { + if ($this->comments === null) { + $this->fetchComments(); + } + + return $this; + } + + /** + * Fetch the object's comments + * + * @return $this + */ + public function fetchComments() + { + $commentsView = $this->backend->select()->from('comment', array( + 'author' => 'comment_author_name', + 'comment' => 'comment_data', + 'expiration' => 'comment_expiration', + 'id' => 'comment_internal_id', + 'name' => 'comment_name', + 'persistent' => 'comment_is_persistent', + 'timestamp' => 'comment_timestamp', + 'type' => 'comment_type' + )); + if ($this->type === self::TYPE_SERVICE) { + $commentsView + ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name)) + ->whereEx(new FilterEqual('service_description', '=', $this->service_description)); + } else { + $commentsView->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + } + $commentsView + ->whereEx(new FilterEqual('comment_type', '=', ['ack', 'comment'])) + ->whereEx(new FilterEqual('object_type', '=', $this->type)); + + $comments = $commentsView->fetchAll(); + + if ((bool) $this->properties->{$this->prefix . 'acknowledged'}) { + $ackCommentIdx = null; + + foreach ($comments as $i => $comment) { + if ($comment->type === 'ack') { + $this->acknowledgement = new Acknowledgement(array( + 'author' => $comment->author, + 'comment' => $comment->comment, + 'entry_time' => $comment->timestamp, + 'expiration_time' => $comment->expiration, + 'sticky' => (int) $this->properties->{$this->prefix . 'acknowledgement_type'} === 2 + )); + $ackCommentIdx = $i; + break; + } + } + + if ($ackCommentIdx !== null) { + unset($comments[$ackCommentIdx]); + } + } + + $this->comments = $comments; + + return $this; + } + + /** + * Fetch the object's contact groups + * + * @return $this + */ + public function fetchContactgroups() + { + $contactsGroups = $this->backend->select()->from('contactgroup', array( + 'contactgroup_name', + 'contactgroup_alias' + )); + if ($this->type === self::TYPE_SERVICE) { + $contactsGroups + ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name)) + ->whereEx(new FilterEqual('service_description', '=', $this->service_description)); + } else { + $contactsGroups->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + } + $this->contactgroups = $contactsGroups; + return $this; + } + + /** + * Fetch the object's contacts + * + * @return $this + */ + public function fetchContacts() + { + $contacts = $this->backend->select()->from("{$this->type}contact", array( + 'contact_name', + 'contact_alias', + 'contact_email', + 'contact_pager', + )); + if ($this->type === self::TYPE_SERVICE) { + $contacts + ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name)) + ->whereEx(new FilterEqual('service_description', '=', $this->service_description)); + } else { + $contacts->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + } + $this->contacts = $contacts; + return $this; + } + + /** + * Fetch this object's obfuscated custom variables + * + * @return $this + */ + public function fetchCustomvars() + { + + if ($this->type === self::TYPE_SERVICE) { + $this->fetchServiceVariables(); + $customvars = $this->serviceVariables; + } else { + $this->fetchHostVariables(); + $customvars = $this->hostVariables; + } + + $this->customvars = $customvars; + $this->hideBlacklistedProperties(); + $this->customvars = $this->obfuscateCustomVars($this->customvars, null); + $this->customvarsWithOriginalNames = $this->obfuscateCustomVars($this->customvarsWithOriginalNames, null); + + return $this; + } + + /** + * Obfuscate custom variables recursively + * + * @param stdClass|array $customvars The custom variables to obfuscate + * + * @return stdClass|array The obfuscated custom variables + */ + protected function obfuscateCustomVars($customvars, $_) + { + return self::protectCustomVars($customvars); + } + + public static function protectCustomVars($customvars) + { + $blacklist = []; + $blacklistPattern = ''; + + if (($blacklistConfig = Config::module('monitoring')->get('security', 'protected_customvars', '')) !== '') { + foreach (explode(',', $blacklistConfig) as $customvar) { + $nonWildcards = array(); + foreach (explode('*', $customvar) as $nonWildcard) { + $nonWildcards[] = preg_quote($nonWildcard, '/'); + } + $blacklist[] = implode('.*', $nonWildcards); + } + $blacklistPattern = '/^(' . implode('|', $blacklist) . ')$/i'; + } + + if (! $blacklistPattern) { + return $customvars; + } + + $obfuscator = function ($vars) use ($blacklistPattern, &$obfuscator) { + $result = []; + foreach ($vars as $name => $value) { + if ($blacklistPattern && preg_match($blacklistPattern, $name)) { + $result[$name] = '***'; + } elseif ($value instanceof stdClass || is_array($value)) { + $obfuscated = $obfuscator($value); + $result[$name] = $value instanceof stdClass ? (object) $obfuscated : $obfuscated; + } else { + $result[$name] = $value; + } + } + + return $result; + }; + $obfuscatedCustomVars = $obfuscator($customvars); + + return $customvars instanceof stdClass ? (object) $obfuscatedCustomVars : $obfuscatedCustomVars; + } + + /** + * Hide all blacklisted properties from the user as restricted by monitoring/blacklist/properties + * + * Currently this only affects the custom variables + */ + protected function hideBlacklistedProperties() + { + if ($this->blacklistedProperties === null) { + $this->blacklistedProperties = new GlobFilter( + Auth::getInstance()->getRestrictions('monitoring/blacklist/properties') + ); + } + + $allProperties = $this->blacklistedProperties->removeMatching( + [$this->type => ['vars' => $this->customvars]] + ); + $this->customvars = isset($allProperties[$this->type]['vars']) + ? $allProperties[$this->type]['vars'] + : []; + + $allProperties = $this->blacklistedProperties->removeMatching( + [$this->type => ['vars' => $this->customvarsWithOriginalNames]] + ); + $this->customvarsWithOriginalNames = isset($allProperties[$this->type]['vars']) + ? $allProperties[$this->type]['vars'] + : []; + } + + /** + * Fetch the host custom variables related to this object + * + * @return $this + */ + public function fetchHostVariables() + { + $query = $this->backend->select()->from('customvar', array( + 'varname', + 'varvalue', + 'is_json' + )) + ->whereEx(new FilterEqual('object_type', '=', static::TYPE_HOST)) + ->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + + $this->hostVariables = []; + + if ($this->type === static::TYPE_HOST) { + $this->customvarsWithOriginalNames = []; + } + + foreach ($query as $row) { + if ($row->is_json) { + $this->hostVariables[strtolower($row->varname)] = json_decode($row->varvalue); + } else { + $this->hostVariables[strtolower($row->varname)] = $row->varvalue; + } + + if ($this->type === static::TYPE_HOST) { + $this->customvarsWithOriginalNames[$row->varname] = $this->hostVariables[strtolower($row->varname)]; + } + } + + return $this; + } + + /** + * Fetch the service custom variables related to this object + * + * @return $this + * + * @throws ProgrammingError In case this object is not a service + */ + public function fetchServiceVariables() + { + if ($this->type !== static::TYPE_SERVICE) { + throw new ProgrammingError('Cannot fetch service custom variables for non-service objects'); + } + + $query = $this->backend->select()->from('customvar', array( + 'varname', + 'varvalue', + 'is_json' + )) + ->whereEx(new FilterEqual('object_type', '=', static::TYPE_SERVICE)) + ->whereEx(new FilterEqual('host_name', '=', $this->host_name)) + ->whereEx(new FilterEqual('service_description', '=', $this->service_description)); + + $this->serviceVariables = []; + $this->customvarsWithOriginalNames = []; + foreach ($query as $row) { + if ($row->is_json) { + $this->customvarsWithOriginalNames[$row->varname] = json_decode($row->varvalue); + $this->serviceVariables[strtolower($row->varname)] = $this->customvarsWithOriginalNames[$row->varname]; + } else { + $this->serviceVariables[strtolower($row->varname)] = $row->varvalue; + $this->customvarsWithOriginalNames[$row->varname] = $row->varvalue; + } + } + + return $this; + } + + /** + * Fetch the object's downtimes + * + * @return $this + */ + public function fetchDowntimes() + { + $downtimes = $this->backend->select()->from('downtime', array( + 'author_name' => 'downtime_author_name', + 'comment' => 'downtime_comment', + 'duration' => 'downtime_duration', + 'end' => 'downtime_end', + 'entry_time' => 'downtime_entry_time', + 'id' => 'downtime_internal_id', + 'is_fixed' => 'downtime_is_fixed', + 'is_flexible' => 'downtime_is_flexible', + 'is_in_effect' => 'downtime_is_in_effect', + 'name' => 'downtime_name', + 'objecttype' => 'object_type', + 'scheduled_end' => 'downtime_scheduled_end', + 'scheduled_start' => 'downtime_scheduled_start', + 'start' => 'downtime_start' + )) + ->whereEx(new FilterEqual('object_type', '=', $this->type)) + ->order('downtime_is_in_effect', 'DESC') + ->order('downtime_scheduled_start', 'ASC'); + if ($this->type === self::TYPE_SERVICE) { + $downtimes + ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name)) + ->whereEx(new FilterEqual('service_description', '=', $this->service_description)); + } else { + $downtimes + ->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + } + $this->downtimes = $downtimes->getQuery()->fetchAll(); + return $this; + } + + /** + * Fetch the object's event history + * + * @return $this + */ + public function fetchEventhistory() + { + $eventHistory = $this->backend + ->select() + ->from( + 'eventhistory', + array( + 'id', + 'object_type', + 'host_name', + 'host_display_name', + 'service_description', + 'service_display_name', + 'timestamp', + 'state', + 'output', + 'type' + ) + ) + ->whereEx(new FilterEqual('object_type', '=', $this->type)) + ->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + + if ($this->type === self::TYPE_SERVICE) { + $eventHistory->whereEx( + new FilterEqual('service_description', '=', $this->service_description) + ); + } + + $this->eventhistory = $eventHistory; + return $this; + } + + /** + * Fetch the object's host groups + * + * @return $this + */ + public function fetchHostgroups() + { + $this->hostgroups = $this->backend->select() + ->from('hostgroup', array('hostgroup_name', 'hostgroup_alias')) + ->whereEx(new FilterEqual('host_name', '=', $this->host_name)) + ->applyFilter($this->getFilter()) + ->fetchPairs(); + return $this; + } + + /** + * Fetch the object's service groups + * + * @return $this + */ + public function fetchServicegroups() + { + $query = $this->backend->select() + ->from('servicegroup', array('servicegroup_name', 'servicegroup_alias')) + ->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + + if ($this->type === self::TYPE_SERVICE) { + $query->whereEx( + new FilterEqual('service_description', '=', $this->service_description) + ); + } + + $this->servicegroups = $query->applyFilter($this->getFilter())->fetchPairs(); + return $this; + } + + /** + * Fetch stats + * + * @return $this + */ + public function fetchStats() + { + $this->stats = $this->backend->select()->from('servicestatussummary', array( + 'services_total', + 'services_ok', + 'services_critical', + 'services_critical_unhandled', + 'services_critical_handled', + 'services_warning', + 'services_warning_unhandled', + 'services_warning_handled', + 'services_unknown', + 'services_unknown_unhandled', + 'services_unknown_handled', + 'services_pending', + )) + ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name)) + ->applyFilter($this->getFilter()) + ->fetchRow(); + return $this; + } + + /** + * Get all action urls configured for this monitored object + * + * @return array All note urls as a string + */ + public function getActionUrls() + { + return $this->resolveAllStrings( + MonitoredObject::parseAttributeUrls($this->action_url) + ); + } + + /** + * Get the type of the object + * + * @param bool $translate + * + * @return string + */ + public function getType($translate = false) + { + if ($translate !== false) { + switch ($this->type) { + case self::TYPE_HOST: + $type = mt('montiroing', 'host'); + break; + case self::TYPE_SERVICE: + $type = mt('monitoring', 'service'); + break; + default: + throw new InvalidArgumentException('Invalid type ' . $this->type); + } + } else { + $type = $this->type; + } + return $type; + } + + /** + * Parse the content of the action_url or notes_url attributes + * + * Find all occurences of http links, separated by whitespaces and quoted + * by single or double-ticks. + * + * @link http://docs.icinga.com/latest/de/objectdefinitions.html + * + * @param string $urlString A string containing one or more urls + * @return array Array of urls as strings + */ + public static function parseAttributeUrls($urlString) + { + if (empty($urlString)) { + return array(); + } + $links = array(); + if (strpos($urlString, "' ") === false) { + $links[] = $urlString; + } else { + // parse notes-url format + foreach (explode("' ", $urlString) as $url) { + $url = strpos($url, "'") === 0 ? substr($url, 1) : $url; + $url = strrpos($url, "'") === strlen($url) - 1 ? substr($url, 0, strlen($url) - 1) : $url; + $links[] = $url; + } + } + return $links; + } + + /** + * Fetch all available data of the object + * + * @return $this + */ + public function populate() + { + $this + ->fetchComments() + ->fetchContactgroups() + ->fetchContacts() + ->fetchCustomvars() + ->fetchDowntimes(); + + // Call fetchHostgroups or fetchServicegroups depending on the object's type + $fetchGroups = 'fetch' . ucfirst($this->type) . 'groups'; + $this->$fetchGroups(); + + return $this; + } + + /** + * Resolve macros in all given strings in the current object context + * + * @param array $strs An array of urls as string + * + * @return array + */ + protected function resolveAllStrings(array $strs) + { + foreach ($strs as $i => $str) { + $strs[$i] = Macro::resolveMacros($str, $this); + } + return $strs; + } + + /** + * Set the object's properties + * + * @param object $properties + * + * @return $this + */ + public function setProperties($properties) + { + $this->properties = (object) $properties; + return $this; + } + + public function __isset($name) + { + if (property_exists($this->properties, $name)) { + return isset($this->properties->$name); + } elseif (property_exists($this, $name)) { + return isset($this->$name); + } + return false; + } + + public function __get($name) + { + if (property_exists($this->properties, $name)) { + return $this->properties->$name; + } elseif (property_exists($this, $name)) { + if ($this->$name === null) { + $fetchMethod = 'fetch' . ucfirst($name); + $this->$fetchMethod(); + } + + return $this->$name; + } elseif (preg_match('/^_(host|service)_(.+)/i', $name, $matches)) { + if (strtolower($matches[1]) === static::TYPE_HOST) { + if ($this->hostVariables === null) { + $this->fetchHostVariables(); + } + + $customvars = $this->hostVariables; + } else { + if ($this->serviceVariables === null) { + $this->fetchServiceVariables(); + } + + $customvars = $this->serviceVariables; + } + + $variableName = strtolower($matches[2]); + if (isset($customvars[$variableName])) { + return $customvars[$variableName]; + } + + return null; // Unknown custom variables MUST NOT throw an error + } elseif (in_array($name, array('contact_name', 'contactgroup_name', 'hostgroup_name', 'servicegroup_name'))) { + if ($name === 'contact_name') { + if ($this->contacts === null) { + $this->fetchContacts(); + } + + return array_map(function ($el) { + return $el->contact_name; + }, $this->contacts); + } elseif ($name === 'contactgroup_name') { + if ($this->contactgroups === null) { + $this->fetchContactgroups(); + } + + return array_map(function ($el) { + return $el->contactgroup_name; + }, $this->contactgroups); + } elseif ($name === 'hostgroup_name') { + if ($this->hostgroups === null) { + $this->fetchHostgroups(); + } + + return array_keys($this->hostgroups); + } else { // $name === 'servicegroup_name' + if ($this->servicegroups === null) { + $this->fetchServicegroups(); + } + + return array_keys($this->servicegroups); + } + } elseif (strpos($name, $this->prefix) !== 0) { + $propertyName = strtolower($name); + $prefixedName = $this->prefix . $propertyName; + if (property_exists($this->properties, $prefixedName)) { + return $this->properties->$prefixedName; + } + + if ($this->type === static::TYPE_HOST) { + if ($this->hostVariables === null) { + $this->fetchHostVariables(); + } + + $customvars = $this->hostVariables; + } else { // $this->type === static::TYPE_SERVICE + if ($this->serviceVariables === null) { + $this->fetchServiceVariables(); + } + + $customvars = $this->serviceVariables; + } + + if (isset($customvars[$propertyName])) { + return $customvars[$propertyName]; + } + } + + throw new InvalidPropertyException('Can\'t access property \'%s\'. Property does not exist.', $name); + } +} diff --git a/modules/monitoring/library/Monitoring/Object/ObjectList.php b/modules/monitoring/library/Monitoring/Object/ObjectList.php new file mode 100644 index 0000000..5237c56 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Object/ObjectList.php @@ -0,0 +1,295 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Object; + +use ArrayIterator; +use Countable; +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterChain; +use Icinga\Data\Filterable; +use Icinga\Module\Monitoring\DataView\Downtime; +use IteratorAggregate; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use Traversable; + +abstract class ObjectList implements Countable, IteratorAggregate, Filterable +{ + /** + * @var string + */ + protected $dataViewName; + + /** + * @var MonitoringBackend + */ + protected $backend; + + /** + * @var array + */ + protected $columns; + + /** + * @var Filter + */ + protected $filter; + + /** + * @var array + */ + protected $objects; + + /** + * @var int + */ + protected $count; + + public function __construct(MonitoringBackend $backend) + { + $this->backend = $backend; + } + + /** + * @param array $columns + * + * @return $this + */ + public function setColumns(array $columns) + { + $this->columns = $columns; + return $this; + } + + /** + * @return array + */ + public function getColumns() + { + return $this->columns; + } + + /** + * @param Filter $filter + * + * @return $this + */ + public function setFilter(Filter $filter) + { + $this->filter = $filter; + return $this; + } + + /** + * @return Filter|FilterChain + */ + public function getFilter() + { + if ($this->filter === null) { + $this->filter = Filter::matchAll(); + } + + return $this->filter; + } + + public function applyFilter(Filter $filter) + { + $this->getFilter()->addFilter($filter); + return $this; + } + + public function addFilter(Filter $filter) + { + $this->getFilter()->addFilter($filter); + } + + public function where($condition, $value = null) + { + $this->getFilter()->addFilter(Filter::where($condition, $value)); + } + + abstract protected function fetchObjects(); + + /** + * @return array + */ + public function fetch() + { + if ($this->objects === null) { + $this->objects = $this->fetchObjects(); + } + return $this->objects; + } + + public function count(): int + { + if ($this->count === null) { + $this->count = (int) $this->backend + ->select() + ->from($this->dataViewName, $this->columns) + ->applyFilter($this->filter) + ->getQuery() + ->count(); + } + + return $this->count; + } + + public function getIterator(): Traversable + { + if ($this->objects === null) { + $this->fetch(); + } + return new ArrayIterator($this->objects); + } + + /** + * Get the comments + * + * @return \Icinga\Module\Monitoring\DataView\Comment + */ + public function getComments() + { + return $this->backend->select()->from('comment')->applyFilter($this->filter); + } + + /** + * Get the scheduled downtimes + * + * @return Downtime + */ + public function getScheduledDowntimes() + { + return $this->backend->select()->from('downtime')->applyFilter($this->filter); + } + + /** + * @return ObjectList + */ + public function getAcknowledgedObjects() + { + $acknowledgedObjects = array(); + foreach ($this as $object) { + if ((bool) $object->acknowledged === true) { + $acknowledgedObjects[] = $object; + } + } + return $this->newFromArray($acknowledgedObjects); + } + + /** + * @return ObjectList + */ + public function getObjectsInDowntime() + { + $objectsInDowntime = array(); + foreach ($this as $object) { + if ((bool) $object->in_downtime === true) { + $objectsInDowntime[] = $object; + } + } + return $this->newFromArray($objectsInDowntime); + } + + /** + * @return ObjectList + */ + public function getUnhandledObjects() + { + $unhandledObjects = array(); + foreach ($this as $object) { + if ((bool) $object->problem === true && (bool) $object->handled === false) { + $unhandledObjects[] = $object; + } + } + return $this->newFromArray($unhandledObjects); + } + + /** + * @return ObjectList + */ + public function getProblemObjects() + { + $handledObjects = array(); + foreach ($this as $object) { + if ((bool) $object->problem === true) { + $handledObjects[] = $object; + } + } + return $this->newFromArray($handledObjects); + } + + /** + * @return ObjectList + */ + abstract public function getUnacknowledgedObjects(); + + /** + * Create a ObjectList from an array of hosts without querying a backend + * + * @return ObjectList + */ + protected function newFromArray(array $objects) + { + $class = get_called_class(); + $list = new $class($this->backend); + $list->objects = $objects; + $list->count = count($objects); + $list->filter = $list->objectsFilter(); + return $list; + } + + /** + * Create a filter that matches exactly the elements of this object list + * + * @param array $columns Override default column names. + * + * @return Filter + */ + abstract public function objectsFilter($columns = array()); + + /** + * Get the feature status + * + * @return array + */ + public function getFeatureStatus() + { + // null - init + // 0 - disabled + // 1 - enabled + // 2 - enabled & disabled + $featureStatus = array( + 'active_checks_enabled' => null, + 'passive_checks_enabled' => null, + 'obsessing' => null, + 'notifications_enabled' => null, + 'event_handler_enabled' => null, + 'flap_detection_enabled' => null + ); + + $features = array(); + + foreach ($featureStatus as $feature => &$status) { + $features[$feature] = &$status; + } + + foreach ($this as $object) { + foreach ($features as $feature => &$status) { + $enabled = (int) $object->{$feature}; + if (! isset($status)) { + $status = $enabled; + } elseif ($status !== $enabled) { + unset($features[$feature]); + if (empty($features)) { + break 2; + } + + break; + } + } + } + + return $featureStatus; + } +} diff --git a/modules/monitoring/library/Monitoring/Object/Service.php b/modules/monitoring/library/Monitoring/Object/Service.php new file mode 100644 index 0000000..a63db6f --- /dev/null +++ b/modules/monitoring/library/Monitoring/Object/Service.php @@ -0,0 +1,220 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Object; + +use Icinga\Data\Filter\FilterEqual; +use Icinga\Module\Monitoring\DataView\Servicestatus; +use InvalidArgumentException; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; + +/** + * An Icinga service + */ +class Service extends MonitoredObject +{ + /** + * Service state 'OK' + */ + const STATE_OK = 0; + + /** + * Service state 'WARNING' + */ + const STATE_WARNING = 1; + + /** + * Service state 'CRITICAL' + */ + const STATE_CRITICAL = 2; + + /** + * Service state 'UNKNOWN' + */ + const STATE_UNKNOWN = 3; + + /** + * Service state 'PENDING' + */ + const STATE_PENDING = 99; + + /** + * Type of the Icinga service + * + * @var string + */ + public $type = self::TYPE_SERVICE; + + /** + * Prefix of the Icinga service + * + * @var string + */ + public $prefix = 'service_'; + + /** + * Host the service is running on + * + * @var Host + */ + protected $host; + + /** + * Service name + * + * @var string + */ + protected $service; + + /** + * Create a new service + * + * @param MonitoringBackend $backend Backend to fetch service information from + * @param string $host Hostname the service is running on + * @param string $service Service name + */ + public function __construct(MonitoringBackend $backend, $host, $service) + { + parent::__construct($backend); + $this->host = new Host($backend, $host); + $this->service = $service; + } + + /** + * Get the host the service is running on + * + * @return Host + */ + public function getHost() + { + return $this->host; + } + + /** + * Get the service name + * + * @return string + */ + public function getName() + { + return $this->service; + } + + /** + * Get the data view + * + * @return Servicestatus + */ + protected function getDataView() + { + return $this->backend->select()->from('servicestatus', array( + 'instance_name', + 'host_attempt', + 'host_icon_image', + 'host_icon_image_alt', + 'host_acknowledged', + 'host_active_checks_enabled', + 'host_address', + 'host_address6', + 'host_alias', + 'host_display_name', + 'host_handled', + 'host_in_downtime', + 'host_is_flapping', + 'host_last_state_change', + 'host_name', + 'host_notifications_enabled', + 'host_passive_checks_enabled', + 'host_state', + 'host_state_type', + 'service_icon_image', + 'service_icon_image_alt', + 'service_acknowledged', + 'service_acknowledgement_type', + 'service_action_url', + 'service_active_checks_enabled', + 'service_active_checks_enabled_changed', + 'service_attempt', + 'service_check_command', + 'service_check_execution_time', + 'service_check_interval', + 'service_check_latency', + 'service_check_source', + 'service_check_timeperiod', + 'service_current_notification_number', + 'service_description', + 'service_display_name', + 'service_event_handler_enabled', + 'service_event_handler_enabled_changed', + 'service_flap_detection_enabled', + 'service_flap_detection_enabled_changed', + 'service_handled', + 'service_in_downtime', + 'service_is_flapping', + 'service_is_reachable', + 'service_last_check', + 'service_last_notification', + 'service_last_state_change', + 'service_long_output', + 'service_next_check', + 'service_next_update', + 'service_notes', + 'service_notes_url', + 'service_notifications_enabled', + 'service_notifications_enabled_changed', + 'service_obsessing', + 'service_obsessing_changed', + 'service_output', + 'service_passive_checks_enabled', + 'service_passive_checks_enabled_changed', + 'service_percent_state_change', + 'service_perfdata', + 'service_process_perfdata' => 'service_process_performance_data', + 'service_state', + 'service_state_type' + )) + ->whereEx(new FilterEqual('host_name', '=', $this->host->getName())) + ->whereEx(new FilterEqual('service_description', '=', $this->service)); + } + + /** + * Get the optional translated textual representation of a service state + * + * @param int $state + * @param bool $translate + * + * @return string + * @throws InvalidArgumentException If the service state is not valid + */ + public static function getStateText($state, $translate = false) + { + $translate = (bool) $translate; + switch ((int) $state) { + case self::STATE_OK: + $text = $translate ? mt('monitoring', 'OK') : 'ok'; + break; + case self::STATE_WARNING: + $text = $translate ? mt('monitoring', 'WARNING') : 'warning'; + break; + case self::STATE_CRITICAL: + $text = $translate ? mt('monitoring', 'CRITICAL') : 'critical'; + break; + case self::STATE_UNKNOWN: + $text = $translate ? mt('monitoring', 'UNKNOWN') : 'unknown'; + break; + case self::STATE_PENDING: + $text = $translate ? mt('monitoring', 'PENDING') : 'pending'; + break; + default: + throw new InvalidArgumentException(sprintf('Invalid service state \'%s\'', $state)); + } + return $text; + } + + public function getNotesUrls() + { + return $this->resolveAllStrings( + MonitoredObject::parseAttributeUrls($this->service_notes_url) + ); + } +} diff --git a/modules/monitoring/library/Monitoring/Object/ServiceList.php b/modules/monitoring/library/Monitoring/Object/ServiceList.php new file mode 100644 index 0000000..5bc0bdb --- /dev/null +++ b/modules/monitoring/library/Monitoring/Object/ServiceList.php @@ -0,0 +1,184 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Object; + +use Icinga\Data\DataArray\ArrayDatasource; +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterOr; +use Icinga\Data\SimpleQuery; +use Icinga\Util\StringHelper; + +/** + * A service list + */ +class ServiceList extends ObjectList +{ + protected $hostStateSummary; + + protected $serviceStateSummary; + + protected $dataViewName = 'servicestatus'; + + protected $columns = array('host_name', 'service_description'); + + protected function fetchObjects() + { + $services = array(); + $query = $this->backend->select()->from($this->dataViewName, $this->columns)->applyFilter($this->filter) + ->getQuery()->getSelectQuery()->query(); + foreach ($query as $row) { + /** @var object $row */ + $service = new Service($this->backend, $row->host_name, $row->service_description); + $service->setProperties($row); + $services[] = $service; + } + return $services; + } + + /** + * Create a state summary of all services that can be consumed by servicesummary.phtml + * + * @return SimpleQuery + */ + public function getServiceStateSummary() + { + if (! $this->serviceStateSummary) { + $this->initStateSummaries(); + } + + $ds = new ArrayDatasource(array((object) $this->serviceStateSummary)); + return $ds->select(); + } + + /** + * Create a state summary of all hosts that can be consumed by hostsummary.phtml + * + * @return SimpleQuery + */ + public function getHostStateSummary() + { + if (! $this->hostStateSummary) { + $this->initStateSummaries(); + } + + $ds = new ArrayDatasource(array((object) $this->hostStateSummary)); + return $ds->select(); + } + + /** + * Calculate the current state summary and populate hostStateSummary and serviceStateSummary + * properties + */ + protected function initStateSummaries() + { + $serviceStates = array_fill_keys(self::getServiceStatesSummaryEmpty(), 0); + $hostStates = array_fill_keys(HostList::getHostStatesSummaryEmpty(), 0); + + foreach ($this as $service) { + $unhandled = false; + if ((bool) $service->problem === true && (bool) $service->handled === false) { + $unhandled = true; + } + + $stateName = 'services_' . $service::getStateText($service->state); + ++$serviceStates[$stateName]; + ++$serviceStates[$stateName . ($unhandled ? '_unhandled' : '_handled')]; + + if (! isset($knownHostStates[$service->getHost()->getName()])) { + $unhandledHost = (bool) $service->host_problem === true && (bool) $service->host_handled === false; + ++$hostStates['hosts_' . $service->getHost()->getStateText($service->host_state)]; + ++$hostStates['hosts_' . $service->getHost()->getStateText($service->host_state) + . ($unhandledHost ? '_unhandled' : '_handled')]; + $knownHostStates[$service->getHost()->getName()] = true; + } + } + + $serviceStates['services_total'] = count($this); + $this->hostStateSummary = $hostStates; + $this->serviceStateSummary = $serviceStates; + } + + /** + * Return an empty array with all possible host state names + * + * @return array An array containing all possible host states as keys and 0 as values. + */ + public static function getServiceStatesSummaryEmpty() + { + return StringHelper::cartesianProduct( + array( + array('services'), + array( + Service::getStateText(Service::STATE_OK), + Service::getStateText(Service::STATE_WARNING), + Service::getStateText(Service::STATE_CRITICAL), + Service::getStateText(Service::STATE_UNKNOWN), + Service::getStateText(Service::STATE_PENDING) + ), + array(null, 'handled', 'unhandled') + ), + '_' + ); + } + + /** + * Returns a Filter that matches all hosts in this HostList + * + * @param array $columns Override filter column names + * + * @return Filter + */ + public function objectsFilter($columns = array('host' => 'host', 'service' => 'service')) + { + $filterExpression = array(); + foreach ($this as $service) { + $filterExpression[] = Filter::matchAll( + Filter::where($columns['host'], $service->getHost()->getName()), + Filter::where($columns['service'], $service->getName()) + ); + } + return FilterOr::matchAny($filterExpression); + } + + /** + * Get the comments + * + * @return \Icinga\Module\Monitoring\DataView\Hostcomment + */ + public function getComments() + { + return $this->backend + ->select() + ->from('servicecomment', array('host_name', 'service_description')) + ->applyFilter(clone $this->filter); + } + + /** + * Get the scheduled downtimes + * + * @return \Icinga\Module\Monitoring\DataView\Servicedowntime + */ + public function getScheduledDowntimes() + { + return $this->backend + ->select() + ->from('servicedowntime', array('host_name', 'service_description')) + ->applyFilter(clone $this->filter); + } + + /** + * @return ObjectList + */ + public function getUnacknowledgedObjects() + { + $unhandledObjects = array(); + foreach ($this as $object) { + if (! in_array((int) $object->state, array(0, 99)) && + (bool) $object->service_acknowledged === false) { + $unhandledObjects[] = $object; + } + } + return $this->newFromArray($unhandledObjects); + } +} diff --git a/modules/monitoring/library/Monitoring/Plugin/Perfdata.php b/modules/monitoring/library/Monitoring/Plugin/Perfdata.php new file mode 100644 index 0000000..b98ffcc --- /dev/null +++ b/modules/monitoring/library/Monitoring/Plugin/Perfdata.php @@ -0,0 +1,550 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Plugin; + +use Icinga\Util\Format; +use InvalidArgumentException; +use Icinga\Exception\ProgrammingError; +use Icinga\Web\Widget\Chart\InlinePie; +use Icinga\Module\Monitoring\Object\Service; +use Zend_Controller_Front; + +class Perfdata +{ + const PERFDATA_OK = 'ok'; + const PERFDATA_WARNING = 'warning'; + const PERFDATA_CRITICAL = 'critical'; + + /** + * The performance data value being parsed + * + * @var string + */ + protected $perfdataValue; + + /** + * Unit of measurement (UOM) + * + * @var string + */ + protected $unit; + + /** + * The label + * + * @var string + */ + protected $label; + + /** + * The value + * + * @var float + */ + protected $value; + + /** + * The minimum value + * + * @var float + */ + protected $minValue; + + /** + * The maximum value + * + * @var float + */ + protected $maxValue; + + /** + * The WARNING threshold + * + * @var ThresholdRange + */ + protected $warningThreshold; + + /** + * The CRITICAL threshold + * + * @var ThresholdRange + */ + protected $criticalThreshold; + + /** + * Create a new Perfdata object based on the given performance data label and value + * + * @param string $label The perfdata label + * @param string $value The perfdata value + */ + public function __construct($label, $value) + { + $this->perfdataValue = $value; + $this->label = $label; + $this->parse(); + + if ($this->unit === '%') { + if ($this->minValue === null) { + $this->minValue = 0.0; + } + if ($this->maxValue === null) { + $this->maxValue = 100.0; + } + } + + $warn = $this->warningThreshold->getMax(); + if ($warn !== null) { + $crit = $this->criticalThreshold->getMax(); + if ($crit !== null && $warn > $crit) { + $this->warningThreshold->setInverted(); + $this->criticalThreshold->setInverted(); + } + } + } + + /** + * Return a new Perfdata object based on the given performance data key=value pair + * + * @param string $perfdata The key=value pair to parse + * + * @return Perfdata + * + * @throws InvalidArgumentException In case the given performance data has no content or a invalid format + */ + public static function fromString($perfdata) + { + if (empty($perfdata)) { + throw new InvalidArgumentException('Perfdata::fromString expects a string with content'); + } elseif (strpos($perfdata, '=') === false) { + throw new InvalidArgumentException( + 'Perfdata::fromString expects a key=value formatted string. Got "' . $perfdata . '" instead' + ); + } + + list($label, $value) = explode('=', $perfdata, 2); + return new static(trim($label), trim($value)); + } + + /** + * Return whether this performance data's value is a number + * + * @return bool True in case it's a number, otherwise False + */ + public function isNumber() + { + return $this->unit === null; + } + + /** + * Return whether this performance data's value are seconds + * + * @return bool True in case it's seconds, otherwise False + */ + public function isSeconds() + { + return in_array($this->unit, array('s', 'ms', 'us')); + } + + /** + * Return whether this performance data's value is a temperature + * + * @return bool True in case it's temperature, otherwise False + */ + public function isTemperature() + { + return in_array($this->unit, array('°c', '°f')); + } + + /** + * Return whether this performance data's value is in percentage + * + * @return bool True in case it's in percentage, otherwise False + */ + public function isPercentage() + { + return $this->unit === '%'; + } + + /** + * Return whether this performance data's value is in bytes + * + * @return bool True in case it's in bytes, otherwise False + */ + public function isBytes() + { + return in_array($this->unit, array('b', 'kb', 'mb', 'gb', 'tb')); + } + + /** + * Return whether this performance data's value is a counter + * + * @return bool True in case it's a counter, otherwise False + */ + public function isCounter() + { + return $this->unit === 'c'; + } + + /** + * Returns whether it is possible to display a visual representation + * + * @return bool True when the perfdata is visualizable + */ + public function isVisualizable() + { + return isset($this->minValue) && isset($this->maxValue) && isset($this->value); + } + + /** + * Return this perfomance data's label + */ + public function getLabel() + { + return $this->label; + } + + /** + * Return the value or null if it is unknown (U) + * + * @return null|float + */ + public function getValue() + { + return $this->value; + } + + /** + * Return the unit as a string + * + * @return string + */ + public function getUnit() + { + return $this->unit; + } + + /** + * Return the value as percentage (0-100) + * + * @return null|float + */ + public function getPercentage() + { + if ($this->isPercentage()) { + return $this->value; + } + + if ($this->maxValue !== null) { + $minValue = $this->minValue !== null ? $this->minValue : 0.0; + if ($this->maxValue == $minValue) { + return null; + } + + if ($this->value > $minValue) { + return (($this->value - $minValue) / ($this->maxValue - $minValue)) * 100; + } + } + } + + /** + * Return this performance data's warning treshold + * + * @return ThresholdRange + */ + public function getWarningThreshold() + { + return $this->warningThreshold; + } + + /** + * Return this performance data's critical treshold + * + * @return ThresholdRange + */ + public function getCriticalThreshold() + { + return $this->criticalThreshold; + } + + /** + * Return the minimum value or null if it is not available + * + * @return null|string + */ + public function getMinimumValue() + { + return $this->minValue; + } + + /** + * Return the maximum value or null if it is not available + * + * @return null|float + */ + public function getMaximumValue() + { + return $this->maxValue; + } + + /** + * Return this performance data as string + * + * @return string + */ + public function __toString() + { + return $this->formatLabel(); + } + + /** + * Parse the current performance data value + * + * @todo Handle optional min/max if UOM == % + */ + protected function parse() + { + $parts = explode(';', $this->perfdataValue); + + $matches = array(); + if (preg_match('@^(-?(?:\d+)?(?:\.\d+)?)([a-zA-Z%°]{1,3})$@u', $parts[0], $matches)) { + $this->unit = strtolower($matches[2]); + $this->value = self::convert($matches[1], $this->unit); + } else { + $this->value = self::convert($parts[0]); + } + + switch (count($parts)) { + /* @noinspection PhpMissingBreakStatementInspection */ + case 5: + if ($parts[4] !== '') { + $this->maxValue = self::convert($parts[4], $this->unit); + } + /* @noinspection PhpMissingBreakStatementInspection */ + case 4: + if ($parts[3] !== '') { + $this->minValue = self::convert($parts[3], $this->unit); + } + /* @noinspection PhpMissingBreakStatementInspection */ + case 3: + $this->criticalThreshold = self::convert( + ThresholdRange::fromString(trim($parts[2])), + $this->unit + ); + // Fallthrough + case 2: + $this->warningThreshold = self::convert( + ThresholdRange::fromString(trim($parts[1])), + $this->unit + ); + } + + if ($this->warningThreshold === null) { + $this->warningThreshold = new ThresholdRange(); + } + if ($this->criticalThreshold === null) { + $this->criticalThreshold = new ThresholdRange(); + } + } + + /** + * Return the given value converted to its smallest supported representation + * + * @param string $value The value to convert + * @param string $fromUnit The unit the value currently represents + * + * @return ThresholdRange|null|float Null in case the value is not a number + */ + protected static function convert($value, $fromUnit = null) + { + if ($value instanceof ThresholdRange) { + $value = clone $value; + + $min = $value->getMin(); + if ($min !== null) { + $value->setMin(self::convert($min, $fromUnit)); + } + + $max = $value->getMax(); + if ($max !== null) { + $value->setMax(self::convert($max, $fromUnit)); + } + + return $value; + } + + if (is_numeric($value)) { + switch ($fromUnit) { + case 'us': + return $value / pow(10, 6); + case 'ms': + return $value / pow(10, 3); + case 'tb': + return floatval($value) * pow(2, 40); + case 'gb': + return floatval($value) * pow(2, 30); + case 'mb': + return floatval($value) * pow(2, 20); + case 'kb': + return floatval($value) * pow(2, 10); + default: + return (float) $value; + } + } + } + + protected function calculatePieChartData() + { + $rawValue = $this->getValue(); + $minValue = $this->getMinimumValue() !== null ? $this->getMinimumValue() : 0; + $usedValue = ($rawValue - $minValue); + + $green = $orange = $red = 0; + + if ($this->criticalThreshold->contains($rawValue)) { + if ($this->warningThreshold->contains($rawValue)) { + $green = $usedValue; + } else { + $orange = $usedValue; + } + } else { + $red = $usedValue; + } + + return array($green, $orange, $red, ($this->getMaximumValue() - $minValue) - $usedValue); + } + + + public function asInlinePie() + { + if (! $this->isVisualizable()) { + throw new ProgrammingError('Cannot calculate piechart data for unvisualizable perfdata entry.'); + } + + $data = $this->calculatePieChartData(); + $pieChart = new InlinePie($data, $this); + $pieChart->setColors(array('#44bb77', '#ffaa44', '#ff5566', '#ddccdd')); + + return $pieChart; + } + + /** + * Format the given value depending on the currently used unit + */ + protected function format($value) + { + if ($value === null) { + return null; + } + + if ($value instanceof ThresholdRange) { + if ($value->getMin()) { + return (string) $value; + } + + $max = $value->getMax(); + return $max === null ? '' : $this->format($max); + } + + if ($this->isPercentage()) { + return (string)$value . '%'; + } + if ($this->isBytes()) { + return Format::bytes($value); + } + if ($this->isSeconds()) { + return Format::seconds($value); + } + if ($this->isTemperature()) { + return (string)$value . strtoupper($this->unit); + } + return number_format($value, 2) . ($this->unit !== null ? ' ' . $this->unit : ''); + } + + /** + * Format the title string that represents this perfdata set + * + * @param bool $html + * + * @return string + */ + public function formatLabel($html = false) + { + return sprintf( + $html ? '<b>%s %s</b> (%s%%)' : '%s %s (%s%%)', + htmlspecialchars($this->getLabel()), + $this->format($this->value), + number_format($this->getPercentage() ?? 0, 2) + ); + } + + public function toArray() + { + return array( + 'label' => $this->getLabel(), + 'value' => $this->format($this->getValue()), + 'min' => isset($this->minValue) && !$this->isPercentage() + ? $this->format($this->minValue) + : '', + 'max' => isset($this->maxValue) && !$this->isPercentage() + ? $this->format($this->maxValue) + : '', + 'warn' => $this->format($this->warningThreshold), + 'crit' => $this->format($this->criticalThreshold) + ); + } + + /** + * Return the state indicated by this perfdata + * + * @see Service + * + * @return int + */ + public function getState() + { + if ($this->value === null) { + return Service::STATE_UNKNOWN; + } + + if (! $this->criticalThreshold->contains($this->value)) { + return Service::STATE_CRITICAL; + } + + if (! $this->warningThreshold->contains($this->value)) { + return Service::STATE_WARNING; + } + + return Service::STATE_OK; + } + + /** + * Return whether the state indicated by this perfdata is worse than + * the state indicated by the other perfdata + * CRITICAL > UNKNOWN > WARNING > OK + * + * @param Perfdata $rhs the other perfdata + * + * @return bool + */ + public function worseThan(Perfdata $rhs) + { + if (($state = $this->getState()) === ($rhsState = $rhs->getState())) { + return $this->getPercentage() > $rhs->getPercentage(); + } + + if ($state === Service::STATE_CRITICAL) { + return true; + } + + if ($state === Service::STATE_UNKNOWN) { + return $rhsState !== Service::STATE_CRITICAL; + } + + if ($state === Service::STATE_WARNING) { + return $rhsState === Service::STATE_OK; + } + + return false; + } +} diff --git a/modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php b/modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php new file mode 100644 index 0000000..ef1ca0c --- /dev/null +++ b/modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php @@ -0,0 +1,144 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Plugin; + +use ArrayIterator; +use IteratorAggregate; +use Traversable; + +class PerfdataSet implements IteratorAggregate +{ + /** + * The performance data being parsed + * + * @var string + */ + protected $perfdataStr; + + /** + * The current parsing position + * + * @var int + */ + protected $parserPos = 0; + + /** + * A list of Perfdata objects + * + * @var array + */ + protected $perfdata = array(); + + /** + * Create a new set of performance data + * + * @param string $perfdataStr A space separated list of label/value pairs + */ + protected function __construct($perfdataStr) + { + if ($perfdataStr && ($perfdataStr = trim($perfdataStr))) { + $this->perfdataStr = $perfdataStr; + $this->parse(); + } + } + + /** + * Return a iterator for this set of performance data + * + * @return ArrayIterator + */ + public function getIterator(): Traversable + { + return new ArrayIterator($this->asArray()); + } + + /** + * Return a new set of performance data + * + * @param string $perfdataStr A space separated list of label/value pairs + * + * @return PerfdataSet + */ + public static function fromString($perfdataStr) + { + return new static($perfdataStr); + } + + /** + * Return this set of performance data as array + * + * @return array + */ + public function asArray() + { + return $this->perfdata; + } + + /** + * Parse the current performance data + */ + protected function parse() + { + while ($this->parserPos < strlen($this->perfdataStr)) { + $label = trim($this->readLabel()); + $value = trim($this->readUntil(' ')); + + if ($label) { + $this->perfdata[] = new Perfdata($label, $value); + } + } + } + + /** + * Return the next label found in the performance data + * + * @return string The label found + */ + protected function readLabel() + { + $this->skipSpaces(); + if (in_array($this->perfdataStr[$this->parserPos], array('"', "'"))) { + $quoteChar = $this->perfdataStr[$this->parserPos++]; + $label = $this->readUntil('='); + $this->parserPos++; + + if (($closingPos = strpos($label, $quoteChar)) > 0) { + $label = substr($label, 0, $closingPos); + } + } else { + $label = $this->readUntil('='); + $this->parserPos++; + } + + $this->skipSpaces(); + return $label; + } + + /** + * Return all characters between the current parser position and the given character + * + * @param string $stopChar The character on which to stop + * + * @return string + */ + protected function readUntil($stopChar) + { + $start = $this->parserPos; + while ($this->parserPos < strlen($this->perfdataStr) && $this->perfdataStr[$this->parserPos] !== $stopChar) { + $this->parserPos++; + } + + return substr($this->perfdataStr, $start, $this->parserPos - $start); + } + + /** + * Advance the parser position to the next non-whitespace character + */ + protected function skipSpaces() + { + while ($this->parserPos < strlen($this->perfdataStr) && $this->perfdataStr[$this->parserPos] === ' ') { + $this->parserPos++; + } + } +} diff --git a/modules/monitoring/library/Monitoring/Plugin/ThresholdRange.php b/modules/monitoring/library/Monitoring/Plugin/ThresholdRange.php new file mode 100644 index 0000000..bd27b8b --- /dev/null +++ b/modules/monitoring/library/Monitoring/Plugin/ThresholdRange.php @@ -0,0 +1,179 @@ +<?php +/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Plugin; + +/** + * The warning/critical threshold of a measured value + */ +class ThresholdRange +{ + /** + * The smallest value inside the range (null stands for -∞) + * + * @var float|null + */ + protected $min; + + /** + * The biggest value inside the range (null stands for ∞) + * + * @var float|null + */ + protected $max; + + /** + * Whether to invert the result of contains() + * + * @var bool + */ + protected $inverted = false; + + /** + * The unmodified range as passed to fromString() + * + * @var string + */ + protected $raw; + + /** + * Create a new instance based on a threshold range conforming to <https://nagios-plugins.org/doc/guidelines.html> + * + * @param string $rawRange + * + * @return ThresholdRange + */ + public static function fromString($rawRange) + { + $range = new static(); + $range->raw = $rawRange; + + if ($rawRange == '') { + return $range; + } + + $rawRange = ltrim($rawRange); + if (substr($rawRange, 0, 1) === '@') { + $range->setInverted(); + $rawRange = substr($rawRange, 1); + } + + if (strpos($rawRange, ':') === false) { + $min = 0.0; + $max = floatval(trim($rawRange)); + } else { + list($min, $max) = explode(':', $rawRange, 2); + $min = trim($min); + $max = trim($max); + + switch ($min) { + case '': + $min = 0.0; + break; + case '~': + $min = null; + break; + default: + $min = floatval($min); + } + + $max = empty($max) ? null : floatval($max); + } + + return $range->setMin($min) + ->setMax($max); + } + + /** + * Set the smallest value inside the range (null stands for -∞) + * + * @param float|null $min + * + * @return $this + */ + public function setMin($min) + { + $this->min = $min; + return $this; + } + + /** + * Get the smallest value inside the range (null stands for -∞) + * + * @return float|null + */ + public function getMin() + { + return $this->min; + } + + /** + * Set the biggest value inside the range (null stands for ∞) + * + * @param float|null $max + * + * @return $this + */ + public function setMax($max) + { + $this->max = $max; + return $this; + } + + /** + * Get the biggest value inside the range (null stands for ∞) + * + * @return float|null + */ + public function getMax() + { + return $this->max; + } + + /** + * Set whether to invert the result of contains() + * + * @param bool $inverted + * + * @return $this + */ + public function setInverted($inverted = true) + { + $this->inverted = $inverted; + return $this; + } + + /** + * Get whether to invert the result of contains() + * + * @return bool + */ + public function isInverted() + { + return $this->inverted; + } + + /** + * Return whether $value is inside $this + * + * @param float $value + * + * @return bool + */ + public function contains($value) + { + return (bool) ($this->inverted ^ ( + ($this->min === null || $this->min <= $value) && ($this->max === null || $this->max >= $value) + )); + } + + /** + * Return the textual representation of $this, suitable for fromString() + * + * @return string + */ + public function __toString() + { + return (string) $this->raw; + } +} diff --git a/modules/monitoring/library/Monitoring/ProvidedHook/ApplicationState.php b/modules/monitoring/library/Monitoring/ProvidedHook/ApplicationState.php new file mode 100644 index 0000000..4e2e61c --- /dev/null +++ b/modules/monitoring/library/Monitoring/ProvidedHook/ApplicationState.php @@ -0,0 +1,32 @@ +<?php +/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\ProvidedHook; + +use Icinga\Application\Hook\ApplicationStateHook; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; + +class ApplicationState extends ApplicationStateHook +{ + public function collectMessages() + { + $backend = MonitoringBackend::instance(); + + $programStatus = $backend + ->select() + ->from( + 'programstatus', + ['is_currently_running', 'status_update_time'] + ) + ->fetchRow(); + + if ($programStatus === false || ! (bool) $programStatus->is_currently_running) { + $message = sprintf( + mt('monitoring', "Monitoring backend '%s' is not running."), + $backend->getName() + ); + + $this->addError('monitoring/backend-down', $programStatus->status_update_time, $message); + } + } +} diff --git a/modules/monitoring/library/Monitoring/ProvidedHook/Health.php b/modules/monitoring/library/Monitoring/ProvidedHook/Health.php new file mode 100644 index 0000000..8f9c893 --- /dev/null +++ b/modules/monitoring/library/Monitoring/ProvidedHook/Health.php @@ -0,0 +1,102 @@ +<?php +/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +namespace Icinga\Module\Monitoring\ProvidedHook; + +use Icinga\Application\Hook\HealthHook; +use Icinga\Date\DateFormatter; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use ipl\Web\Url; + +class Health extends HealthHook +{ + /** @var object */ + protected $programStatus; + + public function getName() + { + return 'Icinga'; + } + + public function getUrl() + { + return Url::fromPath('monitoring/health/info'); + } + + public function checkHealth() + { + $backendName = MonitoringBackend::instance()->getName(); + $programStatus = $this->getProgramStatus(); + if ($programStatus === false) { + $this->setState(self::STATE_UNKNOWN); + $this->setMessage(sprintf(t('%s is currently not up and running'), $backendName)); + return; + } + + if ($programStatus->is_currently_running) { + $this->setState(self::STATE_OK); + $this->setMessage(sprintf( + t( + '%1$s has been up and running with PID %2$d %3$s', + 'Last format parameter represents the time running' + ), + $backendName, + $programStatus->process_id, + DateFormatter::timeSince($programStatus->program_start_time) + )); + + $warningMessages = []; + + if (! $programStatus->active_host_checks_enabled) { + $this->setState(self::STATE_WARNING); + $warningMessages[] = t('Active host checks are disabled'); + } + + if (! $programStatus->active_service_checks_enabled) { + $this->setState(self::STATE_WARNING); + $warningMessages[] = t('Active service checks are disabled'); + } + + if (! $programStatus->notifications_enabled) { + $this->setState(self::STATE_WARNING); + $warningMessages[] = t('Notifications are disabled'); + } + + if ($this->getState() === self::STATE_WARNING) { + $this->setMessage(implode("; ", $warningMessages)); + } + } else { + $this->setState(self::STATE_CRITICAL); + $this->setMessage(sprintf(t('Backend %s is not running'), $backendName)); + } + + $this->setMetrics((array) $programStatus); + } + + protected function getProgramStatus() + { + if ($this->programStatus === null) { + $this->programStatus = MonitoringBackend::instance()->select() + ->from('programstatus', [ + 'program_version', + 'status_update_time', + 'program_start_time', + 'program_end_time', + 'endpoint_name', + 'is_currently_running', + 'process_id', + 'last_command_check', + 'last_log_rotation', + 'notifications_enabled', + 'active_service_checks_enabled', + 'active_host_checks_enabled', + 'event_handlers_enabled', + 'flap_detection_enabled', + 'process_performance_data' + ]) + ->fetchRow(); + } + + return $this->programStatus; + } +} diff --git a/modules/monitoring/library/Monitoring/ProvidedHook/X509/Sni.php b/modules/monitoring/library/Monitoring/ProvidedHook/X509/Sni.php new file mode 100644 index 0000000..c649437 --- /dev/null +++ b/modules/monitoring/library/Monitoring/ProvidedHook/X509/Sni.php @@ -0,0 +1,37 @@ +<?php +/* Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */ + +namespace Icinga\Module\Monitoring\ProvidedHook\X509; + +use Icinga\Data\Filter\Filter; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use Icinga\Module\X509\Hook\SniHook; + +class Sni extends SniHook +{ + public function getHosts(Filter $filter = null) + { + MonitoringBackend::clearInstances(); + + $hosts = MonitoringBackend::instance() + ->select() + ->from('hoststatus', [ + 'host_name', + 'host_address', + 'host_address6' + ]); + if ($filter !== null) { + $hosts->applyFilter($filter); + } + + foreach ($hosts as $host) { + if (! empty($host->host_address)) { + yield $host->host_address => $host->host_name; + } + + if (! empty($host->host_address6)) { + yield $host->host_address6 => $host->host_name; + } + } + } +} diff --git a/modules/monitoring/library/Monitoring/SecurityStep.php b/modules/monitoring/library/Monitoring/SecurityStep.php new file mode 100644 index 0000000..94053b3 --- /dev/null +++ b/modules/monitoring/library/Monitoring/SecurityStep.php @@ -0,0 +1,84 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring; + +use Exception; +use Icinga\Module\Setup\Step; +use Icinga\Application\Config; +use Icinga\Exception\IcingaException; + +class SecurityStep extends Step +{ + protected $data; + + protected $error; + + public function __construct(array $data) + { + $this->data = $data; + } + + public function apply() + { + $config = array(); + $config['security'] = $this->data['securityConfig']; + + try { + Config::fromArray($config) + ->setConfigFile(Config::resolvePath('modules/monitoring/config.ini')) + ->saveIni(); + } catch (Exception $e) { + $this->error = $e; + return false; + } + + $this->error = false; + return true; + } + + public function getSummary() + { + $pageTitle = '<h2>' . mt('monitoring', 'Monitoring Security', 'setup.page.title') . '</h2>'; + $pageDescription = '<p>' . mt( + 'monitoring', + 'Icinga Web 2 will protect your monitoring environment against' + . ' prying eyes using the configuration specified below:' + ) . '</p>'; + + $pageHtml = '' + . '<table>' + . '<tbody>' + . '<tr>' + . '<td><strong>' . mt('monitoring', 'Protected Custom Variables') . '</strong></td>' + . '<td>' . ($this->data['securityConfig']['protected_customvars'] ? ( + $this->data['securityConfig']['protected_customvars'] + ) : mt('monitoring', 'None', 'monitoring.protected_customvars')) . '</td>' + . '</tr>' + . '</tbody>' + . '</table>'; + + return $pageTitle . '<div class="topic">' . $pageDescription . $pageHtml . '</div>'; + } + + public function getReport() + { + if ($this->error === false) { + return array(sprintf( + mt('monitoring', 'Monitoring security configuration has been successfully created: %s'), + Config::resolvePath('modules/monitoring/config.ini') + )); + } elseif ($this->error !== null) { + return array( + sprintf( + mt( + 'monitoring', + 'Monitoring security configuration could not be written to: %s. An error occured:' + ), + Config::resolvePath('modules/monitoring/config.ini') + ), + sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->error)) + ); + } + } +} diff --git a/modules/monitoring/library/Monitoring/Timeline/TimeEntry.php b/modules/monitoring/library/Monitoring/Timeline/TimeEntry.php new file mode 100644 index 0000000..ee313b3 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Timeline/TimeEntry.php @@ -0,0 +1,233 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Timeline; + +use DateTime; +use Icinga\Web\Url; +use Icinga\Exception\ProgrammingError; + +/** + * An event group that is part of a timeline + */ +class TimeEntry +{ + /** + * The name of this group + * + * @var string + */ + protected $name; + + /** + * The amount of events that are part of this group + * + * @var int + */ + protected $value; + + /** + * The date and time of this group + * + * @var DateTime + */ + protected $dateTime; + + /** + * The url to this group's detail view + * + * @var Url + */ + protected $detailUrl; + + /** + * The weight of this group + * + * @var float + */ + protected $weight = 1.0; + + /** + * The label of this group + * + * @var string + */ + protected $label; + + /** + * The CSS class of the entry + * + * @var string + */ + protected $class; + + /** + * Return a new TimeEntry object with the given attributes being set + * + * @param array $attributes The attributes to set + * @return TimeEntry The resulting TimeEntry object + * @throws ProgrammingError If one of the given attributes cannot be set + */ + public static function fromArray(array $attributes) + { + $entry = new TimeEntry(); + + foreach ($attributes as $name => $value) { + $methodName = 'set' . ucfirst($name); + if (method_exists($entry, $methodName)) { + $entry->{$methodName}($value); + } else { + throw new ProgrammingError( + 'Method "%s" does not exist on object of type "%s"', + $methodName, + __CLASS__ + ); + } + } + + return $entry; + } + + /** + * Set this group's name + * + * @param string $name The name to set + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Return the name of this group + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set this group's amount of events + * + * @param int $value The value to set + */ + public function setValue($value) + { + $this->value = intval($value); + } + + /** + * Return the amount of events in this group + * + * @return int + */ + public function getValue() + { + return $this->value; + } + + /** + * Set this group's date and time + * + * @param DateTime $dateTime The date and time to set + */ + public function setDateTime(DateTime $dateTime) + { + $this->dateTime = $dateTime; + } + + /** + * Return the date and time of this group + * + * @return DateTime + */ + public function getDateTime() + { + return $this->dateTime; + } + + /** + * Set the url to this group's detail view + * + * @param Url $detailUrl The url to set + */ + public function setDetailUrl(Url $detailUrl) + { + $this->detailUrl = $detailUrl; + } + + /** + * Return the url to this group's detail view + * + * @return Url + */ + public function getDetailUrl() + { + return $this->detailUrl; + } + + /** + * Set this group's weight + * + * @param float $weight The weight for this group + */ + public function setWeight($weight) + { + $this->weight = floatval($weight); + } + + /** + * Return the weight of this group + * + * @return float + */ + public function getWeight() + { + return $this->weight; + } + + /** + * Set this group's label + * + * @param string $label The label to set + */ + public function setLabel($label) + { + $this->label = $label; + } + + /** + * Return the label of this group + * + * @return string + */ + public function getLabel() + { + return $this->label; + } + + /** + * Get the CSS class + * + * @return string + */ + public function getClass() + { + return $this->class; + } + + /** + * Set the CSS class + * + * @param string $class + * + * @return $this + */ + public function setClass($class) + { + $this->class = $class; + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Timeline/TimeLine.php b/modules/monitoring/library/Monitoring/Timeline/TimeLine.php new file mode 100644 index 0000000..7a192a6 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Timeline/TimeLine.php @@ -0,0 +1,491 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Timeline; + +use DateTime; +use Exception; +use ArrayIterator; +use Icinga\Exception\IcingaException; +use IteratorAggregate; +use Icinga\Data\Filter\Filter; +use Icinga\Web\Hook; +use Icinga\Web\Session\SessionNamespace; +use Icinga\Module\Monitoring\DataView\DataView; +use Traversable; + +/** + * Represents a set of events in a specific range of time + */ +class TimeLine implements IteratorAggregate +{ + /** + * The resultset returned by the dataview + * + * @var array + */ + private $resultset; + + /** + * The groups this timeline uses for display purposes + * + * @var array + */ + private $displayGroups; + + /** + * The session to use + * + * @var SessionNamespace + */ + protected $session; + + /** + * The base that is used to calculate each circle's diameter + * + * @var float + */ + protected $calculationBase; + + /** + * The dataview to fetch entries from + * + * @var DataView + */ + protected $dataview; + + /** + * The names by which to group entries + * + * @var array + */ + protected $identifiers; + + /** + * The range of time for which to display entries + * + * @var TimeRange + */ + protected $displayRange; + + /** + * The range of time for which to calculate forecasts + * + * @var TimeRange + */ + protected $forecastRange; + + /** + * The maximum diameter each circle can have + * + * @var float + */ + protected $circleDiameter = 100.0; + + /** + * The minimum diameter each circle can have + * + * @var float + */ + protected $minCircleDiameter = 1.0; + + /** + * The unit of a circle's diameter + * + * @var string + */ + protected $diameterUnit = 'px'; + + /** + * Return a iterator for this timeline + * + * @return ArrayIterator + */ + public function getIterator(): Traversable + { + return new ArrayIterator($this->toArray()); + } + + /** + * Create a new timeline + * + * The given dataview must provide the following columns: + * - name A string identifying an entry (Corresponds to the keys of "$identifiers") + * - time A unix timestamp that defines where to place an entry on the timeline + * + * @param DataView $dataview The dataview to fetch entries from + * @param array $identifiers The names by which to group entries + */ + public function __construct(DataView $dataview, array $identifiers) + { + $this->dataview = $dataview; + $this->identifiers = $identifiers; + } + + /** + * Set the session to use + * + * @param SessionNamespace $session The session to use + */ + public function setSession(SessionNamespace $session) + { + $this->session = $session; + } + + /** + * Set the range of time for which to display elements + * + * @param TimeRange $range The range of time for which to display elements + */ + public function setDisplayRange(TimeRange $range) + { + $this->displayRange = $range; + } + + /** + * Set the range of time for which to calculate forecasts + * + * @param TimeRange $range The range of time for which to calculate forecasts + */ + public function setForecastRange(TimeRange $range) + { + $this->forecastRange = $range; + } + + /** + * Set the maximum diameter each circle can have + * + * @param string $width The diameter to set, suffixed with its unit + * + * @throws Exception If the given diameter is invalid + */ + public function setMaximumCircleWidth($width) + { + $matches = array(); + if (preg_match('#([\d|\.]+)([a-z]+|%)#', $width, $matches)) { + $this->circleDiameter = floatval($matches[1]); + $this->diameterUnit = $matches[2]; + } else { + throw new IcingaException( + 'Width "%s" is not a valid width', + $width + ); + } + } + + /** + * Set the minimum diameter each circle can have + * + * @param string $width The diameter to set, suffixed with its unit + * + * @throws Exception If the given diameter is invalid or its unit differs from the maximum + */ + public function setMinimumCircleWidth($width) + { + $matches = array(); + if (preg_match('#([\d|\.]+)([a-z]+|%)#', $width, $matches)) { + if ($matches[2] === $this->diameterUnit) { + $this->minCircleDiameter = floatval($matches[1]); + } else { + throw new IcingaException( + 'Unit needs to be in "%s"', + $this->diameterUnit + ); + } + } else { + throw new IcingaException( + 'Width "%s" is not a valid width', + $width + ); + } + } + + /** + * Return all known group types (identifiers) with their respective labels and classess as array + * + * @return array + */ + public function getGroupInfo() + { + $groupInfo = array(); + foreach ($this->identifiers as $name => $attributes) { + if (isset($attributes['groupBy'])) { + $name = $attributes['groupBy']; + } + + $groupInfo[$name]['class'] = $attributes['class']; + $groupInfo[$name]['label'] = $attributes['label']; + } + + return $groupInfo; + } + + /** + * Return the circle's diameter for the given event group + * + * @param TimeEntry $group The group for which to return a circle width + * @param int $precision Amount of decimal places to preserve + * + * @return string + */ + public function calculateCircleWidth(TimeEntry $group, $precision = 0) + { + $base = $this->getCalculationBase(true); + $factor = log($group->getValue() * $group->getWeight(), $base) / 100; + $width = $this->circleDiameter * $factor; + return sprintf( + '%.' . $precision . 'F%s', + $width > $this->minCircleDiameter ? $width : $this->minCircleDiameter, + $this->diameterUnit + ); + } + + /** + * Return an extrapolated circle width for the given event group + * + * @param TimeEntry $group The event group for which to return an extrapolated circle width + * @param int $precision Amount of decimal places to preserve + * + * @return string + */ + public function getExtrapolatedCircleWidth(TimeEntry $group, $precision = 0) + { + $eventCount = 0; + foreach ($this->displayGroups as $groups) { + if (array_key_exists($group->getName(), $groups)) { + $eventCount += $groups[$group->getName()]->getValue(); + } + } + + $extrapolatedCount = (int) $eventCount / count($this->displayGroups); + if ($extrapolatedCount < $group->getValue()) { + return $this->calculateCircleWidth($group, $precision); + } + + return $this->calculateCircleWidth( + TimeEntry::fromArray( + array( + 'value' => $extrapolatedCount, + 'weight' => $group->getWeight() + ) + ), + $precision + ); + } + + /** + * Return the base that should be used to calculate circle widths + * + * @param bool $create Whether to generate a new base if none is known yet + * + * @return float|null + */ + public function getCalculationBase($create) + { + if ($this->calculationBase === null) { + $calculationBase = $this->session !== null ? $this->session->get('calculationBase') : null; + + if ($create) { + $new = $this->generateCalculationBase(); + if ($new > $calculationBase) { + $this->calculationBase = $new; + + if ($this->session !== null) { + $this->session->calculationBase = $new; + } + } else { + $this->calculationBase = $calculationBase; + } + } else { + return $calculationBase; + } + } + + return $this->calculationBase; + } + + /** + * Generate a new base to calculate circle widths with + * + * @return float + */ + protected function generateCalculationBase() + { + $allEntries = $this->groupEntries( + array_merge( + $this->fetchEntries(), + $this->fetchForecasts() + ), + new TimeRange( + $this->displayRange->getStart(), + $this->forecastRange->getEnd(), + $this->displayRange->getInterval() + ) + ); + + $highestValue = 0; + foreach ($allEntries as $groups) { + foreach ($groups as $group) { + if ($group->getValue() * $group->getWeight() > $highestValue) { + $highestValue = $group->getValue() * $group->getWeight(); + } + } + } + + return pow($highestValue, 1 / 100); // 100 == 100% + } + + /** + * Fetch all entries and forecasts by using the dataview associated with this timeline + * + * @return array The dataview's result + */ + private function fetchResults() + { + $hookResults = array(); + foreach (Hook::all('timeline') as $timelineProvider) { + $hookResults = array_merge( + $hookResults, + $timelineProvider->fetchEntries($this->displayRange), + $timelineProvider->fetchForecasts($this->forecastRange) + ); + + foreach ($timelineProvider->getIdentifiers() as $identifier => $attributes) { + if (!array_key_exists($identifier, $this->identifiers)) { + $this->identifiers[$identifier] = $attributes; + } + } + } + + $query = $this->dataview; + $filter = Filter::matchAll( + Filter::where('type', array_keys($this->identifiers)), + Filter::expression('timestamp', '<=', $this->displayRange->getStart()->getTimestamp()), + Filter::expression('timestamp', '>', $this->displayRange->getEnd()->getTimestamp()) + ); + $query->applyFilter($filter); + return array_merge($query->getQuery()->fetchAll(), $hookResults); + } + + /** + * Fetch all entries + * + * @return array The entries to display on the timeline + */ + protected function fetchEntries() + { + if ($this->resultset === null) { + $this->resultset = $this->fetchResults(); + } + + $range = $this->displayRange; + return array_filter( + $this->resultset, + function ($e) use ($range) { + return $range->validateTime((int) $e->time); + } + ); + } + + /** + * Fetch all forecasts + * + * @return array The entries to calculate forecasts with + */ + protected function fetchForecasts() + { + if ($this->resultset === null) { + $this->resultset = $this->fetchResults(); + } + + $range = $this->forecastRange; + return array_filter( + $this->resultset, + function ($e) use ($range) { + return $range->validateTime($e->time); + } + ); + } + + /** + * Return the given entries grouped together + * + * @param array $entries The entries to group + * @param TimeRange $timeRange The range of time to group by + * + * @return array displayGroups The grouped entries + */ + protected function groupEntries(array $entries, TimeRange $timeRange) + { + $counts = array(); + foreach ($entries as $entry) { + $entryTime = new DateTime(); + $entryTime->setTimestamp($entry->time); + $timestamp = $timeRange->findTimeframe($entryTime, true); + + if ($timestamp !== null) { + if (array_key_exists($entry->name, $counts)) { + if (array_key_exists($timestamp, $counts[$entry->name])) { + $counts[$entry->name][$timestamp] += 1; + } else { + $counts[$entry->name][$timestamp] = 1; + } + } else { + $counts[$entry->name][$timestamp] = 1; + } + } + } + + $groups = array(); + foreach ($counts as $name => $data) { + foreach ($data as $timestamp => $count) { + $dateTime = new DateTime(); + $dateTime->setTimestamp($timestamp); + + $groupName = $name; + if (isset($this->identifiers[$name]['groupBy'])) { + $groupName = $this->identifiers[$name]['groupBy']; + } + + if (isset($groups[$timestamp][$groupName])) { + $groups[$timestamp][$groupName]->setValue( + $groups[$timestamp][$groupName]->getValue() + $count + ); + } else { + $groups[$timestamp][$groupName] = TimeEntry::fromArray( + array( + 'name' => $groupName, + 'value' => $count, + 'dateTime' => $dateTime, + 'class' => $this->identifiers[$name]['class'], + 'detailUrl' => $this->identifiers[$name]['detailUrl'], + 'label' => $this->identifiers[$name]['label'] + ) + ); + } + } + } + + return $groups; + } + + /** + * Return the contents of this timeline as array + * + * @return array + */ + protected function toArray() + { + $this->displayGroups = $this->groupEntries($this->fetchEntries(), $this->displayRange); + + $array = array(); + foreach ($this->displayRange as $timestamp => $timeframe) { + $array[] = array( + $timeframe, + array_key_exists($timestamp, $this->displayGroups) ? $this->displayGroups[$timestamp] : array() + ); + } + + return $array; + } +} diff --git a/modules/monitoring/library/Monitoring/Timeline/TimeRange.php b/modules/monitoring/library/Monitoring/Timeline/TimeRange.php new file mode 100644 index 0000000..aa63d3c --- /dev/null +++ b/modules/monitoring/library/Monitoring/Timeline/TimeRange.php @@ -0,0 +1,258 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Timeline; + +use stdClass; +use Iterator; +use DateTime; +use DateInterval; +use Icinga\Util\Format; + +/** + * A range of time split into a specific interval + * + * @see Iterator + */ +class TimeRange implements Iterator +{ + /** + * The start of this time range + * + * @var DateTime + */ + protected $start; + + /** + * The end of this time range + * + * @var DateTime + */ + protected $end; + + /** + * The interval by which this time range is split + * + * @var DateInterval + */ + protected $interval; + + /** + * The current date in the iteration + * + * @var DateTime + */ + protected $current; + + /** + * Whether the date iteration is negative + * + * @var bool + */ + protected $negative; + + /** + * Initialize a new time range + * + * @param DateTime $start When the time range should start + * @param DateTime $end When the time range should end + * @param DateInterval $interval The interval of the time range + */ + public function __construct(DateTime $start, DateTime $end, DateInterval $interval) + { + $this->interval = $interval; + $this->start = $start; + $this->end = $end; + $this->negative = $this->start > $this->end; + } + + /** + * Return when this range of time starts + * + * @return DateTime + */ + public function getStart() + { + return $this->start; + } + + /** + * Return when this range of time ends + * + * @return DateTime + */ + public function getEnd() + { + return $this->end; + } + + /** + * Return the interval by which this time range is split + * + * @return DateInterval + */ + public function getInterval() + { + return $this->interval; + } + + /** + * Return the appropriate timeframe for the given date and time or null if none could be found + * + * @param DateTime $dateTime The date and time for which to search the timeframe + * @param bool $asTimestamp Whether the start of the timeframe should be returned as timestamp + * @return stdClass|int|null An object with a ´start´ and ´end´ property or a timestamp + */ + public function findTimeframe(DateTime $dateTime, $asTimestamp = false) + { + foreach ($this as $timeframeIdentifier => $timeframe) { + if ($this->negative) { + if ($dateTime <= $timeframe->start && $dateTime >= $timeframe->end) { + return $asTimestamp ? $timeframeIdentifier : $timeframe; + } + } elseif ($dateTime >= $timeframe->start && $dateTime <= $timeframe->end) { + return $asTimestamp ? $timeframeIdentifier : $timeframe; + } + } + } + + /** + * Return whether the given time is within this range of time + * + * @param string|int|DateTime $time The timestamp or date and time to check + */ + public function validateTime($time) + { + if ($time instanceof DateTime) { + $dateTime = $time; + } elseif (is_string($time)) { + $dateTime = DateTime::createFromFormat('d/m/Y g:i A', $time); + } else { + $dateTime = new DateTime(); + $dateTime->setTimestamp($time); + } + + return ($this->negative && ($dateTime <= $this->start && $dateTime >= $this->end)) || + (!$this->negative && ($dateTime >= $this->start && $dateTime <= $this->end)); + } + + /** + * Return the appropriate timeframe for the given timeframe start + * + * @param int|DateTime $time The timestamp or date and time for which to return the timeframe + * @return stdClass An object with a ´start´ and ´end´ property + */ + public function getTimeframe($time) + { + if ($time instanceof DateTime) { + $startTime = clone $time; + } else { + $startTime = new DateTime(); + $startTime->setTimestamp($time); + } + + return $this->buildTimeframe($startTime, $this->applyInterval(clone $startTime, 1)); + } + + /** + * Apply the current interval to the given date and time + * + * @param DateTime $dateTime The date and time to apply the interval to + * @param int $adjustBy By how much seconds the resulting date and time should be adjusted + * + * @return DateTime + */ + protected function applyInterval(DateTime $dateTime, $adjustBy) + { + if (!$this->interval->y && !$this->interval->m) { + if ($this->negative) { + return $dateTime->sub($this->interval)->add(new DateInterval('PT' . $adjustBy . 'S')); + } else { + return $dateTime->add($this->interval)->sub(new DateInterval('PT' . $adjustBy . 'S')); + } + } elseif ($this->interval->m) { + for ($i = 0; $i < $this->interval->m; $i++) { + if ($this->negative) { + $dateTime->sub(new DateInterval('PT' . Format::secondsByMonth($dateTime) . 'S')); + } else { + $dateTime->add(new DateInterval('PT' . Format::secondsByMonth($dateTime) . 'S')); + } + } + } elseif ($this->interval->y) { + for ($i = 0; $i < $this->interval->y; $i++) { + if ($this->negative) { + $dateTime->sub(new DateInterval('PT' . Format::secondsByYear($dateTime) . 'S')); + } else { + $dateTime->add(new DateInterval('PT' . Format::secondsByYear($dateTime) . 'S')); + } + } + } + $adjustment = new DateInterval('PT' . $adjustBy . 'S'); + return $this->negative ? $dateTime->add($adjustment) : $dateTime->sub($adjustment); + } + + /** + * Return an object representation of the given timeframe + * + * @param DateTime $start The start of the timeframe + * @param DateTime $end The end of the timeframe + * @return stdClass + */ + protected function buildTimeframe(DateTime $start, DateTime $end) + { + $timeframe = new stdClass(); + $timeframe->start = $start; + $timeframe->end = $end; + return $timeframe; + } + + /** + * Reset the iterator to its initial state + */ + public function rewind(): void + { + $this->current = clone $this->start; + } + + /** + * Return whether the current iteration step is valid + * + * @return bool + */ + public function valid(): bool + { + if ($this->negative) { + return $this->current > $this->end; + } else { + return $this->current < $this->end; + } + } + + /** + * Return the current value in the iteration + * + * @return stdClass + */ + public function current(): object + { + return $this->getTimeframe($this->current); + } + + /** + * Return a unique identifier for the current value in the iteration + * + * @return int + */ + public function key(): int + { + return $this->current->getTimestamp(); + } + + /** + * Advance the iterator position by one + */ + public function next(): void + { + $this->applyInterval($this->current, 0); + } +} diff --git a/modules/monitoring/library/Monitoring/TransportStep.php b/modules/monitoring/library/Monitoring/TransportStep.php new file mode 100644 index 0000000..d138eb4 --- /dev/null +++ b/modules/monitoring/library/Monitoring/TransportStep.php @@ -0,0 +1,143 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring; + +use Exception; +use Icinga\Exception\ProgrammingError; +use Icinga\Module\Setup\Step; +use Icinga\Application\Config; +use Icinga\Exception\IcingaException; + +class TransportStep extends Step +{ + protected $data; + + protected $error; + + public function __construct(array $data) + { + $this->data = $data; + } + + public function apply() + { + $transportConfig = $this->data['transportConfig']; + $transportName = $transportConfig['name']; + unset($transportConfig['name']); + + try { + Config::fromArray(array($transportName => $transportConfig)) + ->setConfigFile(Config::resolvePath('modules/monitoring/commandtransports.ini')) + ->saveIni(); + } catch (Exception $e) { + $this->error = $e; + return false; + } + + $this->error = false; + return true; + } + + public function getSummary() + { + switch ($this->data['transportConfig']['transport']) { + case 'local': + $details = '<p>' . sprintf( + mt( + 'monitoring', + 'Icinga Web 2 will use the named pipe located at "%s"' + . ' to send commands to your monitoring instance.' + ), + $this->data['transportConfig']['path'] + ) . '</p>'; + break; + case 'remote': + $details = '<p>' + . sprintf( + mt( + 'monitoring', + 'Icinga Web 2 will use the named pipe located on a remote machine at "%s" to send commands' + . ' to your monitoring instance by using the connection details listed below:' + ), + $this->data['transportConfig']['path'] + ) + . '</p>' + . '<table>' + . '<tbody>' + . '<tr>' + . '<td><strong>' . mt('monitoring', 'Remote Host') . '</strong></td>' + . '<td>' . $this->data['transportConfig']['host'] . '</td>' + . '</tr>' + . '<tr>' + . '<td><strong>' . mt('monitoring', 'Remote SSH Port') . '</strong></td>' + . '<td>' . $this->data['transportConfig']['port'] . '</td>' + . '</tr>' + . '<tr>' + . '<td><strong>' . mt('monitoring', 'Remote SSH User') . '</strong></td>' + . '<td>' . $this->data['transportConfig']['user'] . '</td>' + . '</tr>' + . '</tbody>' + . '</table>'; + break; + case 'api': + $details = '<p>' + . mt( + 'monitoring', + 'Icinga Web 2 will use the Icinga 2 API to send commands' + . ' to your monitoring instance by using the connection details listed below:' + ) + . '</p>' + . '<table>' + . '<tbody>' + . '<tr>' + . '<td><strong>' . mt('monitoring', 'Host') . '</strong></td>' + . '<td>' . $this->data['transportConfig']['host'] . '</td>' + . '</tr>' + . '<tr>' + . '<td><strong>' . mt('monitoring', 'Port') . '</strong></td>' + . '<td>' . $this->data['transportConfig']['port'] . '</td>' + . '</tr>' + . '<tr>' + . '<td><strong>' . mt('monitoring', 'Username') . '</strong></td>' + . '<td>' . $this->data['transportConfig']['username'] . '</td>' + . '</tr>' + . '<tr>' + . '<td><strong>' . mt('monitoring', 'Password') . '</strong></td>' + . '<td>' . str_repeat('*', strlen($this->data['transportConfig']['password'])) . '</td>' + . '</tr>' + . '</tbody>' + . '</table>'; + break; + default: + throw new ProgrammingError( + 'Unknown command transport type: %s', + $this->data['transportConfig']['transport'] + ); + } + + return '<h2>' . mt('monitoring', 'Command Transport', 'setup.page.title') . '</h2>' + . '<div class="topic">' . $details . '</div>'; + } + + public function getReport() + { + if ($this->error === false) { + return array(sprintf( + mt('monitoring', 'Command transport configuration has been successfully created: %s'), + Config::resolvePath('modules/monitoring/commandtransports.ini') + )); + } elseif ($this->error !== null) { + return array( + sprintf( + mt( + 'monitoring', + 'Command transport configuration could not be written to: %s. An error occured:' + ), + Config::resolvePath('modules/monitoring/commandtransports.ini') + ), + sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->error)) + ); + } + } +} diff --git a/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php new file mode 100644 index 0000000..b001ca8 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php @@ -0,0 +1,339 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Web\Controller; + +use Exception; +use Icinga\Module\Monitoring\Controller; +use Icinga\Module\Monitoring\Forms\Command\Object\CheckNowCommandForm; +use Icinga\Module\Monitoring\Forms\Command\Object\DeleteCommentCommandForm; +use Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimeCommandForm; +use Icinga\Module\Monitoring\Forms\Command\Object\ObjectsCommandForm; +use Icinga\Module\Monitoring\Forms\Command\Object\RemoveAcknowledgementCommandForm; +use Icinga\Module\Monitoring\Forms\Command\Object\ToggleObjectFeaturesCommandForm; +use Icinga\Module\Monitoring\Hook\DetailviewExtensionHook; +use Icinga\Module\Monitoring\Hook\ObjectDetailsTabHook; +use Icinga\Module\Monitoring\Object\Service; +use Icinga\Web\Hook; +use Icinga\Web\Url; +use Icinga\Web\Widget\Tabextension\DashboardAction; +use Icinga\Web\Widget\Tabextension\MenuAction; + +/** + * Base class for the host and service controller + */ +abstract class MonitoredObjectController extends Controller +{ + /** + * The requested host or service + * + * @var \Icinga\Module\Monitoring\Object\Host|\Icinga\Module\Monitoring\Object\Host + */ + protected $object; + + /** + * URL to redirect to after a command was handled + * + * @var string + */ + protected $commandRedirectUrl; + + /** + * List of visible hooked tabs + * + * @var ObjectDetailsTabHook[] + */ + protected $tabHooks = []; + + /** + * (non-PHPDoc) + * @see \Icinga\Web\Controller\ActionController For the method documentation. + */ + public function prepareInit() + { + parent::prepareInit(); + if (Hook::has('ticket')) { + $this->view->tickets = Hook::first('ticket'); + } + if (Hook::has('grapher')) { + $this->view->graphers = Hook::all('grapher'); + } + } + + /** + * Show a host or service + */ + public function showAction() + { + $this->setAutorefreshInterval(10); + $this->setupQuickActionForms(); + $auth = $this->Auth(); + $this->object->populate(); + $this->handleFormatRequest(); + $toggleFeaturesForm = new ToggleObjectFeaturesCommandForm(array( + 'backend' => $this->backend, + 'objects' => $this->object + )); + $toggleFeaturesForm + ->load($this->object) + ->handleRequest(); + $this->view->toggleFeaturesForm = $toggleFeaturesForm; + if (! empty($this->object->comments) && $auth->hasPermission('monitoring/command/comment/delete')) { + $delCommentForm = new DeleteCommentCommandForm(); + $delCommentForm->handleRequest(); + $this->view->delCommentForm = $delCommentForm; + } + if (! empty($this->object->downtimes) && $auth->hasPermission('monitoring/command/downtime/delete')) { + $delDowntimeForm = new DeleteDowntimeCommandForm(); + $delDowntimeForm->handleRequest(); + $this->view->delDowntimeForm = $delDowntimeForm; + } + $this->view->showInstance = $this->backend->select()->from('instance')->count() > 1; + $this->view->object = $this->object; + + $this->view->extensionsHtml = array(); + foreach (Hook::all('Monitoring\DetailviewExtension') as $hook) { + /** @var DetailviewExtensionHook $hook */ + + try { + $html = $hook->setView($this->view)->getHtmlForObject($this->object); + } catch (Exception $e) { + $html = $this->view->escape($e->getMessage()); + } + + if ($html) { + $module = $this->view->escape($hook->getModule()->getName()); + $this->view->extensionsHtml[] = + '<div class="icinga-module module-' . $module . '" data-icinga-module="' . $module . '">' + . $html + . '</div>'; + } + } + } + + /** + * Show the history for a host or service + */ + public function historyAction() + { + $this->getTabs()->activate('history'); + $this->view->history = $this->object->fetchEventhistory()->eventhistory; + $this->applyRestriction('monitoring/filter/objects', $this->view->history); + + $this->setupLimitControl(50); + $this->setupPaginationControl($this->view->history, 50); + $this->view->object = $this->object; + $this->render('object/detail-history', null, true); + } + + /** + * Show the content of a custom tab + */ + public function tabhookAction() + { + $hookName = $this->params->get('hook'); + $this->getTabs()->activate($hookName); + + $hook = $this->tabHooks[$hookName]; + + $this->view->header = $hook->getHeader($this->object, $this->getRequest()); + $this->view->content = $hook->getContent($this->object, $this->getRequest()); + $this->view->object = $this->object; + $this->render('object/detail-tabhook', null, true); + } + + /** + * Handle a command form + * + * @param ObjectsCommandForm $form + * + * @return ObjectsCommandForm + */ + protected function handleCommandForm(ObjectsCommandForm $form) + { + $form + ->setBackend($this->backend) + ->setObjects($this->object) + ->setRedirectUrl(Url::fromPath($this->commandRedirectUrl)->setParams($this->params)) + ->handleRequest(); + $this->view->form = $form; + $this->view->object = $this->object; + $this->view->tabs->remove('dashboard'); + $this->view->tabs->remove('menu-entry'); + $this->_helper->viewRenderer('partials/command/object-command-form', null, true); + $this->setupQuickActionForms(); + return $form; + } + + /** + * Export to JSON if requested + */ + protected function handleFormatRequest($query = null) + { + if ($this->params->get('format') === 'json' + || $this->getRequest()->getHeader('Accept') === 'application/json' + ) { + $payload = (array) $this->object->properties; + $payload['vars'] = $this->object->customvars; + + if ($this->hasPermission('*') || ! $this->hasPermission('no-monitoring/contacts')) { + $payload['contacts'] = $this->object->contacts->fetchPairs(); + $payload['contact_groups'] = $this->object->contactgroups->fetchPairs(); + } else { + $payload['contacts'] = []; + $payload['contact_groups'] = []; + } + + $groupName = $this->object->getType() . 'groups'; + $payload[$groupName] = $this->object->$groupName; + $this->getResponse()->json() + ->setSuccessData($payload) + ->setAutoSanitize() + ->sendResponse(); + } + } + + /** + * Acknowledge a problem + */ + abstract public function acknowledgeProblemAction(); + + /** + * Add a comment + */ + abstract public function addCommentAction(); + + /** + * Reschedule a check + */ + abstract public function rescheduleCheckAction(); + + /** + * Schedule a downtime + */ + abstract public function scheduleDowntimeAction(); + + /** + * Create tabs + */ + protected function createTabs() + { + $tabs = $this->getTabs(); + $object = $this->object; + if ($object->getType() === $object::TYPE_HOST) { + $isService = false; + $params = array( + 'host' => $object->getName() + ); + if ($this->params->has('service')) { + $params['service'] = $this->params->get('service'); + } + } else { + $isService = true; + /** @var Service $object */ + $params = array( + 'host' => $object->getHost()->getName(), + 'service' => $object->getName() + ); + } + $tabs->add( + 'host', + array( + 'title' => sprintf( + $this->translate('Show detailed information for host %s'), + $isService ? $object->getHost()->getName() : $object->getName() + ), + 'label' => $this->translate('Host'), + 'url' => 'monitoring/host/show', + 'urlParams' => $params + ) + ); + if ($isService || $this->params->has('service')) { + $tabs->add( + 'service', + array( + 'title' => sprintf( + $this->translate('Show detailed information for service %s on host %s'), + $isService ? $object->getName() : $this->params->get('service'), + $isService ? $object->getHost()->getName() : $object->getName() + ), + 'label' => $this->translate('Service'), + 'url' => 'monitoring/service/show', + 'urlParams' => $params + ) + ); + } + $tabs->add( + 'services', + array( + 'title' => sprintf( + $this->translate('List all services on host %s'), + $isService ? $object->getHost()->getName() : $object->getName() + ), + 'label' => $this->translate('Services'), + 'url' => 'monitoring/host/services', + 'urlParams' => $params + ) + ); + if ($this->backend->hasQuery('eventhistory')) { + $tabs->add( + 'history', + array( + 'title' => $isService + ? sprintf( + $this->translate('Show all event records of service %s on host %s'), + $object->getName(), + $object->getHost()->getName() + ) + : sprintf($this->translate('Show all event records of host %s'), $object->getName()) + , + 'label' => $this->translate('History'), + 'url' => $isService ? 'monitoring/service/history' : 'monitoring/host/history', + 'urlParams' => $params + ) + ); + } + + /** @var ObjectDetailsTabHook $hook */ + foreach (Hook::all('Monitoring\\ObjectDetailsTab') as $hook) { + $hookName = $hook->getName(); + if ($hook->shouldBeShown($object, $this->Auth())) { + $this->tabHooks[$hookName] = $hook; + $tabs->add($hookName, [ + 'label' => $hook->getLabel(), + 'url' => $isService ? 'monitoring/service/tabhook' : 'monitoring/host/tabhook', + 'urlParams' => $params + [ 'hook' => $hookName ] + ]); + } + } + + $tabs->extend(new DashboardAction())->extend(new MenuAction()); + } + + /** + * Create quick action forms and pass them to the view + */ + protected function setupQuickActionForms() + { + $auth = $this->Auth(); + if ($auth->hasPermission('monitoring/command/schedule-check') + || ($auth->hasPermission('monitoring/command/schedule-check/active-only') + && $this->object->active_checks_enabled + ) + ) { + $this->view->checkNowForm = $checkNowForm = new CheckNowCommandForm(); + $checkNowForm + ->setObjects($this->object) + ->handleRequest(); + } + if (! in_array((int) $this->object->state, array(0, 99)) + && $this->object->acknowledged + && $auth->hasPermission('monitoring/command/remove-acknowledgement') + ) { + $this->view->removeAckForm = $removeAckForm = new RemoveAcknowledgementCommandForm(); + $removeAckForm + ->setObjects($this->object) + ->handleRequest(); + } + } +} diff --git a/modules/monitoring/library/Monitoring/Web/Helper/PluginOutputHookRenderer.php b/modules/monitoring/library/Monitoring/Web/Helper/PluginOutputHookRenderer.php new file mode 100644 index 0000000..50b6c65 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Helper/PluginOutputHookRenderer.php @@ -0,0 +1,105 @@ +<?php +/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Web\Helper; + +use Icinga\Application\Logger; +use Icinga\Web\Hook; + +/** + * Renderer for plugin output based on hooks + */ +class PluginOutputHookRenderer +{ + /** @var array */ + protected $commandMap = []; + + /** + * Register PluginOutput hooks + * + * Map PluginOutput hooks to their responsible commands. + * + * @return $this + */ + public function registerHooks() + { + if (! Hook::has('monitoring/PluginOutput')) { + return $this; + } + + foreach (Hook::all('monitoring/PluginOutput') as $hook) { + /** @var \Icinga\Module\Monitoring\Hook\PluginOutputHook $hook */ + try { + $commands = $hook->getCommands(); + } catch (\Exception $e) { + Logger::error( + 'Failed to get applicable commands from hook "%s". An error occurred: %s', + get_class($hook), + $e + ); + + continue; + } + + if (! is_array($commands)) { + $commands = [$commands]; + } + + foreach ($commands as $command) { + if (! isset($this->commandMap[$command])) { + $this->commandMap[$command] = []; + } + + $this->commandMap[$command][] = $hook; + } + } + + return $this; + } + + protected function renderCommand($command, $output, $detail) + { + if (isset($this->commandMap[$command])) { + foreach ($this->commandMap[$command] as $hook) { + /** @var \Icinga\Module\Monitoring\Hook\PluginOutputHook $hook */ + + try { + $output = $hook->render($command, $output, $detail); + } catch (\Exception $e) { + Logger::error( + 'Failed to render plugin output from hook "%s". An error occurred: %s', + get_class($hook), + $e + ); + + continue; + } + } + } + + return $output; + } + + /** + * Render the given plugin output based on the specified check command + * + * Traverse all hooks which are responsible for the specified check command and call their `render()` methods. + * + * @param string $command Check command + * @param string $output Plugin output + * @param bool $detail Whether the output is requested from the detail area + * + * @return string + */ + public function render($command, $output, $detail) + { + if (empty($this->commandMap)) { + return $output; + } + + $output = $this->renderCommand('*', $output, $detail); + $output = $this->renderCommand($command, $output, $detail); + + return $output; + } +} diff --git a/modules/monitoring/library/Monitoring/Web/Hook/HostActionsHook.php b/modules/monitoring/library/Monitoring/Web/Hook/HostActionsHook.php new file mode 100644 index 0000000..fdfe18f --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Hook/HostActionsHook.php @@ -0,0 +1,15 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Web\Hook; + +use Icinga\Module\Monitoring\Hook\HostActionsHook as BaseHook; + +/** + * Compat only + * + * Please implement hooks in our Hook direcory + */ +abstract class HostActionsHook extends BaseHook +{ +} diff --git a/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php b/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php new file mode 100644 index 0000000..0ffbf45 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php @@ -0,0 +1,15 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Web\Hook; + +use Icinga\Module\Monitoring\Hook\ServiceActionsHook as BaseHook; + +/** + * Compat only + * + * Please implement hooks in our Hook direcory + */ +abstract class ServiceActionsHook extends BaseHook +{ +} diff --git a/modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php b/modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php new file mode 100644 index 0000000..f6f110f --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php @@ -0,0 +1,15 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Web\Hook; + +use Icinga\Module\Monitoring\Hook\TimelineProviderHook as BaseHook; + +/** + * Compat only + * + * Please implement hooks in our Hook direcory + */ +abstract class TimelineProviderHook extends BaseHook +{ +} diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/Action.php b/modules/monitoring/library/Monitoring/Web/Navigation/Action.php new file mode 100644 index 0000000..7e4ffe3 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Navigation/Action.php @@ -0,0 +1,123 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Web\Navigation; + +use Icinga\Data\Filter\Filter; +use Icinga\Web\Navigation\NavigationItem; +use Icinga\Module\Monitoring\Object\Macro; +use Icinga\Module\Monitoring\Object\MonitoredObject; +use Icinga\Web\Url; + +/** + * Action for monitored objects + */ +class Action extends NavigationItem +{ + /** + * Whether this action's macros were already resolved + * + * @var bool + */ + protected $resolved = false; + + /** + * This action's object + * + * @var MonitoredObject + */ + protected $object; + + /** + * The filter to use when being asked whether to render this action + * + * @var string + */ + protected $filter; + + /** + * This action's raw url attribute + * + * @var string + */ + protected $rawUrl; + + /** + * Set this action's object + * + * @param MonitoredObject $object + * + * @return $this + */ + public function setObject(MonitoredObject $object) + { + $this->object = $object; + return $this; + } + + /** + * Return this action's object + * + * @return MonitoredObject + */ + public function getObject() + { + return $this->object; + } + + /** + * Set the filter to use when being asked whether to render this action + * + * @param string $filter + * + * @return $this + */ + public function setFilter($filter) + { + $this->filter = $filter; + return $this; + } + + /** + * Return the filter to use when being asked whether to render this action + * + * @return string + */ + public function getFilter() + { + return $this->filter; + } + + public function setUrl($url) + { + if (is_string($url)) { + $this->rawUrl = $url; + } else { + parent::setUrl($url); + } + + return $this; + } + + public function getUrl() + { + $url = parent::getUrl(); + if (! $this->resolved && $url === null && $this->rawUrl !== null) { + $this->setUrl(Url::fromPath(Macro::resolveMacros($this->rawUrl, $this->getObject()))); + $this->resolved = true; + return parent::getUrl(); + } else { + return $url; + } + } + + public function getRender() + { + if ($this->render === null) { + $filter = $this->getFilter(); + $this->render = $filter ? Filter::fromQueryString($filter)->matches($this->getObject()) : true; + } + + return $this->render; + } +} diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/HostAction.php b/modules/monitoring/library/Monitoring/Web/Navigation/HostAction.php new file mode 100644 index 0000000..2e950f1 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Navigation/HostAction.php @@ -0,0 +1,11 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Web\Navigation; + +/** + * A host action + */ +class HostAction extends Action +{ +} diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/HostNote.php b/modules/monitoring/library/Monitoring/Web/Navigation/HostNote.php new file mode 100644 index 0000000..2cf0cdf --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Navigation/HostNote.php @@ -0,0 +1,11 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Web\Navigation; + +/** + * A host note + */ +class HostNote extends Action +{ +} diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php b/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php new file mode 100644 index 0000000..054e387 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php @@ -0,0 +1,171 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Web\Navigation\Renderer; + +use Exception; +use Icinga\Application\Logger; +use Icinga\Authentication\Auth; +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filterable; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use Icinga\Web\Navigation\Renderer\BadgeNavigationItemRenderer; + +/** + * Render generic DataView columns as badges in menu items + * + * It is possible to configure the class of the rendered badge as option 'class', the + * columns to fetch using the option 'columns' and the DataView from which the columns + * will be fetched using the option 'dataview'. + */ +class MonitoringBadgeNavigationItemRenderer extends BadgeNavigationItemRenderer +{ + /** + * Cached count + * + * @var int + */ + protected $count; + + /** + * Caches the responses for all executed summaries + * + * @var array + */ + protected static $summaries = array(); + + /** + * Accumulates all needed columns for a view to allow fetching the needed columns in + * one single query + * + * @var array + */ + protected static $dataViews = array(); + + /** + * The dataview referred to by the navigation item + * + * @var string + */ + protected $dataView; + + /** + * The columns and titles displayed in the badge + * + * @var array + */ + protected $columns; + + /** + * Set the dataview referred to by the navigation item + * + * @param string $dataView + * + * @return $this + */ + public function setDataView($dataView) + { + $this->dataView = $dataView; + return $this; + } + + /** + * Return the dataview referred to by the navigation item + * + * @return string + */ + public function getDataView() + { + return $this->dataView; + } + + /** + * Set the columns and titles displayed in the badge + * + * @param array $columns + * + * @return $this + */ + public function setColumns(array $columns) + { + $this->columns = $columns; + return $this; + } + + /** + * Return the columns and titles displayed in the badge + * + * @return array + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Apply a restriction on the given data view + * + * @param string $restriction The name of restriction + * @param Filterable $filterable The filterable to restrict + * + * @return Filterable The filterable + */ + protected static function applyRestriction($restriction, Filterable $filterable) + { + $restrictions = Filter::matchAny(); + foreach (Auth::getInstance()->getRestrictions($restriction) as $filter) { + if ($filter === '*') { + $filterable->addFilter(Filter::matchAll()); + return $filterable; + } + $restrictions->addFilter(Filter::fromQueryString($filter)); + } + $filterable->applyFilter($restrictions); + return $filterable; + } + + /** + * Fetch the dataview from the database + * + * @return object + */ + protected function fetchDataView() + { + $summary = MonitoringBackend::instance()->select()->from( + $this->getDataView(), + array_keys($this->getColumns()) + ); + static::applyRestriction('monitoring/filter/objects', $summary); + return $summary->fetchRow(); + } + + /** + * {@inheritdoc} + */ + public function getCount() + { + if ($this->count === null) { + try { + $summary = $this->fetchDataView(); + } catch (Exception $e) { + Logger::debug($e); + $this->count = 1; + $this->state = static::STATE_UNKNOWN; + $this->title = $e->getMessage(); + return $this->count; + } + $count = 0; + $titles = array(); + foreach ($this->getColumns() as $column => $title) { + if (isset($summary->$column) && $summary->$column > 0) { + $titles[] = sprintf($title, $summary->$column); + $count += $summary->$column; + } + } + $this->count = $count; + $this->title = implode('. ', $titles); + } + + return $this->count; + } +} diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/ServiceAction.php b/modules/monitoring/library/Monitoring/Web/Navigation/ServiceAction.php new file mode 100644 index 0000000..a88e94f --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Navigation/ServiceAction.php @@ -0,0 +1,11 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Web\Navigation; + +/** + * A service action + */ +class ServiceAction extends Action +{ +} diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/ServiceNote.php b/modules/monitoring/library/Monitoring/Web/Navigation/ServiceNote.php new file mode 100644 index 0000000..4858bf5 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Navigation/ServiceNote.php @@ -0,0 +1,11 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Web\Navigation; + +/** + * A service note + */ +class ServiceNote extends Action +{ +} diff --git a/modules/monitoring/library/Monitoring/Web/Rest/RestRequest.php b/modules/monitoring/library/Monitoring/Web/Rest/RestRequest.php new file mode 100644 index 0000000..fcbe0ca --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Rest/RestRequest.php @@ -0,0 +1,297 @@ +<?php +/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Web\Rest; + +use Exception; +use Icinga\Application\Logger; +use Icinga\Util\Json; +use Icinga\Module\Monitoring\Exception\CurlException; + +/** + * REST Request + */ +class RestRequest +{ + /** + * Request URI + * + * @var string + */ + protected $uri; + + /** + * Request method + * + * @var string + */ + protected $method; + + /** + * Request content type + * + * @var string + */ + protected $contentType; + + /** + * Whether to authenticate with basic auth + * + * @var bool + */ + protected $hasBasicAuth; + + /** + * Auth username + * + * @var string + */ + protected $username; + + /** + * Auth password + * + * @var string + */ + protected $password; + + /** + * Request payload + * + * @var mixed + */ + protected $payload; + + /** + * Whether strict SSL is enabled + * + * @var bool + */ + protected $strictSsl = true; + + /** + * Request timeout + * + * @var int + */ + protected $timeout = 30; + + /** + * Create a GET REST request + * + * @param string $uri + * + * @return static + */ + public static function get($uri) + { + $request = new static; + $request->uri = $uri; + $request->method = 'GET'; + return $request; + } + + /** + * Create a POST REST request + * + * @param string $uri + * + * @return static + */ + public static function post($uri) + { + $request = new static; + $request->uri = $uri; + $request->method = 'POST'; + return $request; + } + + /** + * Send content type JSON + * + * @return $this + */ + public function sendJson() + { + $this->contentType = 'application/json'; + + return $this; + } + + /** + * Set basic auth credentials + * + * @param string $username + * @param string $password + * + * @return $this + */ + public function authenticateWith($username, $password) + { + $this->hasBasicAuth = true; + $this->username = $username; + $this->password = $password; + + return $this; + } + + /** + * Set request payload + * + * @param mixed $payload + * + * @return $this + */ + public function setPayload($payload) + { + $this->payload = $payload; + + return $this; + } + + /** + * Disable strict SSL + * + * @return $this + */ + public function noStrictSsl() + { + $this->strictSsl = false; + + return $this; + } + + /** + * Serialize payload according to content type + * + * @param mixed $payload + * @param string $contentType + * + * @return string + */ + public function serializePayload($payload, $contentType) + { + switch ($contentType) { + case 'application/json': + $payload = Json::encode($payload); + break; + } + + return $payload; + } + + /** + * Send the request + * + * @return mixed + * + * @throws Exception + */ + public function send() + { + $defaults = array( + 'host' => 'localhost', + 'path' => '/' + ); + + $url = array_merge($defaults, parse_url($this->uri)); + + if (isset($url['port'])) { + $url['host'] .= sprintf(':%u', $url['port']); + } + + if (isset($url['query'])) { + $url['path'] .= sprintf('?%s', $url['query']); + } + + $headers = array( + "{$this->method} {$url['path']} HTTP/1.1", + "Host: {$url['host']}", + "Content-Type: {$this->contentType}", + 'Accept: application/json', + // Bypass "Expect: 100-continue" timeouts + 'Expect:' + ); + + $options = array( + CURLOPT_URL => $this->uri, + CURLOPT_TIMEOUT => $this->timeout, + // Ignore proxy settings + CURLOPT_PROXY => '', + CURLOPT_CUSTOMREQUEST => $this->method + ); + + // Record cURL command line for debugging + $curlCmd = array('curl', '-s', '-X', $this->method, '-H', escapeshellarg('Accept: application/json')); + + if ($this->strictSsl) { + $options[CURLOPT_SSL_VERIFYHOST] = 2; + $options[CURLOPT_SSL_VERIFYPEER] = true; + } else { + $options[CURLOPT_SSL_VERIFYHOST] = false; + $options[CURLOPT_SSL_VERIFYPEER] = false; + $curlCmd[] = '-k'; + } + + if ($this->hasBasicAuth) { + $options[CURLOPT_USERPWD] = sprintf('%s:%s', $this->username, $this->password); + $curlCmd[] = sprintf('-u %s:%s', escapeshellarg($this->username), escapeshellarg($this->password)); + } + + if (! empty($this->payload)) { + $payload = $this->serializePayload($this->payload, $this->contentType); + $options[CURLOPT_POSTFIELDS] = $payload; + $curlCmd[] = sprintf('-d %s', escapeshellarg($payload)); + } + + $options[CURLOPT_HTTPHEADER] = $headers; + + $stream = null; + $logger = Logger::getInstance(); + if ($logger !== null && $logger->getLevel() === Logger::DEBUG) { + $stream = fopen('php://temp', 'w'); + $options[CURLOPT_VERBOSE] = true; + $options[CURLOPT_STDERR] = $stream; + } + + Logger::debug( + 'Executing %s %s', + implode(' ', $curlCmd), + escapeshellarg($this->uri) + ); + + $result = $this->curlExec($options); + + if (is_resource($stream)) { + rewind($stream); + Logger::debug(stream_get_contents($stream)); + fclose($stream); + } + + return Json::decode($result, true); + } + + /** + * Set up a new cURL handle with the given options and call {@link curl_exec()} + * + * @param array $options + * + * @return string The response + * + * @throws CurlException + */ + protected function curlExec(array $options) + { + $ch = curl_init(); + $options[CURLOPT_RETURNTRANSFER] = true; + curl_setopt_array($ch, $options); + $result = curl_exec($ch); + + if ($result === false) { + throw new CurlException('%s', curl_error($ch)); + } + + curl_close($ch); + return $result; + } +} diff --git a/modules/monitoring/library/Monitoring/Web/Widget/CustomVarTable.php b/modules/monitoring/library/Monitoring/Web/Widget/CustomVarTable.php new file mode 100644 index 0000000..b4273b5 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Widget/CustomVarTable.php @@ -0,0 +1,272 @@ +<?php +/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Web\Widget; + +use Icinga\Module\Monitoring\Hook\CustomVarRendererHook; +use Icinga\Module\Monitoring\Object\MonitoredObject; +use ipl\Html\Attributes; +use ipl\Html\BaseHtmlElement; +use ipl\Html\Html; +use ipl\Html\HtmlDocument; +use ipl\Html\HtmlElement; +use ipl\Html\Text; +use ipl\Web\Widget\Icon; +use Closure; + +class CustomVarTable extends BaseHtmlElement +{ + /** @var iterable The variables */ + protected $data; + + /** @var ?MonitoredObject The object the variables are bound to */ + protected $object; + + /** @var Closure Callback to apply hooks */ + protected $hookApplier; + + /** @var array The groups as identified by hooks */ + protected $groups = []; + + /** @var string Header title */ + protected $headerTitle; + + /** @var int The nesting level */ + protected $level = 0; + + protected $tag = 'table'; + + /** @var HtmlElement The table body */ + protected $body; + + protected $defaultAttributes = [ + 'class' => ['custom-var-table', 'name-value-table'] + ]; + + /** + * Create a new CustomVarTable + * + * @param iterable $data + * @param ?MonitoredObject $object + */ + public function __construct($data, MonitoredObject $object = null) + { + $this->data = $data; + $this->object = $object; + $this->body = new HtmlElement('tbody'); + } + + /** + * Set the header to show + * + * @param string $title + * + * @return $this + */ + protected function setHeader($title) + { + $this->headerTitle = (string) $title; + + return $this; + } + + /** + * Add a new row to the body + * + * @param mixed $name + * @param mixed $value + * + * @return void + */ + protected function addRow($name, $value) + { + $this->body->addHtml(new HtmlElement( + 'tr', + Attributes::create(['class' => "level-{$this->level}"]), + new HtmlElement('th', null, Html::wantHtml($name)), + new HtmlElement('td', null, Html::wantHtml($value)) + )); + } + + /** + * Render a variable + * + * @param mixed $name + * @param mixed $value + * + * @return void + */ + protected function renderVar($name, $value) + { + if ($this->object !== null && $this->level === 0) { + list($name, $value, $group) = call_user_func($this->hookApplier, $name, $value); + if ($group !== null) { + $this->groups[$group][] = [$name, $value]; + return; + } + } + + $isArray = is_array($value); + if (! $isArray && $value instanceof \stdClass) { + $value = (array) $value; + $isArray = true; + } + + switch (true) { + case $isArray && is_int(key($value)): + $this->renderArray($name, $value); + break; + case $isArray: + $this->renderObject($name, $value); + break; + default: + $this->renderScalar($name, $value); + } + } + + /** + * Render an array + * + * @param mixed $name + * @param array $array + * + * @return void + */ + protected function renderArray($name, array $array) + { + $numItems = count($array); + $name = (new HtmlDocument())->addHtml( + Html::wantHtml($name), + Text::create(' (Array)') + ); + + $this->addRow($name, sprintf(tp('%d item', '%d items', $numItems), $numItems)); + + ++$this->level; + + ksort($array); + foreach ($array as $key => $value) { + $this->renderVar("[$key]", $value); + } + + --$this->level; + } + + /** + * Render an object (associative array) + * + * @param mixed $name + * @param array $object + * + * @return void + */ + protected function renderObject($name, array $object) + { + $numItems = count($object); + $this->addRow($name, sprintf(tp('%d item', '%d items', $numItems), $numItems)); + + ++$this->level; + + ksort($object); + foreach ($object as $key => $value) { + $this->renderVar($key, $value); + } + + --$this->level; + } + + /** + * Render a scalar + * + * @param mixed $name + * @param mixed $value + * + * @return void + */ + protected function renderScalar($name, $value) + { + if ($value === '') { + $value = new HtmlElement('span', Attributes::create(['class' => 'empty']), Text::create(t('empty string'))); + } + + $this->addRow($name, $value); + } + + /** + * Render a group + * + * @param string $name + * @param iterable $entries + * + * @return void + */ + protected function renderGroup($name, $entries) + { + $table = new self($entries); + + /** @var HtmlDocument $wrapper */ + $wrapper = $this->getWrapper(); + if ($wrapper === null) { + $wrapper = new HtmlDocument(); + $wrapper->addHtml($this); + $this->prependWrapper($wrapper); + } + + $wrapper->addHtml($table->setHeader($name)); + } + + protected function assemble() + { + if ($this->object !== null) { + $this->hookApplier = CustomVarRendererHook::prepareForObject($this->object); + } + + if ($this->headerTitle !== null) { + $this->getAttributes() + ->add('class', 'collapsible') + ->add('data-visible-height', 100) + ->add('data-toggle-element', 'thead') + ->add( + 'id', + preg_replace('/\s+/', '-', strtolower($this->headerTitle)) . '-customvars' + ); + + $this->addHtml(new HtmlElement('thead', null, new HtmlElement( + 'tr', + null, + new HtmlElement( + 'th', + Attributes::create(['colspan' => 2]), + new HtmlElement( + 'span', + null, + new Icon('angle-right'), + new Icon('angle-down') + ), + Text::create($this->headerTitle) + ) + ))); + } + + if (is_array($this->data)) { + ksort($this->data); + } + + foreach ($this->data as $name => $value) { + $this->renderVar($name, $value); + } + + $this->addHtml($this->body); + + // Hooks can return objects as replacement for keys, hence a generator is needed for group entries + $genGenerator = function ($entries) { + foreach ($entries as list($key, $value)) { + yield $key => $value; + } + }; + + foreach ($this->groups as $group => $entries) { + $this->renderGroup($group, $genGenerator($entries)); + } + } +} diff --git a/modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php b/modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php new file mode 100644 index 0000000..48b98ac --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php @@ -0,0 +1,120 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Web\Widget; + +use Icinga\Web\Form; +use Icinga\Web\Request; +use Icinga\Web\Widget\AbstractWidget; + +class SelectBox extends AbstractWidget +{ + /** + * The name of the form that will be created + * + * @var string + */ + private $name; + + /** + * An array containing all intervals with their associated labels + * + * @var array + */ + private $values; + + /** + * The label displayed next to the select box + * + * @var string + */ + private $label; + + /** + * The name of the url parameter to set + * + * @var string + */ + private $parameter; + + /** + * A request object used for initial form population + * + * @var Request + */ + private $request; + + /** + * Create a TimelineIntervalBox + * + * @param string $name The name of the form that will be created + * @param array $values An array containing all intervals with their associated labels + * @param string $label The label displayed next to the select box + * @param string $param The request parameter name to set + */ + public function __construct($name, array $values, $label = 'Select', $param = 'selection') + { + $this->name = $name; + $this->values = $values; + $this->label = $label; + $this->parameter = $param; + } + + /** + * Apply the parameters from the given request on this widget + * + * @param Request $request The request to use for populating the form + */ + public function applyRequest(Request $request) + { + $this->request = $request; + } + + /** + * Return the chosen interval value or null + * + * @param Request $request The request to fetch the value from + * + * @return string|null + */ + public function getInterval(Request $request = null) + { + if ($request === null && $this->request) { + $request = $this->request; + } + + if ($request) { + return $request->getParam('interval'); + } + } + + /** + * Renders this widget and returns the HTML as a string + * + * @return string + */ + public function render() + { + $form = new Form(); + $form->setAttrib('class', Form::DEFAULT_CLASSES . ' inline'); + $form->setMethod('GET'); + $form->setUidDisabled(); + $form->setTokenDisabled(); + $form->setName($this->name); + $form->addElement( + 'select', + $this->parameter, + array( + 'label' => $this->label, + 'multiOptions' => $this->values, + 'autosubmit' => true + ) + ); + + if ($this->request) { + $form->populate($this->request->getParams()); + } + + return $form; + } +} diff --git a/modules/monitoring/library/Monitoring/Web/Widget/StateBadges.php b/modules/monitoring/library/Monitoring/Web/Widget/StateBadges.php new file mode 100644 index 0000000..fdaac51 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Widget/StateBadges.php @@ -0,0 +1,341 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Monitoring\Web\Widget; + +use Icinga\Web\Form; +use Icinga\Web\Navigation\Navigation; +use Icinga\Web\Navigation\NavigationItem; +use Icinga\Web\Url; +use Icinga\Web\Widget\AbstractWidget; +use Icinga\Data\Filter\Filter; + +class StateBadges extends AbstractWidget +{ + /** + * CSS class for the widget + * + * @var string + */ + const CSS_CLASS = 'state-badges'; + + /** + * State critical + * + * @var string + */ + const STATE_CRITICAL = 'state-critical'; + + /** + * State critical handled + * + * @var string + */ + const STATE_CRITICAL_HANDLED = 'state-critical handled'; + + /** + * State down + * + * @var string + */ + const STATE_DOWN = 'state-down'; + + /** + * State down handled + * + * @var string + */ + const STATE_DOWN_HANDLED = 'state-down handled'; + + /** + * State ok + * + * @var string + */ + const STATE_OK = 'state-ok'; + + /** + * State pending + * + * @var string + */ + const STATE_PENDING = 'state-pending'; + + /** + * State unknown + * + * @var string + */ + const STATE_UNKNOWN = 'state-unknown'; + + /** + * State unknown handled + * + * @var string + */ + const STATE_UNKNOWN_HANDLED = 'state-unknown handled'; + + /** + * State unreachable + * + * @var string + */ + const STATE_UNREACHABLE = 'state-unreachable'; + + /** + * State unreachable handled + * + * @var string + */ + const STATE_UNREACHABLE_HANDLED = 'state-unreachable handled'; + + /** + * State up + * + * @var string + */ + const STATE_UP = 'state-up'; + + /** + * State warning + * + * @var string + */ + const STATE_WARNING = 'state-warning'; + + /** + * State warning handled + * + * @var string + */ + const STATE_WARNING_HANDLED = 'state-warning handled'; + + /** + * State badges + * + * @var object[] + */ + protected $badges = array(); + + /** + * Internal counter for badge priorities + * + * @var int + */ + protected $priority = 1; + + /** + * The base filter applied to any badge link + * + * @var Filter + */ + protected $baseFilter; + + /** + * Base URL + * + * @var Url + */ + protected $url; + + /** + * Get the base URL + * + * @return Url + */ + public function getUrl() + { + return $this->url; + } + + /** + * Set the base URL + * + * @param Url|string $url + * + * @return $this + */ + public function setUrl($url) + { + if (! $url instanceof $url) { + $url = Url::fromPath($url); + } + $this->url = $url; + return $this; + } + + /** + * Get the base filter + * + * @return Filter + */ + public function getBaseFilter() + { + return $this->baseFilter; + } + + /** + * Set the base filter + * + * @param Filter $baseFilter + * + * @return $this + */ + public function setBaseFilter($baseFilter) + { + $this->baseFilter = $baseFilter; + return $this; + } + + /** + * Add a state badge + * + * @param string $state + * @param int $count + * @param array $filter + * @param string $translateSingular + * @param string $translatePlural + * @param array $translateArgs + * + * @return $this + */ + public function add( + $state, + $count, + array $filter, + $translateSingular, + $translatePlural, + array $translateArgs = array() + ) { + $this->badges[$state] = (object) array( + 'count' => (int) $count, + 'filter' => $filter, + 'translateArgs' => $translateArgs, + 'translatePlural' => $translatePlural, + 'translateSingular' => $translateSingular + ); + return $this; + } + + /** + * Create a badge + * + * @param string $state + * @param Navigation $badges + * + * @return $this + */ + public function createBadge($state, Navigation $badges) + { + if ($this->has($state)) { + $badge = $this->get($state); + $url = clone $this->url->setParams($badge->filter); + if (isset($this->baseFilter)) { + $url->addFilter($this->baseFilter); + } + $badges->addItem(new NavigationItem($state, array( + 'attributes' => array('class' => 'badge ' . $state), + 'label' => $badge->count, + 'priority' => $this->priority++, + 'title' => vsprintf( + mtp('monitoring', $badge->translateSingular, $badge->translatePlural, $badge->count), + $badge->translateArgs + ), + 'url' => $url + ))); + } + return $this; + } + + /** + * Create a badge group + * + * @param array $states + * @param Navigation $badges + * + * @return $this + */ + public function createBadgeGroup(array $states, Navigation $badges) + { + $group = array_intersect_key($this->badges, array_flip($states)); + if (! empty($group)) { + $groupItem = new NavigationItem( + uniqid(), + array( + 'cssClass' => 'state-badge-group', + 'label' => '', + 'priority' => $this->priority++ + ) + ); + $groupBadges = new Navigation(); + $groupBadges->setLayout(Navigation::LAYOUT_TABS); + foreach (array_keys($group) as $state) { + $this->createBadge($state, $groupBadges); + } + $groupItem->setChildren($groupBadges); + $badges->addItem($groupItem); + } + return $this; + } + + /** + * Get whether a badge for the given state has been added + * + * @param string $state + * + * @return bool + */ + public function has($state) + { + return isset($this->badges[$state]) && $this->badges[$state]->count; + } + + /** + * Get the badge for the given state + * + * @param string $state + * + * @return object + */ + public function get($state) + { + return $this->badges[$state]; + } + + /** + * {@inheritdoc} + */ + public function render() + { + $badges = new Navigation(); + $badges->setLayout(Navigation::LAYOUT_TABS); + $this + ->createBadgeGroup( + array(static::STATE_CRITICAL, static::STATE_CRITICAL_HANDLED), + $badges + ) + ->createBadgeGroup( + array(static::STATE_DOWN, static::STATE_DOWN_HANDLED), + $badges + ) + ->createBadgeGroup( + array(static::STATE_WARNING, static::STATE_WARNING_HANDLED), + $badges + ) + ->createBadgeGroup( + array(static::STATE_UNREACHABLE, static::STATE_UNREACHABLE_HANDLED), + $badges + ) + ->createBadgeGroup( + array(static::STATE_UNKNOWN, static::STATE_UNKNOWN_HANDLED), + $badges + ) + ->createBadge(static::STATE_OK, $badges) + ->createBadge(static::STATE_UP, $badges) + ->createBadge(static::STATE_PENDING, $badges); + return $badges + ->getRenderer() + ->setCssClass(static::CSS_CLASS) + ->render(); + } +} |