summaryrefslogtreecommitdiffstats
path: root/modules/monitoring/library
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:46:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:46:43 +0000
commit3e02d5aff85babc3ffbfcf52313f2108e313aa23 (patch)
treeb01f3923360c20a6a504aff42d45670c58af3ec5 /modules/monitoring/library
parentInitial commit. (diff)
downloadicingaweb2-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')
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/IdoBackend.php10
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/AllcontactsQuery.php75
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/CommandQuery.php61
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentQuery.php158
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php179
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenteventQuery.php39
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenthistoryQuery.php179
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactQuery.php139
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactgroupQuery.php214
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/CustomvarQuery.php116
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php163
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php179
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeeventQuery.php42
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php179
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/EmptyhostgroupQuery.php38
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/EmptyservicegroupQuery.php51
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridQuery.php56
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridhostsQuery.php16
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridservicesQuery.php15
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/EventhistoryQuery.php134
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingendhistoryQuery.php49
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingeventQuery.php36
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingstarthistoryQuery.php179
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php131
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentQuery.php202
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentdeletionhistoryQuery.php44
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommenthistoryQuery.php197
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcontactQuery.php247
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeQuery.php208
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeendhistoryQuery.php42
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimestarthistoryQuery.php206
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostflappingendhistoryQuery.php31
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostflappingstarthistoryQuery.php200
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupQuery.php295
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupsummaryQuery.php142
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostnotificationQuery.php284
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatehistoryQuery.php222
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php338
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatussummaryQuery.php91
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php1599
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/InstanceQuery.php26
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationQuery.php144
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationeventQuery.php52
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php142
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ProgramstatusQuery.php68
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimesummaryQuery.php80
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimevariablesQuery.php18
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentQuery.php218
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentdeletionhistoryQuery.php44
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommenthistoryQuery.php195
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecontactQuery.php235
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeQuery.php222
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeendhistoryQuery.php42
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimestarthistoryQuery.php205
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServiceflappingendhistoryQuery.php31
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServiceflappingstarthistoryQuery.php197
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupQuery.php303
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupsummaryQuery.php113
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicenotificationQuery.php287
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatehistoryQuery.php220
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatusQuery.php524
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatussummaryQuery.php104
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/StatechangeeventQuery.php41
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/StatehistoryQuery.php179
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/StatussummaryQuery.php243
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/UnhandledhostproblemsQuery.php48
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/UnhandledserviceproblemsQuery.php48
-rw-r--r--modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php349
-rw-r--r--modules/monitoring/library/Monitoring/BackendStep.php208
-rw-r--r--modules/monitoring/library/Monitoring/Cli/CliUtils.php122
-rw-r--r--modules/monitoring/library/Monitoring/Command/IcingaApiCommand.php126
-rw-r--r--modules/monitoring/library/Monitoring/Command/IcingaCommand.php21
-rw-r--r--modules/monitoring/library/Monitoring/Command/Instance/DisableNotificationsExpireCommand.php42
-rw-r--r--modules/monitoring/library/Monitoring/Command/Instance/ToggleInstanceFeatureCommand.php122
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/AcknowledgeProblemCommand.php144
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/AddCommentCommand.php80
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/ApiScheduleHostDowntimeCommand.php40
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/CommandAuthor.php38
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/DeleteCommentCommand.php110
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/DeleteDowntimeCommand.php110
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/ObjectCommand.php61
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/ProcessCheckResultCommand.php176
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/PropagateHostDowntimeCommand.php48
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/RemoveAcknowledgementCommand.php21
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/ScheduleHostCheckCommand.php48
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/ScheduleHostDowntimeCommand.php75
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/ScheduleServiceCheckCommand.php92
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/ScheduleServiceDowntimeCommand.php190
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/SendCustomNotificationCommand.php82
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/ToggleObjectFeatureCommand.php113
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/WithCommentCommand.php42
-rw-r--r--modules/monitoring/library/Monitoring/Command/Renderer/IcingaApiCommandRenderer.php322
-rw-r--r--modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandFileCommandRenderer.php478
-rw-r--r--modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandRendererInterface.php11
-rw-r--r--modules/monitoring/library/Monitoring/Command/Transport/ApiCommandTransport.php291
-rw-r--r--modules/monitoring/library/Monitoring/Command/Transport/CommandTransport.php170
-rw-r--r--modules/monitoring/library/Monitoring/Command/Transport/CommandTransportInterface.php22
-rw-r--r--modules/monitoring/library/Monitoring/Command/Transport/LocalCommandFile.php168
-rw-r--r--modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php465
-rw-r--r--modules/monitoring/library/Monitoring/Controller.php159
-rw-r--r--modules/monitoring/library/Monitoring/Data/ColumnFilterIterator.php30
-rw-r--r--modules/monitoring/library/Monitoring/Data/CustomvarProtectionIterator.php25
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Command.php24
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Comment.php82
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Commentevent.php30
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Contact.php73
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Contactgroup.php57
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Customvar.php47
-rw-r--r--modules/monitoring/library/Monitoring/DataView/DataView.php608
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Downtime.php96
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Downtimeevent.php33
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Eventgrid.php60
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Eventgridhosts.php7
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Eventgridservices.php7
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Eventhistory.php60
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Flappingevent.php27
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Hostcomment.php45
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Hostcontact.php17
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Hostdowntime.php50
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Hostgroup.php34
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Hostgroupsummary.php81
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Hoststatus.php129
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Hoststatussummary.php40
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Instance.php33
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Notification.php59
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Notificationevent.php29
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Programstatus.php44
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Runtimesummary.php38
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Runtimevariables.php34
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Servicecomment.php48
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Servicecontact.php8
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Servicedowntime.php50
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Servicegroup.php31
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Servicegroupsummary.php75
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Servicestatus.php180
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Servicestatussummary.php45
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Statechangeevent.php32
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Statussummary.php111
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Unhandledhostproblems.php28
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Unhandledserviceproblems.php28
-rw-r--r--modules/monitoring/library/Monitoring/Exception/CommandTransportException.php13
-rw-r--r--modules/monitoring/library/Monitoring/Exception/CurlException.php13
-rw-r--r--modules/monitoring/library/Monitoring/Exception/UnsupportedBackendException.php11
-rw-r--r--modules/monitoring/library/Monitoring/Hook/CustomVarRendererHook.php98
-rw-r--r--modules/monitoring/library/Monitoring/Hook/DataviewExtensionHook.php20
-rw-r--r--modules/monitoring/library/Monitoring/Hook/DetailviewExtensionHook.php126
-rw-r--r--modules/monitoring/library/Monitoring/Hook/EventDetailsExtensionHook.php79
-rw-r--r--modules/monitoring/library/Monitoring/Hook/HostActionsHook.php52
-rw-r--r--modules/monitoring/library/Monitoring/Hook/IdoQueryExtensionHook.php15
-rw-r--r--modules/monitoring/library/Monitoring/Hook/ObjectActionsHook.php47
-rw-r--r--modules/monitoring/library/Monitoring/Hook/ObjectDetailsTabHook.php60
-rw-r--r--modules/monitoring/library/Monitoring/Hook/PluginOutputHook.php46
-rw-r--r--modules/monitoring/library/Monitoring/Hook/ServiceActionsHook.php52
-rw-r--r--modules/monitoring/library/Monitoring/Hook/TimelineProviderHook.php37
-rw-r--r--modules/monitoring/library/Monitoring/MonitoringWizard.php160
-rw-r--r--modules/monitoring/library/Monitoring/Object/Acknowledgement.php215
-rw-r--r--modules/monitoring/library/Monitoring/Object/Host.php205
-rw-r--r--modules/monitoring/library/Monitoring/Object/HostList.php133
-rw-r--r--modules/monitoring/library/Monitoring/Object/Macro.php83
-rw-r--r--modules/monitoring/library/Monitoring/Object/MonitoredObject.php930
-rw-r--r--modules/monitoring/library/Monitoring/Object/ObjectList.php295
-rw-r--r--modules/monitoring/library/Monitoring/Object/Service.php220
-rw-r--r--modules/monitoring/library/Monitoring/Object/ServiceList.php184
-rw-r--r--modules/monitoring/library/Monitoring/Plugin/Perfdata.php550
-rw-r--r--modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php144
-rw-r--r--modules/monitoring/library/Monitoring/Plugin/ThresholdRange.php179
-rw-r--r--modules/monitoring/library/Monitoring/ProvidedHook/ApplicationState.php32
-rw-r--r--modules/monitoring/library/Monitoring/ProvidedHook/Health.php102
-rw-r--r--modules/monitoring/library/Monitoring/ProvidedHook/X509/Sni.php37
-rw-r--r--modules/monitoring/library/Monitoring/SecurityStep.php84
-rw-r--r--modules/monitoring/library/Monitoring/Timeline/TimeEntry.php233
-rw-r--r--modules/monitoring/library/Monitoring/Timeline/TimeLine.php491
-rw-r--r--modules/monitoring/library/Monitoring/Timeline/TimeRange.php258
-rw-r--r--modules/monitoring/library/Monitoring/TransportStep.php143
-rw-r--r--modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php339
-rw-r--r--modules/monitoring/library/Monitoring/Web/Helper/PluginOutputHookRenderer.php105
-rw-r--r--modules/monitoring/library/Monitoring/Web/Hook/HostActionsHook.php15
-rw-r--r--modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php15
-rw-r--r--modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php15
-rw-r--r--modules/monitoring/library/Monitoring/Web/Navigation/Action.php123
-rw-r--r--modules/monitoring/library/Monitoring/Web/Navigation/HostAction.php11
-rw-r--r--modules/monitoring/library/Monitoring/Web/Navigation/HostNote.php11
-rw-r--r--modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php171
-rw-r--r--modules/monitoring/library/Monitoring/Web/Navigation/ServiceAction.php11
-rw-r--r--modules/monitoring/library/Monitoring/Web/Navigation/ServiceNote.php11
-rw-r--r--modules/monitoring/library/Monitoring/Web/Rest/RestRequest.php297
-rw-r--r--modules/monitoring/library/Monitoring/Web/Widget/CustomVarTable.php272
-rw-r--r--modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php120
-rw-r--r--modules/monitoring/library/Monitoring/Web/Widget/StateBadges.php341
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();
+ }
+}