Merge branch 'feature/view-problems-as-matrix-over-time-5703'

This commit is contained in:
Matthias Jentsch 2014-03-05 20:21:06 +01:00
commit ce20a1e549
7 changed files with 545 additions and 161 deletions

View File

@ -0,0 +1,118 @@
<?php
/**
* This file is part of Icinga Web 2.
*
* Icinga Web 2 - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*
*/
namespace Icinga\Util;
/**
* Provide functions to change and convert colors.
*/
class Color {
/**
* Convert a given color string to an rgb-array containing
* each color as a decimal value.
*
* @param $color The color-string #RRGGBB
*
* @return array The converted rgb-array.
*/
public static function rgbAsArray($color)
{
if (substr($color, 0, 1) !== '#') {
$color = '#' . $color;
}
if (strlen($color) !== 7) {
return;
}
$r = (float)intval(substr($color, 1, 2), 16);
$g = (float)intval(substr($color, 3, 2), 16);
$b = (float)intval(substr($color, 5, 2), 16);
return array($r, $g, $b);
}
/**
* Convert a rgb array to a color-string
*
* @param array $rgb The rgb-array
*
* @return string The color string #RRGGBB
*/
public static function arrayToRgb(array $rgb)
{
$r = (string)dechex($rgb[0]);
$g = (string)dechex($rgb[1]);
$b = (string)dechex($rgb[2]);
return '#'
. (strlen($r) > 1 ? $r : '0' . $r)
. (strlen($g) > 1 ? $g : '0' . $g)
. (strlen($b) > 1 ? $b : '0' . $b);
}
/**
* Change the saturation for a given color.
*
* @param $color string The color to change
* @param $change float The change.
* 0.0 creates a black-and-white image.
* 0.5 reduces the color saturation by half.
* 1.0 causes no change.
* 2.0 doubles the color saturation.
* @return string
*/
public static function changeSaturation($color, $change)
{
$rgb = self::rgbAsArray($color);
$rgb = self::changeRgbSaturation($rgb, $change);
$col = self::arrayToRgb($rgb);
return $col;
}
/**
* @param $rgb array The rgb-array to change
* @param $change float The factor
*
* @return array The updated rgb-array
*/
private static function changeRgbSaturation(array $rgb, $change)
{
$pr = 0.499; // 0.299
$pg = 0.387; // 0.587
$pb = 0.114; // 0.114
$r = $rgb[0];
$g = $rgb[1];
$b = $rgb[2];
$p = sqrt(
$r * $r * $pr +
$g * $g * $pg +
$b * $b * $pb
);
$rgb[0] = (int)($p + ($r - $p) * $change);
$rgb[1] = (int)($p + ($g - $p) * $change);
$rgb[2] = (int)($p + ($b - $p) * $change);
return $rgb;
}
}

View File

@ -16,6 +16,7 @@ class StyleSheet
'css/icinga/main-content.less',
'css/icinga/tabs.less',
'css/icinga/forms.less',
'css/icinga/widgets.less',
'css/icinga/pagination.less',
'css/icinga/monitoring-colors.less',
'css/icinga/login.less',

View File

