Merge branch 'master' into bugfix/commands-6593

This commit is contained in:
Eric Lippmann 2014-09-03 23:23:20 +02:00
commit c3c0043307
70 changed files with 2350 additions and 801 deletions

View File

@ -4,6 +4,7 @@
// namespace Icinga\Application\Controllers;
use Icinga\Logger\Logger;
use Icinga\Web\Controller\ActionController;
use Icinga\Application\Icinga;
@ -21,6 +22,10 @@ class ErrorController extends ActionController
{
$error = $this->_getParam('error_handler');
$exception = $error->exception;
Logger::error($exception);
Logger::error('Stacktrace: %s', $exception->getTraceAsString());
switch ($error->type) {
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:

View File

@ -4,9 +4,11 @@
use Icinga\Module\Monitoring\Controller;
use Icinga\Web\Hook;
use Icinga\Application\Config as IcingaConfig;
use Icinga\Web\Url;
use Icinga\Data\ResourceFactory;
use Icinga\Logger\Logger;
use Icinga\Logger\Writer\FileWriter;
use Icinga\Protocol\File\Reader as FileReader;
/**
* Class ListController
@ -37,15 +39,16 @@ class ListController extends Controller
public function applicationlogAction()
{
$this->addTitleTab('application log');
$config_ini = IcingaConfig::app()->toArray();
if (!in_array('logging', $config_ini) || (
in_array('type', $config_ini['logging']) &&
$config_ini['logging']['type'] === 'file' &&
in_array('target', $config_ini['logging']) &&
file_exists($config_ini['logging']['target'])
)
) {
$resource = ResourceFactory::create('logfile');
$loggerWriter = Logger::getInstance()->getWriter();
if ($loggerWriter instanceof FileWriter) {
$resource = new FileReader(new Zend_Config(array(
'filename' => $loggerWriter->getPath(),
'fields' => '/^(?<datetime>[0-9]{4}(-[0-9]{2}){2}' // date
. 'T[0-9]{2}(:[0-9]{2}){2}([\\+\\-][0-9]{2}:[0-9]{2})?)' // time
. ' - (?<loglevel>[A-Za-z]+)' // loglevel
. ' - (?<message>.*)$/' // message
)));
$this->view->logData = $resource->select()->order('DESC')->paginate();
} else {
$this->view->logData = null;

View File

@ -2,10 +2,11 @@
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
use Zend_Controller_Action_Exception as ActionException;
use Icinga\Web\Controller\ActionController;
use Icinga\Application\Icinga;
use Icinga\Logger\Logger;
use Icinga\Web\FileCache;
use Zend_Controller_Action_Exception as ActionException;
/**
* Delivery static content to clients
@ -30,8 +31,25 @@ class StaticController extends ActionController
public function gravatarAction()
{
$cache = FileCache::instance();
$filename = md5(strtolower(trim($this->_request->getParam('email'))));
$cacheFile = 'gravatar-' . $filename;
header('Cache-Control: public');
header('Pragma: cache');
if ($etag = $cache->etagMatchesCachedFile($cacheFile)) {
header("HTTP/1.1 304 Not Modified");
return;
}
header('Content-Type: image/jpg');
$img = file_get_contents('http://www.gravatar.com/avatar/' . md5(strtolower(trim($this->_request->getParam('email')))) . '?s=200&d=mm');
if ($cache->has($cacheFile)) {
header('ETag: "' . $cache->etagForCachedFile($cacheFile) . '"');
$cache->send($cacheFile);
return;
}
$img = file_get_contents('http://www.gravatar.com/avatar/' . $filename . '?s=200&d=mm');
$cache->store($cacheFile, $img);
header('ETag: "' . $cache->etagForCachedFile($cacheFile) . '"');
echo $img;
}

View File

@ -5,6 +5,7 @@
</div>
<div class="content">
<?php if ($this->logData !== null): ?>
<table class="action">
<tbody>
<?php foreach ($this->logData as $value): ?>
@ -21,4 +22,5 @@
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>

View File

@ -238,6 +238,77 @@ the labels to show you can use the 'disableLegend()' call on the GridChart objec
![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
### The PieChart Object
@ -317,5 +388,6 @@ Rendering is straightforward, assuming $svg is the PieChart/GridChart object, yo
[graph5]: res/GraphExample#5.png
[graph6]: res/GraphExample#6.png
[graph7]: res/GraphExample#7.png
[graph7.1]: res/GraphExample#7.1.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

View File

@ -452,9 +452,8 @@ abstract class ApplicationBootstrap
*/
protected function setupInternationalization()
{
$localeDir = $this->getApplicationDir('locale');
if (file_exists($localeDir) && is_dir($localeDir)) {
Translator::registerDomain(Translator::DEFAULT_DOMAIN, $localeDir);
if ($this->hasLocales()) {
Translator::registerDomain(Translator::DEFAULT_DOMAIN, $this->getLocaleDir());
}
try {
@ -469,4 +468,48 @@ abstract class ApplicationBootstrap
return $this;
}
/**
* @return string Our locale directory
*/
public function getLocaleDir()
{
return $this->getApplicationDir('locale');
}
/**
* return bool Whether Icinga Web has translations
*/
public function hasLocales()
{
$localedir = $this->getLocaleDir();
return file_exists($localedir) && is_dir($localedir);
}
/**
* List all available locales
*
* NOTE: Might be a candidate for a static function in Translator
*
* return array Locale list
*/
public function listLocales()
{
$locales = array();
if (! $this->hasLocales()) {
return $locales;
}
$localedir = $this->getLocaleDir();
$dh = opendir($localedir);
while (false !== ($file = readdir($dh))) {
$filename = $localedir . DIRECTORY_SEPARATOR . $file;
if (preg_match('/^[a-z]{2}_[A-Z]{2}$/', $file) && is_dir($filename)) {
$locales[] = $file;
}
}
closedir($dh);
sort($locales);
return $locales;
}
}

View File

@ -82,6 +82,18 @@ class Config extends Zend_Config
return self::$app[$configname];
}
/**
* Set module config
*
* @param string $moduleName
* @param string $configName
* @param Zend_Config $config
*/
public static function setModuleConfig($moduleName, $configName, Zend_Config $config)
{
self::$modules[$moduleName][$configName] = $config;
}
/**
* Retrieve a module config instance
*

View File

@ -177,7 +177,6 @@ class Module
/**
* Add a pane to dashboard
*
* @param $id
* @param $name
* @return Pane
*/
@ -201,21 +200,19 @@ class Module
/**
* Add a menu Section to the Sidebar menu
*
* @param string $id
* @param string $name
* @param $name
* @param array $properties
* @return mixed
*/
protected function menuSection($id, $name, array $properties = array())
protected function menuSection($name, array $properties = array())
{
if (array_key_exists($id, $this->menuItems)) {
$this->menuItems[$id]->setProperties($properties);
if (array_key_exists($name, $this->menuItems)) {
$this->menuItems[$name]->setProperties($properties);
} else {
$this->menuItems[$id] = new Menu($id, new Zend_Config($properties));
$this->menuItems[$id]->setTitle($name);
$this->menuItems[$name] = new Menu($name, new Zend_Config($properties));
}
return $this->menuItems[$id];
return $this->menuItems[$name];
}
/**
@ -711,12 +708,44 @@ class Module
*/
protected function registerLocales()
{
if (file_exists($this->localedir) && is_dir($this->localedir)) {
if ($this->hasLocales()) {
Translator::registerDomain($this->name, $this->localedir);
}
return $this;
}
/**
* return bool Whether this module has translations
*/
public function hasLocales()
{
return file_exists($this->localedir) && is_dir($this->localedir);
}
/**
* List all available locales
*
* return array Locale list
*/
public function listLocales()
{
$locales = array();
if (! $this->hasLocales()) {
return $locales;
}
$dh = opendir($this->localedir);
while (false !== ($file = readdir($dh))) {
$filename = $this->localedir . DIRECTORY_SEPARATOR . $file;
if (preg_match('/^[a-z]{2}_[A-Z]{2}$/', $file) && is_dir($filename)) {
$locales[] = $file;
}
}
closedir($dh);
sort($locales);
return $locales;
}
/**
* Register web integration
*

View File

@ -13,8 +13,6 @@ use Icinga\Logger\Logger;
use Icinga\Web\Request;
use Icinga\Web\Response;
use Icinga\Web\View;
use Icinga\Web\Session\Session as BaseSession;
use Icinga\Web\Session;
use Icinga\User;
use Icinga\Util\Translator;
use Icinga\Util\DateTimeFactory;
@ -59,13 +57,6 @@ class Web extends ApplicationBootstrap
*/
private $request;
/**
* Session object
*
* @var BaseSession
*/
private $session;
/**
* User object
*
@ -92,7 +83,6 @@ class Web extends ApplicationBootstrap
->setupErrorHandling()
->loadConfig()
->setupResourceFactory()
->setupSession()
->setupUser()
->setupTimezone()
->setupLogger()
@ -172,7 +162,6 @@ class Web extends ApplicationBootstrap
$this->setupFrontController();
$this->setupViewRenderer();
return $this;
}
@ -192,17 +181,6 @@ class Web extends ApplicationBootstrap
return $this;
}
/**
* Initialize a session provider
*
* @return self
*/
private function setupSession()
{
$this->session = Session::create();
return $this;
}
/**
* Inject dependencies into request
*

View File

@ -16,6 +16,13 @@ use Icinga\Chart\Render\RenderContext;
*/
class BarGraph extends Styleable implements Drawable
{
/**
* The dataset order
*
* @var int
*/
private $order = 0;
/**
* The width of the bars.
*
@ -30,14 +37,37 @@ class BarGraph extends Styleable implements Drawable
*/
private $dataSet;
/**
* The tooltips
*
* @var
*/
private $tooltips;
/**
* All graphs
*
* @var
*/
private $graphs;
/**
* 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->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
*
@ -68,23 +122,33 @@ class BarGraph extends Styleable implements Drawable
$doc = $ctx->getDocument();
$group = $doc->createElement('g');
$idx = 0;
foreach ($this->dataSet as $point) {
$rect = new Rect($point[0] - 2, $point[1], 4, 100 - $point[1]);
$rect->setFill($this->fill);
$rect->setStrokeWidth($this->strokeWidth);
$rect->setStrokeColor('black');
$rect->setAttribute('data-icinga-graph-index', $idx++);
$rect->setAttribute('data-icinga-graph-type', 'bar');
$rect->setAdditionalStyle('clip-path: url(#clip);');
/*$rect->setAnimation(
new Animation(
'y',
$ctx->yToAbsolute(100),
$ctx->yToAbsolute($point[1]),
rand(1, 1.5)/2
)
);*/
$group->appendChild($rect->toSvg($ctx));
foreach ($this->dataSet as $x => $point) {
// add white background bar, to prevent other bars from altering transparency effects
$bar = $this->drawSingleBar($point, $idx++, 'white', $this->strokeWidth, $idx)->toSvg($ctx);
$group->appendChild($bar);
// draw actual bar
$bar = $this->drawSingleBar($point, null, $this->fill, $this->strokeWidth, $idx)->toSvg($ctx);
$bar->setAttribute('class', 'chart-data');
if (isset($this->tooltips[$x])) {
$data = array(
'label' => isset($this->graphs[$this->order]['label']) ?
strtolower($this->graphs[$this->order]['label']) : '',
'color' => isset($this->graphs[$this->order]['color']) ?
strtolower($this->graphs[$this->order]['color']) : '#fff'
);
$format = isset($this->graphs[$this->order]['tooltip'])
? $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;
}

View File

@ -41,6 +41,10 @@ class StackedGraph implements Drawable
if (!isset($this->points[$x])) {
$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];
$point[1] = $this->points[$x];
}

View File

@ -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));
}
}

View File

@ -10,6 +10,7 @@ use Icinga\Chart\Axis;
use Icinga\Chart\Graph\BarGraph;
use Icinga\Chart\Graph\LineGraph;
use Icinga\Chart\Graph\StackedGraph;
use Icinga\Chart\Graph\Tooltip;
use Icinga\Chart\Primitive\Canvas;
use Icinga\Chart\Primitive\Rect;
use Icinga\Chart\Primitive\Path;
@ -74,6 +75,16 @@ class GridChart extends Chart
*/
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.
*
@ -169,6 +180,26 @@ class GridChart extends Chart
$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) {
$axis = $this->axis[$axisName];
$graphObj = null;
foreach ($graphs as $graph) {
foreach ($graphs as $dataset => $graph) {
// determine the type and create a graph object for it
switch ($graph['graphType']) {
case self::TYPE_BAR:
$graphObj = new BarGraph($axis->transform($graph['data']));
$graphObj = new BarGraph(
$axis->transform($graph['data']),
$graphs,
$dataset,
$this->tooltips
);
break;
case self::TYPE_LINE:
$graphObj = new LineGraph($axis->transform($graph['data']));

View File

@ -89,6 +89,17 @@ class DbQuery extends SimpleQuery
public function getSelectQuery()
{
$select = $this->dbSelect();
// Add order fields to select for postgres distinct queries (#6351)
if ($this->hasOrder()
&& $this->getDatasource()->getDbType() === 'pgsql'
&& $select->getPart(Zend_Db_Select::DISTINCT) === true) {
foreach ($this->getOrder() as $fieldAndDirection) {
list($alias, $field) = explode('.', $fieldAndDirection[0]);
$this->columns[$field] = $fieldAndDirection[0];
}
}
$select->columns($this->columns);
$this->applyFilterSql($select);
@ -102,6 +113,7 @@ class DbQuery extends SimpleQuery
);
}
}
return $select;
}

View File

@ -132,12 +132,12 @@ abstract class Filter
public static function expression($col, $op, $expression)
{
switch ($op) {
case '=': return new FilterEqual($col, $op, $expression);
case '=': return new FilterMatch($col, $op, $expression);
case '<': return new FilterLessThan($col, $op, $expression);
case '>': return new FilterGreaterThan($col, $op, $expression);
case '>=': return new FilterEqualOrGreaterThan($col, $op, $expression);
case '<=': return new FilterEqualOrLessThan($col, $op, $expression);
case '!=': return new FilterNotEqual($col, $op, $expression);
case '!=': return new FilterMatchNot($col, $op, $expression);
default: throw new ProgrammingError(
'There is no such filter sign: %s',
$op

View File

@ -0,0 +1,22 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Data\Filter;
class FilterMatch extends FilterExpression
{
public function matches($row)
{
$expression = (string) $this->expression;
if (strpos($expression, '*') === false) {
return (string) $row->{$this->column} === $expression;
} else {
$parts = array();
foreach (preg_split('/\*/', $expression) as $part) {
$parts[] = preg_quote($part);
}
return preg_match('/^' . implode('.*', $parts) . '$/', $row->{$this->column});
}
}
}

View File

@ -0,0 +1,22 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Data\Filter;
class FilterMatchNot extends FilterExpression
{
public function matches($row)
{
$expression = (string) $this->expression;
if (strpos($expression, '*') === false) {
return (string) $row->{$this->column} !== $expression;
} else {
$parts = array();
foreach (preg_split('/\*/', $expression) as $part) {
$parts[] = preg_quote($part);
}
return ! preg_match('/^' . implode('.*', $parts) . '$/', $row->{$this->column});
}
}
}

View File

@ -0,0 +1,13 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Data\Filter;
class FilterNotEqual extends FilterExpression
{
public function matches($row)
{
return (string) $row->{$this->column} !== (string) $this->expression;
}
}

View File

@ -24,7 +24,7 @@ class Logger
/**
* The log writer to use
*
* @var LogWriter
* @var \Icinga\Logger\LogWriter
*/
protected $writer;
@ -54,7 +54,7 @@ class Logger
$this->verbosity = $config->level;
if ($config->enable) {
$this->writer = $this->getWriter($config);
$this->writer = $this->createWriter($config);
}
}
@ -73,11 +73,10 @@ class Logger
*
* @param Zend_Config $config The configuration to initialize the writer with
*
* @return LogWriter The requested log writer
*
* @throws ConfigurationError In case the requested writer cannot be found
* @return \Icinga\Logger\LogWriter The requested log writer
* @throws ConfigurationError If the requested writer cannot be found
*/
protected function getWriter(Zend_Config $config)
protected function createWriter(Zend_Config $config)
{
$class = 'Icinga\\Logger\\Writer\\' . ucfirst(strtolower($config->type)) . 'Writer';
if (!class_exists($class)) {
@ -202,4 +201,24 @@ class Logger
static::$instance->log(static::formatMessage(func_get_args()), static::$DEBUG);
}
}
/**
* Get the log writer to use
*
* @return \Icinga\Logger\LogWriter
*/
public function getWriter()
{
return $this->writer;
}
/**
* Get this' instance
*
* @return Logger
*/
public static function getInstance()
{
return static::$instance;
}
}

View File

@ -103,4 +103,12 @@ class FileWriter extends LogWriter
$file->fwrite($text);
$file->fflush();
}
/**
* @return string
*/
public function getPath()
{
return $this->path;
}
}

