summaryrefslogtreecommitdiffstats
path: root/modules/monitoring/application/controllers/EventController.php
diff options
context:
space:
mode:
Diffstat (limited to 'modules/monitoring/application/controllers/EventController.php')
-rw-r--r--modules/monitoring/application/controllers/EventController.php551
1 files changed, 551 insertions, 0 deletions
diff --git a/modules/monitoring/application/controllers/EventController.php b/modules/monitoring/application/controllers/EventController.php
new file mode 100644
index 0000000..13bf537
--- /dev/null
+++ b/modules/monitoring/application/controllers/EventController.php
@@ -0,0 +1,551 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use DateTime;
+use DateTimeZone;
+use Icinga\Module\Monitoring\Hook\EventDetailsExtensionHook;
+use Icinga\Application\Hook;
+use InvalidArgumentException;
+use Icinga\Data\Queryable;
+use Icinga\Date\DateFormatter;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+use Icinga\Util\TimezoneDetect;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+use Icinga\Web\Widget\Tabextension\OutputFormat;
+
+class EventController extends Controller
+{
+ /**
+ * @var string[]
+ */
+ protected $dataViewsByType = array(
+ 'notify' => 'notificationevent',
+ 'comment' => 'commentevent',
+ 'comment_deleted' => 'commentevent',
+ 'ack' => 'commentevent',
+ 'ack_deleted' => 'commentevent',
+ 'dt_comment' => 'commentevent',
+ 'dt_comment_deleted' => 'commentevent',
+ 'flapping' => 'flappingevent',
+ 'flapping_deleted' => 'flappingevent',
+ 'hard_state' => 'statechangeevent',
+ 'soft_state' => 'statechangeevent',
+ 'dt_start' => 'downtimeevent',
+ 'dt_end' => 'downtimeevent'
+ );
+
+ public function init()
+ {
+ if (Hook::has('ticket')) {
+ $this->view->tickets = Hook::first('ticket');
+ }
+ }
+
+ public function showAction()
+ {
+ $type = $this->params->shiftRequired('type');
+ $id = $this->params->shiftRequired('id');
+
+ if (! isset($this->dataViewsByType[$type])
+ || $this->applyRestriction(
+ 'monitoring/filter/objects',
+ $this->backend->select()->from('eventhistory', array('id'))->where('id', $id)
+ )->fetchRow() === false
+ ) {
+ $this->httpNotFound($this->translate('Event not found'));
+ }
+
+ $event = $this->query($type, $id)->fetchRow();
+
+ if ($event === false) {
+ $this->httpNotFound($this->translate('Event not found'));
+ }
+
+ $this->view->object = $object = $event->service_description === null
+ ? new Host($this->backend, $event->host_name)
+ : new Service($this->backend, $event->host_name, $event->service_description);
+ $object->fetch();
+
+ list($icon, $label) = $this->getIconAndLabel($type);
+
+ $this->view->details = array_merge(
+ array(array($this->view->escape($this->translate('Type')), $label)),
+ $this->getDetails($type, $event)
+ );
+
+ $this->view->extensionsHtml = array();
+ /** @var EventDetailsExtensionHook $hook */
+ foreach (Hook::all('Monitoring\\EventDetailsExtension') as $hook) {
+ try {
+ $html = $hook->getHtmlForEvent($event);
+ } 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>';
+ }
+ }
+
+ $this->view->title = $this->translate('Event Overview');
+ $this->getTabs()
+ ->add('event', array(
+ 'title' => $label,
+ 'label' => $label,
+ 'url' => Url::fromRequest(),
+ 'active' => true
+ ))
+ ->extend(new OutputFormat())
+ ->extend(new DashboardAction())
+ ->extend(new MenuAction());
+ }
+
+ /**
+ * Return translated and escaped 'Yes' if the given condition is true, 'No' otherwise, 'N/A' if NULL
+ *
+ * @param bool|null $condition
+ *
+ * @return string
+ */
+ protected function yesOrNo($condition)
+ {
+ if ($condition === null) {
+ return $this->view->escape($this->translate('N/A'));
+ }
+
+ return $this->view->escape($condition ? $this->translate('Yes') : $this->translate('No'));
+ }
+
+ /**
+ * Render the given duration in seconds as human readable HTML or 'N/A' if NULL
+ *
+ * @param int|null $seconds
+ *
+ * @return string
+ */
+ protected function duration($seconds)
+ {
+ return $this->view->escape(
+ $seconds === null ? $this->translate('N/A') : DateFormatter::formatDuration($seconds)
+ );
+ }
+
+ /**
+ * Render the given percent number as human readable HTML or 'N/A' if NULL
+ *
+ * @param float|null $percent
+ *
+ * @return string
+ */
+ protected function percent($percent)
+ {
+ return $this->view->escape(
+ $percent === null ? $this->translate('N/A') : sprintf($this->translate('%.2f%%'), $percent)
+ );
+ }
+
+ /**
+ * Render the given comment message as HTML or 'N/A' if NULL
+ *
+ * @param string|null $message
+ *
+ * @return string
+ */
+ protected function comment($message)
+ {
+ return $this->view->nl2br($this->view->createTicketLinks($this->view->markdown($message)));
+ }
+
+ /**
+ * Render a link to the given contact or 'N/A' if NULL
+ *
+ * @param string|null $name
+ *
+ * @return string
+ */
+ protected function contact($name)
+ {
+ return $name === null
+ ? $this->view->escape($this->translate('N/A'))
+ : $this->view->qlink($name, Url::fromPath('monitoring/show/contact', array('contact_name' => $name)));
+ }
+
+ /**
+ * Render the given monitored object state as human readable HTML or 'N/A' if NULL
+ *
+ * @param bool $isService
+ * @param int|null $state
+ *
+ * @return string
+ */
+ protected function state($isService, $state)
+ {
+ if ($state === null) {
+ return $this->view->escape($this->translate('N/A'));
+ }
+
+ try {
+ $stateText = $isService
+ ? Service::getStateText($state, true)
+ : Host::getStateText($state, true);
+ } catch (InvalidArgumentException $e) {
+ return $this->view->escape($this->translate('N/A'));
+ }
+
+ return '<span class="badge state-' . ($isService ? Service::getStateText($state) : Host::getStateText($state))
+ . '">&nbsp;</span><span class="state-label">' . $this->view->escape($stateText) . '</span>';
+ }
+
+ /**
+ * Render the given plugin output as human readable HTML
+ *
+ * @param string $output
+ *
+ * @return string
+ */
+ protected function pluginOutput($output)
+ {
+ return $this->view->getHelper('PluginOutput')->pluginOutput($output);
+ }
+
+ /**
+ * Return the icon and the label for the given event type
+ *
+ * @param string $eventType
+ *
+ * @return ?string[]
+ */
+ protected function getIconAndLabel($eventType)
+ {
+ switch ($eventType) {
+ case 'notify':
+ return array('bell', $this->translate('Notification', 'tooltip'));
+ case 'comment':
+ return array('comment-empty', $this->translate('Comment', 'tooltip'));
+ case 'comment_deleted':
+ return array('cancel', $this->translate('Comment removed', 'tooltip'));
+ case 'ack':
+ return array('ok', $this->translate('Acknowledged', 'tooltip'));
+ case 'ack_deleted':
+ return array('ok', $this->translate('Acknowledgement removed', 'tooltip'));
+ case 'dt_comment':
+ return array('plug', $this->translate('Downtime scheduled', 'tooltip'));
+ case 'dt_comment_deleted':
+ return array('plug', $this->translate('Downtime removed', 'tooltip'));
+ case 'flapping':
+ return array('flapping', $this->translate('Flapping started', 'tooltip'));
+ case 'flapping_deleted':
+ return array('flapping', $this->translate('Flapping stopped', 'tooltip'));
+ case 'hard_state':
+ return array('warning-empty', $this->translate('Hard state change'));
+ case 'soft_state':
+ return array('spinner', $this->translate('Soft state change'));
+ case 'dt_start':
+ return array('plug', $this->translate('Downtime started', 'tooltip'));
+ case 'dt_end':
+ return array('plug', $this->translate('Downtime ended', 'tooltip'));
+ }
+ }
+
+ /**
+ * Return a query for the given event ID of the given type
+ *
+ * @param string $type
+ * @param int $id
+ *
+ * @return ?Queryable
+ */
+ protected function query($type, $id)
+ {
+ switch ($this->dataViewsByType[$type]) {
+ case 'downtimeevent':
+ return $this->backend->select()
+ ->from('downtimeevent', array(
+ 'entry_time' => 'downtimeevent_entry_time',
+ 'author_name' => 'downtimeevent_author_name',
+ 'comment_data' => 'downtimeevent_comment_data',
+ 'is_fixed' => 'downtimeevent_is_fixed',
+ 'scheduled_start_time' => 'downtimeevent_scheduled_start_time',
+ 'scheduled_end_time' => 'downtimeevent_scheduled_end_time',
+ 'was_started' => 'downtimeevent_was_started',
+ 'actual_start_time' => 'downtimeevent_actual_start_time',
+ 'actual_end_time' => 'downtimeevent_actual_end_time',
+ 'was_cancelled' => 'downtimeevent_was_cancelled',
+ 'is_in_effect' => 'downtimeevent_is_in_effect',
+ 'trigger_time' => 'downtimeevent_trigger_time',
+ 'host_name',
+ 'service_description'
+ ))
+ ->where('downtimeevent_id', $id);
+ case 'commentevent':
+ return $this->backend->select()
+ ->from('commentevent', array(
+ 'entry_type' => 'commentevent_entry_type',
+ 'comment_time' => 'commentevent_comment_time',
+ 'author_name' => 'commentevent_author_name',
+ 'comment_data' => 'commentevent_comment_data',
+ 'is_persistent' => 'commentevent_is_persistent',
+ 'comment_source' => 'commentevent_comment_source',
+ 'expires' => 'commentevent_expires',
+ 'expiration_time' => 'commentevent_expiration_time',
+ 'deletion_time' => 'commentevent_deletion_time',
+ 'host_name',
+ 'service_description'
+ ))
+ ->where('commentevent_id', $id);
+ case 'flappingevent':
+ return $this->backend->select()
+ ->from('flappingevent', array(
+ 'event_time' => 'flappingevent_event_time',
+ 'reason_type' => 'flappingevent_reason_type',
+ 'percent_state_change' => 'flappingevent_percent_state_change',
+ 'low_threshold' => 'flappingevent_low_threshold',
+ 'high_threshold' => 'flappingevent_high_threshold',
+ 'host_name',
+ 'service_description'
+ ))
+ ->where('flappingevent_id', $id)
+ ->where('flappingevent_event_type', $type);
+ case 'notificationevent':
+ return $this->backend->select()
+ ->from('notificationevent', array(
+ 'notification_reason' => 'notificationevent_reason',
+ 'start_time' => 'notificationevent_start_time',
+ 'end_time' => 'notificationevent_end_time',
+ 'state' => 'notificationevent_state',
+ 'output' => 'notificationevent_output',
+ 'long_output' => 'notificationevent_long_output',
+ 'escalated' => 'notificationevent_escalated',
+ 'contacts_notified' => 'notificationevent_contacts_notified',
+ 'host_name',
+ 'service_description'
+ ))
+ ->where('notificationevent_id', $id);
+ case 'statechangeevent':
+ return $this->backend->select()
+ ->from('statechangeevent', array(
+ 'state_time' => 'statechangeevent_state_time',
+ 'state' => 'statechangeevent_state',
+ 'current_check_attempt' => 'statechangeevent_current_check_attempt',
+ 'max_check_attempts' => 'statechangeevent_max_check_attempts',
+ 'last_state' => 'statechangeevent_last_state',
+ 'last_hard_state' => 'statechangeevent_last_hard_state',
+ 'output' => 'statechangeevent_output',
+ 'long_output' => 'statechangeevent_long_output',
+ 'check_source' => 'statechangeevent_check_source',
+ 'host_name',
+ 'service_description'
+ ))
+ ->where('statechangeevent_id', $id)
+ ->where('statechangeevent_state_change', 1)
+ ->where('statechangeevent_state_type', $type);
+ }
+ }
+
+ /**
+ * Return the given event's data prepared for a name-value table
+ *
+ * @param string $type
+ * @param \stdClass $event
+ *
+ * @return ?string[][]
+ */
+ protected function getDetails($type, $event)
+ {
+ switch ($type) {
+ case 'dt_start':
+ case 'dt_end':
+ $details = array(array(
+ array($this->translate('Entry time'), DateFormatter::formatDateTime($event->entry_time)),
+ array($this->translate('Is fixed'), $this->yesOrNo($event->is_fixed)),
+ array($this->translate('Is in effect'), $this->yesOrNo($event->is_in_effect)),
+ array($this->translate('Was started'), $this->yesOrNo($event->was_started))
+ ));
+
+ if ($type === 'dt_end') {
+ $details[] = array(
+ array($this->translate('Was cancelled'), $this->yesOrNo($event->was_cancelled))
+ );
+ }
+
+ $details[] = array(
+ array($this->translate('Trigger time'), DateFormatter::formatDateTime($event->trigger_time)),
+ array(
+ $this->translate('Scheduled start time'),
+ DateFormatter::formatDateTime($event->scheduled_start_time)
+ ),
+ array(
+ $this->translate('Actual start time'),
+ DateFormatter::formatDateTime($event->actual_start_time)
+ ),
+ array(
+ $this->translate('Scheduled end time'),
+ DateFormatter::formatDateTime($event->scheduled_end_time)
+ )
+ );
+
+ if ($type === 'dt_end') {
+ $details[] = array(
+ array(
+ $this->translate('Actual end time'),
+ DateFormatter::formatDateTime($event->actual_end_time)
+ )
+ );
+ }
+
+ $details[] = array(
+ array($this->translate('Author'), $this->contact($event->author_name)),
+ array($this->translate('Comment'), $this->comment($event->comment_data))
+ );
+
+ return call_user_func_array('array_merge', $details);
+ case 'comment':
+ case 'comment_deleted':
+ case 'ack':
+ case 'ack_deleted':
+ case 'dt_comment':
+ case 'dt_comment_deleted':
+ switch ($event->entry_type) {
+ case 'comment':
+ $entryType = $this->translate('User comment');
+ break;
+ case 'downtime':
+ $entryType = $this->translate('Scheduled downtime');
+ break;
+ case 'flapping':
+ $entryType = $this->translate('Flapping');
+ break;
+ case 'ack':
+ $entryType = $this->translate('Acknowledgement');
+ break;
+ default:
+ $entryType = $this->translate('N/A');
+ }
+
+ switch ($event->comment_source) {
+ case 'icinga':
+ $commentSource = $this->translate('Icinga');
+ break;
+ case 'user':
+ $commentSource = $this->translate('User');
+ break;
+ default:
+ $commentSource = $this->translate('N/A');
+ }
+
+ return array(
+ array($this->translate('Time'), DateFormatter::formatDateTime($event->comment_time)),
+ array($this->translate('Source'), $this->view->escape($commentSource)),
+ array($this->translate('Entry type'), $this->view->escape($entryType)),
+ array($this->translate('Author'), $this->contact($event->author_name)),
+ array($this->translate('Is persistent'), $this->yesOrNo($event->is_persistent)),
+ array($this->translate('Expires'), $this->yesOrNo($event->expires)),
+ array($this->translate('Expiration time'), DateFormatter::formatDateTime($event->expiration_time)),
+ array($this->translate('Deletion time'), DateFormatter::formatDateTime($event->deletion_time)),
+ array($this->translate('Message'), $this->comment($event->comment_data))
+ );
+ case 'flapping':
+ case 'flapping_deleted':
+ switch ($event->reason_type) {
+ case 'stopped':
+ $reasonType = $this->translate('Flapping stopped normally');
+ break;
+ case 'disabled':
+ $reasonType = $this->translate('Flapping was disabled');
+ break;
+ default:
+ $reasonType = $this->translate('N/A');
+ }
+
+ return array(
+ array($this->translate('Event time'), DateFormatter::formatDateTime($event->event_time)),
+ array($this->translate('Reason'), $this->view->escape($reasonType)),
+ array($this->translate('State change'), $this->percent($event->percent_state_change)),
+ array($this->translate('Low threshold'), $this->percent($event->low_threshold)),
+ array($this->translate('High threshold'), $this->percent($event->high_threshold))
+ );
+ case 'notify':
+ switch ($event->notification_reason) {
+ case 'normal_notification':
+ $notificationReason = $this->translate('Normal notification');
+ break;
+ case 'ack':
+ $notificationReason = $this->translate('Problem acknowledgement');
+ break;
+ case 'flapping_started':
+ $notificationReason = $this->translate('Flapping started');
+ break;
+ case 'flapping_stopped':
+ $notificationReason = $this->translate('Flapping stopped');
+ break;
+ case 'flapping_disabled':
+ $notificationReason = $this->translate('Flapping was disabled');
+ break;
+ case 'dt_start':
+ $notificationReason = $this->translate('Downtime started');
+ break;
+ case 'dt_end':
+ $notificationReason = $this->translate('Downtime ended');
+ break;
+ case 'dt_cancel':
+ $notificationReason = $this->translate('Downtime was cancelled');
+ break;
+ case 'custom_notification':
+ $notificationReason = $this->translate('Custom notification');
+ break;
+ default:
+ $notificationReason = $this->translate('N/A');
+ }
+
+ $details = array(
+ array($this->translate('Start time'), DateFormatter::formatDateTime($event->start_time)),
+ array($this->translate('End time'), DateFormatter::formatDateTime($event->end_time)),
+ array($this->translate('Reason'), $this->view->escape($notificationReason)),
+ array(
+ $this->translate('State'),
+ $this->state($event->service_description !== null, $event->state)
+ ),
+ array($this->translate('Escalated'), $this->yesOrNo($event->escalated)),
+ array($this->translate('Contacts notified'), (int) $event->contacts_notified),
+ array(
+ $this->translate('Output'),
+ $this->pluginOutput($event->output) . $this->pluginOutput($event->long_output)
+ )
+ );
+
+ return $details;
+ case 'hard_state':
+ case 'soft_state':
+ $isService = $event->service_description !== null;
+
+ $details = array(
+ array($this->translate('State time'), DateFormatter::formatDateTime($event->state_time)),
+ array($this->translate('State'), $this->state($isService, $event->state)),
+ array($this->translate('Check source'), $event->check_source),
+ array($this->translate('Check attempt'), $this->view->escape(sprintf(
+ $this->translate('%d of %d'),
+ (int) $event->current_check_attempt,
+ (int) $event->max_check_attempts
+ ))),
+ array($this->translate('Last state'), $this->state($isService, $event->last_state)),
+ array($this->translate('Last hard state'), $this->state($isService, $event->last_hard_state)),
+ array(
+ $this->translate('Output'),
+ $this->pluginOutput($event->output) . $this->pluginOutput($event->long_output)
+ )
+ );
+
+ return $details;
+ }
+ }
+}