mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-26 15:24:05 +02:00
Merge branch 'master' into bugfix/commands-6593
Conflicts: modules/monitoring/application/controllers/TimelineController.php
This commit is contained in:
commit
cde3ec6bc8
@ -26,7 +26,8 @@ class JavaScript
|
|||||||
'js/icinga/behavior/tooltip.js',
|
'js/icinga/behavior/tooltip.js',
|
||||||
'js/icinga/behavior/sparkline.js',
|
'js/icinga/behavior/sparkline.js',
|
||||||
'js/icinga/behavior/tristate.js',
|
'js/icinga/behavior/tristate.js',
|
||||||
'js/icinga/behavior/navigation.js'
|
'js/icinga/behavior/navigation.js',
|
||||||
|
'js/icinga/behavior/form.js'
|
||||||
);
|
);
|
||||||
|
|
||||||
protected static $vendorFiles = array(
|
protected static $vendorFiles = array(
|
||||||
|
205
library/Icinga/Web/View/DateTimeRenderer.php
Normal file
205
library/Icinga/Web/View/DateTimeRenderer.php
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
<?php
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
namespace Icinga\Web\View;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
class DateTimeRenderer
|
||||||
|
{
|
||||||
|
const TYPE_DATETIME = 0;
|
||||||
|
const TYPE_TIME = 1;
|
||||||
|
const TYPE_TIMESPAN = 2;
|
||||||
|
|
||||||
|
const HOUR = 3600;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The present DateTime
|
||||||
|
*
|
||||||
|
* @var DateTime
|
||||||
|
*/
|
||||||
|
protected $now;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The DateTime tense
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $future;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The given DateTime type
|
||||||
|
*
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
protected $type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The given DateTime
|
||||||
|
*
|
||||||
|
* @var DateTime
|
||||||
|
*/
|
||||||
|
protected $dateTime;
|
||||||
|
|
||||||
|
public function __construct($dateTime, $future = false)
|
||||||
|
{
|
||||||
|
$this->future = $future;
|
||||||
|
$this->now = new DateTime();
|
||||||
|
$this->dateTime = $this->normalize($dateTime);
|
||||||
|
$this->detectType();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new DateTimeRenderer
|
||||||
|
*
|
||||||
|
* @param DateTime|int $dateTime
|
||||||
|
* @param bool $future
|
||||||
|
*
|
||||||
|
* @return DateTimeRenderer
|
||||||
|
*/
|
||||||
|
public static function create($dateTime, $future = false)
|
||||||
|
{
|
||||||
|
return new static($dateTime, $future);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects the DateTime context
|
||||||
|
*/
|
||||||
|
protected function detectType()
|
||||||
|
{
|
||||||
|
if ($this->now->format('Y-m-d') !== $this->dateTime->format('Y-m-d')) {
|
||||||
|
$this->type = self::TYPE_DATETIME;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
$this->now->format('Y-m-d') === $this->dateTime->format('Y-m-d') &&
|
||||||
|
(abs($this->now->getTimestamp() - $this->dateTime->getTimestamp()) >= self::HOUR)
|
||||||
|
) {
|
||||||
|
$this->type = self::TYPE_TIME;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
$this->now->format('Y-m-d') === $this->dateTime->format('Y-m-d') &&
|
||||||
|
(abs($this->now->getTimestamp() - $this->dateTime->getTimestamp()) < self::HOUR)
|
||||||
|
) {
|
||||||
|
$this->type = self::TYPE_TIMESPAN;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes the given DateTime
|
||||||
|
*
|
||||||
|
* @param DateTime|int $dateTime
|
||||||
|
*
|
||||||
|
* @return DateTime
|
||||||
|
*/
|
||||||
|
public static function normalize($dateTime)
|
||||||
|
{
|
||||||
|
if (! ($dt = $dateTime) instanceof DateTime) {
|
||||||
|
$dt = new DateTime();
|
||||||
|
$dt->setTimestamp($dateTime);
|
||||||
|
}
|
||||||
|
return $dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether DateTime is a date with time
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isDateTime()
|
||||||
|
{
|
||||||
|
return $this->type === self::TYPE_DATETIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether DateTime is a time of the current day
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isTime()
|
||||||
|
{
|
||||||
|
return $this->type === self::TYPE_TIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether DateTime is in a defined interval
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isTimespan()
|
||||||
|
{
|
||||||
|
return $this->type === self::TYPE_TIMESPAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of the DateTime
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getType()
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the DateTime on the basis of the type and returns suited text
|
||||||
|
*
|
||||||
|
* @param string $dateTimeText
|
||||||
|
* @param string $timeText
|
||||||
|
* @param string $timespanText
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function render($dateTimeText, $timeText, $timespanText)
|
||||||
|
{
|
||||||
|
if ($this->isDateTime()) {
|
||||||
|
return sprintf($dateTimeText, $this);
|
||||||
|
} elseif ($this->isTime()) {
|
||||||
|
return sprintf($timeText, $this);
|
||||||
|
} elseif ($this->isTimespan()) {
|
||||||
|
return sprintf($timespanText, $this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dateTimeText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a rendered html wrapped text
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
switch ($this->type) {
|
||||||
|
case self::TYPE_DATETIME:
|
||||||
|
$format = $this->dateTime->format('d.m.Y - H:i:s');
|
||||||
|
break;
|
||||||
|
case self::TYPE_TIME:
|
||||||
|
$format = $this->dateTime->format('H:i:s');
|
||||||
|
break;
|
||||||
|
case self::TYPE_TIMESPAN:
|
||||||
|
$format = $this->dateTime->diff($this->now)->format(t('%im %ss', 'timespan'));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$format = $this->dateTime->format('d.m.Y - H:i:s');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$css = '';
|
||||||
|
if ($this->type === self::TYPE_TIMESPAN) {
|
||||||
|
$css = $this->future === true ? 'timeuntil' : 'timesince';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'<span class="%s" title="%s">%s</span>',
|
||||||
|
$css,
|
||||||
|
$this->dateTime->format('d.m.Y - H:i:s'),
|
||||||
|
$format
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -44,3 +44,7 @@ $this->addHelperFunction('prefixedTimeUntil', function ($timestamp, $ucfirst = f
|
|||||||
Format::prefixedTimeUntil($timestamp, $ucfirst)
|
Format::prefixedTimeUntil($timestamp, $ucfirst)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$this->addHelperFunction('dateTimeRenderer', function ($dateTimeOrTimestamp, $future = false) {
|
||||||
|
return DateTimeRenderer::create($dateTimeOrTimestamp, $future);
|
||||||
|
});
|
||||||
|
@ -28,29 +28,32 @@ class HistoryColorGrid extends AbstractWidget {
|
|||||||
private $maxValue = 1;
|
private $maxValue = 1;
|
||||||
|
|
||||||
private $start = null;
|
private $start = null;
|
||||||
|
|
||||||
private $end = null;
|
private $end = null;
|
||||||
|
|
||||||
private $data = array();
|
private $data = array();
|
||||||
|
|
||||||
private $color;
|
private $color;
|
||||||
|
public $opacity = 1.0;
|
||||||
|
|
||||||
public function __construct($color = '#51e551') {
|
public function __construct($color = '#51e551', $start = null, $end = null) {
|
||||||
$this->setColor($color);
|
$this->setColor($color);
|
||||||
|
if (isset($start)) {
|
||||||
|
$this->start = $this->tsToDateStr($start);
|
||||||
|
}
|
||||||
|
if (isset($end)) {
|
||||||
|
$this->end = $this->tsToDateStr($end);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the displayed data-set
|
* Set the displayed data-set
|
||||||
*
|
*
|
||||||
* @param $data array The values to display.
|
* @param $events array The history events to display as an array of arrays:
|
||||||
* properties for each entry:
|
|
||||||
* value: The value to display
|
* value: The value to display
|
||||||
* caption: The caption on mouse-over
|
* caption: The caption on mouse-over
|
||||||
* url: The url to open on click.
|
* url: The url to open on click.
|
||||||
*/
|
*/
|
||||||
public function setData(array $data)
|
public function setData(array $events)
|
||||||
{
|
{
|
||||||
$this->data = $data;
|
$this->data = $events;
|
||||||
$start = time();
|
$start = time();
|
||||||
$end = time();
|
$end = time();
|
||||||
foreach ($this->data as $entry) {
|
foreach ($this->data as $entry) {
|
||||||
@ -68,9 +71,13 @@ class HistoryColorGrid extends AbstractWidget {
|
|||||||
$start = $time;
|
$start = $time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!isset($this->start)) {
|
||||||
$this->start = $this->tsToDateStr($start);
|
$this->start = $this->tsToDateStr($start);
|
||||||
|
}
|
||||||
|
if (!isset($this->end)) {
|
||||||
$this->end = $this->tsToDateStr($end);
|
$this->end = $this->tsToDateStr($end);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the used color.
|
* Set the used color.
|
||||||
@ -82,6 +89,16 @@ class HistoryColorGrid extends AbstractWidget {
|
|||||||
$this->color = $color;
|
$this->color = $color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the used opacity
|
||||||
|
*
|
||||||
|
* @param $opacity
|
||||||
|
*/
|
||||||
|
public function setOpacity($opacity)
|
||||||
|
{
|
||||||
|
$this->opacity = $opacity;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the color to display for the given value.
|
* Calculate the color to display for the given value.
|
||||||
*
|
*
|
||||||
@ -104,16 +121,17 @@ class HistoryColorGrid extends AbstractWidget {
|
|||||||
*/
|
*/
|
||||||
private function renderDay($day)
|
private function renderDay($day)
|
||||||
{
|
{
|
||||||
if (array_key_exists($day, $this->data)) {
|
if (array_key_exists($day, $this->data) && $this->data[$day]['value'] > 0) {
|
||||||
$entry = $this->data[$day];
|
$entry = $this->data[$day];
|
||||||
return'<a ' .
|
return'<a ' .
|
||||||
'style="background-color:' . $this->calculateColor($entry['value']) . ';" ' .
|
'style="background-color:' . $this->calculateColor($entry['value']) . '; '
|
||||||
|
. ' opacity: ' . $this->opacity . ';"' .
|
||||||
'title="' . $entry['caption'] . '" ' .
|
'title="' . $entry['caption'] . '" ' .
|
||||||
'href="' . $entry['url'] . '"' .
|
'href="' . $entry['url'] . '"' .
|
||||||
'> </a>';
|
'> </a>';
|
||||||
} else {
|
} else {
|
||||||
return '<a ' .
|
return '<a ' .
|
||||||
'style="background-color:' . $this->calculateColor(0) . ';" ' .
|
'style="background-color:' . $this->calculateColor(0) . '; ' . ' opacity: ' . $this->opacity . ';" ' .
|
||||||
'title="No entries for ' . $day . '" ' .
|
'title="No entries for ' . $day . '" ' .
|
||||||
'></a>';
|
'></a>';
|
||||||
}
|
}
|
||||||
@ -130,13 +148,14 @@ class HistoryColorGrid extends AbstractWidget {
|
|||||||
{
|
{
|
||||||
$weeks = $grid['weeks'];
|
$weeks = $grid['weeks'];
|
||||||
$months = $grid['months'];
|
$months = $grid['months'];
|
||||||
|
$years = $grid['years'];
|
||||||
$html = '<table class="historycolorgrid">';
|
$html = '<table class="historycolorgrid">';
|
||||||
$html .= '<tr><th></th>';
|
$html .= '<tr><th></th>';
|
||||||
$old = -1;
|
$old = -1;
|
||||||
foreach ($months as $month) {
|
foreach ($months as $week => $month) {
|
||||||
if ($old !== $month) {
|
if ($old !== $month) {
|
||||||
$old = $month;
|
$old = $month;
|
||||||
$txt = $this->monthName($month);
|
$txt = $this->monthName($month, $years[$week]);
|
||||||
} else {
|
} else {
|
||||||
$txt = '';
|
$txt = '';
|
||||||
}
|
}
|
||||||
@ -157,6 +176,7 @@ class HistoryColorGrid extends AbstractWidget {
|
|||||||
*/
|
*/
|
||||||
private function renderVertical($grid)
|
private function renderVertical($grid)
|
||||||
{
|
{
|
||||||
|
$years = $grid['years'];
|
||||||
$weeks = $grid['weeks'];
|
$weeks = $grid['weeks'];
|
||||||
$months = $grid['months'];
|
$months = $grid['months'];
|
||||||
$html = '<table class="historycolorgrid">';
|
$html = '<table class="historycolorgrid">';
|
||||||
@ -176,7 +196,7 @@ class HistoryColorGrid extends AbstractWidget {
|
|||||||
}
|
}
|
||||||
if ($old !== $months[$index]) {
|
if ($old !== $months[$index]) {
|
||||||
$old = $months[$index];
|
$old = $months[$index];
|
||||||
$txt = $this->monthName($old);
|
$txt = $this->monthName($old, $years[$index]);
|
||||||
} else {
|
} else {
|
||||||
$txt = '';
|
$txt = '';
|
||||||
}
|
}
|
||||||
@ -220,6 +240,7 @@ class HistoryColorGrid extends AbstractWidget {
|
|||||||
$weeks = array(array());
|
$weeks = array(array());
|
||||||
$week = 0;
|
$week = 0;
|
||||||
$months = array();
|
$months = array();
|
||||||
|
$years = array();
|
||||||
$start = strtotime($this->start);
|
$start = strtotime($this->start);
|
||||||
$year = intval(date('Y', $start));
|
$year = intval(date('Y', $start));
|
||||||
$month = intval(date('n', $start));
|
$month = intval(date('n', $start));
|
||||||
@ -232,6 +253,7 @@ class HistoryColorGrid extends AbstractWidget {
|
|||||||
|
|
||||||
$date = $this->toDateStr($day, $month, $year);
|
$date = $this->toDateStr($day, $month, $year);
|
||||||
$weeks[0][$weekday] = $date;
|
$weeks[0][$weekday] = $date;
|
||||||
|
$years[0] = $year;
|
||||||
$months[0] = $month;
|
$months[0] = $month;
|
||||||
while ($date !== $this->end) {
|
while ($date !== $this->end) {
|
||||||
$day++;
|
$day++;
|
||||||
@ -242,6 +264,7 @@ class HistoryColorGrid extends AbstractWidget {
|
|||||||
// PRESENT => The last day of week determines the month
|
// PRESENT => The last day of week determines the month
|
||||||
if ($this->weekFlow === self::CAL_GROW_INTO_PRESENT) {
|
if ($this->weekFlow === self::CAL_GROW_INTO_PRESENT) {
|
||||||
$months[$week] = $month;
|
$months[$week] = $month;
|
||||||
|
$years[$week] = $year;
|
||||||
}
|
}
|
||||||
$week++;
|
$week++;
|
||||||
}
|
}
|
||||||
@ -257,21 +280,25 @@ class HistoryColorGrid extends AbstractWidget {
|
|||||||
// PAST => The first day of each week determines the month
|
// PAST => The first day of each week determines the month
|
||||||
if ($this->weekFlow === self::CAL_GROW_INTO_PAST) {
|
if ($this->weekFlow === self::CAL_GROW_INTO_PAST) {
|
||||||
$months[$week] = $month;
|
$months[$week] = $month;
|
||||||
|
$years[$week] = $year;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$date = $this->toDateStr($day, $month, $year);
|
$date = $this->toDateStr($day, $month, $year);
|
||||||
$weeks[$week][$weekday] = $date;
|
$weeks[$week][$weekday] = $date;
|
||||||
};
|
};
|
||||||
|
$years[$week] = $year;
|
||||||
$months[$week] = $month;
|
$months[$week] = $month;
|
||||||
if ($this->weekFlow == self::CAL_GROW_INTO_PAST) {
|
if ($this->weekFlow == self::CAL_GROW_INTO_PAST) {
|
||||||
return array(
|
return array(
|
||||||
'weeks' => array_reverse($weeks),
|
'weeks' => array_reverse($weeks),
|
||||||
'months' => array_reverse($months)
|
'months' => array_reverse($months),
|
||||||
|
'years' => array_reverse($years)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return array(
|
return array(
|
||||||
'weeks' => $weeks,
|
'weeks' => $weeks,
|
||||||
'months' => $months
|
'months' => $months,
|
||||||
|
'years' => $years
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,9 +309,10 @@ class HistoryColorGrid extends AbstractWidget {
|
|||||||
*
|
*
|
||||||
* @return string The
|
* @return string The
|
||||||
*/
|
*/
|
||||||
private function monthName($month)
|
private function monthName($month, $year)
|
||||||
{
|
{
|
||||||
$dt = DateTimeFactory::create('2000-' . $month . '-01');
|
// TODO: find a way to render years without messing up the layout
|
||||||
|
$dt = DateTimeFactory::create($year . '-' . $month . '-01');
|
||||||
return $dt->format('M');
|
return $dt->format('M');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@ use Icinga\Web\Widget\FilterBox;
|
|||||||
use Icinga\Web\Widget\Chart\HistoryColorGrid;
|
use Icinga\Web\Widget\Chart\HistoryColorGrid;
|
||||||
use Icinga\Data\Filter\Filter;
|
use Icinga\Data\Filter\Filter;
|
||||||
use Icinga\Web\Widget;
|
use Icinga\Web\Widget;
|
||||||
|
use Icinga\Module\Monitoring\Web\Widget\SelectBox;
|
||||||
|
use Icinga\Module\Monitoring\Form\StatehistoryForm;
|
||||||
|
|
||||||
class Monitoring_ListController extends Controller
|
class Monitoring_ListController extends Controller
|
||||||
{
|
{
|
||||||
@ -362,15 +364,43 @@ class Monitoring_ListController extends Controller
|
|||||||
|
|
||||||
public function statehistorysummaryAction()
|
public function statehistorysummaryAction()
|
||||||
{
|
{
|
||||||
$this->addTitleTab('statehistorysummary', 'Critical Events');
|
$this->addTitleTab('statehistorysummary', 'State Summary');
|
||||||
|
|
||||||
|
$form = new StatehistoryForm();
|
||||||
|
$form->setEnctype(Zend_Form::ENCTYPE_URLENCODED);
|
||||||
|
$form->setMethod('get');
|
||||||
|
$form->setTokenDisabled();
|
||||||
|
$form->setRequest($this->getRequest());
|
||||||
|
$form->buildForm();
|
||||||
|
$this->view->form = $form;
|
||||||
|
|
||||||
|
$orientation = $this->params->shift('horizontal', 0) ? 'horizontal' : 'vertical';
|
||||||
|
|
||||||
|
$orientationBox = new SelectBox(
|
||||||
|
'orientation',
|
||||||
|
array(
|
||||||
|
'0' => t('Vertical'),
|
||||||
|
'1' => t('Horizontal')
|
||||||
|
),
|
||||||
|
t('Orientation'),
|
||||||
|
'horizontal'
|
||||||
|
);
|
||||||
|
$orientationBox->applyRequest($this->getRequest());
|
||||||
|
|
||||||
$query = $this->backend->select()->from(
|
$query = $this->backend->select()->from(
|
||||||
'stateHistorySummary',
|
'stateHistorySummary',
|
||||||
array('day', 'cnt_critical')
|
array('day', $form->getValue('state'))
|
||||||
)->getQuery()->order('day');
|
);
|
||||||
$query->limit(365);
|
$this->params->shift('objecttype');
|
||||||
$this->view->summary = $query->fetchAll();
|
$this->params->shift('from');
|
||||||
$this->view->grid = new HistoryColorGrid();
|
$this->params->shift('to');
|
||||||
$this->handleFormatRequest($query);
|
$this->params->shift('state');
|
||||||
|
$this->params->shift('btn_submit');
|
||||||
|
$this->applyFilters($query);
|
||||||
|
$this->view->summary = $query->getQuery()->fetchAll();
|
||||||
|
$this->view->column = $form->getValue('state');
|
||||||
|
$this->view->orientationBox = $orientationBox;
|
||||||
|
$this->view->orientation = $orientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function contactgroupsAction()
|
public function contactgroupsAction()
|
||||||
@ -653,6 +683,7 @@ class Monitoring_ListController extends Controller
|
|||||||
'hosts',
|
'hosts',
|
||||||
'services',
|
'services',
|
||||||
'eventhistory',
|
'eventhistory',
|
||||||
|
'statehistorysummary',
|
||||||
'notifications'
|
'notifications'
|
||||||
))) {
|
))) {
|
||||||
$tabs->extend(new OutputFormat())->extend(new DashboardAction());
|
$tabs->extend(new OutputFormat())->extend(new DashboardAction());
|
||||||
|
@ -12,7 +12,8 @@ use Icinga\Util\DateTimeFactory;
|
|||||||
use Icinga\Module\Monitoring\Controller;
|
use Icinga\Module\Monitoring\Controller;
|
||||||
use Icinga\Module\Monitoring\Timeline\TimeLine;
|
use Icinga\Module\Monitoring\Timeline\TimeLine;
|
||||||
use Icinga\Module\Monitoring\Timeline\TimeRange;
|
use Icinga\Module\Monitoring\Timeline\TimeRange;
|
||||||
use Icinga\Module\Monitoring\Web\Widget\TimelineIntervalBox;
|
use Icinga\Module\Monitoring\Web\Widget\SelectBox;
|
||||||
|
use Icinga\Module\Monitoring\DataView\EventHistory as EventHistoryView;
|
||||||
|
|
||||||
class Monitoring_TimelineController extends Controller
|
class Monitoring_TimelineController extends Controller
|
||||||
{
|
{
|
||||||
@ -84,7 +85,7 @@ class Monitoring_TimelineController extends Controller
|
|||||||
*/
|
*/
|
||||||
private function setupIntervalBox()
|
private function setupIntervalBox()
|
||||||
{
|
{
|
||||||
$box = new TimelineIntervalBox(
|
$box = new SelectBox(
|
||||||
'intervalBox',
|
'intervalBox',
|
||||||
array(
|
array(
|
||||||
'4h' => t('4 Hours'),
|
'4h' => t('4 Hours'),
|
||||||
@ -92,7 +93,9 @@ class Monitoring_TimelineController extends Controller
|
|||||||
'1w' => t('One week'),
|
'1w' => t('One week'),
|
||||||
'1m' => t('One month'),
|
'1m' => t('One month'),
|
||||||
'1y' => t('One year')
|
'1y' => t('One year')
|
||||||
)
|
),
|
||||||
|
t('TimeLine interval'),
|
||||||
|
'interval'
|
||||||
);
|
);
|
||||||
$box->applyRequest($this->getRequest());
|
$box->applyRequest($this->getRequest());
|
||||||
$this->view->intervalBox = $box;
|
$this->view->intervalBox = $box;
|
||||||
|
154
modules/monitoring/application/forms/StatehistoryForm.php
Normal file
154
modules/monitoring/application/forms/StatehistoryForm.php
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
<?php
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
namespace Icinga\Module\Monitoring\Form;
|
||||||
|
|
||||||
|
use \Zend_Form;
|
||||||
|
use Icinga\Web\Form;
|
||||||
|
use Icinga\Data\Filter\Filter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the filter for the statehistorysummary
|
||||||
|
*/
|
||||||
|
class StatehistoryForm extends Form
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Return the corresponding filter-object
|
||||||
|
*
|
||||||
|
* @returns Filter
|
||||||
|
*/
|
||||||
|
public function getFilter()
|
||||||
|
{
|
||||||
|
$baseFilter = Filter::matchAny(
|
||||||
|
Filter::expression('type', '=', 'hard_state')
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($this->getValue('objecttype', 'hosts') === 'hosts') {
|
||||||
|
$objectTypeFilter = Filter::expression('object_type', '=', 'host');
|
||||||
|
} else {
|
||||||
|
$objectTypeFilter = Filter::expression('object_type', '=', 'service');
|
||||||
|
}
|
||||||
|
|
||||||
|
$states = array(
|
||||||
|
'cnt_down_hard' => Filter::expression('state', '=', '1'),
|
||||||
|
'cnt_unreachable_hard' => Filter::expression('state', '=', '2'),
|
||||||
|
'cnt_up' => Filter::expression('state', '=', '0'),
|
||||||
|
'cnt_critical_hard' => Filter::expression('state', '=', '2'),
|
||||||
|
'cnt_warning_hard' => Filter::expression('state', '=', '1'),
|
||||||
|
'cnt_unknown_hard' => Filter::expression('state', '=', '3'),
|
||||||
|
'cnt_ok' => Filter::expression('state', '=', '0')
|
||||||
|
);
|
||||||
|
$state = $this->getValue('state', 'cnt_critical_hard');
|
||||||
|
$stateFilter = $states[$state];
|
||||||
|
if (in_array($state, array('cnt_ok', 'cnt_up'))) {
|
||||||
|
return Filter::matchAll($objectTypeFilter, $stateFilter);
|
||||||
|
}
|
||||||
|
return Filter::matchAll($baseFilter, $objectTypeFilter, $stateFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the confirmation form
|
||||||
|
*
|
||||||
|
* @see Form::create()
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
$this->addElement(
|
||||||
|
'select',
|
||||||
|
'from',
|
||||||
|
array(
|
||||||
|
'label' => t('From'),
|
||||||
|
'value' => $this->getRequest()->getParam('from', strtotime('3 months ago')),
|
||||||
|
'multiOptions' => array(
|
||||||
|
strtotime('midnight 3 months ago') => t('3 Months'),
|
||||||
|
strtotime('midnight 4 months ago') => t('4 Months'),
|
||||||
|
strtotime('midnight 8 months ago') => t('8 Months'),
|
||||||
|
strtotime('midnight 12 months ago') => t('1 Year'),
|
||||||
|
strtotime('midnight 24 months ago') => t('2 Years')
|
||||||
|
),
|
||||||
|
'class' => 'autosubmit'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$this->addElement(
|
||||||
|
'select',
|
||||||
|
'to',
|
||||||
|
array(
|
||||||
|
'label' => t('To'),
|
||||||
|
'value' => $this->getRequest()->getParam('to', time()),
|
||||||
|
'multiOptions' => array(
|
||||||
|
time() => t('Today')
|
||||||
|
),
|
||||||
|
'class' => 'autosubmit'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$objectType = $this->getRequest()->getParam('objecttype', 'services');
|
||||||
|
$this->addElement(
|
||||||
|
'select',
|
||||||
|
'objecttype',
|
||||||
|
array(
|
||||||
|
'label' => t('Object type'),
|
||||||
|
'value' => $objectType,
|
||||||
|
'multiOptions' => array(
|
||||||
|
'services' => t('Services'),
|
||||||
|
'hosts' => t('Hosts')
|
||||||
|
),
|
||||||
|
'class' => 'autosubmit'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if ($objectType === 'services') {
|
||||||
|
$serviceState = $this->getRequest()->getParam('state', 'cnt_critical_hard');
|
||||||
|
if (in_array($serviceState, array('cnt_down_hard', 'cnt_unreachable_hard', 'cnt_up'))) {
|
||||||
|
$serviceState = 'cnt_critical_hard';
|
||||||
|
}
|
||||||
|
$this->addElement(
|
||||||
|
'select',
|
||||||
|
'state',
|
||||||
|
array(
|
||||||
|
'label' => t('State'),
|
||||||
|
'value' => $serviceState,
|
||||||
|
'multiOptions' => array(
|
||||||
|
'cnt_critical_hard' => t('Critical'),
|
||||||
|
'cnt_warning_hard' => t('Warning'),
|
||||||
|
'cnt_unknown_hard' => t('Unknown'),
|
||||||
|
'cnt_ok' => t('Ok')
|
||||||
|
),
|
||||||
|
'class' => 'autosubmit'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$hostState = $this->getRequest()->getParam('state', 'cnt_down_hard');
|
||||||
|
if (in_array($hostState, array('cnt_ok', 'cnt_critical_hard', 'cnt_warning', 'cnt_unknown'))) {
|
||||||
|
$hostState = 'cnt_down_hard';
|
||||||
|
}
|
||||||
|
$this->addElement(
|
||||||
|
'select',
|
||||||
|
'state',
|
||||||
|
array(
|
||||||
|
'label' => t('State'),
|
||||||
|
'value' => $hostState,
|
||||||
|
'multiOptions' => array(
|
||||||
|
'cnt_up' => t('Up'),
|
||||||
|
'cnt_down_hard' => t('Down'),
|
||||||
|
'cnt_unreachable_hard' => t('Unreachable')
|
||||||
|
),
|
||||||
|
'class' => 'autosubmit'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->enableAutoSubmit(array('from', 'objecttype', 'state'));
|
||||||
|
$this->addElement(
|
||||||
|
'button',
|
||||||
|
'btn_submit',
|
||||||
|
array(
|
||||||
|
'type' => 'submit',
|
||||||
|
'escape' => false,
|
||||||
|
'value' => '1',
|
||||||
|
'class' => 'btn btn-cta btn-common',
|
||||||
|
'label' => t('Apply')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -44,7 +44,14 @@ foreach ($notifications as $notification):
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<tr class="state <?= $stateName ?>">
|
<tr class="state <?= $stateName ?>">
|
||||||
<td class="state"><?= $this->timeSince($notification->notification_start_time) ?></td>
|
<td class="state">
|
||||||
|
<?= $this->dateTimeRenderer($notification->notification_start_time)->render(
|
||||||
|
$this->translate('on %s', 'datetime'),
|
||||||
|
$this->translate('at %s', 'time'),
|
||||||
|
$this->translate('%s ago', 'timespan')
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
<td style="font-size: 0.8em">
|
<td style="font-size: 0.8em">
|
||||||
<?php if ($isService): ?>
|
<?php if ($isService): ?>
|
||||||
<a href="<?= $href ?>"><?= $notification->service ?></a> on <?= $notification->host ?>
|
<a href="<?= $href ?>"><?= $notification->service ?></a> on <?= $notification->host ?>
|
||||||
|
@ -1,42 +1,131 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Icinga\Data\Filter\Filter;
|
use Icinga\Data\Filter\Filter;
|
||||||
|
use Icinga\Web\Widget\Chart\HistoryColorGrid;
|
||||||
|
?>
|
||||||
|
|
||||||
|
|
||||||
|
<? if (!$compact): ?>
|
||||||
|
<div class="controls">
|
||||||
|
<?= $this->tabs->render($this); ?>
|
||||||
|
<div class="fake-controls">
|
||||||
|
<br />
|
||||||
|
<?= $form ?>
|
||||||
|
</div>
|
||||||
|
<?php if (! $this->filterEditor): ?>
|
||||||
|
<?= $this->filterPreview ?>
|
||||||
|
<?php endif ?>
|
||||||
|
<?= $this->filterEditor ?>
|
||||||
|
</div>
|
||||||
|
<? endif; ?>
|
||||||
|
|
||||||
?><div class="controls">
|
|
||||||
<?= $this->tabs ?>
|
|
||||||
<h1>History - Critical Events</h1>
|
|
||||||
</div>
|
|
||||||
<div class="content" data-base-target="_next">
|
<div class="content" data-base-target="_next">
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
$grid->setColor('#f05060');
|
$settings = array(
|
||||||
|
'cnt_up' => array(
|
||||||
|
'tooltip' => t('%d ok on %s'),
|
||||||
|
'color' => '#49DF96',
|
||||||
|
'opacity' => '0.55'
|
||||||
|
),
|
||||||
|
'cnt_unreachable_hard' => array(
|
||||||
|
'tooltip' => t('%d unreachable on %s'),
|
||||||
|
'color' => '#77AAFF',
|
||||||
|
'opacity' => '0.55'
|
||||||
|
),
|
||||||
|
'cnt_critical_hard' => array(
|
||||||
|
'tooltip' => t('%d critical on %s'),
|
||||||
|
'color' => '#ff5566',
|
||||||
|
'opacity' => '0.9'
|
||||||
|
),
|
||||||
|
|
||||||
|
'cnt_warning_hard' => array(
|
||||||
|
'tooltip' => t('%d warning on %s'),
|
||||||
|
'color' => '#ffaa44',
|
||||||
|
'opacity' => '1.0'
|
||||||
|
),
|
||||||
|
|
||||||
|
'cnt_down_hard' => array(
|
||||||
|
'tooltip' => t('%d down on %s'),
|
||||||
|
'color' => '#ff5566',
|
||||||
|
'opacity' => '0.9'
|
||||||
|
),
|
||||||
|
'cnt_unknown_hard' => array(
|
||||||
|
'tooltip' => t('%d unknown on %s'),
|
||||||
|
'color' => '#cc77ff',
|
||||||
|
'opacity' => '0.7'
|
||||||
|
),
|
||||||
|
'cnt_ok' => array(
|
||||||
|
'tooltip' => t('%d ok on %s'),
|
||||||
|
'color' => '#49DF96',
|
||||||
|
'opacity' => '0.55'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$from = intval($form->getValue('from', strtotime('3 months ago')));
|
||||||
|
$to = intval($form->getValue('to', time()));
|
||||||
|
|
||||||
|
// don't display more than ten years, or else this will get really slow
|
||||||
|
if ($to - $from > 315360000) {
|
||||||
|
$from = $to - 315360000;
|
||||||
|
}
|
||||||
$data = array();
|
$data = array();
|
||||||
|
|
||||||
if (count($summary) === 0) {
|
if (count($summary) === 0) {
|
||||||
echo t('No history entry matching the filter');
|
echo t('No state changes in the selected time period.');
|
||||||
}
|
}
|
||||||
foreach ($summary as $entry) {
|
foreach ($summary as $entry) {
|
||||||
$day = $entry->day;
|
$day = $entry->day;
|
||||||
$value = $entry->cnt_critical;
|
$value = $entry->$column;
|
||||||
$caption = $value . ' ' .
|
$caption = sprintf(
|
||||||
t('critical events on ') . $this->dateFormat()->formatDate(strtotime($day));
|
$settings[$column]['tooltip'],
|
||||||
$filter = Filter::matchAll(
|
$value,
|
||||||
|
$this->dateFormat()->formatDate(strtotime($day))
|
||||||
|
);
|
||||||
|
$linkFilter = Filter::matchAll(
|
||||||
Filter::expression('timestamp', '<', strtotime($day . ' 23:59:59')),
|
Filter::expression('timestamp', '<', strtotime($day . ' 23:59:59')),
|
||||||
Filter::expression('timestamp', '>', strtotime($day . ' 00:00:00')),
|
Filter::expression('timestamp', '>', strtotime($day . ' 00:00:00')),
|
||||||
Filter::expression('object_type', '=', 'service'),
|
$form->getFilter(),
|
||||||
Filter::expression('state', '=', '2'),
|
$filter
|
||||||
Filter::matchAny(
|
|
||||||
Filter::expression('type', '=', 'hard_state'),
|
|
||||||
Filter::expression('type', '=', 'hard_state')
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
$data[$day] = array(
|
$data[$day] = array(
|
||||||
'value' => $value,
|
'value' => $value,
|
||||||
'caption' => $caption,
|
'caption' => $caption,
|
||||||
'url' => $this->href('monitoring/list/eventhistory?' . $filter->toQueryString())
|
'url' => $this->href('monitoring/list/eventhistory?' . $linkFilter->toQueryString())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$grid->setData($data);
|
|
||||||
|
$f = new DateTime();
|
||||||
|
$f->setTimestamp($from);
|
||||||
|
$t = new DateTime();
|
||||||
|
$t->setTimestamp($to);
|
||||||
|
$diff = $t->diff($f);
|
||||||
|
$step = 124;
|
||||||
|
|
||||||
|
for ($i = 0; $i < $diff->days; $i += $step) {
|
||||||
|
$end = clone $f;
|
||||||
|
if ($diff->days - $i > $step) {
|
||||||
|
// full range, move last day to next chunk
|
||||||
|
$end->add(new DateInterval('P' . ($step - 1) . 'D'));
|
||||||
|
} else {
|
||||||
|
// include last day
|
||||||
|
$end->add(new DateInterval('P' . ($diff->days - $i) . 'D'));
|
||||||
|
}
|
||||||
|
$grid = new HistoryColorGrid(null, $f->getTimestamp(), $end->getTimestamp());
|
||||||
|
$grid->setColor($settings[$column]['color']);
|
||||||
|
$grid->opacity = $settings[$column]['opacity'];
|
||||||
|
$grid->orientation = $orientation;
|
||||||
|
$grid->setData($data);
|
||||||
|
$grids[] = $grid;
|
||||||
|
|
||||||
|
$f->add(new DateInterval('P' . $step . 'D'));
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
<?= $grid ?>
|
<div style="width: 33.5em;">
|
||||||
|
<? foreach (array_reverse($grids) as $key => $grid) { ?>
|
||||||
|
<div style=" <?= $this->orientation === 'horizontal' ? '' : 'display: inline-block; vertical-align: top; top; margin: 0.5em;' ?>">
|
||||||
|
<?= $grid; ?>
|
||||||
|
<?= $this->orientation === 'horizontal' ? '<br />' : '' ?>
|
||||||
|
</div>
|
||||||
|
<? } ?>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,6 +10,7 @@ class StateHistorySummaryQuery extends IdoQuery
|
|||||||
'statehistory' => array(
|
'statehistory' => array(
|
||||||
'day' => 'DATE(sh.state_time)',
|
'day' => 'DATE(sh.state_time)',
|
||||||
'cnt_events' => 'COUNT(*)',
|
'cnt_events' => 'COUNT(*)',
|
||||||
|
'objecttype_id' => 'sho.objecttype_id',
|
||||||
'cnt_up' => 'SUM(CASE WHEN sho.objecttype_id = 1 AND sh.state = 0 THEN 1 ELSE 0 END)',
|
'cnt_up' => 'SUM(CASE WHEN sho.objecttype_id = 1 AND sh.state = 0 THEN 1 ELSE 0 END)',
|
||||||
'cnt_down_hard' => 'SUM(CASE WHEN sho.objecttype_id = 1 AND sh.state = 1 AND state_type = 1 THEN 1 ELSE 0 END)',
|
'cnt_down_hard' => 'SUM(CASE WHEN sho.objecttype_id = 1 AND sh.state = 1 AND state_type = 1 THEN 1 ELSE 0 END)',
|
||||||
'cnt_down' => 'SUM(CASE WHEN sho.objecttype_id = 1 AND sh.state = 1 THEN 1 ELSE 0 END)',
|
'cnt_down' => 'SUM(CASE WHEN sho.objecttype_id = 1 AND sh.state = 1 THEN 1 ELSE 0 END)',
|
||||||
@ -23,6 +24,19 @@ class StateHistorySummaryQuery extends IdoQuery
|
|||||||
'cnt_warning' => 'SUM(CASE WHEN sho.objecttype_id = 2 AND sh.state = 1 THEN 1 ELSE 0 END)',
|
'cnt_warning' => 'SUM(CASE WHEN sho.objecttype_id = 2 AND sh.state = 1 THEN 1 ELSE 0 END)',
|
||||||
'cnt_warning_hard' => 'SUM(CASE WHEN sho.objecttype_id = 2 AND sh.state = 1 AND state_type = 1 THEN 1 ELSE 0 END)',
|
'cnt_warning_hard' => 'SUM(CASE WHEN sho.objecttype_id = 2 AND sh.state = 1 AND state_type = 1 THEN 1 ELSE 0 END)',
|
||||||
'cnt_ok' => 'SUM(CASE WHEN sho.objecttype_id = 2 AND sh.state = 0 THEN 1 ELSE 0 END)',
|
'cnt_ok' => 'SUM(CASE WHEN sho.objecttype_id = 2 AND sh.state = 0 THEN 1 ELSE 0 END)',
|
||||||
|
'host' => 'sho.name1 COLLATE latin1_general_ci',
|
||||||
|
'service' => 'sho.name2 COLLATE latin1_general_ci',
|
||||||
|
'host_name' => 'sho.name1 COLLATE latin1_general_ci',
|
||||||
|
'service_description' => 'sho.name2 COLLATE latin1_general_ci',
|
||||||
|
'timestamp' => 'UNIX_TIMESTAMP(sh.state_time)'
|
||||||
|
),
|
||||||
|
|
||||||
|
'servicegroups' => array(
|
||||||
|
'servicegroup' => 'sgo.name1'
|
||||||
|
),
|
||||||
|
|
||||||
|
'hostgroups' => array(
|
||||||
|
'hostgroup' => 'hgo.name1'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -35,8 +49,42 @@ class StateHistorySummaryQuery extends IdoQuery
|
|||||||
array('sho' => $this->prefix . 'objects'),
|
array('sho' => $this->prefix . 'objects'),
|
||||||
'sh.object_id = sho.object_id AND sho.is_active = 1',
|
'sh.object_id = sho.object_id AND sho.is_active = 1',
|
||||||
array()
|
array()
|
||||||
)->where('sh.state_time >= ?', '2013-11-20 00:00:00')
|
)
|
||||||
->group('DATE(sh.state_time)');
|
->group('DATE(sh.state_time)');
|
||||||
$this->joinedVirtualTables = array('statehistory' => true);
|
$this->joinedVirtualTables = array('statehistory' => true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function joinHostgroups()
|
||||||
|
{
|
||||||
|
$this->select->join(
|
||||||
|
array('hgm' => $this->prefix . 'hostgroup_members'),
|
||||||
|
'hgm.host_object_id = sho.object_id',
|
||||||
|
array()
|
||||||
|
)->join(
|
||||||
|
array('hgs' => $this->prefix . 'hostgroups'),
|
||||||
|
'hgm.hostgroup_id = hgs.hostgroup_id',
|
||||||
|
array()
|
||||||
|
)->join(
|
||||||
|
array('hgo' => $this->prefix . 'objects'),
|
||||||
|
'hgo.object_id = hgs.hostgroup_object_id',
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function joinServicegroups()
|
||||||
|
{
|
||||||
|
$this->select->join(
|
||||||
|
array('sgm' => $this->prefix . 'servicegroup_members'),
|
||||||
|
'sgm.service_object_id = sho.object_id',
|
||||||
|
array()
|
||||||
|
)->join(
|
||||||
|
array('sgs' => $this->prefix . 'servicegroups'),
|
||||||
|
'sgm.servicegroup_id = sgs.servicegroup_id',
|
||||||
|
array()
|
||||||
|
)->join(
|
||||||
|
array('sgo' => $this->prefix . 'objects'),
|
||||||
|
'sgo.object_id = sgs.servicegroup_object_id',
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,7 @@ use Icinga\Web\Form;
|
|||||||
use Icinga\Web\Request;
|
use Icinga\Web\Request;
|
||||||
use Icinga\Web\Widget\AbstractWidget;
|
use Icinga\Web\Widget\AbstractWidget;
|
||||||
|
|
||||||
/**
|
class SelectBox extends AbstractWidget
|
||||||
* @todo Might be better if this is a generic selection widget.
|
|
||||||
*/
|
|
||||||
class TimelineIntervalBox extends AbstractWidget
|
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The name of the form that will be created
|
* The name of the form that will be created
|
||||||
@ -27,6 +24,20 @@ class TimelineIntervalBox extends AbstractWidget
|
|||||||
*/
|
*/
|
||||||
private $values;
|
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
|
* A request object used for initial form population
|
||||||
*
|
*
|
||||||
@ -39,11 +50,15 @@ class TimelineIntervalBox extends AbstractWidget
|
|||||||
*
|
*
|
||||||
* @param string $name The name of the form that will be created
|
* @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 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)
|
public function __construct($name, array $values, $label = 'Select', $param = 'selection')
|
||||||
{
|
{
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
$this->values = $values;
|
$this->values = $values;
|
||||||
|
$this->label = $label;
|
||||||
|
$this->parameter = $param;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,9 +104,9 @@ class TimelineIntervalBox extends AbstractWidget
|
|||||||
$form->setName($this->name);
|
$form->setName($this->name);
|
||||||
$form->addElement(
|
$form->addElement(
|
||||||
'select',
|
'select',
|
||||||
'interval',
|
$this->parameter,
|
||||||
array(
|
array(
|
||||||
'label' => 'Timeline Interval',
|
'label' => $this->label,
|
||||||
'multiOptions' => $this->values,
|
'multiOptions' => $this->values,
|
||||||
'autosubmit' => true
|
'autosubmit' => true
|
||||||
)
|
)
|
@ -19,7 +19,7 @@ table.historycolorgrid td {
|
|||||||
margin: 1em;
|
margin: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.historycolorgrid td.hover {
|
table.historycolorgrid td:hover {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
104
public/js/icinga/behavior/form.js
Normal file
104
public/js/icinga/behavior/form.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controls behavior of form elements, depending reload and
|
||||||
|
*/
|
||||||
|
(function(Icinga, $) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Icinga.Behaviors = Icinga.Behaviors || {};
|
||||||
|
|
||||||
|
var Form = function (icinga) {
|
||||||
|
Icinga.EventListener.call(this, icinga);
|
||||||
|
this.on('keyup change', 'form input', this.onChange, this);
|
||||||
|
|
||||||
|
// store the modification state of all input fields
|
||||||
|
this.inputs = {};
|
||||||
|
};
|
||||||
|
Form.prototype = new Icinga.EventListener();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param evt
|
||||||
|
*/
|
||||||
|
Form.prototype.onChange = function (evt) {
|
||||||
|
var el = evt.target;
|
||||||
|
var form = evt.data.self.uniqueFormName($(el).closest('form')[0] || {});
|
||||||
|
evt.data.self.inputs[form] = evt.data.self.inputs[form] || {};
|
||||||
|
if (el.value !== '') {
|
||||||
|
evt.data.self.inputs[form][el.name] = true;
|
||||||
|
} else {
|
||||||
|
evt.data.self.inputs[form][el.name] = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to generate an unique form name using the action
|
||||||
|
* and the name of the given form element
|
||||||
|
*
|
||||||
|
* @param form {HTMLFormElement} The
|
||||||
|
* @returns {String} The unique name
|
||||||
|
*/
|
||||||
|
Form.prototype.uniqueFormName = function(form)
|
||||||
|
{
|
||||||
|
return (form.name || 'undefined') + '.' + (form.action || 'undefined');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutates the HTML before it is placed in the DOM after a reload
|
||||||
|
*
|
||||||
|
* @param content {String} The content to be rendered
|
||||||
|
* @param $container {jQuery} The target container where the html will be rendered in
|
||||||
|
* @param action {String} The action-url that caused the reload
|
||||||
|
* @param autorefresh {Boolean} Whether the rendering is due to an autoRefresh
|
||||||
|
*
|
||||||
|
* @returns {string|NULL} The content to be rendered, or NULL, when nothing should be changed
|
||||||
|
*/
|
||||||
|
Form.prototype.renderHook = function(content, $container, action, autorefresh) {
|
||||||
|
var origFocus = document.activeElement;
|
||||||
|
var containerId = $container.attr('id');
|
||||||
|
var icinga = this.icinga;
|
||||||
|
var self = this.icinga.behaviors.form;
|
||||||
|
var changed = false;
|
||||||
|
$container.find('form').each(function () {
|
||||||
|
var form = self.uniqueFormName(this);
|
||||||
|
if (autorefresh) {
|
||||||
|
// check if an element in this container was changed
|
||||||
|
$(this).find('input').each(function () {
|
||||||
|
var name = this.name;
|
||||||
|
if (self.inputs[form] && self.inputs[form][name]) {
|
||||||
|
icinga.logger.debug(
|
||||||
|
'form input: ' + form + '.' + name + ' was changed and aborts reload...'
|
||||||
|
);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// user-triggered reload, forget all changes to forms in this container
|
||||||
|
self.inputs[form] = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (changed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
// is the focus among the elements to be replaced?
|
||||||
|
$container.has(origFocus).length &&
|
||||||
|
// is an autorefresh
|
||||||
|
autorefresh &&
|
||||||
|
|
||||||
|
// and has focus
|
||||||
|
$(origFocus).length &&
|
||||||
|
!$(origFocus).hasClass('autofocus') &&
|
||||||
|
$(origFocus).closest('form').length
|
||||||
|
) {
|
||||||
|
icinga.logger.debug('Not changing content for ' + containerId + ' form has focus');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
};
|
||||||
|
|
||||||
|
Icinga.Behaviors.Form = Form;
|
||||||
|
|
||||||
|
}) (Icinga, jQuery);
|
@ -75,8 +75,9 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (document.activeElement === document.body) {
|
||||||
$('input.autofocus', el).focus();
|
$('input.autofocus', el).focus();
|
||||||
|
}
|
||||||
var searchField = $('#menu input.search', el);
|
var searchField = $('#menu input.search', el);
|
||||||
// Remember initial search field value if any
|
// Remember initial search field value if any
|
||||||
if (searchField.length && searchField.val().length) {
|
if (searchField.length && searchField.val().length) {
|
||||||
|
@ -661,6 +661,7 @@
|
|||||||
// Container update happens here
|
// Container update happens here
|
||||||
var scrollPos = false;
|
var scrollPos = false;
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var origFocus = document.activeElement;
|
||||||
var containerId = $container.attr('id');
|
var containerId = $container.attr('id');
|
||||||
if (typeof containerId !== 'undefined') {
|
if (typeof containerId !== 'undefined') {
|
||||||
if (autorefresh) {
|
if (autorefresh) {
|
||||||
@ -670,13 +671,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var origFocus = document.activeElement;
|
var discard = false;
|
||||||
if (
|
$.each(self.icinga.behaviors, function(name, behavior) {
|
||||||
// Do not reload menu when search field has content
|
if (behavior.renderHook) {
|
||||||
(containerId === 'menu' && $(origFocus).length && $(origFocus).val().length)
|
var changed = behavior.renderHook(content, $container, action, autorefresh);
|
||||||
// TODO: remove once #7146 is solved
|
if (!changed) {
|
||||||
|| (containerId !== 'menu' && typeof containerId !== 'undefined' && autorefresh && origFocus && $(origFocus).closest('form').length && $container.has($(origFocus)) && $(origFocus).closest('#' + containerId).length && ! $(origFocus).hasClass('autosubmit'))) {
|
discard = true;
|
||||||
this.icinga.logger.debug('Not changing content for ', containerId, ' form has focus');
|
} else {
|
||||||
|
content = changed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (discard) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
/**
|
/**
|
||||||
* Raise or lower current log level
|
* Raise or lower current log level
|
||||||
*
|
*
|
||||||
* Messages blow this threshold will be silently discarded
|
* Messages below this threshold will be silently discarded
|
||||||
*/
|
*/
|
||||||
setLevel: function (level) {
|
setLevel: function (level) {
|
||||||
if ('undefined' !== typeof this.numericLevel(level)) {
|
if ('undefined' !== typeof this.numericLevel(level)) {
|
||||||
|
@ -543,25 +543,39 @@
|
|||||||
refreshTimeSince: function () {
|
refreshTimeSince: function () {
|
||||||
|
|
||||||
$('.timesince').each(function (idx, el) {
|
$('.timesince').each(function (idx, el) {
|
||||||
var m = el.innerHTML.match(/^(.*?)(-?\d+)m\s(-?\d+)s/);
|
|
||||||
|
// todo remove after replace timeSince
|
||||||
|
var mp = el.innerHTML.match(/^(.*?)(-?\d+)d\s(-?\d+)h/);
|
||||||
|
if (mp !== null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var m = el.innerHTML.match(/^(.*?)(-?\d+)(.+\s)(-?\d+)(.+)/);
|
||||||
if (m !== null) {
|
if (m !== null) {
|
||||||
var nm = parseInt(m[2]);
|
var nm = parseInt(m[2]);
|
||||||
var ns = parseInt(m[3]);
|
var ns = parseInt(m[4]);
|
||||||
if (ns < 59) {
|
if (ns < 59) {
|
||||||
ns++;
|
ns++;
|
||||||
} else {
|
} else {
|
||||||
ns = 0;
|
ns = 0;
|
||||||
nm++;
|
nm++;
|
||||||
}
|
}
|
||||||
$(el).html(m[1] + nm + 'm ' + ns + 's');
|
$(el).html(m[1] + nm + m[3] + ns + m[5]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.timeuntil').each(function (idx, el) {
|
$('.timeuntil').each(function (idx, el) {
|
||||||
var m = el.innerHTML.match(/^(.*?)(-?\d+)m\s(-?\d+)s/);
|
|
||||||
|
// todo remove after replace timeUntil
|
||||||
|
var mp = el.innerHTML.match(/^(.*?)(-?\d+)d\s(-?\d+)h/);
|
||||||
|
if (mp !== null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var m = el.innerHTML.match(/^(.*?)(-?\d+)(.+\s)(-?\d+)(.+)/);
|
||||||
if (m !== null) {
|
if (m !== null) {
|
||||||
var nm = parseInt(m[2]);
|
var nm = parseInt(m[2]);
|
||||||
var ns = parseInt(m[3]);
|
var ns = parseInt(m[4]);
|
||||||
var signed = '';
|
var signed = '';
|
||||||
var sec = 0;
|
var sec = 0;
|
||||||
|
|
||||||
@ -589,7 +603,7 @@
|
|||||||
nm = Math.floor(sec/60);
|
nm = Math.floor(sec/60);
|
||||||
ns = sec - nm * 60;
|
ns = sec - nm * 60;
|
||||||
|
|
||||||
$(el).html(m[1] + signed + nm + 'm ' + ns + 's');
|
$(el).html(m[1] + signed + nm + m[3] + ns + m[5]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
102
test/php/library/Icinga/Web/View/DateTimeRendererTest.php
Normal file
102
test/php/library/Icinga/Web/View/DateTimeRendererTest.php
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
namespace Tests\Icinga\Web;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use Icinga\Test\BaseTestCase;
|
||||||
|
use Icinga\Web\View\DateTimeRenderer;
|
||||||
|
|
||||||
|
class DateTimeRendererTest extends BaseTestCase
|
||||||
|
{
|
||||||
|
public function testWhetherCreateCreatesDateTimeRenderer()
|
||||||
|
{
|
||||||
|
$dateTime = new DateTime();
|
||||||
|
$dt = DateTimeRenderer::create($dateTime);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(
|
||||||
|
'Icinga\Web\View\DateTimeRenderer',
|
||||||
|
$dt,
|
||||||
|
'Dashboard::create() could not create DateTimeRenderer'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherCreateCreatesDateTimeRenderer
|
||||||
|
*/
|
||||||
|
public function testWhetherIsDateTimeReturnsRightType()
|
||||||
|
{
|
||||||
|
$dateTime = new DateTime('+1 day');
|
||||||
|
$dt = DateTimeRenderer::create($dateTime);
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
$dt->isDateTime(),
|
||||||
|
'Dashboard::isDateTime() returns wrong type'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherCreateCreatesDateTimeRenderer
|
||||||
|
*/
|
||||||
|
public function testWhetherIsTimeReturnsRightType()
|
||||||
|
{
|
||||||
|
$dateTime = new DateTime('+1 hour');
|
||||||
|
$dt = DateTimeRenderer::create($dateTime);
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
$dt->isTime(),
|
||||||
|
'Dashboard::isTime() returns wrong type'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherCreateCreatesDateTimeRenderer
|
||||||
|
*/
|
||||||
|
public function testWhetherIsTimespanReturnsRightType()
|
||||||
|
{
|
||||||
|
$dateTime = new DateTime('+1 minute');
|
||||||
|
$dt = DateTimeRenderer::create($dateTime);
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
$dt->isTimespan(),
|
||||||
|
'Dashboard::isTimespan() returns wrong type'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherCreateCreatesDateTimeRenderer
|
||||||
|
*/
|
||||||
|
public function testWhetherNormalizeReturnsNormalizedDateTime()
|
||||||
|
{
|
||||||
|
$dateTime = time();
|
||||||
|
$dt = DateTimeRenderer::normalize($dateTime);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(
|
||||||
|
'DateTime',
|
||||||
|
$dt,
|
||||||
|
'Dashboard::normalize() returns wrong instance'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherCreateCreatesDateTimeRenderer
|
||||||
|
*/
|
||||||
|
public function testWhetherRenderReturnsRightText()
|
||||||
|
{
|
||||||
|
$dateTime = new DateTime('+1 minute');
|
||||||
|
$dt = DateTimeRenderer::create($dateTime);
|
||||||
|
|
||||||
|
$text = $dt->render(
|
||||||
|
'#1 The service is down since %s',
|
||||||
|
'#2 The service is down since %s o\'clock.',
|
||||||
|
'#3 The service is down for %s.'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertRegExp(
|
||||||
|
'/#3/',
|
||||||
|
$text,
|
||||||
|
'Dashboard::render() returns wrong text'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user