View File

@ -2,9 +2,9 @@
namespace Icinga\Protocol\File;
use RuntimeException;
use Icinga\Exception\IcingaException;
/**
* Exception thrown if a file reader specific error occurs
*/
class FileReaderException extends RuntimeException {}
class FileReaderException extends IcingaException {}

View File

@ -40,7 +40,7 @@ class Reader extends FilterIterator
{
foreach (array('filename', 'fields') as $key) {
if (! isset($config->{$key})) {
throw new FileReaderException('The directive `' . $key . '\' is required');
throw new FileReaderException('The directive `%s\' is required', $key);
}
}
$this->fields = $config->fields;
@ -81,7 +81,7 @@ class Reader extends FilterIterator
if ($matched === false) {
throw new FileReaderException('Failed parsing regular expression!');
} else if ($matched === 1) {
foreach ($data as $key) {
foreach ($data as $key => $value) {
if (is_int($key)) {
unset($data[$key]);
}
@ -137,30 +137,32 @@ class Reader extends FilterIterator
*/
public function fetchPairs(Query $query)
{
$skipLines = $query->getOffset();
$readLines = $query->getLimit();
if ($skipLines === null) {
$skipLines = 0;
$skip = $query->getOffset();
$read = $query->getLimit();
if ($skip === null) {
$skip = 0;
}
$lines = array();
if ($query->sortDesc()) {
$count = $this->count($query);
if ($count <= $skipLines) {
if ($count <= $skip) {
return $lines;
} else if ($count < ($skipLines + $readLines)) {
$readLines = $count - $skipLines;
$skipLines = 0;
} else if ($count < ($skip + $read)) {
$read = $count - $skip;
$skip = 0;
} else {
$skipLines = $count - ($skipLines + $readLines);
$skip = $count - ($skip + $read);
}
}
foreach ($this as $index => $line) {
if ($index >= $skipLines) {
if ($index >= $skipLines + $readLines) {
$index = 0;
foreach ($this as $line) {
if ($index >= $skip) {
if ($index >= $skip + $read) {
break;
}
$lines[] = $line;
}
++$index;
}
if ($query->sortDesc()) {
$lines = array_reverse($lines);

View File

@ -307,7 +307,7 @@ class Connection
$results = @ldap_search(
$this->ds,
$base,
(string) $query,
$query->create(),
$fields,
0, // Attributes and values
0 // No limit - at least where possible
@ -619,7 +619,7 @@ class Connection
$result = @ldap_read(
$ds,
'',
(string) $query,
$query->create(),
$query->listFields()
);

View File

@ -4,8 +4,6 @@
namespace Icinga\Protocol\Ldap;
use Icinga\Exception\IcingaException;
/**
* Search class
*
@ -84,7 +82,7 @@ class Query
public function limit($count = null, $offset = null)
{
if (! preg_match('~^\d+~', $count . $offset)) {
throw new IcingaException(
throw new Exception(
'Got invalid limit: %s, %s',
$count,
$offset
@ -302,21 +300,11 @@ class Query
*
* @string
*/
public function __toString()
{
return $this->render();
}
/**
* Returns the LDAP filter that will be applied
*
* @string
*/
protected function render()
public function create()
{
$parts = array();
if (! isset($this->filters['objectClass']) || $this->filters['objectClass'] === null) {
// throw new IcingaException('Object class is mandatory');
throw new Exception('Object class is mandatory');
}
foreach ($this->filters as $key => $value) {
$parts[] = sprintf(

View File

@ -348,7 +348,9 @@ class ActionController extends Zend_Controller_Action
// Cast preference app.show_benchmark to bool because preferences loaded from a preferences storage are
// always strings
if ((bool) $user->getPreferences()->get('app.show_benchmark', false) === true) {
$layout->benchmark = $this->renderBenchmark();
if (!$this->_helper->viewRenderer->getNoRender()) {
$layout->benchmark = $this->renderBenchmark();
}
}
}

View File

@ -0,0 +1,275 @@
<?php
namespace Icinga\Web;
class FileCache
{
/**
* FileCache singleton instances
*
* @var array
*/
protected static $instances = array();
/**
* Cache instance base directory
*
* @var string
*/
protected $basedir;
/**
* Instance name
*
* @var string
*/
protected $name;
/**
* Whether the cache is enabled
*
* @var bool
*/
protected $enabled = false;
/**
* The protected constructor creates a new instance with the given name
*
* @param string $name Cache instance name
*/
protected function __construct($name)
{
$this->name = $name;
$tmpdir = sys_get_temp_dir();
$basedir = $tmpdir . '/FileCache_' . $name;
if (file_exists($basedir) && is_writeable($basedir)) {
$this->basedir = $basedir;
$this->enabled = true;
} elseif (file_exists($tmpdir) && is_writeable($tmpdir)) {
if (mkdir($basedir, '0750', true)) {
$this->enabled = true;
$this->basedir = $basedir;
}
}
}
/**
* Store the given content to the desired file name
*
* @param string $file new (relative) filename
* @param string $content the content to be stored
*
* @return bool whether the file has been stored
*/
public function store($file, $content)
{
if (! $this->enabled) {
return false;
}
return file_put_contents($this->filename($file), $content);
}
/**
* Find out whether a given file exists
*
* @param string $file the (relative) filename
* @param int $newerThan optional timestamp to compare against
*
* @return bool whether such file exists
*/
public function has($file, $newerThan = null)
{
if (! $this->enabled) {
return false;
}
$filename = $this->filename($file);
if (! file_exists($filename) || ! is_readable($filename)) {
return false;
}
if ($newerThan === null) {
return true;
}
$info = stat($file);
if ($info === false) {
return false;
}
return (int) $newerThan < $info['mtime'];
}
/**
* Get a specific file or false if no such file available
*
* @param string $file the disired file name
*
* @return string|bool Filename content or false
*/
public function get($file)
{
if ($this->has($file)) {
return file_get_contents($this->filename($file));
}
return false;
}
/**
* Send a specific file to the browser (output)
*
* @param string $file the disired file name
*
* @return bool Whether the file has been sent
*/
public function send($file)
{
if ($this->has($file)) {
readfile($this->filename($file));
return true;
}
return false;
}
/**
* Get absolute filename for a given file
*
* @param string $file the disired file name
*
* @return string absolute filename
*/
protected function filename($file)
{
return $this->basedir . '/' . $file;
}
/**
* Whether the given ETag matches a cached file
*
* If no ETag is given we'll try to fetch the one from the current
* HTTP request.
*
* @param string $file The cached file you want to check
* @param string $match The ETag to match against
*
* @return string|bool ETag on match, otherwise false
*/
public function etagMatchesCachedFile($file, $match = null)
{
return self::etagMatchesFiles($this->filename($file), $match);
}
/**
* Create an ETag for the given file
*
* @param string $file The desired cache file
*
* @return string your ETag
*/
public function etagForCachedFile($file)
{
return self::etagForFiles($this->filename($file));
}
/**
* Whether the given ETag matchesspecific file(s) on disk
*
* If no ETag is given we'll try to fetch the one from the current
* HTTP request.
*
* @param string|array $files file(s) to check
* @param string $match ETag to match against
*
* @return string|bool ETag on match, otherwise false
*/
public static function etagMatchesFiles($files, $match = null)
{
if ($match === null) {
$match = isset($_SERVER['HTTP_IF_NONE_MATCH'])
? trim($_SERVER['HTTP_IF_NONE_MATCH'], '"')
: false;
}
if (! $match) {
return false;
}
$etag = self::etagForFiles($files);
return $match === $etag ? $etag : false;
}
/**
* Create ETag for the given files
*
* Custom algorithm creating an ETag based on filenames, mtimes
* and file sizes. Supports single files or a list of files. This
* way we are able to create ETags for virtual files depending on
* multiple source files (e.g. compressed JS, CSS).
*
* @param string|array $files Single file or a list of such
*
* @return string The generated ETag
*/
public static function etagForFiles($files)
{
if (is_string($files)) {
$files = array($files);
}
$sizes = array();
$mtimes = array();
foreach ($files as $file) {
$file = realpath($file);
if ($file !== false && $info = stat($file)) {
$mtimes[] = $info['mtime'];
$sizes[] = $info['size'];
} else {
$mtimes[] = time();
$sizes[] = 0;
}
}
return sprintf(
'%s-%s-%s',
hash('crc32', implode('|', $files)),
hash('crc32', implode('|', $sizes)),
hash('crc32', implode('|', $mtimes))
);
}
/**
* Factory creating your cache instance
*
* @param string $name Instance name
*
* @return FileCache
*/
public static function instance($name = 'icingaweb')
{
if ($name !== 'icingaweb') {
$name = 'icingaweb/modules/' . $name;
}
if (!array_key_exists($name, self::$instances)) {
self::$instances[$name] = new static($name);
}
return self::$instances[$name];
}
}

View File

@ -5,6 +5,7 @@
namespace Icinga\Web;
use Icinga\Application\Icinga;
use Icinga\Web\FileCache;
use JShrink\Minifier;
class JavaScript
@ -24,12 +25,14 @@ class JavaScript
protected static $vendorFiles = array(
'js/vendor/jquery-2.1.0',
'js/vendor/jquery.sparkline'
'js/vendor/jquery.sparkline',
'js/vendor/jquery.tipsy'
);
protected static $ie8VendorFiles = array(
'js/vendor/jquery-1.11.0',
'js/vendor/jquery.sparkline'
'js/vendor/jquery.sparkline',
'js/vendor/jquery.tipsy'
);
public static function listModuleFiles()
@ -62,36 +65,58 @@ class JavaScript
$js = $out = '';
$min = $minified ? '.min' : '';
// TODO: Cache header
header('Content-Type: application/javascript');
$cacheFile = '/tmp/cache_icinga' . $min . '.js';
if (file_exists($cacheFile)) {
readfile($cacheFile);
exit;
}
// We do not minify vendor files
// Prepare vendor file list
$vendorFiles = array();
foreach (self::$vendorFiles as $file) {
$out .= file_get_contents($basedir . '/' . $file . $min . '.js');
$vendorFiles[] = $basedir . '/' . $file . $min . '.js';
}
// Prepare Icinga JS file list
$jsFiles = array();
foreach (self::$jsFiles as $file) {
$js .= file_get_contents($basedir . '/' . $file);
$jsFiles[] = $basedir . '/' . $file;
}
foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $name => $module) {
if ($module->hasJs()) {
$js .= file_get_contents($module->getJsFilename());
$jsFiles[] = $module->getJsFilename();
}
}
$files = array_merge($vendorFiles, $jsFiles);
if ($etag = FileCache::etagMatchesFiles($files)) {
header("HTTP/1.1 304 Not Modified");
return;
} else {
$etag = FileCache::etagForFiles($files);
}
header('Cache-Control: public');
header('ETag: "' . $etag . '"');
header('Content-Type: application/javascript');
$cacheFile = 'icinga-' . $etag . $min . '.js';
$cache = FileCache::instance();
if ($cache->has($cacheFile)) {
$cache->send($cacheFile);
return;
}
// We do not minify vendor files
foreach ($vendorFiles as $file) {
$out .= file_get_contents($file);
}
foreach ($jsFiles as $file) {
$js .= file_get_contents($file);
}
if ($minified) {
require_once 'IcingaVendor/JShrink/Minifier.php';
$out .= Minifier::minify($js, array('flaggedComments' => false));
} else {
$out .= $js;
}
// Not yet, this is for tests only. Waiting for Icinga\Web\Cache
// file_put_contents($cacheFile, $out);
$cache->store($cacheFile, $out);
echo $out;
}
}

View File

@ -5,7 +5,6 @@
namespace Icinga\Web;
use Icinga\Exception\ConfigurationError;
use Icinga\Logger\Logger;
use Zend_Config;
use RecursiveIterator;
use Icinga\Application\Config;
@ -173,34 +172,34 @@ class Menu implements RecursiveIterator
*/
protected function addMainMenuItems()
{
$this->add('dashboard', t('Dashboard'), array(
$this->add(t('Dashboard'), array(
'url' => 'dashboard',
'icon' => 'img/icons/dashboard.png',
'priority' => 10
));
$section = $this->add('system', t('System'), array(
$section = $this->add(t('System'), array(
'icon' => 'img/icons/configuration.png',
'priority' => 200
));
$section->add('preferences', t('Preferences'), array(
$section->add(t('Preferences'), array(
'url' => 'preference',
'priority' => 200
));
$section->add('configuration', t('Configuration'), array(
$section->add(t('Configuration'), array(
'url' => 'config',
'priority' => 300
));
$section->add('modules', t('Modules'), array(
$section->add(t('Modules'), array(
'url' => 'config/modules',
'priority' => 400
));
$section->add('applicationlog', t('ApplicationLog'), array(
$section->add(t('ApplicationLog'), array(
'url' => 'list/applicationlog',
'priority' => 500
));
$this->add('logout', t('Logout'), array(
$this->add(t('Logout'), array(
'url' => 'authentication/logout',
'icon' => 'img/icons/logout.png',
'priority' => 300
@ -428,10 +427,9 @@ class Menu implements RecursiveIterator
* @param array $config
* @return Menu
*/
public function add($id, $name, $config = array())
public function add($name, $config = array())
{
$config['title'] = $name;
return $this->addSubMenu($id, new Zend_Config($config));
return $this->addSubMenu($name, new Zend_Config($config));
}
/**

View File

@ -47,7 +47,7 @@ class Session
public static function getSession()
{
if (self::$session === null) {
throw new ProgrammingError('No session created yet');
self::create();
}
return self::$session;

View File

@ -5,6 +5,7 @@
namespace Icinga\Web;
use Icinga\Application\Icinga;
use Icinga\Web\FileCache;
use Icinga\Web\LessCompiler;
class StyleSheet
@ -23,6 +24,8 @@ class StyleSheet
'css/icinga/monitoring-colors.less',
'css/icinga/selection-toolbar.less',
'css/icinga/login.less',
'css/icinga/charts.less',
'css/vendor/tipsy.css'
);
public static function compileForPdf()
@ -44,28 +47,45 @@ class StyleSheet
public static function send($minified = false)
{
$app = Icinga::app();
$basedir = $app->getBootstrapDirecory();
foreach (self::$lessFiles as $file) {
$lessFiles[] = $basedir . '/' . $file;
}
$files = $lessFiles;
foreach ($app->getModuleManager()->getLoadedModules() as $name => $module) {
if ($module->hasCss()) {
$files[] = $module->getCssFilename();
}
}
if ($etag = FileCache::etagMatchesFiles($files)) {
header("HTTP/1.1 304 Not Modified");
return;
} else {
$etag = FileCache::etagForFiles($files);
}
header('Cache-Control: public');
header('ETag: "' . $etag . '"');
header('Content-Type: text/css');
$min = $minified ? '.min' : '';
$cacheFile = '/tmp/cache_icinga' . $min . '.css';
if (file_exists($cacheFile)) {
readfile($cacheFile);
exit;
$cacheFile = 'icinga-' . $etag . $min . '.css';
$cache = FileCache::instance();
if ($cache->has($cacheFile)) {
$cache->send($cacheFile);
return;
}
$less = new LessCompiler();
$basedir = Icinga::app()->getBootstrapDirecory();
foreach (self::$lessFiles as $file) {
$less->addFile($basedir . '/' . $file);
foreach ($lessFiles as $file) {
$less->addFile($file);
}
$less->addLoadedModules();
if ($minified) {
$less->compress();
}
$out = $less->compile();
// Not yet, this is for tests only. Waiting for Icinga\Web\Cache
// file_put_contents($cacheFile, $out);
$cache->store($cacheFile, $out);
echo $out;
}
}

View File

@ -8,7 +8,6 @@ use Icinga\Application\Icinga;
use Icinga\Application\Config as IcingaConfig;
use Icinga\Exception\ConfigurationError;
use Icinga\Exception\ProgrammingError;
use Icinga\Web\Widget\AbstractWidget;
use Icinga\Web\Widget\Dashboard\Pane;
use Icinga\Web\Widget\Dashboard\Component as DashboardComponent;
use Icinga\Web\Url;
@ -96,7 +95,7 @@ class Dashboard extends AbstractWidget
$current = $this->panes[$pane->getName()];
$current->addComponents($pane->getComponents());
} 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 all panes of this dashboard
*
* @return array
*/
public function getPanes()
{
return $this->panes;
}
/**
* 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
*
* @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 $url The url to use for the component
* @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 string|null $url The url to use for the component
*
* @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
*
* @param string $pane The name of the pane to check for emptyness
* Check if this dashboard has a specific pane
*
* @param $pane string The name of the pane
* @return bool
*/
public function isEmptyPane($pane)
public function hasPane($pane)
{
$paneObj = $this->getPane($pane);
if ($paneObj === null) {
return true;
}
$cmps = $paneObj->getComponents();
return !empty($cmps);
return array_key_exists($pane, $this->panes);
}
/**
@ -305,11 +308,11 @@ class Dashboard extends AbstractWidget
return $active;
}
/**
* @see determineActivePane()
*/
public function getActivePane()
{
if ($active = $this->getTabs()->getActiveName()) {
return $this->getPane($active);
}
return $this->determineActivePane();
}
@ -323,10 +326,12 @@ class Dashboard extends AbstractWidget
$active = $this->getTabs()->getActiveName();
if (! $active) {
if ($active = Url::fromRequest()->getParam($this->tabParam)) {
if ($this->isEmptyPane($active)) {
$active = $this->setDefaultPane();
} else {
if ($this->hasPane($active)) {
$this->activate($active);
} else {
throw new ProgrammingError(
'Try to get an inexistent pane.'
);
}
} else {
$active = $this->setDefaultPane();

View File

@ -30,13 +30,6 @@ class Component extends AbstractWidget
*/
private $url;
/**
* The id of this Component
*
* @var string
*/
private $id;
/**
* The title being displayed on top of the component
* @var
@ -49,6 +42,13 @@ class Component extends AbstractWidget
*/
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
*
@ -67,14 +67,12 @@ EOD;
/**
* Create a new component displaying the given url in the provided pane
*
* @param string $id The id to use for this component
* @param string $title The title to use for this component
* @param Url|string $url The url this component uses for displaying information
* @param Pane $pane The pane this Component will be added to
*/
public function __construct($id, $title, $url, Pane $pane)
public function __construct($title, $url, Pane $pane)
{
$this->id = $id;
$this->title = $title;
$this->pane = $pane;
if ($url instanceof Url) {
@ -126,6 +124,26 @@ EOD;
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
*
@ -145,6 +163,10 @@ EOD;
*/
public function render()
{
if ($this->disabled === true) {
return '';
}
$view = $this->view();
$url = clone($this->url);
$url->setParam('view', 'compact');
@ -195,14 +217,14 @@ EOD;
/**
* Create a @see Component instance from the given Zend config, using the provided title
* @param $id The id for this component
*
* @param $title The title for this component
* @param Zend_Config $config The configuration defining url, parameters, height, width, etc.
* @param Pane $pane The pane this component belongs to
*
* @return Component A newly created Component for use in the Dashboard
*/
public static function fromIni($id, $title, Zend_Config $config, Pane $pane)
public static function fromIni($title, Zend_Config $config, Pane $pane)
{
$height = null;
$width = null;
@ -210,27 +232,7 @@ EOD;
$parameters = $config->toArray();
unset($parameters['url']); // otherwise there's an url = parameter in the Url
$cmp = new Component($id, $title, Url::fromPath($url, $parameters), $pane);
$cmp = new Component($title, Url::fromPath($url, $parameters), $pane);
return $cmp;
}
/**
* Set the components id
*
* @param $id string
*/
public function setId($id)
{
$this->id = $id;
}
/**
* Retrieve the components id
*
* @return string
*/
public function getId()
{
return $this->id;
}
}

View File

@ -39,7 +39,7 @@ class Pane extends AbstractWidget
/**
* Create a new pane
*
* @param $name The pane to create
* @param string $name The pane to create
*/
public function __construct($name)
{
@ -83,44 +83,54 @@ class Pane extends AbstractWidget
/**
* Return true if a component with the given title exists in this pane
*
* @param string $id The id of the component to check for existence
* @param string $title The title of the component to check for existence
*
* @return bool
*/
public function hasComponent($id)
public function hasComponent($title)
{
return array_key_exists($id, $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
*
* @param string $id The id of the component to return
* @param string $title The title of the component to return
*
* @return Component The component with the given title
* @throws ProgrammingError If the component doesn't exist
*/
public function getComponent($id)
public function getComponent($title)
{
if ($this->hasComponent($id)) {
return $this->components[$id];
if ($this->hasComponent($title)) {
return $this->components[$title];
}
throw new ProgrammingError(
'Trying to access invalid component: %s',
$id
$title
);
}
/**
* Removes the component with the given id if it exists in this pane
* Removes the component with the given title if it exists in this pane
*
* @param string $id The pane
* @param string $title The pane
* @return Pane $this
*/
public function removeComponent($id)
public function removeComponent($title)
{
if ($this->hasComponent($id)) {
unset($this->components[$id]);
if ($this->hasComponent($title)) {
unset($this->components[$title]);
}
return $this;
}
@ -146,7 +156,6 @@ class Pane extends AbstractWidget
/**
* Add a component to this pane, optionally creating it if $component is a string
*
* @param string $id An unique Identifier
* @param string|Component $component The component object or title
* (if a new component will be created)
* @param string|null $url An Url to be used when component is a string
@ -154,12 +163,12 @@ class Pane extends AbstractWidget
* @return self
* @throws \Icinga\Exception\ConfigurationError
*/
public function addComponent($id, $component, $url = null)
public function addComponent($component, $url = null)
{
if ($component instanceof Component) {
$this->components[$component->getId()] = $component;
} elseif (is_string($id) && is_string($component) && $url !== null) {
$this->components[$id] = new Component($id, $component, $url, $this);
$this->components[$component->getTitle()] = $component;
} elseif (is_string($component) && $url !== null) {
$this->components[$component] = new Component($component, $url, $this);
} else {
throw new ConfigurationError('Invalid component added: %s', $component);
}
@ -176,15 +185,15 @@ class Pane extends AbstractWidget
{
/* @var $component Component */
foreach ($components as $component) {
if (array_key_exists($component->getId(), $this->components)) {
if (preg_match('/-(\d+)$/', $component->getId(), $m)) {
$name = preg_replace('/-\d+$/', $m[1]++, $component->getId());
if (array_key_exists($component->getTitle(), $this->components)) {
if (preg_match('/_(\d+)$/', $component->getTitle(), $m)) {
$name = preg_replace('/_\d+$/', $m[1]++, $component->getTitle());
} else {
$name = $component->getId() . '-2';
$name = $component->getTitle() . '_2';
}
$this->components[$name] = $component;
} else {
$this->components[$component->getId()] = $component;
$this->components[$component->getTitle()] = $component;
}
}
@ -194,18 +203,17 @@ class Pane extends AbstractWidget
/**
* Add a component to the current pane
*
* @param $id
* @param $title
* @param null $url
* @return mixed
* @param $url
* @return Component
*
* @see addComponent()
*/
public function add($id, $title, $url = null)
public function add($title, $url = null)
{
$this->addComponent($id, $title, $url);
$this->addComponent($title, $url);
return $this->components[$id];
return $this->components[$title];
}
/**

View File

@ -183,9 +183,10 @@ class FilterEditor extends AbstractWidget
{
$name = 'sign_' . $filter->getId();
$signs = array(
'=' => '=',
'>' => '>',
'<' => '<',
'=' => '=',
'!=' => '!=',
'>' => '>',
'<' => '<',
'>=' => '>=',
'<=' => '<=',
);

View File

@ -4,7 +4,8 @@
/* @var $this \Icinga\Application\Modules\Module */
$section = $this->menuSection('documentation', $this->translate('Documentation'), array(
$section = $this->menuSection($this->translate('Documentation'), array(
'title' => 'Documentation',
'icon' => 'img/icons/comment.png',
'url' => 'doc',
'priority' => 80

View File

@ -92,7 +92,7 @@ class Monitoring_ChartController extends Controller
'services_warning_unhandled',
'services_pending'
)
)->getQuery()->fetchAll();
)->order('hostgroup')->getQuery()->fetchAll();
$this->view->height = intval($this->getParam('height', 500));
$this->view->width = intval($this->getParam('width', 500));
if (count($query) === 1) {
@ -117,7 +117,7 @@ class Monitoring_ChartController extends Controller
'services_warning_unhandled',
'services_pending'
)
)->getQuery()->fetchAll();
)->order('servicegroup')->getQuery()->fetchAll();
$this->view->height = intval($this->getParam('height', 500));
$this->view->width = intval($this->getParam('width', 500));
@ -147,30 +147,35 @@ class Monitoring_ChartController extends Controller
->setXAxis(new StaticAxis())
->setAxisMin(null, 0);
$tooltip = t('<b>{title}:</b><br />{value} of {sum} services are {label}');
$this->view->chart->drawBars(
array(
'label' => t('Ok'),
'color' => '#44bb77',
'stack' => 'stack1',
'data' => $okBars
'data' => $okBars,
'tooltip' => $tooltip
),
array(
'label' => t('Warning'),
'color' => '#ffaa44',
'stack' => 'stack1',
'data' => $warningBars
'data' => $warningBars,
'tooltip' => $tooltip
),
array(
'label' => t('Critical'),
'color' => '#ff5566',
'stack' => 'stack1',
'data' => $critBars
'data' => $critBars,
'tooltip' => $tooltip
),
array(
'label' => t('Unknown'),
'color' => '#dd66ff',
'stack' => 'stack1',
'data' => $unknownBars
'data' => $unknownBars,
'tooltip' => $tooltip
)
);
}
@ -194,6 +199,7 @@ class Monitoring_ChartController extends Controller
$hostgroup->hosts_unreachable_unhandled
);
}
$tooltip = t('<b>{title}:</b><br /> {value} of {sum} hosts are {label}');
$this->view->chart = new GridChart();
$this->view->chart->alignTopLeft();
$this->view->chart->setAxisLabel('', t('Hosts'))
@ -204,19 +210,22 @@ class Monitoring_ChartController extends Controller
'label' => t('Up'),
'color' => '#44bb77',
'stack' => 'stack1',
'data' => $upBars
'data' => $upBars,
'tooltip' => $tooltip
),
array(
'label' => t('Down'),
'color' => '#ff5566',
'stack' => 'stack1',
'data' => $downBars
'data' => $downBars,
'tooltip' => $tooltip
),
array(
'label' => t('Unreachable'),
'color' => '#dd66ff',
'stack' => 'stack1',
'data' => $unreachableBars
'data' => $unreachableBars,
'tooltip' => $tooltip
)
);
}

View File

@ -34,8 +34,21 @@ class Monitoring_ListController extends Controller
protected function hasBetterUrl()
{
$request = $this->getRequest();
$url = clone($this->url);
if ($this->getRequest()->isPost()) {
if ($request->getPost('sort')) {
$url->setParam('sort', $request->getPost('sort'));
if ($request->getPost('dir')) {
$url->setParam('dir', $request->getPost('dir'));
} else {
$url->removeParam('dir');
}
return $url;
}
$q = $this->getRequest()->getPost('q');
} else {
$q = $url->shift('q');
@ -488,15 +501,8 @@ class Monitoring_ListController extends Controller
$request = $this->getRequest();
$limit = $params->shift('limit');
$sort = null;
$dir = null;
if ($request->isPost()) {
$sort = $request->getPost('sort', null);
$dir = $request->getPost('dir', null);
}
$sort = $params->shift('sort', $sort);
$dir = $params->shift('dir', $dir);
$sort = $params->shift('sort');
$dir = $params->shift('dir');
$page = $params->shift('page');
$format = $params->shift('format');
$view = $params->shift('view');
@ -533,7 +539,9 @@ class Monitoring_ListController extends Controller
$query->applyFilter($filter);
}
$this->view->filter = $filter;
$query->order($sort, $dir);
if ($sort) {
$query->order($sort, $dir);
}
$this->applyRestrictions($query);
$this->handleFormatRequest($query);
return $query;

View File

@ -89,9 +89,8 @@ class Monitoring_ShowController extends Controller
$this->getTabs()->activate('history');
//$this->view->object->populate();
$this->view->object->fetchEventHistory();
$this->view->history = $this->view->object->eventhistory->paginate($this->params->get('limit', 50));
$this->handleFormatRequest($this->view->object->eventhistory);
$this->view->history = $this->view->object->eventhistory
->paginate($this->params->get('limit', 50));
}
public function servicesAction()

View File

@ -2,9 +2,9 @@
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
use DateTime;
use DateInterval;
use Zend_Config;
use \DateTime;
use \DateInterval;
use \Zend_Config;
use Icinga\Web\Url;
use Icinga\Util\Format;
use Icinga\Application\Config;

View File

@ -1,284 +0,0 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
use Icinga\Module\Monitoring\Object\AbstractObject;
/**
* Class Zend_View_Helper_MonitoringProperties
*/
class Zend_View_Helper_MonitoringProperties extends Zend_View_Helper_Abstract
{
/**
* Value for check type active
*/
const CHECK_ACTIVE = 'ACTIVE';
/**
* Value for check type passive
*/
const CHECK_PASSIVE = 'PASSIVE';
/**
* Value for check type disabled
*/
const CHECK_DISABLED = 'DISABLED';
/**
* Return value for not available
*/
const VALUE_NA = 'N/A';
/**
* Return value for "YES"
*/
const VALUE_YES = 'YES';
/**
* Return value for "NO"
*/
const VALUE_NO = 'NO';
/**
* Label / value mapping for object keys
*
* Keys can be callables in this object
*
* @var array
*/
private static $keys = array(
'buildAttempt' => 'Current Attempt',
'buildCheckType' => 'Check Type',
'buildLatency' => 'Check Latency / Duration',
'buildLastStateChange' => 'Last State Change',
'buildLastNotification' => 'Last Notification',
'buildFlapping' => 'Is This %s Flapping?',
'buildScheduledDowntime' => 'In Scheduled Downtime?',
'status_update_time' => 'Last Update'
);
private static $notificationReasons = array(
0 => 'NORMAL',
1 => 'ACKNOWLEDGEMENT',
2 => 'FLAPPING START',
3 => 'FLAPPING STOP',
4 => 'FLAPPING DISABLED',
5 => 'DOWNTIME START',
6 => 'DOWNTIME END',
7 => 'DOWNTIME CANCELLED',
8 => 'CUSTOM',
9 => 'STALKING'
);
/**
* Return the object type
* @param stdClass $object
* @return mixed
*/
private function getObjectType($object)
{
$keys = array_keys(get_object_vars($object));
$keyParts = explode('_', array_shift($keys), 2);
return array_shift($keyParts);
}
/**
* Drop all object specific attribute prefixes
* @param stdClass $object
* @param $type
* @return object
*/
private function dropObjectType($object, $type)
{
$vars = get_object_vars($object);
$out = array();
foreach ($vars as $name => $value) {
$name = str_replace($type. '_', '', $name);
$out[$name] = $value;
}
return (object)$out;
}
/**
* Get string for attempt
* @param stdClass $object
* @return string
*/
private function buildAttempt($object)
{
return sprintf(
'%s/%s (%s state)',
$object->current_check_attempt,
$object->max_check_attempts,
($object->state_type === '1') ? 'HARD' : 'SOFT'
);
}
/**
* Generic fomatter for float values
* @param $value
* @return string
*/
private function floatFormatter($value)
{
return sprintf('%.4f', $value);
}
/**
* Get the string for check type
* @param stdClass $object
* @return string
*/
private function buildCheckType($object)
{
if ($object->passive_checks_enabled === '1' && $object->active_checks_enabled === '0') {
return self::CHECK_PASSIVE;
} elseif ($object->passive_checks_enabled === '0' && $object->active_checks_enabled === '0') {
return self::CHECK_DISABLED;
}
return self::CHECK_ACTIVE;
}
/**
* Get string for latency
* @param stdClass $object
* @return string
*/
private function buildLatency($object)
{
$val = '';
if ($this->buildCheckType($object) === self::CHECK_PASSIVE) {
$val .= self::VALUE_NA;
} else {
$val .= $this->floatFormatter(
(isset($object->check_latency)) ? $object->check_latency : 0
);
}
$val .= ' / '. $this->floatFormatter(
isset($object->check_execution_time) ? $object->check_execution_time : 0
). ' seconds';
return $val;
}
/**
* Get string for next check
* @param stdClass $object
* @return string
*/
private function buildNextCheck($object)
{
if ($this->buildCheckType($object) === self::CHECK_PASSIVE) {
return self::VALUE_NA;
} else {
return $object->next_check;
}
}
/**
* Get date for last state change
* @param stdClass $object
* @return string
*/
private function buildLastStateChange($object)
{
return strftime('%Y-%m-%d %H:%M:%S', $object->last_state_change);
}
/**
* Get string for "last notification"
* @param stdClass $object
* @return string
*/
private function buildLastNotification($object)
{
$val = '';
if ($object->last_notification === '0000-00-00 00:00:00') {
$val .= self::VALUE_NA;
} else {
$val .= $object->last_notification;
}
$val .= sprintf(' (notification %d)', $object->current_notification_number);
return $val;
}
/**
* Get string for "is flapping"
* @param stdClass $object
* @return string
*/
private function buildFlapping($object)
{
$val = '';
if ($object->is_flapping === '0') {
$val .= self::VALUE_NO;
} else {
$val .= self::VALUE_YES;
}
$val .= sprintf(' (%.2f%% state change)', $object->percent_state_change);
return $val;
}
/**
* Get string for scheduled downtime
* @param stdClass $object
* @return string
*/
private function buildScheduledDowntime($object)
{
if ($object->in_downtime === '1') {
return self::VALUE_YES;
}
return self::VALUE_NO;
}
/**
* Get an array which represent monitoring properties
*
* @param stdClass $object
* @return array
*/
public function monitoringProperties($object)
{
$type = $this->getObjectType($object);
//$object = $this->dropObjectType($object, $type);
$out = array();
foreach (self::$keys as $property => $label) {
$label = sprintf($label, ucfirst($type));
if (is_callable(array(&$this, $property))) {
$out[$label] = $this->$property($object);
} elseif (isset($object->{$property})) {
$out[$label] = $object->{$property};
}
}
return $out;
}
public function getNotificationType($notification)
{
$reason = intval($notification->notification_reason);
if (!isset(self::$notificationReasons[$reason])) {
return 'N/A';
}
$type = self::$notificationReasons[$reason];
if ($reason === 8) {
if (intval($notification->notification_type) === 0) {
$type .= '(UP)';
} else {
$type .= '(OK)';
}
}
return $type;
}
}

View File

@ -22,73 +22,72 @@ $this->provideConfigTab('security', array(
/*
* Problems Section
*/
$section = $this->menuSection('problems', $this->translate('Problems'), array(
$section = $this->menuSection($this->translate('Problems'), array(
'icon' => 'img/icons/error.png',
'priority' => 20
));
$section->add('unhandled hosts', $this->translate('Unhandled Hosts'), array(
$section->add($this->translate('Unhandled Hosts'), array(
'url' => 'monitoring/list/hosts?host_problem=1&host_handled=0',
'priority' => 40
));
$section->add('unhandled services', $this->translate('Unhandled Services'), array(
$section->add($this->translate('Unhandled Services'), array(
'url' => 'monitoring/list/services?service_problem=1&service_handled=0&sort=service_severity',
'priority' => 40
));
$section->add('host problems', $this->translate('Host Problems'), array(
$section->add($this->translate('Host Problems'), array(
'url' => 'monitoring/list/hosts?host_problem=1&sort=host_severity',
'priority' => 50
));
$section->add('service prolems', $this->translate('Service Problems'), array(
$section->add($this->translate('Service Problems'), array(
'url' => 'monitoring/list/services?service_problem=1&sort=service_severity&dir=desc',
'priority' => 50
));
$section->add('current downtimes', $this->translate('Current Downtimes'))
->setUrl('monitoring/list/downtimes?downtime_is_in_effect=1');
$section->add($this->translate('Current Downtimes'))->setUrl('monitoring/list/downtimes?downtime_is_in_effect=1');
/*
* Overview Section
*/
$section = $this->menuSection('overview', $this->translate('Overview'), array(
$section = $this->menuSection($this->translate('Overview'), array(
'icon' => 'img/icons/hostgroup.png',
'priority' => 30
));
$section->add('tactical overview', $this->translate('Tactical Overview'), array(
$section->add($this->translate('Tactical Overview'), array(
'url' => 'monitoring/tactical',
'priority' => 40
));
$section->add('hosts', $this->translate('Hosts'), array(
$section->add($this->translate('Hosts'), array(
'url' => 'monitoring/list/hosts',
'priority' => 50
));
$section->add('services', $this->translate('Services'), array(
$section->add($this->translate('Services'), array(
'url' => 'monitoring/list/services',
'priority' => 50
));
$section->add('servicematrix', $this->translate('Servicematrix'), array(
$section->add($this->translate('Servicematrix'), array(
'url' => 'monitoring/list/servicematrix?service_problem=1',
'priority' => 51
));
$section->add('servicegroups', $this->translate('Servicegroups'), array(
$section->add($this->translate('Servicegroups'), array(
'url' => 'monitoring/list/servicegroups',
'priority' => 60
));
$section->add('hostgroups', $this->translate('Hostgroups'), array(
$section->add($this->translate('Hostgroups'), array(
'url' => 'monitoring/list/hostgroups',
'priority' => 60
));
$section->add('contactgroups', $this->translate('Contactgroups'), array(
$section->add($this->translate('Contactgroups'), array(
'url' => 'monitoring/list/contactgroups',
'priority' => 61
));
$section->add('downtimes', $this->translate('Downtimes'), array(
$section->add($this->translate('Downtimes'), array(
'url' => 'monitoring/list/downtimes',
'priority' => 71
));
$section->add('comments', $this->translate('Comments'), array(
$section->add($this->translate('Comments'), array(
'url' => 'monitoring/list/comments?comment_type=(comment|ack)',
'priority' => 70
));
$section->add('contacts', $this->translate('Contacts'), array(
$section->add($this->translate('Contacts'), array(
'url' => 'monitoring/list/contacts',
'priority' => 70
));
@ -96,33 +95,31 @@ $section->add('contacts', $this->translate('Contacts'), array(
/*
* History Section
*/
$section = $this->menuSection('history', $this->translate('History'), array(
'title' => $this->translate('History'),
$section = $this->menuSection($this->translate('History'), array(
'icon' => 'img/icons/history.png'
));
$section->add('critical events', $this->translate('Critical Events'), array(
'title' => $this->translate('Critical Events'),
$section->add($this->translate('Critical Events'), array(
'url' => 'monitoring/list/statehistorysummary',
'priority' => 50
));
$section->add('notifications', $this->translate('Notifications'), array(
$section->add($this->translate('Notifications'), array(
'url' => 'monitoring/list/notifications'
));
$section->add('events', $this->translate('Events'), array(
$section->add($this->translate('Events'), array(
'title' => $this->translate('All Events'),
'url' => 'monitoring/list/eventhistory?timestamp>=-7%20days'
));
$section->add('timeline', $this->translate('Timeline'))->setUrl('monitoring/timeline');
$section->add($this->translate('Timeline'))->setUrl('monitoring/timeline');
/*
* System Section
*/
$section = $this->menuSection('system', $this->translate('System'));
$section->add('process info', $this->translate('Process Info'), array(
$section = $this->menuSection($this->translate('System'));
$section->add($this->translate('Process Info'), array(
'url' => 'monitoring/process/info',
'priority' => 120
));
$section->add('performance info', $this->translate('Performance Info'), array(
$section->add($this->translate('Performance Info'), array(
'url' => 'monitoring/process/performance',
'priority' => 130
));
@ -130,19 +127,16 @@ $section->add('performance info', $this->translate('Performance Info'), array(
/*
* Dashboard
*/
$dashboard = $this->dashboard('current incidents')->setTitle($this->translate('Current Incidents'));
$dashboard = $this->dashboard($this->translate('Current Incidents'));
$dashboard->add(
'service problems',
$this->translate('Service Problems'),
'monitoring/list/services?service_problem=1&limit=10&sort=service_severity'
);
$dashboard->add(
'recently recovered services',
$this->translate('Recently Recovered Services'),
'monitoring/list/services?service_state=0&limit=10&sort=service_last_state_change&dir=desc'
);
$dashboard->add(
'host problems',
$this->translate('Host Problems'),
'monitoring/list/hosts?host_problem=1&sort=host_severity'
);

View File

@ -69,33 +69,31 @@ class Backend implements Selectable, Queryable, ConnectionInterface
*/
public static function createBackend($backendName = null)
{
$allBackends = array();
$defaultBackend = null;
foreach (IcingaConfig::module('monitoring', 'backends') as $name => $config) {
if (!(bool) $config->get('disabled', false) && $defaultBackend === null) {
$defaultBackend = $config;
}
$allBackends[$name] = $config;
$config = IcingaConfig::module('monitoring', 'backends');
if ($config->count() === 0) {
throw new ConfigurationError(t('No backend has been configured'));
}
if (empty($allBackends)) {
throw new ConfigurationError('No backend has been configured');
}
if ($defaultBackend === null) {
throw new ConfigurationError('All backends are disabled');
}
if ($backendName === null) {
$backendConfig = $defaultBackend;
} else {
if (!array_key_exists($backendName, $allBackends)) {
if ($backendName !== null) {
$backendConfig = $config->get($backendName);
if ($backendConfig === null) {
throw new ConfigurationError('No configuration for backend %s', $backendName);
}
$backendConfig = $allBackends[$backendName];
if ((bool) $backendConfig->get('disabled', false)) {
if ((bool) $backendConfig->get('disabled', false) === true) {
throw new ConfigurationError(
'Configuration for backend %s available but backend is disabled',
t('Configuration for backend %s available but backend is disabled'),
$backendName
);
}
} else {
foreach ($config as $name => $backendConfig) {
if ((bool) $backendConfig->get('disabled', false) === false) {
$backendName = $name;
break;
}
}
if ($backendName === null) {
throw new ConfigurationError(t('All backends are disabled'));
}
}
$resource = ResourceFactory::create($backendConfig->resource);
if ($backendConfig->type === 'ido' && $resource->getDbType() !== 'oracle') {

View File

@ -51,10 +51,12 @@ class CommandQuery extends IdoQuery
{
$this->select->join(
array('cnc' => $this->prefix . 'contact_notificationcommands'),
'cnc.command_object_id = co.object_id'
'cnc.command_object_id = co.object_id',
array()
)->join(
array('con' => $this->prefix . 'contacts'),
'con.contact_id = cnc.contact_id'
'con.contact_id = cnc.contact_id',
array()
);
}
}

View File

@ -45,7 +45,7 @@ class CommentdeletionhistoryQuery extends IdoQuery
array()
)->join(
array('h' => $this->prefix . 'commenthistory'),
'o.' . $this->object_id . ' = h.' . $this->object_id . " AND o.is_active = 1 AND h.deletion_time > '1970-01-01 00:00:00' AND h.entry_type <> 2",
'o.' . $this->object_id . ' = h.' . $this->object_id . " AND o.is_active = 1 AND h.deletion_time > '1970-01-02 00:00:00' AND h.entry_type <> 2",
array()
);
$this->joinedVirtualTables = array('commenthistory' => true);

View File

@ -23,8 +23,8 @@ class DowntimeQuery extends IdoQuery
'downtime_triggered_by_id' => 'sd.triggered_by_id',
'downtime_scheduled_start' => 'UNIX_TIMESTAMP(sd.scheduled_start_time)',
'downtime_scheduled_end' => 'UNIX_TIMESTAMP(sd.scheduled_end_time)',
'downtime_start' => "UNIX_TIMESTAMP(CASE WHEN sd.trigger_time != '0000-00-00 00:00:00' then sd.trigger_time ELSE sd.scheduled_start_time END)",
'downtime_end' => 'CASE WHEN sd.is_fixed THEN UNIX_TIMESTAMP(sd.scheduled_end_time) ELSE UNIX_TIMESTAMP(sd.trigger_time) + sd.duration END',
'downtime_start' => "UNIX_TIMESTAMP(CASE WHEN UNIX_TIMESTAMP(sd.trigger_time) > 0 then sd.trigger_time ELSE sd.scheduled_start_time END)",
'downtime_end' => 'CASE WHEN sd.is_fixed > 0 THEN UNIX_TIMESTAMP(sd.scheduled_end_time) ELSE UNIX_TIMESTAMP(sd.trigger_time) + sd.duration END',
'downtime_duration' => 'sd.duration',
'downtime_is_in_effect' => 'sd.is_in_effect',
'downtime_internal_id' => 'sd.internal_downtime_id',

View File

@ -47,7 +47,7 @@ class DowntimeendhistoryQuery extends IdoQuery
array('h' => $this->prefix . 'downtimehistory'),
'o.' . $this->object_id . ' = h.' . $this->object_id . ' AND o.is_active = 1',
array()
)->where('h.actual_end_time > ?', '1970-01-01 00:00:00');
)->where('h.actual_end_time > ?', '1970-01-02 00:00:00');
$this->joinedVirtualTables = array('downtimehistory' => true);
}
}

View File

@ -47,7 +47,7 @@ class DowntimestarthistoryQuery extends IdoQuery
array('h' => $this->prefix . 'downtimehistory'),
'o.' . $this->object_id . ' = h.' . $this->object_id . ' AND o.is_active = 1',
array()
)->where('h.actual_start_time > ?', '1970-01-01 00:00:00');
)->where('h.actual_start_time > ?', '1970-01-02 00:00:00');
$this->joinedVirtualTables = array('downtimehistory' => true);
}
}

View File

@ -4,6 +4,7 @@
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
use Icinga\Logger\Logger;
use Zend_Db_Select;
class GroupSummaryQuery extends IdoQuery
@ -69,8 +70,15 @@ class GroupSummaryQuery extends IdoQuery
)
);
$groupColumn = 'hostgroup';
if (in_array('servicegroup', $this->desiredColumns)) {
$groupColumn = 'servicegroup';
}
$union = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL);
$this->select->from(array('statussummary' => $union), '*')->group($columns[0]);
$this->select->from(array('statussummary' => $union), array($groupColumn))->group(array($groupColumn));
$this->joinedVirtualTables = array(
'servicestatussummary' => true,
'hoststatussummary' => true

View File

@ -85,7 +85,8 @@ class NotificationhistoryQuery extends IdoQuery
$this->select->group('n.object_id')
->group('n.start_time')
->group('n.output')
->group('n.state');
->group('n.state')
->group('o.objecttype_id');
}
$this->joinedVirtualTables = array('history' => true);

View File

@ -41,7 +41,7 @@ class StatusQuery extends IdoQuery
'host_next_check' => 'CASE hs.should_be_scheduled WHEN 1 THEN UNIX_TIMESTAMP(hs.next_check) ELSE NULL END',
'host_check_execution_time' => 'hs.execution_time',
'host_check_latency' => 'hs.latency',
'host_problem' => 'CASE WHEN hs.current_state = 0 THEN 0 ELSE 1 END',
'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END',
'host_notifications_enabled' => 'hs.notifications_enabled',
@ -278,20 +278,41 @@ class StatusQuery extends IdoQuery
ELSE 0
END'
),
'serviceproblemsummary' => array(
'host_unhandled_services' => 'sps.unhandled_services_count'
),
'lasthostcomment' => array(
'host_last_comment' => 'hlc.last_comment_data',
'host_last_downtime' => 'hlc.last_downtime_data',
'host_last_flapping' => 'hlc.last_flapping_data',
'host_last_ack' => 'hlc.last_ack_data',
'lasthostcommentgeneric' => array(
'host_last_comment' => 'hlcg.last_comment_data'
),
'lastservicecomment' => array(
'service_last_comment' => 'slc.last_comment_data',
'service_last_downtime' => 'slc.last_downtime_data',
'service_last_flapping' => 'slc.last_flapping_data',
'service_last_ack' => 'slc.last_ack_data',
'lasthostcommentdowntime' => array(
'host_last_downtime' => 'hlcd.last_downtime_data'
),
'lasthostcommentflapping' => array(
'host_last_flapping' => 'hlcf.last_flapping_data'
),
'lasthostcommentack' => array(
'host_last_ack' => 'hlca.last_ack_data'
),
'lastservicecommentgeneric' => array(
'service_last_comment' => 'slcg.last_comment_data'
),
'lastservicecommentdowntime' => array(
'service_last_downtime' => 'slcd.last_downtime_data'
),
'lastservicecommentflapping' => array(
'service_last_flapping' => 'slcf.last_flapping_data'
),
'lastservicecommentack' => array(
'service_last_ack' => 'slca.last_ack_data'
)
);
@ -483,41 +504,117 @@ class StatusQuery extends IdoQuery
);
}
protected function getLastCommentSubQuery()
/**
* Create a subquery to join comments into status query
* @param int $entryType
* @param string $fieldName
* @return Zend_Db_Expr
*/
protected function getLastCommentSubQuery($entryType, $fieldName)
{
$sub = '(SELECT'
. ' lc.object_id,'
. " CASE WHEN lc.entry_type = 1 THEN CONCAT('[' || c.author_name || '] ' || c.comment_data) ELSE NULL END AS last_comment_data,"
. " CASE WHEN lc.entry_type = 2 THEN CONCAT('[' || c.author_name || '] ' || c.comment_data) ELSE NULL END AS last_downtime_data,"
. " CASE WHEN lc.entry_type = 3 THEN CONCAT('[' || c.author_name || '] ' || c.comment_data) ELSE NULL END AS last_flapping_data,"
. " CASE WHEN lc.entry_type = 4 THEN CONCAT('[' || c.author_name || '] ' || c.comment_data) ELSE NULL END AS last_ack_data"
. ' FROM icinga_comments c'
. ' JOIN (SELECT'
. ' MAX(comment_id) as comment_id,'
. ' object_id,'
. ' entry_type'
. ' FROM icinga_comments'
. ' WHERE entry_type = 1 OR entry_type = 4'
. ' GROUP BY object_id, entry_type'
. ') lc ON lc.comment_id = c.comment_id GROUP BY lc.object_id)';
. ' c.object_id,'
. " '[' || c.author_name || '] ' || c.comment_data AS $fieldName"
. ' FROM icinga_comments c JOIN ('
. ' SELECT MAX(comment_id) AS comment_id, object_id FROM icinga_comments'
. ' WHERE entry_type = ' . $entryType . ' GROUP BY object_id'
. ' ) lc ON c.comment_id = lc.comment_id)';
return new Zend_Db_Expr($sub);
}
protected function joinLasthostcomment()
/**
* Join last host comment
*/
protected function joinLasthostcommentgeneric()
{
$this->select->joinLeft(
array('hlc' => $this->getLastCommentSubQuery()),
'hlc.object_id = hs.host_object_id',
array('hlcg' => $this->getLastCommentSubQuery(1, 'last_comment_data')),
'hlcg.object_id = hs.host_object_id',
array()
);
}
// TODO: Terribly slow. As I have no idea of how to fix this we should remove it.
protected function joinLastservicecomment()
/**
* Join last host downtime comment
*/
protected function joinLasthostcommentdowntime()
{
$this->select->joinLeft(
array('slc' => $this->getLastCommentSubQuery()),
'slc.object_id = ss.service_object_id',
array('hlcd' => $this->getLastCommentSubQuery(2, 'last_downtime_data')),
'hlcg.object_id = hs.host_object_id',
array()
);
}
/**
* Join last host flapping comment
*/
protected function joinLastHostcommentflapping()
{
$this->select->joinLeft(
array('hlcf' => $this->getLastCommentSubQuery(3, 'last_flapping_data')),
'hlcg.object_id = hs.host_object_id',
array()
);
}
/**
* Join last host acknowledgement comment
*/
protected function joinLasthostcommentack()
{
$this->select->joinLeft(
array('hlca' => $this->getLastCommentSubQuery(4, 'last_ack_data')),
'hlca.object_id = hs.host_object_id',
array()
);
}
/**
* Join last service comment
*/
protected function joinLastservicecommentgeneric()
{
$this->select->joinLeft(
array('slcg' => $this->getLastCommentSubQuery(1, 'last_comment_data')),
'slcg.object_id = ss.service_object_id',
array()
);
}
/**
* Join last service downtime comment
*/
protected function joinLastservicecommentdowntime()
{
$this->select->joinLeft(
array('slcd' => $this->getLastCommentSubQuery(2, 'last_downtime_data')),
'slcd.object_id = ss.service_object_id',
array()
);
}
/**
* Join last service flapping comment
*/
protected function joinLastservicecommentflapping()
{
$this->select->joinLeft(
array('slcf' => $this->getLastCommentSubQuery(3, 'last_flapping_data')),
'slcf.object_id = ss.service_object_id',
array()
);
}
/**
* Join last service acknowledgement comment
*/
protected function joinLastservicecommentack()
{
$this->select->joinLeft(
array('slca' => $this->getLastCommentSubQuery(4, 'last_ack_data')),
'slca.object_id = ss.service_object_id',
array()
);
}

View File

@ -33,23 +33,27 @@ class StatusSummaryQuery extends IdoQuery
'hosts_flapping' => 'SUM(CASE WHEN object_type = \'host\' AND is_flapping = 1 THEN 1 ELSE 0 END)'
),
'servicestatussummary' => array(
'services_total' => 'SUM(CASE WHEN object_type = \'service\' THEN 1 ELSE 0 END)',
'services_problem' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 THEN 1 ELSE 0 END)',
'services_problem_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 AND (acknowledged + in_downtime + host_problem) > 0 THEN 1 ELSE 0 END)',
'services_problem_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 AND (acknowledged + in_downtime + host_problem) = 0 THEN 1 ELSE 0 END)',
'services_ok' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 THEN 1 ELSE 0 END)',
'services_ok_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
'services_pending' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 THEN 1 ELSE 0 END)',
'services_pending_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
'services_warning' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 THEN 1 ELSE 0 END)',
'services_warning_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) > 0 THEN 1 ELSE 0 END)',
'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) = 0 THEN 1 ELSE 0 END)',
'services_warning_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND (acknowledged + in_downtime + host_problem) > 0 THEN 1 ELSE 0 END)',
'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND (acknowledged + in_downtime + host_problem) = 0 THEN 1 ELSE 0 END)',
'services_warning_passive' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
'services_warning_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
'services_critical' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 THEN 1 ELSE 0 END)',
'services_critical_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) > 0 THEN 1 ELSE 0 END)',
'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) = 0 THEN 1 ELSE 0 END)',
'services_critical_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND (acknowledged + in_downtime + host_problem) > 0 THEN 1 ELSE 0 END)',
'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND (acknowledged + in_downtime + host_problem) = 0 THEN 1 ELSE 0 END)',
'services_critical_passive' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
'services_critical_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
'services_unknown' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 THEN 1 ELSE 0 END)',
'services_unknown_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) > 0 THEN 1 ELSE 0 END)',
'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) = 0 THEN 1 ELSE 0 END)',
'services_unknown_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND (acknowledged + in_downtime + host_problem) > 0 THEN 1 ELSE 0 END)',
'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND (acknowledged + in_downtime + host_problem) = 0 THEN 1 ELSE 0 END)',
'services_unknown_passive' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
'services_unknown_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
'services_active' => 'SUM(CASE WHEN object_type = \'service\' AND is_active_checked = 1 THEN 1 ELSE 0 END)',
@ -131,6 +135,7 @@ class StatusSummaryQuery extends IdoQuery
'acknowledged' => 'hs.problem_has_been_acknowledged',
'in_downtime' => 'CASE WHEN (hs.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END',
'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END',
'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END',
'is_passive_checked' => 'CASE WHEN hs.active_checks_enabled = 0 AND hs.passive_checks_enabled = 1 THEN 1 ELSE 0 END',
'is_active_checked' => 'hs.active_checks_enabled',
'is_processing_events' => 'hs.event_handler_enabled',
@ -144,6 +149,7 @@ class StatusSummaryQuery extends IdoQuery
'acknowledged' => 'ss.problem_has_been_acknowledged',
'in_downtime' => 'CASE WHEN (ss.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END',
'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END',
'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END',
'is_passive_checked' => 'CASE WHEN ss.active_checks_enabled = 0 AND ss.passive_checks_enabled = 1 THEN 1 ELSE 0 END',
'is_active_checked' => 'ss.active_checks_enabled',
'is_processing_events' => 'ss.event_handler_enabled',

View File

@ -31,6 +31,8 @@ abstract class DataView implements Browsable, Filterable, Sortable
protected $connection;
protected $isSorted = false;
/**
* Create a new view
*
@ -99,6 +101,7 @@ public function dump()
protected function applyUrlFilter($request = null)
{
$url = Url::fromRequest();
$limit = $url->shift('limit');
$sort = $url->shift('sort');
$dir = $url->shift('dir');
@ -132,20 +135,19 @@ public function dump()
}
}
$order = isset($params['order']) ? $params['order'] : null;
if ($order !== null) {
if (strtolower($order) === 'desc') {
$order = self::SORT_DESC;
} else {
$order = self::SORT_ASC;
if (isset($params['sort'])) {
$order = isset($params['order']) ? $params['order'] : null;
if ($order !== null) {
if (strtolower($order) === 'desc') {
$order = self::SORT_DESC;
} else {
$order = self::SORT_ASC;
}
}
$view->sort($params['sort'], $order);
}
$view->sort(
isset($params['sort']) ? $params['sort'] : null,
$order
);
return $view;
}
@ -226,6 +228,7 @@ public function dump()
foreach ($sortColumns['columns'] as $column) {
$this->query->order($column, $order);
}
$this->isSorted = true;
}
return $this;
}
@ -285,6 +288,7 @@ public function dump()
*/
public function getQuery()
{
if (! $this->isSorted) { $this->sort(); }
return $this->query;
}

View File

@ -37,20 +37,4 @@ class Groupsummary extends DataView
'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
)
);
}
}

View File

@ -1,104 +0,0 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Test\Modules\Monitoring\Application\Views\Helpers;
use Zend_View_Helper_MonitoringProperties;
use Icinga\Test\BaseTestCase;
require_once realpath(BaseTestCase::$moduleDir . '/monitoring/application/views/helpers/MonitoringProperties.php');
class HostStruct4Properties
{
public $host_name = 'localhost';
public $host_address = '127.0.0.1';
public $host_state = '1';
public $host_handled = '1';
public $in_downtime = '1';
public $acknowledged = '1';
public $check_command = 'check-host-alive';
public $last_state_change = '1372937083';
public $host_alias = 'localhost';
public $output = 'DDD';
public $long_output = '';
public $perfdata = '';
public $current_check_attempt = '1';
public $max_check_attempts = '10';
public $attempt = '1/10';
public $last_check = '2013-07-04 11:24:42';
public $next_check = '2013-07-04 11:29:43';
public $heck_type = '1';
public $last_hard_state_change = '2013-07-04 11:24:43';
public $last_hard_state = '0';
public $last_time_up = '2013-07-04 11:20:23';
public $last_time_down = '2013-07-04 11:24:43';
public $last_time_unreachable = '0000-00-00 00:00:00';
public $state_type = '1';
public $last_notification = '0000-00-00 00:00:00';
public $next_notification = '0000-00-00 00:00:00';
public $no_more_notifications = '0';
public $host_notifications_enabled = '1';
public $host_problem_has_been_acknowledged = '1';
public $host_acknowledgement_type = '2';
public $current_notification_number = '0';
public $passive_checks_enabled = '1';
public $active_checks_enabled = '0';
public $event_handler_enabled = '0';
public $flap_detection_enabled = '1';
public $is_flapping = '0';
public $percent_state_change = '12.36842';
public $check_latency = '0.12041';
public $check_execution_time = '0';
public $scheduled_downtime_depth = '1';
public $host_failure_prediction_enabled = '1';
public $host_process_performance_data = '1';
public $host_obsessing = '1';
public $host_modified_host_attributes = '14';
public $host_event_handler = '';
public $host_normal_check_interval = '5';
public $host_retry_check_interval = '1';
public $host_check_timeperiod_object_id = '27';
public $host_status_update_time = '2013-07-08 10:10:10';
}
/**
* @TODO(el): This test is subject to bug #4679
*/
class MonitoringPropertiesTest extends BaseTestCase
{
public function testOutput1()
{
$host = new HostStruct4Properties();
$host->current_check_attempt = '5';
$propertyHelper = new Zend_View_Helper_MonitoringProperties();
$items = $propertyHelper->monitoringProperties($host);
$this->assertEquals('5/10 (HARD state)', $items['Current Attempt']);
}
public function testOutput2()
{
$host = new HostStruct4Properties();
$host->current_check_attempt = '5';
$host->active_checks_enabled = '1';
$host->passive_checks_enabled = '0';
$host->is_flapping = '1';
$propertyHelper = new Zend_View_Helper_MonitoringProperties();
$items = $propertyHelper->monitoringProperties($host);
$test = array(
'Current Attempt' => "5/10 (HARD state)",
'Check Type' => "ACTIVE",
'Check Latency / Duration' => "0.1204 / 0.0000 seconds",
'Last State Change' => "2013-07-04 11:24:43",
'Last Notification' => "N/A (notification 0)",
'Is This Host Flapping?' => "YES (12.37% state change)",
'In Scheduled Downtime?' => "YES"
);
$this->assertEquals($test, $items);
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace Tests\Icinga\Module\Monitoring\Regression;
// 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__) . '/../../../../../test/php/bootstrap.php');
use Icinga\Application\Config;
use Icinga\Module\Monitoring\Backend;
use Icinga\Test\BaseTestCase;
use Mockery;
use Zend_Config;
class Bug7043Test extends BaseTestCase
{
public function tearDown()
{
parent::tearDown();
Mockery::close(); // Necessary because some tests run in a separate process
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testBackendDefaultName()
{
Mockery::mock('alias:Icinga\Data\ResourceFactory')
->shouldReceive('create')
->andReturn(
Mockery::mock('Icinga\Data\Db\DbConnection')
->shouldReceive('getDbType')
->andReturn('mysql')
->shouldReceive('setTablePrefix')
->getMock()
);
Config::setModuleConfig('monitoring', 'backends', new Zend_Config(array(
'backendName' => array(
'type' => 'ido',
'resource' => 'ido'
)
)));
$defaultBackend = Backend::createBackend();
$this->assertEquals('backendName', $defaultBackend->getName(), 'Default backend has name set');
}
}

View File

@ -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;
}

30
public/css/vendor/tipsy.css vendored Normal file
View File

@ -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; }

View File

@ -53,6 +53,8 @@
}
});
$('td.state span.timesince').attr('title', null);
var moduleName = el.data('icingaModule');
if (moduleName) {
if (icinga.hasModule(moduleName)) {
@ -109,6 +111,62 @@
if (searchField.length && searchField.val().length) {
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('change', 'form.auto input', this.formChanged);
// $(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) {

View File

@ -329,8 +329,7 @@
this.icinga.ui.reloadCss();
}
var redirect = req.getResponseHeader('X-Icinga-Redirect');
if (this.processRedirectHeader(req)) return;
if (req.getResponseHeader('X-Icinga-Redirect')) return;
// div helps getting an XML tree
var $resp = $('<div>' + req.responseText + '</div>');
@ -567,6 +566,7 @@
delete this.requests[req.$target.attr('id')];
this.icinga.ui.fadeNotificationsAway();
this.processRedirectHeader(req);
if (typeof req.loadNext !== 'undefined') {
if ($('#col2').length) {
@ -665,7 +665,11 @@
var self = this;
var containerId = $container.attr('id');
if (typeof containerId !== 'undefined') {
scrollPos = $container.scrollTop();
if (autorefresh) {
scrollPos = $container.scrollTop();
} else {
scrollPos = 0;
}
}
var origFocus = document.activeElement;

View File

@ -10,6 +10,9 @@
'use strict';
// The currently hovered tooltip
var tooltip = null;
// Stores the icinga-data-url of the last focused table.
var focusedTableDataUrl = null;
@ -767,7 +770,6 @@
this.debugTimer = null;
this.timeCounterTimer = null;
}
};
}(Icinga, jQuery));

View File

@ -200,6 +200,40 @@
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
*/

33
public/js/vendor/SOURCE.jquery.tipsy vendored Normal file
View File

@ -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

262
public/js/vendor/jquery.tipsy.js vendored Normal file
View File

@ -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);

2
public/js/vendor/jquery.tipsy.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -26,10 +26,10 @@ class DbBackendFormTest extends BaseTestCase
*/
public function testValidBackendIsValid()
{
$this->setUpUserBackendMock()
$this->setUpResourceFactoryMock();
Mockery::mock('overload:Icinga\Authentication\Backend\DbUserBackend')
->shouldReceive('count')
->andReturn(2);
$this->setUpResourceFactoryMock();
$form = new DbBackendForm();
$form->setBackendName('test');
@ -49,10 +49,10 @@ class DbBackendFormTest extends BaseTestCase
*/
public function testInvalidBackendIsNotValid()
{
$this->setUpUserBackendMock()
$this->setUpResourceFactoryMock();
Mockery::mock('overload:Icinga\Authentication\Backend\DbUserBackend')
->shouldReceive('count')
->andReturn(0);
$this->setUpResourceFactoryMock();
$form = new DbBackendForm();
$form->setBackendName('test');
@ -66,18 +66,10 @@ class DbBackendFormTest extends BaseTestCase
);
}
protected function setUpUserBackendMock()
{
return Mockery::mock('overload:Icinga\Authentication\Backend\DbUserBackend');
}
protected function setUpResourceFactoryMock()
{
Mockery::mock('alias:Icinga\Data\ResourceFactory')
->shouldReceive('getResourceConfig')
->andReturn(new \Zend_Config(array()))
->shouldReceive('createResource')
->with(Mockery::type('\Zend_Config'))
->shouldReceive('create')
->andReturn(Mockery::mock('Icinga\Data\Db\DbConnection'));
}
}

View File

@ -182,7 +182,7 @@ class FilterTest extends BaseTestCase
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);
$this->assertFalse($filter->matches($this->row(0)));
$this->assertTrue($filter->matches($this->row(1)));

View File

@ -109,10 +109,10 @@ class QueryTest extends BaseTestCase
$this->assertEquals('testIntColumn', $cols[0][0]);
}
public function test__toString()
public function testCreateQuery()
{
$select = $this->prepareSelect();
$res = '(&(objectClass=dummyClass)(testIntColumn=1)(testStringColumn=test)(testWildcard=abc*))';
$this->assertEquals($res, (string) $select);
$this->assertEquals($res, $select->create());
}
}

View File

@ -27,7 +27,7 @@ class TranslatorTest extends BaseTestCase
public function testWhetherGetAvailableLocaleCodesReturnsAllAvailableLocaleCodes()
{
$this->assertEquals(
array('de_DE', 'fr_FR'),
array(Translator::DEFAULT_LOCALE, 'de_DE', 'fr_FR'),
Translator::getAvailableLocaleCodes(),
'Translator::getAvailableLocaleCodes does not return all available locale codes'
);

View File

@ -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'
);
}
}