@ -0,0 +1,347 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga Web 2.
*
* Icinga Web 2 - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Web\Widget\Chart;
use Icinga\Util\DateTimeFactory;
use Icinga\Util\Color;
use Icinga\Web\Widget\Widget;
use \DateInterval;
use \Zend_View_Abstract;
/**
* Display a colored grid that visualizes a set of values for each day
* on a given time-frame.
*/
class HistoryColorGrid implements Widget {
const ORIENTATION_VERTICAL = 'vertical';
const ORIENTATION_HORIZONTAL = 'horizontal';
public $orientation = self::ORIENTATION_HORIZONTAL;
private $maxValue = 1;
private $start = null;
private $end = null;
private $data = array();
private $color;
public function __construct($color = '#51e551') {
$this->setColor($color);
}
/**
* Set the displayed data-set
*
* @param $data array The values to display.
* properties for each entry:
* value: The value to display
* caption: The caption on mouse-over
* url: The url to open on click.
*/
public function setData(array $data)
{
$this->data = $data;
$start = time();
$end = time();
foreach ($this->data as $entry) {
$entry['value'] = intval($entry['value']);
}
foreach ($this->data as $date => $entry) {
$time = strtotime($date);
if ($entry['value'] > $this->maxValue) {
$this->maxValue = $entry['value'];
}
if ($time > $end) {
$end = $time;
}
if ($time < $start) {
$start = $time;
}
}
$this->start = $this->tsToDateStr($start);
$this->end = $this->tsToDateStr($end);
}
/**
* Set the used color.
*
* @param $color
*/
public function setColor($color)
{
$this->color = $color;
}
/**
* Calculate the color to display for the given value.
*
* @param $value integer
*
* @return string The color-string to use for this entry.
*/
private function calculateColor($value)
{
$saturation = $value / $this->maxValue;
return Color::changeSaturation($this->color, $saturation);
}
/**
* Render the html to display the given $day
*
* @param $day string The day to display YYYY-MM-DD
*
* @return string The rendered html
*/
private function renderDay($day)
{
if (array_key_exists($day, $this->data)) {
$entry = $this->data[$day];
return'<a ' .
'style="background-color:' . $this->calculateColor($entry['value']) . ';" ' .
'title="' . $entry['caption'] . '"; ' .
'href="' . $entry['url'] . '"' .
'></a>';
} else {
return '<a ' .
'style="background-color:' . $this->calculateColor(0) . ';" ' .
'title="No entries for ' . $day . '"; ' .
'></a>';
}
}
/**
* Render the grid with an horizontal alignment.
*
* @param array $grid The values returned from the createGrid function
*
* @return string The rendered html
*/
private function renderHorizontal($grid)
{
$weeks = $grid['weeks'];
$months = $grid['months'];
$html = '<table class="historycolorgrid">';
$html .= '<tr><th></th>';
$old = -1;
foreach ($months as $month) {
if ($old !== $month) {
$old = $month;
$txt = $this->monthName($month);
} else {
$txt = '';
}
$html .= '<th>' . $txt . '</th>';
}
$html .= '</tr>';
for ($i = 0; $i < 7; $i++) {
$html .= $this->renderWeekdayHorizontal($i, $weeks);
}
$html .= '</table>';
return $html;
}
/**
* @param $grid
*
* @return string
*/
private function renderVertical($grid)
{
$weeks = $grid['weeks'];
$months = $grid['months'];
$html = '<table class="historycolorgrid">';
$html .= '<tr>';
for ($i = 0; $i < 7; $i++) {
$html .= '<th>' . $this->weekdayName($i) . "</th>";
}
$html .= '</tr>';
$old = -1;
foreach ($weeks as $index => $week) {
for ($i = 0; $i < 7; $i++) {
if (array_key_exists($i, $week)) {
$html .= '<td>' . $this->renderDay($week[$i]) . '</td>';
} else {
$html .= '<td></td>';
}
}
if ($old !== $months[$index]) {
$old = $months[$index];
$txt = $this->monthName($old);
} else {
$txt = '';
}
$html .= '<td class="weekday">' . $txt . '</td></tr>';
}
$html .= '</table>';
return $html;
}
/**
* Render the row for the given weekday.
*
* @param integer $weekday The day to render (0-6)
* @param array $weeks The weeks
*
* @return string The formatted table-row
*/
private function renderWeekdayHorizontal($weekday, &$weeks)
{
$html = '<tr><td class="weekday">' . $this->weekdayName($weekday) . '</td>';
foreach ($weeks as $week) {
if (array_key_exists($weekday, $week)) {
$html .= '<td>' . $this->renderDay($week[$weekday]) . '</td>';
} else {
$html .= '<td></td>';
}
}
$html .= '</tr>';
return $html;
}
/**
* @return array
*/
private function createGrid()
{
$weeks = array(array());
$week = 0;
$months = array();
$start = strtotime($this->start);
$year = intval(date('Y', $start));
$month = intval(date('n', $start));
$day = intval(date('j', $start));
$weekday = intval(date('w', $start));
$date = $this->toDateStr($day, $month, $year);
$weeks[0][$weekday] = $date;
$months[0] = $month;
while ($date !== $this->end) {
$day++;
$weekday++;
if ($weekday > 6) {
$weekday = 0;
$weeks[] = array();
$week++;
$months[$week] = $month;
}
if ($day > cal_days_in_month(CAL_GREGORIAN, $month, $year)) {
$month++;
if ($month > 12) {
$year++;
$month = 1;
}
$day = 1;
}
$date = $this->toDateStr($day, $month, $year);
$weeks[$week][$weekday] = $date;
};
$months[$week] = $month;
return array(
'weeks' => $weeks,
'months' => $months
);
}
/**
* Get the localized month-name for the given month
*
* @param integer $month The month-number
*
* @return string The
*/
private function monthName($month)
{
$dt = DateTimeFactory::create('2000-' . $month . '-01');
return $dt->format('M');
}
/**
* @param $weekday
*
* @return string
*/
private function weekdayName($weekday)
{
$sun = DateTimeFactory::create('last Sunday');
$interval = new DateInterval('P' . $weekday . 'D');
$sun->add($interval);
return $sun->format('D');
}
/**
*
*
* @param $timestamp
*
* @return bool|string
*/
private function tsToDateStr($timestamp)
{
return date('Y-m-d', $timestamp);
}
/**
* @param $day
* @param $mon
* @param $year
*
* @return string
*/
private function toDateStr($day, $mon, $year)
{
$day = $day > 9 ? (string)$day : '0' . (string)$day;
$mon = $mon > 9 ? (string)$mon : '0' . (string)$mon;
return $year . '-' . $mon . '-' . $day;
}
/**
* @param Zend_View_Abstract $view
*
* @return string
*/
public function render(Zend_View_Abstract $view)
{
if (empty($this->data)) {
return '<div>No entries</div>';
}
$grid = $this->createGrid();
if ($this->orientation === self::ORIENTATION_HORIZONTAL) {
return $this->renderHorizontal($grid);
}
return $this->renderVertical($grid);
}
}

