' : '' ?> +
diff --git a/library/Icinga/Web/JavaScript.php b/library/Icinga/Web/JavaScript.php index 6aa36d608..6224b5b82 100644 --- a/library/Icinga/Web/JavaScript.php +++ b/library/Icinga/Web/JavaScript.php @@ -26,7 +26,8 @@ class JavaScript 'js/icinga/behavior/tooltip.js', 'js/icinga/behavior/sparkline.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( diff --git a/library/Icinga/Web/View/DateTimeRenderer.php b/library/Icinga/Web/View/DateTimeRenderer.php new file mode 100644 index 000000000..639befb7f --- /dev/null +++ b/library/Icinga/Web/View/DateTimeRenderer.php @@ -0,0 +1,205 @@ +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( + '%s', + $css, + $this->dateTime->format('d.m.Y - H:i:s'), + $format + ); + } +} diff --git a/library/Icinga/Web/View/helpers/format.php b/library/Icinga/Web/View/helpers/format.php index 0d902acfd..a203f2fb3 100644 --- a/library/Icinga/Web/View/helpers/format.php +++ b/library/Icinga/Web/View/helpers/format.php @@ -44,3 +44,7 @@ $this->addHelperFunction('prefixedTimeUntil', function ($timestamp, $ucfirst = f Format::prefixedTimeUntil($timestamp, $ucfirst) ); }); + +$this->addHelperFunction('dateTimeRenderer', function ($dateTimeOrTimestamp, $future = false) { + return DateTimeRenderer::create($dateTimeOrTimestamp, $future); +}); diff --git a/library/Icinga/Web/Widget/Chart/HistoryColorGrid.php b/library/Icinga/Web/Widget/Chart/HistoryColorGrid.php index 974e02060..56de318e8 100644 --- a/library/Icinga/Web/Widget/Chart/HistoryColorGrid.php +++ b/library/Icinga/Web/Widget/Chart/HistoryColorGrid.php @@ -28,29 +28,32 @@ class HistoryColorGrid extends AbstractWidget { private $maxValue = 1; private $start = null; - private $end = null; - private $data = array(); - private $color; + public $opacity = 1.0; - public function __construct($color = '#51e551') { + public function __construct($color = '#51e551', $start = null, $end = null) { $this->setColor($color); + if (isset($start)) { + $this->start = $this->tsToDateStr($start); + } + if (isset($end)) { + $this->end = $this->tsToDateStr($end); + } } /** * Set the displayed data-set * - * @param $data array The values to display. - * properties for each entry: + * @param $events array The history events to display as an array of arrays: * value: The value to display * caption: The caption on mouse-over * 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(); $end = time(); foreach ($this->data as $entry) { @@ -68,8 +71,12 @@ class HistoryColorGrid extends AbstractWidget { $start = $time; } } - $this->start = $this->tsToDateStr($start); - $this->end = $this->tsToDateStr($end); + if (!isset($this->start)) { + $this->start = $this->tsToDateStr($start); + } + if (!isset($this->end)) { + $this->end = $this->tsToDateStr($end); + } } /** @@ -82,6 +89,16 @@ class HistoryColorGrid extends AbstractWidget { $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. * @@ -104,16 +121,17 @@ class HistoryColorGrid extends AbstractWidget { */ 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]; return'calculateColor($entry['value']) . ';" ' . + 'style="background-color:' . $this->calculateColor($entry['value']) . '; ' + . ' opacity: ' . $this->opacity . ';"' . 'title="' . $entry['caption'] . '" ' . 'href="' . $entry['url'] . '"' . '> '; } else { return 'calculateColor(0) . ';" ' . + 'style="background-color:' . $this->calculateColor(0) . '; ' . ' opacity: ' . $this->opacity . ';" ' . 'title="No entries for ' . $day . '" ' . '>'; } @@ -130,13 +148,14 @@ class HistoryColorGrid extends AbstractWidget { { $weeks = $grid['weeks']; $months = $grid['months']; + $years = $grid['years']; $html = '
'; $old = -1; - foreach ($months as $month) { + foreach ($months as $week => $month) { if ($old !== $month) { $old = $month; - $txt = $this->monthName($month); + $txt = $this->monthName($month, $years[$week]); } else { $txt = ''; } @@ -157,6 +176,7 @@ class HistoryColorGrid extends AbstractWidget { */ private function renderVertical($grid) { + $years = $grid['years']; $weeks = $grid['weeks']; $months = $grid['months']; $html = ' |
---|
= $this->timeSince($notification->notification_start_time) ?> | ++ = $this->dateTimeRenderer($notification->notification_start_time)->render( + $this->translate('on %s', 'datetime'), + $this->translate('at %s', 'time'), + $this->translate('%s ago', 'timespan') + ); + ?> + |
= $notification->service ?> on = $notification->host ?>
diff --git a/modules/monitoring/application/views/scripts/list/statehistorysummary.phtml b/modules/monitoring/application/views/scripts/list/statehistorysummary.phtml
index 55c41ffcb..cdb19dba7 100644
--- a/modules/monitoring/application/views/scripts/list/statehistorysummary.phtml
+++ b/modules/monitoring/application/views/scripts/list/statehistorysummary.phtml
@@ -1,42 +1,131 @@
+
+
+ if (!$compact): ?>
+
+ = $this->tabs->render($this); ?>
+
+ endif; ?>
-?>
+
+ filterEditor): ?>
+ = $this->filterPreview ?>
+
+ = $this->filterEditor ?>
+ + = $form ?> +
-= $this->tabs ?>
-
History - Critical Events-
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();
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) {
$day = $entry->day;
- $value = $entry->cnt_critical;
- $caption = $value . ' ' .
- t('critical events on ') . $this->dateFormat()->formatDate(strtotime($day));
- $filter = Filter::matchAll(
+ $value = $entry->$column;
+ $caption = sprintf(
+ $settings[$column]['tooltip'],
+ $value,
+ $this->dateFormat()->formatDate(strtotime($day))
+ );
+ $linkFilter = Filter::matchAll(
Filter::expression('timestamp', '<', strtotime($day . ' 23:59:59')),
Filter::expression('timestamp', '>', strtotime($day . ' 00:00:00')),
- Filter::expression('object_type', '=', 'service'),
- Filter::expression('state', '=', '2'),
- Filter::matchAny(
- Filter::expression('type', '=', 'hard_state'),
- Filter::expression('type', '=', 'hard_state')
- )
+ $form->getFilter(),
+ $filter
);
$data[$day] = array(
'value' => $value,
'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 ?>
+
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StateHistorySummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StateHistorySummaryQuery.php
index cdaee0697..9004d5250 100644
--- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StateHistorySummaryQuery.php
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StateHistorySummaryQuery.php
@@ -10,6 +10,7 @@ class StateHistorySummaryQuery extends IdoQuery
'statehistory' => array(
'day' => 'DATE(sh.state_time)',
'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_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)',
@@ -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_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)',
+ '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'),
'sh.object_id = sho.object_id AND sho.is_active = 1',
array()
- )->where('sh.state_time >= ?', '2013-11-20 00:00:00')
+ )
->group('DATE(sh.state_time)');
$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()
+ );
+ }
}
diff --git a/modules/monitoring/library/Monitoring/Web/Widget/TimelineIntervalBox.php b/modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php
similarity index 77%
rename from modules/monitoring/library/Monitoring/Web/Widget/TimelineIntervalBox.php
rename to modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php
index 4d6dbdad5..c58540faf 100644
--- a/modules/monitoring/library/Monitoring/Web/Widget/TimelineIntervalBox.php
+++ b/modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php
@@ -8,10 +8,7 @@ use Icinga\Web\Form;
use Icinga\Web\Request;
use Icinga\Web\Widget\AbstractWidget;
-/**
- * @todo Might be better if this is a generic selection widget.
- */
-class TimelineIntervalBox extends AbstractWidget
+class SelectBox extends AbstractWidget
{
/**
* The name of the form that will be created
@@ -27,6 +24,20 @@ class TimelineIntervalBox extends AbstractWidget
*/
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
*
@@ -39,11 +50,15 @@ class TimelineIntervalBox extends AbstractWidget
*
* @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)
+ public function __construct($name, array $values, $label = 'Select', $param = 'selection')
{
$this->name = $name;
$this->values = $values;
+ $this->label = $label;
+ $this->parameter = $param;
}
/**
@@ -89,9 +104,9 @@ class TimelineIntervalBox extends AbstractWidget
$form->setName($this->name);
$form->addElement(
'select',
- 'interval',
+ $this->parameter,
array(
- 'label' => 'Timeline Interval',
+ 'label' => $this->label,
'multiOptions' => $this->values,
'autosubmit' => true
)
diff --git a/public/css/icinga/widgets.less b/public/css/icinga/widgets.less
index f546443c4..9fe42c899 100644
--- a/public/css/icinga/widgets.less
+++ b/public/css/icinga/widgets.less
@@ -19,7 +19,7 @@ table.historycolorgrid td {
margin: 1em;
}
-table.historycolorgrid td.hover {
+table.historycolorgrid td:hover {
opacity: 0.5;
}
diff --git a/public/js/icinga/behavior/form.js b/public/js/icinga/behavior/form.js
new file mode 100644
index 000000000..d289183d6
--- /dev/null
+++ b/public/js/icinga/behavior/form.js
@@ -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);
diff --git a/public/js/icinga/events.js b/public/js/icinga/events.js
index f226836cc..9c889dfa8 100644
--- a/public/js/icinga/events.js
+++ b/public/js/icinga/events.js
@@ -75,8 +75,9 @@
}
});
- $('input.autofocus', el).focus();
-
+ if (document.activeElement === document.body) {
+ $('input.autofocus', el).focus();
+ }
var searchField = $('#menu input.search', el);
// Remember initial search field value if any
if (searchField.length && searchField.val().length) {
diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js
index 9b6c2bd00..9c3feab6e 100644
--- a/public/js/icinga/loader.js
+++ b/public/js/icinga/loader.js
@@ -661,6 +661,7 @@
// Container update happens here
var scrollPos = false;
var self = this;
+ var origFocus = document.activeElement;
var containerId = $container.attr('id');
if (typeof containerId !== 'undefined') {
if (autorefresh) {
@@ -670,13 +671,18 @@
}
}
- var origFocus = document.activeElement;
- if (
- // Do not reload menu when search field has content
- (containerId === 'menu' && $(origFocus).length && $(origFocus).val().length)
- // TODO: remove once #7146 is solved
- || (containerId !== 'menu' && typeof containerId !== 'undefined' && autorefresh && origFocus && $(origFocus).closest('form').length && $container.has($(origFocus)) && $(origFocus).closest('#' + containerId).length && ! $(origFocus).hasClass('autosubmit'))) {
- this.icinga.logger.debug('Not changing content for ', containerId, ' form has focus');
+ var discard = false;
+ $.each(self.icinga.behaviors, function(name, behavior) {
+ if (behavior.renderHook) {
+ var changed = behavior.renderHook(content, $container, action, autorefresh);
+ if (!changed) {
+ discard = true;
+ } else {
+ content = changed;
+ }
+ }
+ });
+ if (discard) {
return;
}
diff --git a/public/js/icinga/logger.js b/public/js/icinga/logger.js
index 1bdc2d718..07135e007 100644
--- a/public/js/icinga/logger.js
+++ b/public/js/icinga/logger.js
@@ -37,7 +37,7 @@
/**
* 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) {
if ('undefined' !== typeof this.numericLevel(level)) {
diff --git a/public/js/icinga/ui.js b/public/js/icinga/ui.js
index ef5e43f70..28ce353e1 100644
--- a/public/js/icinga/ui.js
+++ b/public/js/icinga/ui.js
@@ -543,25 +543,39 @@
refreshTimeSince: function () {
$('.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) {
var nm = parseInt(m[2]);
- var ns = parseInt(m[3]);
+ var ns = parseInt(m[4]);
if (ns < 59) {
ns++;
} else {
ns = 0;
nm++;
}
- $(el).html(m[1] + nm + 'm ' + ns + 's');
+ $(el).html(m[1] + nm + m[3] + ns + m[5]);
}
});
$('.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) {
var nm = parseInt(m[2]);
- var ns = parseInt(m[3]);
+ var ns = parseInt(m[4]);
var signed = '';
var sec = 0;
@@ -589,7 +603,7 @@
nm = Math.floor(sec/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]);
}
});
},
diff --git a/test/php/library/Icinga/Web/View/DateTimeRendererTest.php b/test/php/library/Icinga/Web/View/DateTimeRendererTest.php
new file mode 100644
index 000000000..e45d3fdfe
--- /dev/null
+++ b/test/php/library/Icinga/Web/View/DateTimeRendererTest.php
@@ -0,0 +1,102 @@
+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'
+ );
+ }
+}
+ foreach (array_reverse($grids) as $key => $grid) { ?>
+
+ = $grid; ?>
+ = $this->orientation === 'horizontal' ? '
+ } ?>
+ ' : '' ?> + |