getTabs()->add( 'timeline', array( 'title' => $this->translate('Show the number of historical event records grouped by time and type'), 'label' => $this->translate('Timeline'), 'url' => Url::fromRequest() ) )->extend(new DashboardAction())->extend(new MenuAction())->activate('timeline'); $this->view->title = $this->translate('Timeline'); // TODO: filter for hard_states (precedence adjustments necessary!) $this->setupIntervalBox(); list($displayRange, $forecastRange) = $this->buildTimeRanges(); $detailUrl = Url::fromPath('monitoring/list/eventhistory'); $timeline = new TimeLine( $this->applyRestriction( 'monitoring/filter/objects', $this->backend->select()->from( 'eventhistory', array( 'name' => 'type', 'time' => 'timestamp' ) ) ), array( 'notification_ack' => array( 'class' => 'timeline-notification', 'detailUrl' => $detailUrl, 'label' => mt('monitoring', 'Notifications'), 'groupBy' => 'notification_*' ), 'notification_flapping' => array( 'class' => 'timeline-notification', 'detailUrl' => $detailUrl, 'label' => mt('monitoring', 'Notifications'), 'groupBy' => 'notification_*' ), 'notification_flapping_end' => array( 'class' => 'timeline-notification', 'detailUrl' => $detailUrl, 'label' => mt('monitoring', 'Notifications'), 'groupBy' => 'notification_*' ), 'notification_dt_start' => array( 'class' => 'timeline-notification', 'detailUrl' => $detailUrl, 'label' => mt('monitoring', 'Notifications'), 'groupBy' => 'notification_*' ), 'notification_dt_end' => array( 'class' => 'timeline-notification', 'detailUrl' => $detailUrl, 'label' => mt('monitoring', 'Notifications'), 'groupBy' => 'notification_*' ), 'notification_custom' => array( 'class' => 'timeline-notification', 'detailUrl' => $detailUrl, 'label' => mt('monitoring', 'Notifications'), 'groupBy' => 'notification_*' ), 'notification_state' => array( 'class' => 'timeline-notification', 'detailUrl' => $detailUrl, 'label' => mt('monitoring', 'Notifications'), 'groupBy' => 'notification_*' ), 'hard_state' => array( 'class' => 'timeline-hard-state', 'detailUrl' => $detailUrl, 'label' => mt('monitoring', 'Hard state changes') ), 'comment' => array( 'class' => 'timeline-comment', 'detailUrl' => $detailUrl, 'label' => mt('monitoring', 'Comments') ), 'ack' => array( 'class' => 'timeline-ack', 'detailUrl' => $detailUrl, 'label' => mt('monitoring', 'Acknowledgements') ), 'dt_start' => array( 'class' => 'timeline-downtime-start', 'detailUrl' => $detailUrl, 'label' => mt('monitoring', 'Started downtimes') ), 'dt_end' => array( 'class' => 'timeline-downtime-end', 'detailUrl' => $detailUrl, 'label' => mt('monitoring', 'Ended downtimes') ) ) ); $timeline->setMaximumCircleWidth('6em'); $timeline->setMinimumCircleWidth('0.3em'); $timeline->setDisplayRange($displayRange); $timeline->setForecastRange($forecastRange); $beingExtended = $this->getRequest()->getParam('extend') == 1; $timeline->setSession($this->Window()->getSessionNamespace('timeline', !$beingExtended)); $this->view->timeline = $timeline; $this->view->nextRange = $forecastRange; $this->view->beingExtended = $beingExtended; $this->view->intervalFormat = $this->getIntervalFormat(); $oldBase = $timeline->getCalculationBase(false); $this->view->switchedContext = $oldBase !== null && $oldBase !== $timeline->getCalculationBase(true); } /** * Create a select box the user can choose the timeline interval from */ private function setupIntervalBox() { $box = new SelectBox( 'intervalBox', array( '4h' => mt('monitoring', '4 Hours'), '1d' => mt('monitoring', 'One day'), '1w' => mt('monitoring', 'One week'), '1m' => mt('monitoring', 'One month'), '1y' => mt('monitoring', 'One year') ), mt('monitoring', 'TimeLine interval'), 'interval' ); $box->applyRequest($this->getRequest()); $this->view->intervalBox = $box; } /** * Return the chosen interval * * @return DateInterval The chosen interval */ private function getTimelineInterval() { switch ($this->view->intervalBox->getInterval()) { case '1d': return new DateInterval('P1D'); case '1w': return new DateInterval('P1W'); case '1m': return new DateInterval('P1M'); case '1y': return new DateInterval('P1Y'); default: return new DateInterval('PT4H'); } } /** * Get an appropriate datetime format string for the chosen interval * * @return string */ private function getIntervalFormat() { switch ($this->view->intervalBox->getInterval()) { case '1d': return $this->getDateFormat(); case '1w': return '\W\e\ek W\\of Y'; case '1m': return 'F Y'; case '1y': return 'Y'; default: return $this->getDateFormat() . '\' . $this->getTimeFormat(); } } /** * Return a preload interval based on the chosen timeline interval and the given date and time * * @param DateTime $dateTime The date and time to use * * @return DateInterval The interval to pre-load */ private function getPreloadInterval(DateTime $dateTime) { switch ($this->view->intervalBox->getInterval()) { case '1d': return DateInterval::createFromDateString('1 week -1 second'); case '1w': return DateInterval::createFromDateString('8 weeks -1 second'); case '1m': $dateCopy = clone $dateTime; for ($i = 0; $i < 6; $i++) { $dateCopy->sub(new DateInterval('PT' . Format::secondsByMonth($dateCopy) . 'S')); } return $dateCopy->add(new DateInterval('PT1S'))->diff($dateTime); case '1y': $dateCopy = clone $dateTime; for ($i = 0; $i < 4; $i++) { $dateCopy->sub(new DateInterval('PT' . Format::secondsByYear($dateCopy) . 'S')); } return $dateCopy->add(new DateInterval('PT1S'))->diff($dateTime); default: return DateInterval::createFromDateString('1 day -1 second'); } } /** * Extrapolate the given datetime based on the chosen timeline interval * * @param DateTime $dateTime The datetime to extrapolate */ private function extrapolateDateTime(DateTime &$dateTime) { switch ($this->view->intervalBox->getInterval()) { case '1d': $dateTime->setTimestamp(strtotime('tomorrow', $dateTime->getTimestamp()) - 1); break; case '1w': $dateTime->setTimestamp(strtotime('next monday', $dateTime->getTimestamp()) - 1); break; case '1m': $dateTime->setTimestamp( strtotime( 'last day of this month', strtotime( 'tomorrow', $dateTime->getTimestamp() ) - 1 ) ); break; case '1y': $dateTime->setTimestamp(strtotime('1 january next year', $dateTime->getTimestamp()) - 1); break; default: $hour = $dateTime->format('G'); $end = $hour < 4 ? 4 : ($hour < 8 ? 8 : ($hour < 12 ? 12 : ($hour < 16 ? 16 : ($hour < 20 ? 20 : 24)))); $dateTime = DateTime::createFromFormat( 'd/m/y G:i:s', $dateTime->format('d/m/y') . ($end - 1) . ':59:59' ); } } /** * Return a display- and forecast time range * * Assembles a time range each for display and forecast purposes based on the start- and * end time if given in the current request otherwise based on the current time and a * end time that is calculated based on the chosen timeline interval. * * @return array The resulting time ranges */ private function buildTimeRanges() { $startTime = new DateTime(); $startParam = $this->_request->getParam('start'); $startTimestamp = is_numeric($startParam) ? intval($startParam) : strtotime($startParam ?? ''); if ($startTimestamp !== false) { $startTime->setTimestamp($startTimestamp); } else { $this->extrapolateDateTime($startTime); } $endTime = clone $startTime; $endParam = $this->_request->getParam('end'); $endTimestamp = is_numeric($endParam) ? intval($endParam) : strtotime($endParam ?? ''); if ($endTimestamp !== false) { $endTime->setTimestamp($endTimestamp); } else { $endTime->sub($this->getPreloadInterval($startTime)); } $forecastStart = clone $endTime; $forecastStart->sub(new DateInterval('PT1S')); $forecastEnd = clone $forecastStart; $forecastEnd->sub($this->getPreloadInterval($forecastStart)); $timelineInterval = $this->getTimelineInterval(); return array( new TimeRange($startTime, $endTime, $timelineInterval), new TimeRange($forecastStart, $forecastEnd, $timelineInterval) ); } /** * Get the user's preferred time format or the application's default * * @return string */ private function getTimeFormat() { return 'H:i'; } /** * Get the user's preferred date format or the application's default * * @return string */ private function getDateFormat() { return 'Y-m-d'; } }