View File

@ -40,6 +40,7 @@ use Icinga\Web\Widget\Chart\PieChart;
use Icinga\Module\Monitoring\Backend;
use Icinga\Web\Widget\SortBox;
use Icinga\Web\Widget\FilterBox;
use Icinga\Web\Widget\Chart\HistoryColorGrid;
use Icinga\Application\Config as IcingaConfig;
use Icinga\Module\Monitoring\DataView\DataView;
@ -277,11 +278,12 @@ class Monitoring_ListController extends MonitoringController
public function statehistorysummaryAction()
{
$this->addTitleTab('statehistorysummary');
$query = StateHistorySummary::fromRequest(
$this->_request, array('day', 'cnt_events')
)->getQuery();
$query = StateHistorySummary::fromRequest(
$this->_request, array('day', 'cnt_critical')
)->getQuery()->order('day');
$query->paginate(365);
$this->view->summary = $query->fetchAll();
$this->view->grid = new HistoryColorGrid();
$this->handleFormatRequest($query);
}
@ -483,7 +485,6 @@ class Monitoring_ListController extends MonitoringController
$domain,
'monitoring'
);
}
protected function addTitleTab($action)

View File

@ -1,159 +1,27 @@
<div class="controls">
<?= $this->tabs ?>
</div>
<style>
table.colorizedsummary td {
width: 1em;
height: 1em;
margin: 0.5em;
}
<h1>
State History Summary for all Hosts
</h1>
table.colorizedsummary a {
margin: 0;
display: block;
float: left;
width: 1em;
height: 1em;
}
</style>
<div class="content">
<?php
if (empty($this->summary)) {
echo 'No entries found</div>';
return;
$grid->setColor('#FC0707');
$data = array();
foreach ($summary as $entry) {
$day = $entry->day;
$value = $entry->cnt_critical;
$caption = $value . ' ' .
t('critical events on ') . $this->dateFormat()->formatDate(strtotime($day));
$data[$day] = array(
'value' => $value,
'caption' => $caption,
'url' => $this->href(
'monitoring/list/eventhistory',
array(
'timestamp<' => strtotime($day . ' 23:59:59'),
'timestamp>' => strtotime($day . ' 00:00:00')
)
)
);
}
$cols = array('cnt_events'/*, 'cnt_up', 'cnt_down', 'cnt_ok', 'cnt_critical'*/);
$show = array('cnt_events');
$colors = (object) array();
$max = (object) array();
foreach ($cols as $col) {
$max->$col = 0;
}
foreach ($this->summary as & $row) {
foreach ($cols as $col) {
if ($row->$col > $max->$col) {
$max->$col = $row->$col;
}
}
}
$colors->cnt_events = array(
$max->cnt_events * 0.80 => '#0b5',
$max->cnt_events * 0.60 => '#2b6',
$max->cnt_events * 0.40 => '#4b7',
$max->cnt_events * 0.20 => '#7b9',
0 => '#9ba',
);
/*
$colors->cnt_up = array(
$max->cnt_up * 0.80 => '#0b5',
$max->cnt_up * 0.60 => '#2b6',
$max->cnt_up * 0.40 => '#4b7',
$max->cnt_up * 0.20 => '#7b9',
0 => '#9ba',
);
$colors->cnt_ok = array(
$max->cnt_ok * 0.80 => '#0b5',
$max->cnt_ok * 0.60 => '#2b6',
$max->cnt_ok * 0.40 => '#4b7',
$max->cnt_ok * 0.20 => '#7b9',
0 => '#9ba',
);
$colors->cnt_critical = array(
$max->cnt_critical * 0.8 => '#f34',
$max->cnt_critical * 0.6 => '#f56',
$max->cnt_critical * 0.4 => '#f67',
$max->cnt_critical * 0.2 => '#f9a',
0 => '#fcd',
);
$colors->cnt_down = array(
$max->cnt_down * 0.8 => '#f34',
$max->cnt_down * 0.6 => '#f56',
$max->cnt_down * 0.4 => '#f67',
$max->cnt_down * 0.2 => '#f9a',
0 => '#fcd',
);
*/
function getColor(& $colors, $value, $what = 'cnt_events') {
foreach ($colors->$what as $level => $color) {
if ($value >= $level) {
return $color;
}
}
return 'red';
}
$startDay = '2014-20-02';
$start = strtotime($startDay);
$wday = date('w', $start);
$week = date('w', $start);
if ($wday === 0) {
$wday = 6;
} else {
$wday--;
}
?>
<table class="colorizedsummary">
<tr>
<th>M</th>
<th>T</th>
<th>W</th>
<th>T</th>
<th>F</th>
<th>S</th>
<th>S</th>
</tr>
<tr>
<!-- <th><?= $startDay ?></th>-->
<!--
<tr>
<?php
foreach (array_keys((array) $this->summary[0]) as $title) {
echo '<th>' . $this->escape($title) . '</th>';
}
?>
</tr>
-->
<?php
for ($i = 0; $i <= $wday; $i++) {
echo '<td></td>';
}
foreach ($this->summary as $row) {
$wday--;
if ($wday < 0) {
$wday = 6;
$week--;
echo '</tr><tr><!--<th>' . $row->day . '</th>-->';
}
echo '<td>';
foreach ($show as $col) {
echo '<a style="background-color: '
. getColor($colors, $row->$col, $col)
. '" title="'
. $this->escape($row->$col . ' ' . strtoupper(substr($col, 4)) . ' Events on ' . $row->day)
. '"></a>';
}
echo '</td>';
}
?>
</tr>
</table>
</div>
$grid->setData($data);
echo $grid->render($this);

View File

@ -0,0 +1,37 @@
table.historycolorgrid {
font-size: 1.5em;
padding: 15px;
}
table.historycolorgrid th {
width: 1em;
height: 1em;
margin: 0.5em;
font-size: 0.55em;
font-weight: normal;
}
table.historycolorgrid td {
width: 1em;
height: 1em;
margin: 1em;
}
table.historycolorgrid td.hover {
opacity: 0.5;
}
table.historycolorgrid td.weekday {
font-size: 0.55em;
font-weight: normal;
width: 2.5em;
opacity: 1.0;
}
table.historycolorgrid a {
margin: 0;
display: block;
float: left;
width: 1em;
height: 1em;
}

View File

@ -58,7 +58,6 @@
width: '2em',
height: '2em',
});
},
/**
@ -89,6 +88,9 @@
$(document).on('keyup', '#menu input.search', {self: this}, this.submitForm);
$('.historycolorgrid td').on('mouseenter', this.historycolorgridHover);
$('.historycolorgrid td').on('mouseleave', this.historycolorgidUnhover);
// TBD: a global autocompletion handler
// $(document).on('keyup', 'form.auto input', this.formChangeDelayed);
// $(document).on('change', 'form.auto input', this.formChanged);
@ -109,6 +111,14 @@
icinga.ui.fixControls();
},
historycolorgridHover: function () {
$(this).addClass('hover');
},
historycolorgidUnhover: function() {
$(this).removeClass('hover');
},
/**
*
*/
@ -260,6 +270,8 @@
$(document).off('click', 'tr[href]', this.linkClicked);
$(document).off('submit', 'form', this.submitForm);
$(document).off('change', 'form select.autosubmit', this.submitForm);
$(document).off('mouseenter', '.historycolorgrid td', this.historycolorgridHover);
$(document).off('mouseenter', '.historycolorgrid td', this.historycolorgidUnhover);
},
destroy: function() {