Merge branch 'master' into bugfix/rebuild-application-log-logic-7060
This commit is contained in:
commit
2eccc6188f
|
@ -238,6 +238,77 @@ the labels to show you can use the 'disableLegend()' call on the GridChart objec
|
||||||
|
|
||||||
![Various Line Graph Options][graph7]
|
![Various Line Graph Options][graph7]
|
||||||
|
|
||||||
|
|
||||||
|
### Tooltips
|
||||||
|
|
||||||
|
It is possible to specify custom tooltip format strings when creating bar charts.
|
||||||
|
Tooltips provide information about the points of each bar chart column, by aggregating
|
||||||
|
the values of all data sets with the same x-coordinate.
|
||||||
|
|
||||||
|
When no custom format string is given, a sane default format string is used, but its usually
|
||||||
|
clearer for the user to describe the data of each chart more accurately with a custom one.
|
||||||
|
|
||||||
|
|
||||||
|
**Example #9.1: Bar Charts with custom tooltips**
|
||||||
|
|
||||||
|
$this->chart->drawBars(
|
||||||
|
array(
|
||||||
|
'label' => 'Hosts critical',
|
||||||
|
'palette' => Palette::PROBLEM,
|
||||||
|
'stack' => 'stack1',
|
||||||
|
'data' => $data2,
|
||||||
|
'tooltip' => '{title}<br/> {value} of {sum} hosts are ok.'
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'label' => 'Hosts warning',
|
||||||
|
'stack' => 'stack1',
|
||||||
|
'palette' => Palette::WARNING,
|
||||||
|
'data' => $data,
|
||||||
|
'tooltip' => '{title}<br/> Oh no, {value} of {sum} hosts are down!'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
As you can see, you can specify a format string for each data set, which allows you to
|
||||||
|
pass a custom message for all "down" hosts, one custom message for all "Ok" hosts and so on.
|
||||||
|
In contrast to that, the aggregation of values works on a column basis and will give you the
|
||||||
|
sum of all y-values with the same x-coordinate and not the aggregation of all values of the data set.
|
||||||
|
|
||||||
|
#### Rich Tooltips
|
||||||
|
|
||||||
|
It is also possible to use HTML in the tooltip strings to create rich tooltip markups, which can
|
||||||
|
be useful to provide extended output that spans over multiple lines. Please keep in mind that
|
||||||
|
users without JavaScript will see the tooltip with all of its html-tags stripped.
|
||||||
|
|
||||||
|
![Various Line Graph Options][graph7.1]
|
||||||
|
|
||||||
|
#### Available replacements
|
||||||
|
|
||||||
|
The available replacements depend on the used chart type, since the tooltip data is
|
||||||
|
instantiated and populated by the chart. All bar graphs have the following replacements available:
|
||||||
|
|
||||||
|
Aggregated values, are calculated from the data points of each column:
|
||||||
|
|
||||||
|
- sum: The amount of all Y-values of the current column
|
||||||
|
- max: The biggest occurring Y-value of the current column
|
||||||
|
- min: The smallest occurring Y-value of the current column
|
||||||
|
|
||||||
|
|
||||||
|
Column values are also defined by the current column, but are not
|
||||||
|
the product of any aggregation
|
||||||
|
|
||||||
|
- title: The x-value of the current column
|
||||||
|
|
||||||
|
|
||||||
|
Row values are defined by the properties the current data set, and are only useful for rendering the
|
||||||
|
generic tooltip correctly, since you could also just directly write
|
||||||
|
those values into your custom tooltip.
|
||||||
|
|
||||||
|
- label: The name of the current data set
|
||||||
|
- color: The color of this data set
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Pie Charts
|
## Pie Charts
|
||||||
|
|
||||||
### The PieChart Object
|
### The PieChart Object
|
||||||
|
@ -317,5 +388,6 @@ Rendering is straightforward, assuming $svg is the PieChart/GridChart object, yo
|
||||||
[graph5]: res/GraphExample#5.png
|
[graph5]: res/GraphExample#5.png
|
||||||
[graph6]: res/GraphExample#6.png
|
[graph6]: res/GraphExample#6.png
|
||||||
[graph7]: res/GraphExample#7.png
|
[graph7]: res/GraphExample#7.png
|
||||||
|
[graph7.1]: res/GraphExample#7.1.png
|
||||||
[graph8]: res/GraphExample#8.png
|
[graph8]: res/GraphExample#8.png
|
||||||
[graph9]: res/GraphExample#9.png
|
[graph9]: res/GraphExample#9.png
|
Binary file not shown.
After Width: | Height: | Size: 7.3 KiB |
|
@ -16,6 +16,13 @@ use Icinga\Chart\Render\RenderContext;
|
||||||
*/
|
*/
|
||||||
class BarGraph extends Styleable implements Drawable
|
class BarGraph extends Styleable implements Drawable
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* The dataset order
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $order = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The width of the bars.
|
* The width of the bars.
|
||||||
*
|
*
|
||||||
|
@ -30,14 +37,37 @@ class BarGraph extends Styleable implements Drawable
|
||||||
*/
|
*/
|
||||||
private $dataSet;
|
private $dataSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tooltips
|
||||||
|
*
|
||||||
|
* @var
|
||||||
|
*/
|
||||||
|
private $tooltips;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All graphs
|
||||||
|
*
|
||||||
|
* @var
|
||||||
|
*/
|
||||||
|
private $graphs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new BarGraph with the given dataset
|
* Create a new BarGraph with the given dataset
|
||||||
*
|
*
|
||||||
* @param array $dataSet An array of datapoints
|
* @param array $dataSet An array of data points
|
||||||
|
* @param int $order The graph number displayed by this BarGraph
|
||||||
|
* @param array $tooltips The tooltips to display for each value
|
||||||
*/
|
*/
|
||||||
public function __construct(array $dataSet)
|
public function __construct(
|
||||||
{
|
array $dataSet,
|
||||||
|
array &$graphs,
|
||||||
|
$order,
|
||||||
|
array $tooltips = null
|
||||||
|
) {
|
||||||
|
$this->order = $order;
|
||||||
$this->dataSet = $dataSet;
|
$this->dataSet = $dataSet;
|
||||||
|
$this->tooltips = $tooltips;
|
||||||
|
$this->graphs = $graphs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,6 +86,30 @@ class BarGraph extends Styleable implements Drawable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a single rectangle
|
||||||
|
*
|
||||||
|
* @param array $point The
|
||||||
|
* @param null $index
|
||||||
|
* @param string $fill The fill color to use
|
||||||
|
* @param $strokeWidth
|
||||||
|
*
|
||||||
|
* @return Rect
|
||||||
|
*/
|
||||||
|
private function drawSingleBar($point, $index = null, $fill, $strokeWidth)
|
||||||
|
{
|
||||||
|
$rect = new Rect($point[0] - ($this->barWidth / 2), $point[1], $this->barWidth, 100 - $point[1]);
|
||||||
|
$rect->setFill($fill);
|
||||||
|
$rect->setStrokeWidth($strokeWidth);
|
||||||
|
$rect->setStrokeColor('black');
|
||||||
|
if (isset($index)) {
|
||||||
|
$rect->setAttribute('data-icinga-graph-index', $index);
|
||||||
|
}
|
||||||
|
$rect->setAttribute('data-icinga-graph-type', 'bar');
|
||||||
|
$rect->setAdditionalStyle('clip-path: url(#clip);');
|
||||||
|
return $rect;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render this BarChart
|
* Render this BarChart
|
||||||
*
|
*
|
||||||
|
@ -68,23 +122,33 @@ class BarGraph extends Styleable implements Drawable
|
||||||
$doc = $ctx->getDocument();
|
$doc = $ctx->getDocument();
|
||||||
$group = $doc->createElement('g');
|
$group = $doc->createElement('g');
|
||||||
$idx = 0;
|
$idx = 0;
|
||||||
foreach ($this->dataSet as $point) {
|
foreach ($this->dataSet as $x => $point) {
|
||||||
$rect = new Rect($point[0] - 2, $point[1], 4, 100 - $point[1]);
|
// add white background bar, to prevent other bars from altering transparency effects
|
||||||
$rect->setFill($this->fill);
|
$bar = $this->drawSingleBar($point, $idx++, 'white', $this->strokeWidth, $idx)->toSvg($ctx);
|
||||||
$rect->setStrokeWidth($this->strokeWidth);
|
$group->appendChild($bar);
|
||||||
$rect->setStrokeColor('black');
|
|
||||||
$rect->setAttribute('data-icinga-graph-index', $idx++);
|
// draw actual bar
|
||||||
$rect->setAttribute('data-icinga-graph-type', 'bar');
|
$bar = $this->drawSingleBar($point, null, $this->fill, $this->strokeWidth, $idx)->toSvg($ctx);
|
||||||
$rect->setAdditionalStyle('clip-path: url(#clip);');
|
$bar->setAttribute('class', 'chart-data');
|
||||||
/*$rect->setAnimation(
|
if (isset($this->tooltips[$x])) {
|
||||||
new Animation(
|
$data = array(
|
||||||
'y',
|
'label' => isset($this->graphs[$this->order]['label']) ?
|
||||||
$ctx->yToAbsolute(100),
|
strtolower($this->graphs[$this->order]['label']) : '',
|
||||||
$ctx->yToAbsolute($point[1]),
|
'color' => isset($this->graphs[$this->order]['color']) ?
|
||||||
rand(1, 1.5)/2
|
strtolower($this->graphs[$this->order]['color']) : '#fff'
|
||||||
)
|
);
|
||||||
);*/
|
$format = isset($this->graphs[$this->order]['tooltip'])
|
||||||
$group->appendChild($rect->toSvg($ctx));
|
? $this->graphs[$this->order]['tooltip'] : null;
|
||||||
|
$bar->setAttribute(
|
||||||
|
'title',
|
||||||
|
$this->tooltips[$x]->renderNoHtml($this->order, $data, $format)
|
||||||
|
);
|
||||||
|
$bar->setAttribute(
|
||||||
|
'title-rich',
|
||||||
|
$this->tooltips[$x]->render($this->order, $data, $format)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$group->appendChild($bar);
|
||||||
}
|
}
|
||||||
return $group;
|
return $group;
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,10 @@ class StackedGraph implements Drawable
|
||||||
if (!isset($this->points[$x])) {
|
if (!isset($this->points[$x])) {
|
||||||
$this->points[$x] = 0;
|
$this->points[$x] = 0;
|
||||||
}
|
}
|
||||||
|
// store old y-value for displaying the actual (non-aggregated)
|
||||||
|
// value in the tooltip
|
||||||
|
$point[2] = $point[1];
|
||||||
|
|
||||||
$this->points[$x] += $point[1];
|
$this->points[$x] += $point[1];
|
||||||
$point[1] = $this->points[$x];
|
$point[1] = $this->points[$x];
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
<?php
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
namespace Icinga\Chart\Graph;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tooltip that stores and aggregates information about displayed data
|
||||||
|
* points of a graph and replaces them in a format string to render the description
|
||||||
|
* for specific data points of the graph.
|
||||||
|
*
|
||||||
|
* When render() is called, placeholders for the keys for each data entry will be replaced by
|
||||||
|
* the current value of this data set and the formatted string will be returned.
|
||||||
|
* The content of the replaced keys can change for each data set and depends on how the data
|
||||||
|
* is passed to this class. There are several types of properties:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>Global properties</li>: Key-value pairs that stay the same every time render is called, and are
|
||||||
|
* passed to an instance in the constructor.
|
||||||
|
* <li>Aggregated properties</li>: Global properties that are created automatically from
|
||||||
|
* all attached data points.
|
||||||
|
* <li>Local properties</li>: Key-value pairs that only apply to a single data point and
|
||||||
|
* are passed to the render-function.
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
class Tooltip
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The default format string used
|
||||||
|
* when no other format is specified
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $defaultFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All aggregated points
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $points = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains all static replacements
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $data = array(
|
||||||
|
'sum' => 0
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to format the displayed tooltip.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $tooltipFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new tooltip with the specified default format string
|
||||||
|
*
|
||||||
|
* Allows you to set the global data for this tooltip, that is displayed every
|
||||||
|
* time render is called.
|
||||||
|
*
|
||||||
|
* @param array $data Map of global properties
|
||||||
|
* @param string $format The default format string
|
||||||
|
*/
|
||||||
|
public function __construct (
|
||||||
|
$data = array(),
|
||||||
|
$format = '<b>{title}</b></b><br> {value} of {sum} {label}'
|
||||||
|
) {
|
||||||
|
$this->data = array_merge($this->data, $data);
|
||||||
|
$this->defaultFormat = $format;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a single data point to update the aggregated properties for this tooltip
|
||||||
|
*
|
||||||
|
* @param $point array Contains the (x,y) values of the data set
|
||||||
|
*/
|
||||||
|
public function addDataPoint($point)
|
||||||
|
{
|
||||||
|
// set x-value
|
||||||
|
if (!isset($this->data['title'])) {
|
||||||
|
$this->data['title'] = $point[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// aggregate y-values
|
||||||
|
$y = (int)$point[1];
|
||||||
|
if (isset($point[2])) {
|
||||||
|
// load original value in case value already aggregated
|
||||||
|
$y = (int)$point[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($this->data['min']) || $this->data['min'] > $y) {
|
||||||
|
$this->data['min'] = $y;
|
||||||
|
}
|
||||||
|
if (!isset($this->data['max']) || $this->data['max'] < $y) {
|
||||||
|
$this->data['max'] = $y;
|
||||||
|
}
|
||||||
|
$this->data['sum'] += $y;
|
||||||
|
$this->points[] = $y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the tooltip for a certain data point
|
||||||
|
*
|
||||||
|
* @param array $order Which data set to render
|
||||||
|
* @param array $data The local data for this tooltip
|
||||||
|
* @param string $format Use a custom format string for this data set
|
||||||
|
*
|
||||||
|
* @return mixed|string The tooltip value
|
||||||
|
*/
|
||||||
|
public function render($order, $data = array(), $format = null)
|
||||||
|
{
|
||||||
|
if (isset($format)) {
|
||||||
|
$str = $format;
|
||||||
|
} else {
|
||||||
|
$str = $this->defaultFormat;
|
||||||
|
}
|
||||||
|
$data['value'] = $this->points[$order];
|
||||||
|
foreach (array_merge($this->data, $data) as $key => $value) {
|
||||||
|
$str = str_replace('{' . $key . '}', $value, $str);
|
||||||
|
}
|
||||||
|
return $str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the tooltip for a certain data point but remove all
|
||||||
|
* occurring html tags
|
||||||
|
*
|
||||||
|
* This is useful for rendering clean tooltips on client without JavaScript
|
||||||
|
*
|
||||||
|
* @param array $order Which data set to render
|
||||||
|
* @param array $data The local data for this tooltip
|
||||||
|
* @param string $format Use a custom format string for this data set
|
||||||
|
*
|
||||||
|
* @return mixed|string The tooltip value, without any HTML tags
|
||||||
|
*/
|
||||||
|
public function renderNoHtml($order, $data, $format)
|
||||||
|
{
|
||||||
|
return strip_tags($this->render($order, $data, $format));
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ use Icinga\Chart\Axis;
|
||||||
use Icinga\Chart\Graph\BarGraph;
|
use Icinga\Chart\Graph\BarGraph;
|
||||||
use Icinga\Chart\Graph\LineGraph;
|
use Icinga\Chart\Graph\LineGraph;
|
||||||
use Icinga\Chart\Graph\StackedGraph;
|
use Icinga\Chart\Graph\StackedGraph;
|
||||||
|
use Icinga\Chart\Graph\Tooltip;
|
||||||
use Icinga\Chart\Primitive\Canvas;
|
use Icinga\Chart\Primitive\Canvas;
|
||||||
use Icinga\Chart\Primitive\Rect;
|
use Icinga\Chart\Primitive\Rect;
|
||||||
use Icinga\Chart\Primitive\Path;
|
use Icinga\Chart\Primitive\Path;
|
||||||
|
@ -74,6 +75,16 @@ class GridChart extends Chart
|
||||||
*/
|
*/
|
||||||
private $stacks = array();
|
private $stacks = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An associative array containing all Tooltips used to render the titles
|
||||||
|
*
|
||||||
|
* Each tooltip represents the summary for all y-values of a certain x-value
|
||||||
|
* in the grid chart
|
||||||
|
*
|
||||||
|
* @var Tooltip
|
||||||
|
*/
|
||||||
|
private $tooltips = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the current dataset has the proper structure for this chart.
|
* Check if the current dataset has the proper structure for this chart.
|
||||||
*
|
*
|
||||||
|
@ -169,6 +180,26 @@ class GridChart extends Chart
|
||||||
$this->legend->addDataset($graph);
|
$this->legend->addDataset($graph);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$this->initTooltips($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function initTooltips($data)
|
||||||
|
{
|
||||||
|
foreach ($data as &$graph) {
|
||||||
|
foreach ($graph['data'] as $x => $point) {
|
||||||
|
if (!array_key_exists($x, $this->tooltips)) {
|
||||||
|
$this->tooltips[$x] = new Tooltip(
|
||||||
|
array(
|
||||||
|
'color' => $graph['color'],
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$this->tooltips[$x]->addDataPoint($point);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -353,11 +384,16 @@ class GridChart extends Chart
|
||||||
foreach ($this->graphs as $axisName => $graphs) {
|
foreach ($this->graphs as $axisName => $graphs) {
|
||||||
$axis = $this->axis[$axisName];
|
$axis = $this->axis[$axisName];
|
||||||
$graphObj = null;
|
$graphObj = null;
|
||||||
foreach ($graphs as $graph) {
|
foreach ($graphs as $dataset => $graph) {
|
||||||
// determine the type and create a graph object for it
|
// determine the type and create a graph object for it
|
||||||
switch ($graph['graphType']) {
|
switch ($graph['graphType']) {
|
||||||
case self::TYPE_BAR:
|
case self::TYPE_BAR:
|
||||||
$graphObj = new BarGraph($axis->transform($graph['data']));
|
$graphObj = new BarGraph(
|
||||||
|
$axis->transform($graph['data']),
|
||||||
|
$graphs,
|
||||||
|
$dataset,
|
||||||
|
$this->tooltips
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case self::TYPE_LINE:
|
case self::TYPE_LINE:
|
||||||
$graphObj = new LineGraph($axis->transform($graph['data']));
|
$graphObj = new LineGraph($axis->transform($graph['data']));
|
||||||
|
|
|
@ -25,12 +25,14 @@ class JavaScript
|
||||||
|
|
||||||
protected static $vendorFiles = array(
|
protected static $vendorFiles = array(
|
||||||
'js/vendor/jquery-2.1.0',
|
'js/vendor/jquery-2.1.0',
|
||||||
'js/vendor/jquery.sparkline'
|
'js/vendor/jquery.sparkline',
|
||||||
|
'js/vendor/jquery.tipsy'
|
||||||
);
|
);
|
||||||
|
|
||||||
protected static $ie8VendorFiles = array(
|
protected static $ie8VendorFiles = array(
|
||||||
'js/vendor/jquery-1.11.0',
|
'js/vendor/jquery-1.11.0',
|
||||||
'js/vendor/jquery.sparkline'
|
'js/vendor/jquery.sparkline',
|
||||||
|
'js/vendor/jquery.tipsy'
|
||||||
);
|
);
|
||||||
|
|
||||||
public static function listModuleFiles()
|
public static function listModuleFiles()
|
||||||
|
|
|
@ -24,6 +24,8 @@ class StyleSheet
|
||||||
'css/icinga/monitoring-colors.less',
|
'css/icinga/monitoring-colors.less',
|
||||||
'css/icinga/selection-toolbar.less',
|
'css/icinga/selection-toolbar.less',
|
||||||
'css/icinga/login.less',
|
'css/icinga/login.less',
|
||||||
|
'css/icinga/charts.less',
|
||||||
|
'css/vendor/tipsy.css'
|
||||||
);
|
);
|
||||||
|
|
||||||
public static function compileForPdf()
|
public static function compileForPdf()
|
||||||
|
|
|
@ -8,7 +8,6 @@ use Icinga\Application\Icinga;
|
||||||
use Icinga\Application\Config as IcingaConfig;
|
use Icinga\Application\Config as IcingaConfig;
|
||||||
use Icinga\Exception\ConfigurationError;
|
use Icinga\Exception\ConfigurationError;
|
||||||
use Icinga\Exception\ProgrammingError;
|
use Icinga\Exception\ProgrammingError;
|
||||||
use Icinga\Web\Widget\AbstractWidget;
|
|
||||||
use Icinga\Web\Widget\Dashboard\Pane;
|
use Icinga\Web\Widget\Dashboard\Pane;
|
||||||
use Icinga\Web\Widget\Dashboard\Component as DashboardComponent;
|
use Icinga\Web\Widget\Dashboard\Component as DashboardComponent;
|
||||||
use Icinga\Web\Url;
|
use Icinga\Web\Url;
|
||||||
|
@ -96,7 +95,7 @@ class Dashboard extends AbstractWidget
|
||||||
$current = $this->panes[$pane->getName()];
|
$current = $this->panes[$pane->getName()];
|
||||||
$current->addComponents($pane->getComponents());
|
$current->addComponents($pane->getComponents());
|
||||||
} else {
|
} else {
|
||||||
$this->panes = array_filter(array_merge($this->panes, $panes));
|
$this->panes[$pane->getName()] = $pane;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +127,16 @@ class Dashboard extends AbstractWidget
|
||||||
return $this->tabs;
|
return $this->tabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all panes of this dashboard
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getPanes()
|
||||||
|
{
|
||||||
|
return $this->panes;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populate this dashboard via the given configuration file
|
* Populate this dashboard via the given configuration file
|
||||||
*
|
*
|
||||||
|
@ -164,9 +173,9 @@ class Dashboard extends AbstractWidget
|
||||||
*
|
*
|
||||||
* @TODO: Should only allow component objects to be added directly as soon as we store more information
|
* @TODO: Should only allow component objects to be added directly as soon as we store more information
|
||||||
*
|
*
|
||||||
* @param string $pane The pane to add the component to
|
* @param string $pane The pane to add the component to
|
||||||
* @param Component|string $component The component to add or the title of the newly created component
|
* @param Component|string $component The component to add or the title of the newly created component
|
||||||
* @param $url The url to use for the component
|
* @param string|null $url The url to use for the component
|
||||||
*
|
*
|
||||||
* @return self
|
* @return self
|
||||||
*/
|
*/
|
||||||
|
@ -198,20 +207,14 @@ class Dashboard extends AbstractWidget
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if a pane doesn't exist or doesn't have any components in it
|
* Check if this dashboard has a specific pane
|
||||||
*
|
|
||||||
* @param string $pane The name of the pane to check for emptyness
|
|
||||||
*
|
*
|
||||||
|
* @param $pane string The name of the pane
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isEmptyPane($pane)
|
public function hasPane($pane)
|
||||||
{
|
{
|
||||||
$paneObj = $this->getPane($pane);
|
return array_key_exists($pane, $this->panes);
|
||||||
if ($paneObj === null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
$cmps = $paneObj->getComponents();
|
|
||||||
return !empty($cmps);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -305,11 +308,11 @@ class Dashboard extends AbstractWidget
|
||||||
return $active;
|
return $active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see determineActivePane()
|
||||||
|
*/
|
||||||
public function getActivePane()
|
public function getActivePane()
|
||||||
{
|
{
|
||||||
if ($active = $this->getTabs()->getActiveName()) {
|
|
||||||
return $this->getPane($active);
|
|
||||||
}
|
|
||||||
return $this->determineActivePane();
|
return $this->determineActivePane();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,10 +326,12 @@ class Dashboard extends AbstractWidget
|
||||||
$active = $this->getTabs()->getActiveName();
|
$active = $this->getTabs()->getActiveName();
|
||||||
if (! $active) {
|
if (! $active) {
|
||||||
if ($active = Url::fromRequest()->getParam($this->tabParam)) {
|
if ($active = Url::fromRequest()->getParam($this->tabParam)) {
|
||||||
if ($this->isEmptyPane($active)) {
|
if ($this->hasPane($active)) {
|
||||||
$active = $this->setDefaultPane();
|
|
||||||
} else {
|
|
||||||
$this->activate($active);
|
$this->activate($active);
|
||||||
|
} else {
|
||||||
|
throw new ProgrammingError(
|
||||||
|
'Try to get an inexistent pane.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$active = $this->setDefaultPane();
|
$active = $this->setDefaultPane();
|
||||||
|
|
|
@ -42,6 +42,13 @@ class Component extends AbstractWidget
|
||||||
*/
|
*/
|
||||||
private $pane;
|
private $pane;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The disabled option is used to "delete" default dashlets provided by modules
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $disabled = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The template string used for rendering this widget
|
* The template string used for rendering this widget
|
||||||
*
|
*
|
||||||
|
@ -117,6 +124,26 @@ EOD;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the disabled property
|
||||||
|
*
|
||||||
|
* @param boolean $disabled
|
||||||
|
*/
|
||||||
|
public function setDisabled($disabled)
|
||||||
|
{
|
||||||
|
$this->disabled = $disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the disabled property
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function getDisabled()
|
||||||
|
{
|
||||||
|
return $this->disabled;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return this component's structure as array
|
* Return this component's structure as array
|
||||||
*
|
*
|
||||||
|
@ -136,6 +163,10 @@ EOD;
|
||||||
*/
|
*/
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
|
if ($this->disabled === true) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
$view = $this->view();
|
$view = $this->view();
|
||||||
$url = clone($this->url);
|
$url = clone($this->url);
|
||||||
$url->setParam('view', 'compact');
|
$url->setParam('view', 'compact');
|
||||||
|
|
|
@ -39,7 +39,7 @@ class Pane extends AbstractWidget
|
||||||
/**
|
/**
|
||||||
* Create a new pane
|
* Create a new pane
|
||||||
*
|
*
|
||||||
* @param $name The pane to create
|
* @param string $name The pane to create
|
||||||
*/
|
*/
|
||||||
public function __construct($name)
|
public function __construct($name)
|
||||||
{
|
{
|
||||||
|
@ -92,6 +92,16 @@ class Pane extends AbstractWidget
|
||||||
return array_key_exists($title, $this->components);
|
return array_key_exists($title, $this->components);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current pane has any components
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasComponents()
|
||||||
|
{
|
||||||
|
return ! empty($this->components);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a component with the given name if existing
|
* Return a component with the given name if existing
|
||||||
*
|
*
|
||||||
|
|
|
@ -99,7 +99,7 @@ class Monitoring_ChartController extends Controller
|
||||||
'services_warning_unhandled',
|
'services_warning_unhandled',
|
||||||
'services_pending'
|
'services_pending'
|
||||||
)
|
)
|
||||||
)->getQuery()->fetchAll();
|
)->order('hostgroup')->getQuery()->fetchAll();
|
||||||
$this->view->height = intval($this->getParam('height', 500));
|
$this->view->height = intval($this->getParam('height', 500));
|
||||||
$this->view->width = intval($this->getParam('width', 500));
|
$this->view->width = intval($this->getParam('width', 500));
|
||||||
if (count($query) === 1) {
|
if (count($query) === 1) {
|
||||||
|
@ -124,7 +124,7 @@ class Monitoring_ChartController extends Controller
|
||||||
'services_warning_unhandled',
|
'services_warning_unhandled',
|
||||||
'services_pending'
|
'services_pending'
|
||||||
)
|
)
|
||||||
)->getQuery()->fetchAll();
|
)->order('servicegroup')->getQuery()->fetchAll();
|
||||||
$this->view->height = intval($this->getParam('height', 500));
|
$this->view->height = intval($this->getParam('height', 500));
|
||||||
$this->view->width = intval($this->getParam('width', 500));
|
$this->view->width = intval($this->getParam('width', 500));
|
||||||
|
|
||||||
|
@ -154,30 +154,35 @@ class Monitoring_ChartController extends Controller
|
||||||
->setXAxis(new \Icinga\Chart\Unit\StaticAxis())
|
->setXAxis(new \Icinga\Chart\Unit\StaticAxis())
|
||||||
->setAxisMin(null, 0);
|
->setAxisMin(null, 0);
|
||||||
|
|
||||||
|
$tooltip = t('<b>{title}:</b><br />{value} of {sum} services are {label}');
|
||||||
$this->view->chart->drawBars(
|
$this->view->chart->drawBars(
|
||||||
array(
|
array(
|
||||||
'label' => t('Ok'),
|
'label' => t('Ok'),
|
||||||
'color' => '#44bb77',
|
'color' => '#44bb77',
|
||||||
'stack' => 'stack1',
|
'stack' => 'stack1',
|
||||||
'data' => $okBars
|
'data' => $okBars,
|
||||||
|
'tooltip' => $tooltip
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'label' => t('Warning'),
|
'label' => t('Warning'),
|
||||||
'color' => '#ffaa44',
|
'color' => '#ffaa44',
|
||||||
'stack' => 'stack1',
|
'stack' => 'stack1',
|
||||||
'data' => $warningBars
|
'data' => $warningBars,
|
||||||
|
'tooltip' => $tooltip
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'label' => t('Critical'),
|
'label' => t('Critical'),
|
||||||
'color' => '#ff5566',
|
'color' => '#ff5566',
|
||||||
'stack' => 'stack1',
|
'stack' => 'stack1',
|
||||||
'data' => $critBars
|
'data' => $critBars,
|
||||||
|
'tooltip' => $tooltip
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'label' => t('Unknown'),
|
'label' => t('Unknown'),
|
||||||
'color' => '#dd66ff',
|
'color' => '#dd66ff',
|
||||||
'stack' => 'stack1',
|
'stack' => 'stack1',
|
||||||
'data' => $unknownBars
|
'data' => $unknownBars,
|
||||||
|
'tooltip' => $tooltip
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -201,6 +206,7 @@ class Monitoring_ChartController extends Controller
|
||||||
$hostgroup->hosts_unreachable_unhandled
|
$hostgroup->hosts_unreachable_unhandled
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
$tooltip = t('<b>{title}:</b><br /> {value} of {sum} hosts are {label}');
|
||||||
$this->view->chart = new GridChart();
|
$this->view->chart = new GridChart();
|
||||||
$this->view->chart->alignTopLeft();
|
$this->view->chart->alignTopLeft();
|
||||||
$this->view->chart->setAxisLabel('', t('Hosts'))
|
$this->view->chart->setAxisLabel('', t('Hosts'))
|
||||||
|
@ -211,19 +217,22 @@ class Monitoring_ChartController extends Controller
|
||||||
'label' => t('Up'),
|
'label' => t('Up'),
|
||||||
'color' => '#44bb77',
|
'color' => '#44bb77',
|
||||||
'stack' => 'stack1',
|
'stack' => 'stack1',
|
||||||
'data' => $upBars
|
'data' => $upBars,
|
||||||
|
'tooltip' => $tooltip
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'label' => t('Down'),
|
'label' => t('Down'),
|
||||||
'color' => '#ff5566',
|
'color' => '#ff5566',
|
||||||
'stack' => 'stack1',
|
'stack' => 'stack1',
|
||||||
'data' => $downBars
|
'data' => $downBars,
|
||||||
|
'tooltip' => $tooltip
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'label' => t('Unreachable'),
|
'label' => t('Unreachable'),
|
||||||
'color' => '#dd66ff',
|
'color' => '#dd66ff',
|
||||||
'stack' => 'stack1',
|
'stack' => 'stack1',
|
||||||
'data' => $unreachableBars
|
'data' => $unreachableBars,
|
||||||
|
'tooltip' => $tooltip
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,20 +37,4 @@ class Groupsummary extends DataView
|
||||||
'services_pending'
|
'services_pending'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSortRules()
|
|
||||||
{
|
|
||||||
if (in_array('servicegroup', $this->getQuery()->getColumns())) {
|
|
||||||
return array(
|
|
||||||
'servicegroup' => array(
|
|
||||||
'order' => self::SORT_ASC
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return array(
|
|
||||||
'hostgroup' => array(
|
|
||||||
'order' => self::SORT_ASC
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
/* Add hover effects to chart data */
|
||||||
|
.chart-data:hover {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-data:hover {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/* tipsy, facebook style tooltips for jquery
|
||||||
|
version 1.0.0a
|
||||||
|
(c) 2008-2010 jason frame [jason@onehackoranother.com]
|
||||||
|
released under the MIT license */
|
||||||
|
|
||||||
|
.tipsy { font-size: 14px; position: absolute; padding: 5px; z-index: 100000; }
|
||||||
|
.tipsy-inner { background-color: #000; color: #FFF; max-width: 200px; padding: 5px 8px 4px 8px; text-align: center; }
|
||||||
|
|
||||||
|
/* Rounded corners */
|
||||||
|
.tipsy-inner { border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; }
|
||||||
|
|
||||||
|
/* Uncomment for shadow */
|
||||||
|
/*.tipsy-inner { box-shadow: 0 0 5px #000000; -webkit-box-shadow: 0 0 5px #000000; -moz-box-shadow: 0 0 5px #000000; }*/
|
||||||
|
|
||||||
|
.tipsy-arrow { position: absolute; width: 0; height: 0; line-height: 0; border: 5px dashed #000; }
|
||||||
|
|
||||||
|
/* Rules to colour arrows */
|
||||||
|
.tipsy-arrow-n { border-bottom-color: #000; }
|
||||||
|
.tipsy-arrow-s { border-top-color: #000; }
|
||||||
|
.tipsy-arrow-e { border-left-color: #000; }
|
||||||
|
.tipsy-arrow-w { border-right-color: #000; }
|
||||||
|
|
||||||
|
.tipsy-n .tipsy-arrow { top: 0px; left: 50%; margin-left: -5px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent; }
|
||||||
|
.tipsy-nw .tipsy-arrow { top: 0; left: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;}
|
||||||
|
.tipsy-ne .tipsy-arrow { top: 0; right: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;}
|
||||||
|
.tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -5px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
|
||||||
|
.tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
|
||||||
|
.tipsy-se .tipsy-arrow { bottom: 0; right: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
|
||||||
|
.tipsy-e .tipsy-arrow { right: 0; top: 50%; margin-top: -5px; border-left-style: solid; border-right: none; border-top-color: transparent; border-bottom-color: transparent; }
|
||||||
|
.tipsy-w .tipsy-arrow { left: 0; top: 50%; margin-top: -5px; border-right-style: solid; border-left: none; border-top-color: transparent; border-bottom-color: transparent; }
|
|
@ -53,6 +53,8 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('td.state span.timesince').attr('title', null);
|
||||||
|
|
||||||
var moduleName = el.data('icingaModule');
|
var moduleName = el.data('icingaModule');
|
||||||
if (moduleName) {
|
if (moduleName) {
|
||||||
if (icinga.hasModule(moduleName)) {
|
if (icinga.hasModule(moduleName)) {
|
||||||
|
@ -109,6 +111,62 @@
|
||||||
if (searchField.length && searchField.val().length) {
|
if (searchField.length && searchField.val().length) {
|
||||||
this.searchValue = searchField.val();
|
this.searchValue = searchField.val();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$('[title]').each(function () {
|
||||||
|
var $el = $(this);
|
||||||
|
$el.attr('title', $el.attr('title-rich') || $el.attr('title'));
|
||||||
|
// $el.attr('title', null);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('svg rect.chart-data[title]', el).tipsy({ gravity: 'e', html: true });
|
||||||
|
$('.historycolorgrid a[title]', el).tipsy({ gravity: 's', offset: 2 });
|
||||||
|
$('img.icon[title]', el).tipsy({ gravity: $.fn.tipsy.autoNS, offset: 2 });
|
||||||
|
$('[title]', el).tipsy({ gravity: $.fn.tipsy.autoNS, delayIn: 500 });
|
||||||
|
|
||||||
|
// Rescue or remove all orphaned tooltips
|
||||||
|
$('.tipsy').each(function () {
|
||||||
|
function isElementInDOM(ele) {
|
||||||
|
while (ele = ele.parentNode) {
|
||||||
|
if (ele == document) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
var arrow = $('.tipsy-arrow', this)[0];
|
||||||
|
if (!icinga.utils.elementsOverlap(arrow, $('#main')[0])) {
|
||||||
|
$(this).remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// all tooltips are direct children of the body
|
||||||
|
// so we need find out whether the tooltip belongs applied area,
|
||||||
|
// by checking if both areas overlap
|
||||||
|
if (!icinga.utils.elementsOverlap(arrow, el)) {
|
||||||
|
// tooltip does not belong to this area
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pointee = $.data(this, 'tipsy-pointee');
|
||||||
|
if (!pointee || !isElementInDOM(pointee)) {
|
||||||
|
var orphan = this;
|
||||||
|
var oldTitle = $(this).find('.tipsy-inner').html();
|
||||||
|
var migrated = false;
|
||||||
|
// try to find an element with the same title
|
||||||
|
$('[original-title="' + oldTitle + '"]').each(function() {
|
||||||
|
// get stored instance of Tipsy from newly created element
|
||||||
|
// point it to the orphaned tooltip
|
||||||
|
var tipsy = $.data(this, 'tipsy');
|
||||||
|
tipsy.$tip = $(orphan);
|
||||||
|
$.data(this, 'tipsy', tipsy);
|
||||||
|
|
||||||
|
// orphaned tooltip has the new element as pointee
|
||||||
|
$.data(orphan, 'tipsy-pointee', this);
|
||||||
|
migrated = true;
|
||||||
|
});
|
||||||
|
if (!migrated) {
|
||||||
|
$(orphan).remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -162,6 +220,13 @@
|
||||||
// $(document).on('keyup', 'form.auto input', this.formChangeDelayed);
|
// $(document).on('keyup', 'form.auto input', this.formChangeDelayed);
|
||||||
// $(document).on('change', 'form.auto input', this.formChanged);
|
// $(document).on('change', 'form.auto input', this.formChanged);
|
||||||
// $(document).on('change', 'form.auto select', this.submitForm);
|
// $(document).on('change', 'form.auto select', this.submitForm);
|
||||||
|
|
||||||
|
$(document).on('mouseenter', '[title-original]', { gravity: 'n' }, function(event) {
|
||||||
|
icinga.ui.hoverTooltip (this, event.data);
|
||||||
|
});
|
||||||
|
$(document).on('mouseleave', '[title-original]', {}, function() {
|
||||||
|
icinga.ui.unhoverTooltip (this);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
menuTitleHovered: function (event) {
|
menuTitleHovered: function (event) {
|
||||||
|
|
|
@ -10,6 +10,9 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
// The currently hovered tooltip
|
||||||
|
var tooltip = null;
|
||||||
|
|
||||||
// Stores the icinga-data-url of the last focused table.
|
// Stores the icinga-data-url of the last focused table.
|
||||||
var focusedTableDataUrl = null;
|
var focusedTableDataUrl = null;
|
||||||
|
|
||||||
|
@ -767,7 +770,6 @@
|
||||||
this.debugTimer = null;
|
this.debugTimer = null;
|
||||||
this.timeCounterTimer = null;
|
this.timeCounterTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}(Icinga, jQuery));
|
}(Icinga, jQuery));
|
||||||
|
|
|
@ -200,6 +200,40 @@
|
||||||
return params;
|
return params;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether two HTMLElements overlap
|
||||||
|
*
|
||||||
|
* @param a {HTMLElement}
|
||||||
|
* @param b {HTMLElement}
|
||||||
|
*
|
||||||
|
* @returns {Boolean} whether elements overlap, will return false when one
|
||||||
|
* element is not in the DOM
|
||||||
|
*/
|
||||||
|
elementsOverlap: function(a, b)
|
||||||
|
{
|
||||||
|
// a bounds
|
||||||
|
var aoff = $(a).offset();
|
||||||
|
if (!aoff) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var at = aoff.top;
|
||||||
|
var ah = a.offsetHeight;
|
||||||
|
var al = aoff.left;
|
||||||
|
var aw = a.offsetWidth;
|
||||||
|
|
||||||
|
// b bounds
|
||||||
|
var boff = $(b).offset();
|
||||||
|
if (!boff) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var bt = boff.top;
|
||||||
|
var bh = b.offsetHeight;
|
||||||
|
var bl = boff.left;
|
||||||
|
var bw = b.offsetWidth;
|
||||||
|
|
||||||
|
return !(at > (bt + bh) || bt > (at + ah)) && !(bl > (al + aw) || al > (bl + bw));
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleanup
|
* Cleanup
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
jquery.tipsy.js SOURCE
|
||||||
|
======================
|
||||||
|
|
||||||
|
This file contains information about how to acquire and install the source files for jquery.tipsy
|
||||||
|
|
||||||
|
|
||||||
|
# version
|
||||||
|
|
||||||
|
1.0.0a
|
||||||
|
|
||||||
|
|
||||||
|
# license
|
||||||
|
|
||||||
|
MIT license
|
||||||
|
|
||||||
|
|
||||||
|
# used files
|
||||||
|
|
||||||
|
src/javascript/tipsy.css
|
||||||
|
src/javascript/jquery.tipsy.js
|
||||||
|
|
||||||
|
|
||||||
|
# source
|
||||||
|
|
||||||
|
https://github.com/jaz303/tipsy.git
|
||||||
|
|
||||||
|
|
||||||
|
# installation
|
||||||
|
|
||||||
|
|
||||||
|
mv src/javascript/tipsy.css ICINGAWEB/public/css/vendor/tipsy.css
|
||||||
|
mv src/javascript/jquery.tipsy.js ICINGAWEB/public/js/vendor/jquery.tipsy.js
|
||||||
|
uglifyjs src/javascript/jquery.tipsy.js ICINGAWEB/public/js/vendor/jquery.tipsy.min.js
|
|
@ -0,0 +1,262 @@
|
||||||
|
// tipsy, facebook style tooltips for jquery
|
||||||
|
// version 1.0.0a
|
||||||
|
// (c) 2008-2010 jason frame [jason@onehackoranother.com]
|
||||||
|
// released under the MIT license
|
||||||
|
|
||||||
|
(function($) {
|
||||||
|
|
||||||
|
function maybeCall(thing, ctx) {
|
||||||
|
return (typeof thing == 'function') ? (thing.call(ctx)) : thing;
|
||||||
|
};
|
||||||
|
|
||||||
|
function isElementInDOM(ele) {
|
||||||
|
while (ele = ele.parentNode) {
|
||||||
|
if (ele == document) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Tipsy(element, options) {
|
||||||
|
this.$element = $(element);
|
||||||
|
this.options = options;
|
||||||
|
this.enabled = true;
|
||||||
|
this.fixTitle();
|
||||||
|
};
|
||||||
|
|
||||||
|
Tipsy.prototype = {
|
||||||
|
show: function() {
|
||||||
|
var title = this.getTitle();
|
||||||
|
if (title && this.enabled) {
|
||||||
|
var $tip = this.tip();
|
||||||
|
|
||||||
|
$tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
|
||||||
|
$tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity
|
||||||
|
$tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body);
|
||||||
|
|
||||||
|
var pos = $.extend({}, this.$element.offset(), {
|
||||||
|
width: this.$element[0].offsetWidth,
|
||||||
|
height: this.$element[0].offsetHeight
|
||||||
|
});
|
||||||
|
|
||||||
|
var actualWidth = $tip[0].offsetWidth,
|
||||||
|
actualHeight = $tip[0].offsetHeight,
|
||||||
|
gravity = maybeCall(this.options.gravity, this.$element[0]);
|
||||||
|
|
||||||
|
var tp;
|
||||||
|
switch (gravity.charAt(0)) {
|
||||||
|
case 'n':
|
||||||
|
tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
|
||||||
|
break;
|
||||||
|
case 'e':
|
||||||
|
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset};
|
||||||
|
break;
|
||||||
|
case 'w':
|
||||||
|
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gravity.length == 2) {
|
||||||
|
if (gravity.charAt(1) == 'w') {
|
||||||
|
tp.left = pos.left + pos.width / 2 - 15;
|
||||||
|
} else {
|
||||||
|
tp.left = pos.left + pos.width / 2 - actualWidth + 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$tip.css(tp).addClass('tipsy-' + gravity);
|
||||||
|
$tip.find('.tipsy-arrow')[0].className = 'tipsy-arrow tipsy-arrow-' + gravity.charAt(0);
|
||||||
|
if (this.options.className) {
|
||||||
|
$tip.addClass(maybeCall(this.options.className, this.$element[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.fade) {
|
||||||
|
$tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity});
|
||||||
|
} else {
|
||||||
|
$tip.css({visibility: 'visible', opacity: this.options.opacity});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hide: function() {
|
||||||
|
if (this.options.fade) {
|
||||||
|
this.tip().stop().fadeOut(function() { $(this).remove(); });
|
||||||
|
} else {
|
||||||
|
this.tip().remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
fixTitle: function() {
|
||||||
|
var $e = this.$element;
|
||||||
|
if ($e.attr('title') || typeof($e.attr('original-title')) != 'string') {
|
||||||
|
$e.attr('original-title', $e.attr('title') || '').removeAttr('title');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getTitle: function() {
|
||||||
|
var title, $e = this.$element, o = this.options;
|
||||||
|
this.fixTitle();
|
||||||
|
var title, o = this.options;
|
||||||
|
if (typeof o.title == 'string') {
|
||||||
|
title = $e.attr(o.title == 'title' ? 'original-title' : o.title);
|
||||||
|
} else if (typeof o.title == 'function') {
|
||||||
|
title = o.title.call($e[0]);
|
||||||
|
}
|
||||||
|
title = ('' + title).replace(/(^\s*|\s*$)/, "");
|
||||||
|
return title || o.fallback;
|
||||||
|
},
|
||||||
|
|
||||||
|
tip: function() {
|
||||||
|
if (!this.$tip) {
|
||||||
|
this.$tip = $('<div class="tipsy"></div>').html('<div class="tipsy-arrow"></div><div class="tipsy-inner"></div>');
|
||||||
|
this.$tip.data('tipsy-pointee', this.$element[0]);
|
||||||
|
}
|
||||||
|
return this.$tip;
|
||||||
|
},
|
||||||
|
|
||||||
|
validate: function() {
|
||||||
|
if (!this.$element[0].parentNode) {
|
||||||
|
this.hide();
|
||||||
|
this.$element = null;
|
||||||
|
this.options = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
enable: function() { this.enabled = true; },
|
||||||
|
disable: function() { this.enabled = false; },
|
||||||
|
toggleEnabled: function() { this.enabled = !this.enabled; }
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.tipsy = function(options) {
|
||||||
|
|
||||||
|
if (options === true) {
|
||||||
|
return this.data('tipsy');
|
||||||
|
} else if (typeof options == 'string') {
|
||||||
|
var tipsy = this.data('tipsy');
|
||||||
|
if (tipsy) tipsy[options]();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
options = $.extend({}, $.fn.tipsy.defaults, options);
|
||||||
|
|
||||||
|
function get(ele) {
|
||||||
|
var tipsy = $.data(ele, 'tipsy');
|
||||||
|
if (!tipsy) {
|
||||||
|
tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options));
|
||||||
|
$.data(ele, 'tipsy', tipsy);
|
||||||
|
}
|
||||||
|
return tipsy;
|
||||||
|
}
|
||||||
|
|
||||||
|
function enter() {
|
||||||
|
var tipsy = get(this);
|
||||||
|
var ele = this;
|
||||||
|
tipsy.hoverState = 'in';
|
||||||
|
if (options.delayIn == 0) {
|
||||||
|
tipsy.show();
|
||||||
|
} else {
|
||||||
|
tipsy.fixTitle();
|
||||||
|
setTimeout(function() {
|
||||||
|
if (tipsy.hoverState == 'in' && ele && isElementInDOM(ele))
|
||||||
|
tipsy.show();
|
||||||
|
}, options.delayIn);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function leave() {
|
||||||
|
var tipsy = get(this);
|
||||||
|
tipsy.hoverState = 'out';
|
||||||
|
if (options.delayOut == 0) {
|
||||||
|
tipsy.hide();
|
||||||
|
} else {
|
||||||
|
setTimeout(function() { if (tipsy.hoverState == 'out') tipsy.hide(); }, options.delayOut);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!options.live) this.each(function() { get(this); });
|
||||||
|
|
||||||
|
if (options.trigger != 'manual') {
|
||||||
|
var binder = options.live ? 'live' : 'bind',
|
||||||
|
eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus',
|
||||||
|
eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur';
|
||||||
|
this[binder](eventIn, enter)[binder](eventOut, leave);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.tipsy.defaults = {
|
||||||
|
className: null,
|
||||||
|
delayIn: 0,
|
||||||
|
delayOut: 0,
|
||||||
|
fade: false,
|
||||||
|
fallback: '',
|
||||||
|
gravity: 'n',
|
||||||
|
html: false,
|
||||||
|
live: false,
|
||||||
|
offset: 0,
|
||||||
|
opacity: 0.8,
|
||||||
|
title: 'title',
|
||||||
|
trigger: 'hover'
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.tipsy.revalidate = function() {
|
||||||
|
$('.tipsy').each(function() {
|
||||||
|
var pointee = $.data(this, 'tipsy-pointee');
|
||||||
|
if (!pointee || !isElementInDOM(pointee)) {
|
||||||
|
$(this).remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Overwrite this method to provide options on a per-element basis.
|
||||||
|
// For example, you could store the gravity in a 'tipsy-gravity' attribute:
|
||||||
|
// return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' });
|
||||||
|
// (remember - do not modify 'options' in place!)
|
||||||
|
$.fn.tipsy.elementOptions = function(ele, options) {
|
||||||
|
return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.tipsy.autoNS = function() {
|
||||||
|
return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.tipsy.autoWE = function() {
|
||||||
|
return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* yields a closure of the supplied parameters, producing a function that takes
|
||||||
|
* no arguments and is suitable for use as an autogravity function like so:
|
||||||
|
*
|
||||||
|
* @param margin (int) - distance from the viewable region edge that an
|
||||||
|
* element should be before setting its tooltip's gravity to be away
|
||||||
|
* from that edge.
|
||||||
|
* @param prefer (string, e.g. 'n', 'sw', 'w') - the direction to prefer
|
||||||
|
* if there are no viewable region edges effecting the tooltip's
|
||||||
|
* gravity. It will try to vary from this minimally, for example,
|
||||||
|
* if 'sw' is preferred and an element is near the right viewable
|
||||||
|
* region edge, but not the top edge, it will set the gravity for
|
||||||
|
* that element's tooltip to be 'se', preserving the southern
|
||||||
|
* component.
|
||||||
|
*/
|
||||||
|
$.fn.tipsy.autoBounds = function(margin, prefer) {
|
||||||
|
return function() {
|
||||||
|
var dir = {ns: prefer[0], ew: (prefer.length > 1 ? prefer[1] : false)},
|
||||||
|
boundTop = $(document).scrollTop() + margin,
|
||||||
|
boundLeft = $(document).scrollLeft() + margin,
|
||||||
|
$this = $(this);
|
||||||
|
|
||||||
|
if ($this.offset().top < boundTop) dir.ns = 'n';
|
||||||
|
if ($this.offset().left < boundLeft) dir.ew = 'w';
|
||||||
|
if ($(window).width() + $(document).scrollLeft() - $this.offset().left < margin) dir.ew = 'e';
|
||||||
|
if ($(window).height() + $(document).scrollTop() - $this.offset().top < margin) dir.ns = 's';
|
||||||
|
|
||||||
|
return dir.ns + (dir.ew ? dir.ew : '');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
})(jQuery);
|
File diff suppressed because one or more lines are too long
|
@ -182,7 +182,7 @@ class FilterTest extends BaseTestCase
|
||||||
|
|
||||||
public function testComplexFilterFromQueryString()
|
public function testComplexFilterFromQueryString()
|
||||||
{
|
{
|
||||||
$q = 'host=localhost|nohost*&problem&service=*www*|ups*&state!=1&!handled';
|
$q = '(host=localhost|host=nohost*)&problem&(service=*www*|service=ups*)&state!=1&!handled';
|
||||||
$filter = Filter::fromQueryString($q);
|
$filter = Filter::fromQueryString($q);
|
||||||
$this->assertFalse($filter->matches($this->row(0)));
|
$this->assertFalse($filter->matches($this->row(0)));
|
||||||
$this->assertTrue($filter->matches($this->row(1)));
|
$this->assertTrue($filter->matches($this->row(1)));
|
||||||
|
|
|
@ -0,0 +1,509 @@
|
||||||
|
<?php
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
namespace Tests\Icinga\Web;
|
||||||
|
|
||||||
|
// Necessary as some of these tests disable phpunit's preservation
|
||||||
|
// of the global state (e.g. autoloaders are in the global state)
|
||||||
|
require_once realpath(dirname(__FILE__) . '/../../../../bootstrap.php');
|
||||||
|
|
||||||
|
use Mockery;
|
||||||
|
use Icinga\Application\Icinga;
|
||||||
|
use Icinga\Web\Widget\Dashboard;
|
||||||
|
use Icinga\Web\Widget\Dashboard\Pane;
|
||||||
|
use Icinga\Web\Widget\Dashboard\Component;
|
||||||
|
use Icinga\Test\BaseTestCase;
|
||||||
|
|
||||||
|
class ComponentWithMockedView extends Component
|
||||||
|
{
|
||||||
|
public function view()
|
||||||
|
{
|
||||||
|
$mock = Mockery::mock('Icinga\Web\View');
|
||||||
|
$mock->shouldReceive('escape');
|
||||||
|
|
||||||
|
return $mock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DashboardWithPredefinableActiveName extends Dashboard
|
||||||
|
{
|
||||||
|
public $activeName = '';
|
||||||
|
|
||||||
|
public function getTabs()
|
||||||
|
{
|
||||||
|
return Mockery::mock('Icinga\Web\Widget\Tabs')
|
||||||
|
->shouldReceive('getActiveName')->andReturn($this->activeName)
|
||||||
|
->shouldReceive('activate')
|
||||||
|
->getMock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DashboardTest extends BaseTestCase
|
||||||
|
{
|
||||||
|
public function tearDown()
|
||||||
|
{
|
||||||
|
parent::tearDown();
|
||||||
|
Mockery::close(); // Necessary because some tests run in a separate process
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setupIcingaMock(\Zend_Controller_Request_Abstract $request)
|
||||||
|
{
|
||||||
|
$moduleMock = Mockery::mock('Icinga\Application\Modules\Module');
|
||||||
|
$moduleMock->shouldReceive('getPaneItems')->andReturn(array(
|
||||||
|
'test-pane' => new Pane('Test Pane')
|
||||||
|
));
|
||||||
|
|
||||||
|
$moduleManagerMock = Mockery::mock('Icinga\Application\Modules\Manager');
|
||||||
|
$moduleManagerMock->shouldReceive('getLoadedModules')->andReturn(array(
|
||||||
|
'test-module' => $moduleMock
|
||||||
|
));
|
||||||
|
|
||||||
|
$bootstrapMock = Mockery::mock('Icinga\Application\ApplicationBootstrap')->shouldDeferMissing();
|
||||||
|
$bootstrapMock->shouldReceive('getFrontController->getRequest')->andReturnUsing(
|
||||||
|
function () use ($request) { return $request; }
|
||||||
|
)->shouldReceive('getApplicationDir')->andReturn(self::$appDir);
|
||||||
|
|
||||||
|
$bootstrapMock->shouldReceive('getModuleManager')->andReturn($moduleManagerMock);
|
||||||
|
|
||||||
|
Icinga::setApp($bootstrapMock, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWhetherCreatePaneCreatesAPane()
|
||||||
|
{
|
||||||
|
$dashboard = new Dashboard();
|
||||||
|
$pane = $dashboard->createPane('test')->getPane('test');
|
||||||
|
|
||||||
|
$this->assertEquals('test', $pane->getTitle(), 'Dashboard::createPane() could not create a pane');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherCreatePaneCreatesAPane
|
||||||
|
*/
|
||||||
|
public function testMergePanesWithDifferentPaneName()
|
||||||
|
{
|
||||||
|
$dashboard = new Dashboard();
|
||||||
|
$dashboard->createPane('test1');
|
||||||
|
$dashboard->createPane('test2');
|
||||||
|
|
||||||
|
$panes = array(
|
||||||
|
new Pane('test1a'),
|
||||||
|
new Pane('test2a')
|
||||||
|
);
|
||||||
|
|
||||||
|
$dashboard->mergePanes($panes);
|
||||||
|
|
||||||
|
$this->assertCount(4, $dashboard->getPanes(), 'Dashboard::mergePanes() could not merge different panes');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherCreatePaneCreatesAPane
|
||||||
|
*/
|
||||||
|
public function testMergePanesWithSamePaneName()
|
||||||
|
{
|
||||||
|
$dashboard = new Dashboard();
|
||||||
|
$dashboard->createPane('test1');
|
||||||
|
$dashboard->createPane('test2');
|
||||||
|
|
||||||
|
$panes = array(
|
||||||
|
new Pane('test1'),
|
||||||
|
new Pane('test3')
|
||||||
|
);
|
||||||
|
|
||||||
|
$dashboard->mergePanes($panes);
|
||||||
|
|
||||||
|
$this->assertCount(3, $dashboard->getPanes(), 'Dashboard::mergePanes() could not merge same panes');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherCreatePaneCreatesAPane
|
||||||
|
*/
|
||||||
|
public function testWhetherGetPaneReturnsAPaneByName()
|
||||||
|
{
|
||||||
|
$dashboard = new Dashboard();
|
||||||
|
$dashboard->createPane('test1');
|
||||||
|
|
||||||
|
$pane = $dashboard->getPane('test1');
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'test1',
|
||||||
|
$pane->getName(),
|
||||||
|
'Dashboard:getPane() could not return pane by name'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherCreatePaneCreatesAPane
|
||||||
|
*/
|
||||||
|
public function testLoadPaneItemsProvidedByEnabledModules()
|
||||||
|
{
|
||||||
|
$dashboard = Dashboard::load();
|
||||||
|
|
||||||
|
$this->assertCount(
|
||||||
|
1,
|
||||||
|
$dashboard->getPanes(),
|
||||||
|
'Dashboard::load() could not load panes from enabled modules'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Icinga\Exception\ProgrammingError
|
||||||
|
* @depends testWhetherCreatePaneCreatesAPane
|
||||||
|
*/
|
||||||
|
public function testWhetherGetPaneThrowsAnExceptionOnNotExistentPaneName()
|
||||||
|
{
|
||||||
|
$dashboard = new Dashboard();
|
||||||
|
$dashboard->createPane('test1');
|
||||||
|
|
||||||
|
$dashboard->getPane('test2');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherGetPaneReturnsAPaneByName
|
||||||
|
*/
|
||||||
|
public function testWhetherRenderNotRendersPanesDisabledComponent()
|
||||||
|
{
|
||||||
|
$dashboard = new Dashboard();
|
||||||
|
$dashboard->createPane('test1');
|
||||||
|
$pane = $dashboard->getPane('test1');
|
||||||
|
$component = new ComponentWithMockedView('test', 'test', $pane);
|
||||||
|
$component->setDisabled(true);
|
||||||
|
$pane->addComponent($component);
|
||||||
|
|
||||||
|
$rendered = $dashboard->render();
|
||||||
|
|
||||||
|
$greaterThanOne = strlen($rendered) > 1;
|
||||||
|
|
||||||
|
$this->assertFalse(
|
||||||
|
$greaterThanOne,
|
||||||
|
'Dashboard::render() disabled component is rendered, but should not'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherGetPaneReturnsAPaneByName
|
||||||
|
*/
|
||||||
|
public function testWhetherRenderRendersPanesEnabledComponent()
|
||||||
|
{
|
||||||
|
$dashboard = new Dashboard();
|
||||||
|
$dashboard->createPane('test1');
|
||||||
|
$pane = $dashboard->getPane('test1');
|
||||||
|
$component = new ComponentWithMockedView('test', 'test', $pane);
|
||||||
|
$pane->addComponent($component);
|
||||||
|
|
||||||
|
$rendered = $dashboard->render();
|
||||||
|
|
||||||
|
$greaterThanOne = strlen($rendered) > 1;
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
$greaterThanOne,
|
||||||
|
'Dashboard::render() could not render enabled component'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWhetherRenderNotRendersNotExistentPane()
|
||||||
|
{
|
||||||
|
$dashboard = new Dashboard();
|
||||||
|
|
||||||
|
$rendered = $dashboard->render();
|
||||||
|
|
||||||
|
$greaterThanOne = strlen($rendered) > 1;
|
||||||
|
|
||||||
|
$this->assertFalse(
|
||||||
|
$greaterThanOne,
|
||||||
|
'Dashboard::render() not existent pane ist rendered, but should not'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherGetPaneReturnsAPaneByName
|
||||||
|
*/
|
||||||
|
public function testWhetherGetPaneKeyTitleArrayReturnFormedArray()
|
||||||
|
{
|
||||||
|
$dashboard = new Dashboard();
|
||||||
|
$dashboard->createPane('test1')->getPane('test1')->setTitle('Test1');
|
||||||
|
$dashboard->createPane('test2')->getPane('test2')->setTitle('Test2');
|
||||||
|
|
||||||
|
$result = $dashboard->getPaneKeyTitleArray();
|
||||||
|
|
||||||
|
$expected = array(
|
||||||
|
'test1' => 'Test1',
|
||||||
|
'test2' => 'Test2'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
$expected,
|
||||||
|
$result,
|
||||||
|
'Dashboard::getPaneKeyTitleArray() could not return valid expectation'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherCreatePaneCreatesAPane
|
||||||
|
*/
|
||||||
|
public function testWhetherHasPanesHasPanes()
|
||||||
|
{
|
||||||
|
$dashboard = new Dashboard();
|
||||||
|
$dashboard->createPane('test1');
|
||||||
|
$dashboard->createPane('test2');
|
||||||
|
|
||||||
|
$hasPanes = $dashboard->hasPanes();
|
||||||
|
|
||||||
|
$this->assertTrue($hasPanes, 'Dashboard::hasPanes() could not return valid expectation');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWhetherHasPanesHasNoPanes()
|
||||||
|
{
|
||||||
|
$dashboard = new Dashboard();
|
||||||
|
|
||||||
|
$hasPanes = $dashboard->hasPanes();
|
||||||
|
|
||||||
|
$this->assertFalse($hasPanes, 'Dashboard::hasPanes() has panes but should not');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherGetPaneReturnsAPaneByName
|
||||||
|
*/
|
||||||
|
public function testWhetherRemoveComponentRemovesComponent()
|
||||||
|
{
|
||||||
|
$dashboard = new Dashboard();
|
||||||
|
$dashboard->createPane('test1');
|
||||||
|
$pane = $dashboard->getPane('test1');
|
||||||
|
|
||||||
|
$component = new Component('test', 'test', $pane);
|
||||||
|
$pane->addComponent($component);
|
||||||
|
|
||||||
|
$component2 = new Component('test2', 'test2', $pane);
|
||||||
|
$pane->addComponent($component2);
|
||||||
|
|
||||||
|
$dashboard->removeComponent('test1', 'test');
|
||||||
|
|
||||||
|
$result = $dashboard->getPane('test1')->hasComponent('test');
|
||||||
|
|
||||||
|
$this->assertFalse(
|
||||||
|
$result,
|
||||||
|
'Dashboard::removeComponent() could not remove component from the pane'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherGetPaneReturnsAPaneByName
|
||||||
|
*/
|
||||||
|
public function testWhetherRemoveComponentRemovesComponentByConcatenation()
|
||||||
|
{
|
||||||
|
$dashboard = new Dashboard();
|
||||||
|
$dashboard->createPane('test1');
|
||||||
|
$pane = $dashboard->getPane('test1');
|
||||||
|
|
||||||
|
$component = new Component('test', 'test', $pane);
|
||||||
|
$pane->addComponent($component);
|
||||||
|
|
||||||
|
$component2 = new Component('test2', 'test2', $pane);
|
||||||
|
$pane->addComponent($component2);
|
||||||
|
|
||||||
|
$dashboard->removeComponent('test1.test', null);
|
||||||
|
|
||||||
|
$result = $dashboard->getPane('test1')->hasComponent('test');
|
||||||
|
|
||||||
|
$this->assertFalse(
|
||||||
|
$result,
|
||||||
|
'Dashboard::removeComponent() could not remove component from the pane'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherGetPaneReturnsAPaneByName
|
||||||
|
*/
|
||||||
|
public function testWhetherToArrayReturnsDashboardStructureAsArray()
|
||||||
|
{
|
||||||
|
$dashboard = new Dashboard();
|
||||||
|
$dashboard->createPane('test1');
|
||||||
|
$pane = $dashboard->getPane('test1');
|
||||||
|
|
||||||
|
$component = new Component('test', 'test', $pane);
|
||||||
|
$pane->addComponent($component);
|
||||||
|
|
||||||
|
$result = $dashboard->toArray();
|
||||||
|
|
||||||
|
$expected = array(
|
||||||
|
'test1' => array(
|
||||||
|
'title' => 'test1'
|
||||||
|
),
|
||||||
|
'test1.test' => array(
|
||||||
|
'url' => 'test'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
$expected,
|
||||||
|
$result,
|
||||||
|
'Dashboard::toArray() could not return valid expectation'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherGetPaneReturnsAPaneByName
|
||||||
|
*/
|
||||||
|
public function testWhetherSetComponentUrlUpdatesTheComponentUrl()
|
||||||
|
{
|
||||||
|
$dashboard = new Dashboard();
|
||||||
|
$dashboard->createPane('test1');
|
||||||
|
$pane = $dashboard->getPane('test1');
|
||||||
|
$component = new Component('test', 'test', $pane);
|
||||||
|
$pane->addComponent($component);
|
||||||
|
|
||||||
|
$dashboard->setComponentUrl('test1', 'test', 'new');
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'new',
|
||||||
|
$component->getUrl()->getPath(),
|
||||||
|
'Dashboard::setComponentUrl() could not return valid expectation'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherGetPaneReturnsAPaneByName
|
||||||
|
*/
|
||||||
|
public function testWhetherSetComponentUrlUpdatesTheComponentUrlConcatenation()
|
||||||
|
{
|
||||||
|
$dashboard = new Dashboard();
|
||||||
|
$dashboard->createPane('test1');
|
||||||
|
$pane = $dashboard->getPane('test1');
|
||||||
|
$component = new Component('test', 'test', $pane);
|
||||||
|
$pane->addComponent($component);
|
||||||
|
|
||||||
|
$dashboard->setComponentUrl('test1.test', null, 'new');
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'new',
|
||||||
|
$component->getUrl()->getPath(),
|
||||||
|
'Dashboard::setComponentUrl() could not return valid expectation'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherGetPaneReturnsAPaneByName
|
||||||
|
*/
|
||||||
|
public function testWhetherSetComponentUrlUpdatesTheComponentUrlNotExistentPane()
|
||||||
|
{
|
||||||
|
$dashboard = new Dashboard();
|
||||||
|
$dashboard->createPane('test1');
|
||||||
|
$pane = $dashboard->getPane('test1');
|
||||||
|
$component = new Component('test', 'test', $pane);
|
||||||
|
$pane->addComponent($component);
|
||||||
|
|
||||||
|
$dashboard->setComponentUrl('test3.test', null, 'new');
|
||||||
|
|
||||||
|
$result = $dashboard->getPane('test3')->getComponent('test');
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'new',
|
||||||
|
$result->getUrl()->getPath(),
|
||||||
|
'Dashboard::setComponentUrl() could not return valid expectation'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Icinga\Exception\ConfigurationError
|
||||||
|
*/
|
||||||
|
public function testWhetherDetermineActivePaneThrowsAnExceptionIfCouldNotDetermine()
|
||||||
|
{
|
||||||
|
$dashboard = new Dashboard();
|
||||||
|
$dashboard->determineActivePane();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @runInSeparateProcess
|
||||||
|
* @preserveGlobalState disabled
|
||||||
|
* @expectedException \Icinga\Exception\ProgrammingError
|
||||||
|
* @depends testWhetherCreatePaneCreatesAPane
|
||||||
|
*/
|
||||||
|
public function testWhetherDetermineActivePaneThrowsAnExceptionIfCouldNotDetermineInvalidPane()
|
||||||
|
{
|
||||||
|
$dashboard = new DashboardWithPredefinableActiveName();
|
||||||
|
$dashboard->createPane('test1');
|
||||||
|
|
||||||
|
Mockery::mock('alias:Icinga\Web\Url')
|
||||||
|
->shouldReceive('fromRequest->getParam')->andReturn('test2');
|
||||||
|
|
||||||
|
$dashboard->determineActivePane();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherCreatePaneCreatesAPane
|
||||||
|
*/
|
||||||
|
public function testWhetherDetermineActivePaneDeterminesActivePane()
|
||||||
|
{
|
||||||
|
$dashboard = new DashboardWithPredefinableActiveName();
|
||||||
|
$dashboard->activeName = 'test2';
|
||||||
|
$dashboard->createPane('test1');
|
||||||
|
$dashboard->createPane('test2');
|
||||||
|
|
||||||
|
$activePane = $dashboard->determineActivePane();
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'test2',
|
||||||
|
$activePane->getTitle(),
|
||||||
|
'Dashboard::determineActivePane() could not determine active pane'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @runInSeparateProcess
|
||||||
|
* @preserveGlobalState disabled
|
||||||
|
* @depends testWhetherCreatePaneCreatesAPane
|
||||||
|
*/
|
||||||
|
public function testWhetherDetermineActivePaneDeterminesActiveValidPane()
|
||||||
|
{
|
||||||
|
$dashboard = new DashboardWithPredefinableActiveName();
|
||||||
|
$dashboard->createPane('test1');
|
||||||
|
$dashboard->createPane('test2');
|
||||||
|
|
||||||
|
Mockery::mock('alias:Icinga\Web\Url')
|
||||||
|
->shouldReceive('fromRequest->getParam')->andReturn('test2');
|
||||||
|
|
||||||
|
$activePane = $dashboard->determineActivePane();
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'test2',
|
||||||
|
$activePane->getTitle(),
|
||||||
|
'Dashboard::determineActivePane() could not determine active pane'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherCreatePaneCreatesAPane
|
||||||
|
*/
|
||||||
|
public function testWhetherGetActivePaneReturnsActivePane()
|
||||||
|
{
|
||||||
|
$dashboard = new DashboardWithPredefinableActiveName();
|
||||||
|
$dashboard->activeName = 'test2';
|
||||||
|
$dashboard->createPane('test1');
|
||||||
|
$dashboard->createPane('test2');
|
||||||
|
|
||||||
|
$activePane = $dashboard->getActivePane();
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'test2',
|
||||||
|
$activePane->getTitle(),
|
||||||
|
'Dashboard::determineActivePane() could not get expected active pane'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWhetherLoadConfigPanes()
|
||||||
|
{
|
||||||
|
$this->markTestIncomplete(
|
||||||
|
'Dashboard::loadConfigPanes() is not fully implemented yet or rather not used'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testWhetherLoadConfigPanes
|
||||||
|
*/
|
||||||
|
public function testWhetherReadConfigPopulatesDashboard()
|
||||||
|
{
|
||||||
|
$this->markTestIncomplete(
|
||||||
|
'Dashboard::readConfig() is not fully implemented yet or rather not used'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue