diff --git a/library/Icinga/Web/Widget/Chart/InlinePie.php b/library/Icinga/Web/Widget/Chart/InlinePie.php
index c219ab4d7..73590c1c2 100644
--- a/library/Icinga/Web/Widget/Chart/InlinePie.php
+++ b/library/Icinga/Web/Widget/Chart/InlinePie.php
@@ -32,6 +32,8 @@ namespace Icinga\Web\Widget\Chart;
use Icinga\Web\Widget\AbstractWidget;
use Icinga\Web\Url;
+use Icinga\Util\Format;
+use Icinga\Logger\Logger;
/**
* A SVG-PieChart intended to be displayed as a small icon next to labels, to offer a better visualization of the
@@ -44,14 +46,32 @@ use Icinga\Web\Url;
*/
class InlinePie extends AbstractWidget
{
+ const NUMBER_FORMAT_TIME = 'time';
+ const NUMBER_FORMAT_BYTES = 'bytes';
+ const NUMBER_FORMAT_RATIO = 'ratio';
+
/**
+ * The template string used for rendering this widget
* The template string used for rendering this widget
*
* @var string
*/
private $template =<<<'EOD'
-
+
{{title}} {{label}}: {{formatted}} ({{percent}}%)';
+
+ /**
+ * The number format used to render numeric values in tooltips
+ *
+ * @var array
+ */
+ private $format = self::NUMBER_FORMAT_BYTES;
+
/**
* Set the data to be displayed.
*
@@ -127,7 +182,12 @@ EOD;
*/
public function setLabels($labels = null)
{
- $this->url->setParam('labels', implode(',', $labels));
+ if ($labels != null) {
+ $this->url->setParam('labels', implode(',', $labels));
+ } else {
+ $this->url->removeKey('labels');
+ }
+ $this->labels = $labels;
return $this;
}
@@ -146,6 +206,34 @@ EOD;
}
}
+ /**
+ * Set the used number format
+ *
+ * @param $format string 'bytes' or 'time'
+ */
+ public function setNumberFormat($format)
+ {
+ $this->format = $format;
+ }
+
+ /**
+ * A format string used to render the content of the piechar tooltips
+ *
+ * Placeholders using curly braces '{FOO}' are replace with their specific values. Available
+ * values are:
+ *
+ * label : The description for the current value
+ * formatted : A string representing the formatted value
+ * value : The raw (non-formatted) value used to render the piechart
+ * percent : The percentage of the current value
+ *
+ * Note: Changes will only affect JavaScript sparklines and not the SVG charts used for fallback
+ */
+ public function setTooltipFormat($format)
+ {
+ $this->tooltipFormat = $format;
+ }
+
/**
* @param $height
*
@@ -157,6 +245,26 @@ EOD;
return $this;
}
+ /**
+ * Set the border width of the pie chart
+ *
+ * @param float $width Width in px
+ */
+ public function setBorderWidth($width)
+ {
+ $this->borderWidth = $width;
+ }
+
+ /**
+ * Set the color of the pie chart border
+ *
+ * @param string $col The color string
+ */
+ public function setBorderColor($col)
+ {
+ $this->borderColor = $col;
+ }
+
/**
* @param $width
*
@@ -191,10 +299,12 @@ EOD;
/**
* Create a new InlinePie
*
- * @param array $data The data displayed by the slices
- * @param array $colors The colors displayed by the slices
+ * @param array $data The data displayed by the slices
+ * @param array $colors The colors displayed by the slices
+ * @param array $labels The labels to display for each slice
+ * @param string $unit The number format
*/
- public function __construct(array $data, array $colors = null)
+ public function __construct(array $data, array $colors = null, array $labels = null, $unit = self::NUMBER_FORMAT_BYTES)
{
$this->url = Url::fromPath('svg/chart.php');
if (array_key_exists('data', $data)) {
@@ -215,6 +325,15 @@ EOD;
}
}
+ /**
+ * Create a serialization containing the current label array
+ *
+ * @return string A serialized array of labels
+ */
+ private function createLabelString () {
+ return isset($this->labels) && is_array($this->labels) ? implode(',', $this->labels) : '';
+ }
+
/**
* Renders this widget via the given view and returns the
* HTML as a string
@@ -224,19 +343,53 @@ EOD;
public function render()
{
$template = $this->template;
- // Locale-ignorant string cast:
- $data = array();
- foreach ($this->data as $dat) {
- $data[] = sprintf('%F', $dat);
- }
$template = preg_replace('{{url}}', $this->url, $template);
+
+ // style
$template = preg_replace('{{width}}', $this->width, $template);
$template = preg_replace('{{height}}', $this->height, $template);
$template = preg_replace('{{title}}', $this->title, $template);
$template = preg_replace('{{style}}', $this->style, $template);
- $template = preg_replace('{{data}}', implode(',', $data), $template);
$template = preg_replace('{{colors}}', implode(',', $this->colors), $template);
+ $template = preg_replace('{{borderWidth}}', $this->borderWidth, $template);
+ $template = preg_replace('{{borderColor}}', $this->borderColor, $template);
+
+ // values
+ $data = array();
+ foreach ($this->data as $dat) {
+ // Locale-ignorant string cast:
+ $data[] = sprintf('%F', $dat);
+ }
+ $formatted = array();
+ foreach ($this->data as $key => $value) {
+ $formatted[$key] = $this->formatValue($value);
+ }
+ $template = preg_replace('{{data}}', implode(',', $data), $template);
+ $template = preg_replace('{{formatted}}', implode(',', $formatted), $template);
+ $template = preg_replace('{{labels}}', $this->createLabelString(), $template);
+ $template = preg_replace('{{tooltipFormat}}', $this->tooltipFormat, $template);
return $template;
}
+
+ /**
+ * Format the given value depending on the current value of numberFormat
+ *
+ * @param float $value The value to format
+ *
+ * @return string The formatted value
+ */
+ private function formatValue($value)
+ {
+ if ($this->format === self::NUMBER_FORMAT_BYTES) {
+ return Format::bytes($value);
+ } else if ($this->format === self::NUMBER_FORMAT_TIME) {
+ return Format::duration($value);
+ } else if ($this->format === self::NUMBER_FORMAT_RATIO) {
+ return $value;
+ } else {
+ Logger::warning('Unknown format string "' . $this->format . '" for InlinePie, value not formatted.');
+ return $value;
+ }
+ }
}
// @codeCoverageIgnoreEnd
diff --git a/modules/monitoring/application/views/helpers/Perfdata.php b/modules/monitoring/application/views/helpers/Perfdata.php
index ae2c74141..49cc3429a 100644
--- a/modules/monitoring/application/views/helpers/Perfdata.php
+++ b/modules/monitoring/application/views/helpers/Perfdata.php
@@ -18,11 +18,10 @@ class Zend_View_Helper_Perfdata extends Zend_View_Helper_Abstract
if (!$perfdata->isPercentage() && $perfdata->getMaximumValue() === null) {
continue;
}
-
- $pieChart = new InlinePie($this->calculatePieChartData($perfdata));
+ $pieChart = $this->createInlinePie($perfdata);
if ($compact) {
$pieChart->setTitle(
- htmlspecialchars($label) . ': ' . htmlspecialchars($this->formatPerfdataValue($perfdata))
+ htmlspecialchars($label) /* . ': ' . htmlspecialchars($this->formatPerfdataValue($perfdata) */
);
if (!$float) {
$result .= $pieChart->render();
@@ -31,6 +30,9 @@ class Zend_View_Helper_Perfdata extends Zend_View_Helper_Abstract
}
} else {
$pieChart->setTitle(htmlspecialchars($label));
+ if (! $perfdata->isPercentage()) {
+ $pieChart->setTooltipFormat('{{label}}: {{formatted}} ({{percent}}%)');
+ }
$pieChart->setStyle('float: left; margin: 0.2em 0.5em 0.2em 0.5em;');
$table[] = '' . $pieChart->render()
. htmlspecialchars($label)
@@ -82,4 +84,22 @@ class Zend_View_Helper_Perfdata extends Zend_View_Helper_Abstract
return $perfdata->getValue();
}
+
+ protected function createInlinePie(Perfdata $perfdata)
+ {
+ $pieChart = new InlinePie($this->calculatePieChartData($perfdata));
+ $pieChart->setHeight(32)->setWidth(32);
+ if ($perfdata->isBytes()) {
+ $pieChart->setLabels(array(t('Used'), t('Used'), t('Used'), t('Free')));
+ $pieChart->setNumberFormat(InlinePie::NUMBER_FORMAT_BYTES);
+ } else if ($perfdata->isSeconds()) {
+ $pieChart->setLabels(array(t('Runtime'), t('Runtime'), t('Runtime'), t('Tolerance')));
+ $pieChart->setNumberFormat(InlinePie::NUMBER_FORMAT_TIME);
+ } else {
+ $pieChart->setLabels(array(t('Packet Loss'), t('Packet Loss'), t('Packet Loss'), t('Packet Return')));
+ $pieChart->setTooltipFormat('{{label}}: {{formatted}}%');
+ $pieChart->setNumberFormat(InlinePie::NUMBER_FORMAT_RATIO);
+ }
+ return $pieChart;
+ }
}
diff --git a/public/js/icinga/events.js b/public/js/icinga/events.js
index 1b0178d31..df94ecbf6 100644
--- a/public/js/icinga/events.js
+++ b/public/js/icinga/events.js
@@ -70,8 +70,33 @@
$('input.autofocus', el).focus();
// replace all sparklines
- $('span.sparkline', el).sparkline('html', { enableTagOptions: true });
-
+ $('span.sparkline', el).each(function(i, element) {
+ // read custom options
+ var $spark = $(element);
+ var labels = $spark.attr('labels').split(',');
+ var formatted = $spark.attr('formatted').split(',');
+ var tooltipChartTitle = $spark.attr('sparkTooltipChartTitle') || '';
+ var format = $spark.attr('tooltipformat');
+ $spark.sparkline(
+ 'html',
+ {
+ enableTagOptions: true,
+ tooltipFormatter: function (sparkline, options, fields) {
+ var out = format;
+ var replace = {
+ title: tooltipChartTitle,
+ label: labels[fields.offset] ? labels[fields.offset] : fields.offset,
+ formatted: formatted[fields.offset] ? formatted[fields.offset] : '',
+ value: fields.value,
+ percent: Math.round(fields.percent * 100) / 100
+ };
+ $.each(replace, function(key, value) {
+ out = out.replace('{{' + key + '}}', value);
+ });
+ return out;
+ }
+ });
+ });
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/utils.js b/public/js/icinga/utils.js
index 1e037aafe..c279f5508 100644
--- a/public/js/icinga/utils.js
+++ b/public/js/icinga/utils.js
@@ -53,6 +53,19 @@
return hours + ':' + minutes + ':' + seconds;
},
+ /**
+ * Format the given byte-value into a human-readable string
+ *
+ * @param {number} The amount of bytes to format
+ * @returns {string} The formatted string
+ */
+ formatBytes: function (bytes) {
+ var log2 = Math.log(bytes) / Math.LN2;
+ var pot = Math.floor(log2 / 10);
+ var unit = (['b', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB'])[pot];
+ return ((bytes / Math.pow(1024, pot)).toFixed(2)) + ' ' + unit;
+ },
+
/**
* Return whether the given element is visible in the users view
*