Merge branch 'master' into bugfix/commands-6593

Conflicts:
	application/forms/Preference/GeneralForm.php
	application/views/helpers/FormDateTime.php
	modules/monitoring/application/forms/Command/CommandForm.php
This commit is contained in:
Eric Lippmann 2014-09-08 10:16:05 +02:00
commit 0e7ca591ec
86 changed files with 1745 additions and 517 deletions

2
.gitignore vendored
View File

@ -11,6 +11,8 @@
build/
development/
# ./configure output
config.log
autom4te.cache

View File

@ -8,7 +8,8 @@ use Icinga\Web\Url;
use Icinga\Data\ResourceFactory;
use Icinga\Logger\Logger;
use Icinga\Logger\Writer\FileWriter;
use Icinga\Protocol\File\Reader as FileReader;
use Icinga\Protocol\File\FileReader;
use \Zend_Controller_Action_Exception as ActionError;
/**
* Class ListController
@ -38,20 +39,21 @@ class ListController extends Controller
*/
public function applicationlogAction()
{
if (! Logger::writesToFile()) {
throw new ActionError('Site not found', 404);
}
$this->addTitleTab('application log');
$pattern = '/^(?<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
$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;
}
$resource = new FileReader(new Zend_Config(array(
'filename' => $loggerWriter->getPath(),
'fields' => $pattern
)));
$this->view->logData = $resource->select()->order('DESC')->paginate();
}
}

View File

@ -3,9 +3,8 @@
// {{{ICINGA_LICENSE_HEADER}}}
use Icinga\Web\Controller\ActionController;
use Icinga\Application\Icinga;
use Icinga\Web\Widget;
use Icinga\Web\Url;
use Icinga\Web\Widget\SearchDashboard;
/**
* Search controller
@ -14,47 +13,13 @@ class SearchController extends ActionController
{
public function indexAction()
{
$search = $this->_request->getParam('q');
if (! $search) {
$this->view->tabs = Widget::create('tabs')->add(
'search',
array(
'title' => $this->translate('Search'),
'url' => '/search',
)
)->activate('search');
$this->render('hint');
return;
}
$dashboard = Widget::create('dashboard')->createPane($this->translate('Search'));
$pane = $dashboard->getPane($this->translate('Search'));
$suffix = strlen($search) ? ': ' . rtrim($search, '*') . '*' : '';
$pane->addComponent(
$this->translate('Hosts') . $suffix,
Url::fromPath('monitoring/list/hosts', array(
'host_name' => $search . '*',
'sort' => 'host_severity',
'limit' => 10,
)
));
$pane->addComponent(
$this->translate('Services') . $suffix,
Url::fromPath('monitoring/list/services', array(
'service_description' => $search . '*',
'sort' => 'service_severity',
'limit' => 10,
)
));
$pane->addComponent('Hostgroups' . $suffix, Url::fromPath('monitoring/list/hostgroups', array(
'hostgroup' => $search . '*',
'limit' => 10,
)));
$pane->addComponent('Servicegroups' . $suffix, Url::fromPath('monitoring/list/servicegroups', array(
'servicegroup' => $search . '*',
'limit' => 10,
)));
$dashboard->activate($this->translate('Search'));
$this->view->dashboard = $dashboard;
$this->view->tabs = $dashboard->getTabs();
$this->view->dashboard = SearchDashboard::search($this->params->get('q'));
// NOTE: This renders the dashboard twice. Remove this once we can catch exceptions thrown in view scripts.
$this->view->dashboard->render();
}
public function hintAction()
{
}
}

View File

@ -47,7 +47,7 @@ class StaticController extends ActionController
$cache->send($cacheFile);
return;
}
$img = file_get_contents('http://www.gravatar.com/avatar/' . $filename . '?s=200&d=mm');
$img = file_get_contents('http://www.gravatar.com/avatar/' . $filename . '?s=120&d=mm');
$cache->store($cacheFile, $img);
header('ETag: "' . $cache->etagForCachedFile($cacheFile) . '"');
echo $img;

View File

@ -29,17 +29,17 @@ $iframeClass = $isIframe ? ' iframe' : '';
<title><?= $this->title ? $this->escape($this->title) : 'Icinga Web' ?></title>
<!-- TODO: viewport and scale settings make no sense for us, fix this -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="<?= $this->href($cssfile) ?>" media="screen" type="text/css" />
<? if ($isIframe): ?>
<? if ($isIframe): ?>
<base target="_parent"/>
<?php else: ?>
<?php else: ?>
<script type="text/javascript">
(function() {
var html = document.getElementsByTagName('html')[0];
html.className = html.className.replace(/no-js/, 'js');
}());
</script>
<?php endif ?>
<?php endif ?>
<link rel="stylesheet" href="<?= $this->href($cssfile) ?>" media="screen" type="text/css" />
<!-- Respond.js IE8 support of media queries -->
<!--[if lt IE 9]>
<script src="<?= $this->baseUrl('js/vendor/respond.min.js');?>"></script>

View File

@ -3,6 +3,7 @@
use Icinga\Web\Url;
use Icinga\Web\Menu;
use Icinga\Web\MenuRenderer;
use Icinga\Web\Widget\SearchDashboard;
// Don't render a menu for unauthenticated users unless menu is auth aware
if (! $this->auth()->isAuthenticated()) {
@ -11,8 +12,10 @@ if (! $this->auth()->isAuthenticated()) {
?>
<div id="menu" data-base-target="_main">
<? if (SearchDashboard::search('dummy')->getPane('search')->hasComponents()): ?>
<form action="<?= $this->href('search') ?>" method="get" role="search">
<input type="text" name="q" class="search autofocus" placeholder="<?= $this->translate('Search...') ?>" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
</form>
<? endif; ?>
<?= new MenuRenderer(Menu::load(), Url::fromRequest()->without('renderLayout')->getRelativeUrl()); ?>
</div>

View File

@ -3,7 +3,6 @@
// {{{ICINGA_LICENSE_HEADER}}}
use Icinga\Application\Icinga;
use Icinga\Application\Config;
use Icinga\Util\DateTimeFactory;
/**
@ -108,10 +107,8 @@ class Zend_View_Helper_DateFormat extends Zend_View_Helper_Abstract
*/
public function getDateFormat()
{
return $this->request->getUser()->getPreferences()->get(
'app.dateFormat',
Config::app()->global !== null ? Config::app()->global->get('dateFormat', 'd/m/Y') : 'd/m/Y'
);
// TODO(mh): Missing localized format (#6077)
return 'd/m/Y';
}
/**
@ -121,10 +118,8 @@ class Zend_View_Helper_DateFormat extends Zend_View_Helper_Abstract
*/
public function getTimeFormat()
{
return $this->request->getUser()->getPreferences()->get(
'app.timeFormat',
Config::app()->global !== null ? Config::app()->global->get('timeFormat', 'g:i A') : 'g:i A'
);
// TODO(mh): Missing localized format (#6077)
return 'g:i A';
}
/**

View File

@ -2,7 +2,7 @@
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
use Zend_View_Helper_FormElement;
use \Zend_View_Helper_FormElement;
/**
* Helper to generate a "datetime" element

View File

@ -1,12 +1,8 @@
<div class="controls">
<?= $this->tabs ?>
</div>
<div class="content">
<h1><?= $this->translate("I'm ready to search, waiting for your input") ?></h1>
<p><strong><?= $this->translate('Hint') ?>: </strong><?= $this->translate(
'Please use the asterisk (*) as a placeholder for wildcard searches.'
. " For convenience I'll always add a wildcard after the last character"
. ' you typed.'
. " For convenience I'll always add a wildcard in front and after your"
. ' search string.'
) ?></p>
</div>

View File

@ -1,5 +1,5 @@
<div class="controls">
<?= $this->tabs ?>
<?= $this->dashboard->getTabs() ?>
</div>
<div class="content dashboard">

View File

@ -1,7 +1,5 @@
[global]
timezone = "Europe/Berlin"
dateFormat = "d/m/Y"
timeFormat = "g:i A"
; Contains the directories that will be searched for available modules. Modules that
; don't exist in these directories can still be symlinked in the module folder, but

0
doc/api/.gitkeep Normal file
View File

View File

@ -163,6 +163,31 @@ class Module
*/
protected $paneItems = array();
/**
* @var array
*/
protected $searchUrls = array();
/**
* @param string $title
* @param string $url
*/
public function provideSearchUrl($title, $url)
{
$searchUrl = (object) array(
'title' => $title,
'url' => $url
);
$this->searchUrls[] = $searchUrl;
}
public function getSearchUrls()
{
$this->launchConfigScript();
return $this->searchUrls;
}
/**
* Get all Menu Items
*

View File

@ -10,6 +10,7 @@ use Icinga\Authentication\Manager as AuthenticationManager;
use Icinga\Exception\ConfigurationError;
use Icinga\Exception\NotReadableError;
use Icinga\Logger\Logger;
use Icinga\Util\TimezoneDetect;
use Icinga\Web\Request;
use Icinga\Web\Response;
use Icinga\Web\View;
@ -273,10 +274,11 @@ class Web extends ApplicationBootstrap
*/
protected function setupTimezone()
{
$userTimezone = null;
if ($this->user !== null && $this->user->getPreferences() !== null) {
$userTimezone = $this->user->getPreferences()->get('app.timezone');
} else {
$userTimezone = null;
$detect = new TimezoneDetect();
$userTimezone = $this->user->getPreferences()->get('app.timezone', $detect->getTimezoneName());
}
try {

View File

@ -144,7 +144,7 @@ class BarGraph extends Styleable implements Drawable
$this->tooltips[$x]->renderNoHtml($this->order, $data, $format)
);
$bar->setAttribute(
'title-rich',
'data-title-rich',
$this->tooltips[$x]->render($this->order, $data, $format)
);
}

View File

@ -12,7 +12,7 @@ use Icinga\Data\Db\DbConnection;
use Icinga\Protocol\Livestatus\Connection as LivestatusConnection;
use Icinga\Protocol\Statusdat\Reader as StatusdatReader;
use Icinga\Protocol\Ldap\Connection as LdapConnection;
use Icinga\Protocol\File\Reader as FileReader;
use Icinga\Protocol\File\FileReader;
/**
* Create resources from names or resource configuration

View File

@ -28,6 +28,13 @@ class Logger
*/
protected $writer;
/**
* The configured type
*
* @string Type (syslog, file)
*/
protected $type = 'none';
/**
* The maximum severity to emit
*
@ -85,6 +92,7 @@ class Logger
$config->type
);
}
$this->type = $config->type;
return new $class($config);
}
@ -212,6 +220,16 @@ class Logger
return $this->writer;
}
public static function writesToSyslog()
{
return static::$instance && static::$instance->type === 'syslog';
}
public static function writesToFile()
{
return static::$instance && static::$instance->type === 'file';
}
/**
* Get this' instance
*

View File

@ -0,0 +1,82 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Protocol\File;
use FilterIterator;
use Icinga\Util\File;
/**
* Class FileIterator
*
* Iterate over a file, yielding only fields of non-empty lines which match a PCRE expression
*/
class FileIterator extends FilterIterator
{
/**
* A PCRE string with the fields to extract from the file's lines as named subpatterns
*
* @var string
*/
protected $fields;
/**
* An associative array of the current line's fields ($field => $value)
*
* @var array
*/
protected $currentData;
public function __construct($filename, $fields)
{
$this->fields = $fields;
$f = new File($filename);
$f->setFlags(
File::DROP_NEW_LINE |
File::READ_AHEAD |
File::SKIP_EMPTY
);
parent::__construct($f);
}
/**
* Return the current data
*
* @return array
*/
public function current()
{
return $this->currentData;
}
/**
* Accept lines matching the given PCRE pattern
*
* @return bool
*
* @throws FileReaderException If PHP failed parsing the PCRE pattern
*/
public function accept()
{
$data = array();
$matched = preg_match(
$this->fields,
$this->getInnerIterator()->current(),
$data
);
if ($matched === false) {
throw new FileReaderException('Failed parsing regular expression!');
} else if ($matched === 1) {
foreach ($data as $key => $value) {
if (is_int($key)) {
unset($data[$key]);
}
}
$this->currentData = $data;
return true;
}
return false;
}
}

View File

@ -8,13 +8,13 @@ use Icinga\Data\SimpleQuery;
use Icinga\Data\Filter\Filter;
/**
* Class Query
* Class FileQuery
*
* Query for Datasource Icinga\Protocol\File\Reader
* Query for Datasource Icinga\Protocol\File\FileReader
*
* @package Icinga\Protocol\File
*/
class Query extends SimpleQuery
class FileQuery extends SimpleQuery
{
/**
* Sort direction
@ -41,7 +41,7 @@ class Query extends SimpleQuery
*
* @param string $dir Sort direction, 'ASC' or 'DESC' (default)
*
* @return Query
* @return FileQuery
*/
public function order($field, $direction = null)
{
@ -66,7 +66,7 @@ class Query extends SimpleQuery
*
* @param string $expression the filter expression to be applied
*
* @return Query
* @return FileQuery
*/
public function andWhere($expression)
{

View File

@ -4,16 +4,15 @@
namespace Icinga\Protocol\File;
use FilterIterator;
use Iterator;
use Icinga\Data\Selectable;
use Countable;
use Icinga\Util\Enumerate;
use Zend_Config;
use Icinga\Protocol\File\FileReaderException;
use Icinga\Util\File;
/**
* Read file line by line
*/
class Reader extends FilterIterator
class FileReader implements Selectable, Countable
{
/**
* A PCRE string with the fields to extract from the file's lines as named subpatterns
@ -23,11 +22,11 @@ class Reader extends FilterIterator
protected $fields;
/**
* An associative array of the current line's fields ($field => $value)
* Name of the target file
*
* @var array
* @var string
*/
protected $currentData;
protected $filename;
/**
* Create a new reader
@ -39,67 +38,34 @@ class Reader extends FilterIterator
public function __construct(Zend_Config $config)
{
foreach (array('filename', 'fields') as $key) {
if (! isset($config->{$key})) {
if (isset($config->{$key})) {
$this->{$key} = $config->{$key};
} else {
throw new FileReaderException('The directive `%s\' is required', $key);
}
}
$this->fields = $config->fields;
$f = new File($config->filename);
$f->setFlags(
File::DROP_NEW_LINE |
File::READ_AHEAD |
File::SKIP_EMPTY
);
parent::__construct($f);
}
/**
* Return the current data
* Instantiate a FileIterator object with the target file
*
* @return array
* @return FileIterator
*/
public function current()
public function iterate()
{
return $this->currentData;
}
/**
* Accept lines matching the given PCRE pattern
*
* @return bool
*
* @throws FileReaderException If PHP failed parsing the PCRE pattern
*/
public function accept()
{
$data = array();
$matched = @preg_match(
$this->fields,
$this->getInnerIterator()->current(),
$data
return new Enumerate(
new FileIterator($this->filename, $this->fields)
);
if ($matched === false) {
throw new FileReaderException('Failed parsing regular expression!');
} else if ($matched === 1) {
foreach ($data as $key => $value) {
if (is_int($key)) {
unset($data[$key]);
}
}
$this->currentData = $data;
return true;
}
return false;
}
/**
* Instantiate a Query object
* Instantiate a FileQuery object
*
* @return Query
* @return FileQuery
*/
public function select()
{
return new Query($this);
return new FileQuery($this);
}
/**
@ -109,17 +75,17 @@ class Reader extends FilterIterator
*/
public function count()
{
return iterator_count($this);
return iterator_count($this->iterate());
}
/**
* Fetch result as an array of objects
*
* @param Query $query
* @param FileQuery $query
*
* @return array
*/
public function fetchAll(Query $query)
public function fetchAll(FileQuery $query)
{
$all = array();
foreach ($this->fetchPairs($query) as $index => $value) {
@ -131,11 +97,11 @@ class Reader extends FilterIterator
/**
* Fetch result as a key/value pair array
*
* @param Query $query
* @param FileQuery $query
*
* @return array
*/
public function fetchPairs(Query $query)
public function fetchPairs(FileQuery $query)
{
$skip = $query->getOffset();
$read = $query->getLimit();
@ -154,15 +120,13 @@ class Reader extends FilterIterator
$skip = $count - ($skip + $read);
}
}
$index = 0;
foreach ($this as $line) {
foreach ($this->iterate() as $index => $line) {
if ($index >= $skip) {
if ($index >= $skip + $read) {
break;
}
$lines[] = $line;
}
++$index;
}
if ($query->sortDesc()) {
$lines = array_reverse($lines);
@ -173,11 +137,11 @@ class Reader extends FilterIterator
/**
* Fetch first result row
*
* @param Query $query
* @param FileQuery $query
*
* @return object
*/
public function fetchRow(Query $query)
public function fetchRow(FileQuery $query)
{
$all = $this->fetchAll($query);
if (isset($all[0])) {
@ -189,11 +153,11 @@ class Reader extends FilterIterator
/**
* Fetch first result column
*
* @param Query $query
* @param FileQuery $query
*
* @return array
*/
public function fetchColumn(Query $query)
public function fetchColumn(FileQuery $query)
{
$column = array();
foreach ($this->fetchPairs($query) as $pair) {
@ -208,11 +172,11 @@ class Reader extends FilterIterator
/**
* Fetch first column value from first result row
*
* @param Query $query
* @param FileQuery $query
*
* @return mixed
*/
public function fetchOne(Query $query)
public function fetchOne(FileQuery $query)
{
$pairs = $this->fetchPairs($query);
if (isset($pairs[0])) {

View File

@ -0,0 +1,62 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Util;
use Iterator;
/**
* Class Enumerate
*
* @see https://docs.python.org/2/library/functions.html#enumerate
*
* @package Icinga\Util
*/
class Enumerate implements Iterator
{
/**
* @var Iterator
*/
protected $iterator;
/**
* @var int
*/
protected $key;
/**
* @param Iterator $iterator
*/
public function __construct(Iterator $iterator)
{
$this->iterator = $iterator;
}
public function rewind()
{
$this->iterator->rewind();
$this->key = 0;
}
public function next()
{
$this->iterator->next();
++$this->key;
}
public function valid()
{
return $this->iterator->valid();
}
public function current()
{
return $this->iterator->current();
}
public function key()
{
return $this->key;
}
}

View File

@ -0,0 +1,108 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Util;
use Icinga\Application\Platform;
/**
* Retrieve timezone information from cookie
*/
class TimezoneDetect
{
/**
* If detection was successful
*
* @var bool
*/
private static $success;
/**
* Timezone offset in minutes
*
* @var int
*/
private static $offset = 0;
/**
* @var string
*/
private static $timezoneName;
/**
* Cookie name
*
* @var string
*/
public static $cookieName = 'icingaweb2-tzo';
/**
* Timezone name
*
* @var string
*/
private static $timezone;
/**
* Create new object and try to identify the timezone
*/
public function __construct()
{
if (self::$success !== null) {
return;
}
if (Platform::isCli() === false && array_key_exists(self::$cookieName, $_COOKIE)) {
list($offset, $dst) = explode(',', $_COOKIE[self::$cookieName]);
$timezoneName = timezone_name_from_abbr('', (int)$offset, (int)$dst);
self::$success = (bool)$timezoneName;
if (self::$success === true) {
self::$offset = $offset;
self::$timezoneName = $timezoneName;
}
}
}
/**
* Get offset
*
* @return int
*/
public function getOffset()
{
return self::$offset;
}
/**
* Get timezone name
*
* @return string
*/
public function getTimezoneName()
{
return self::$timezoneName;
}
/**
* True on success
*
* @return bool
*/
public function success()
{
return self::$success;
}
/**
* Reset object
*/
public function reset()
{
self::$success = null;
self::$timezoneName = null;
self::$offset = 0;
}
}

View File

@ -123,7 +123,12 @@ class Hook
*/
private static function assertValidHook($instance, $name)
{
$base_class = self::$BASE_NS . ucfirst($name) . self::$classSuffix;
$base_class = self::$BASE_NS . ucfirst($name) . 'Hook';
if (strpos($base_class, self::$classSuffix) === false) {
$base_class .= self::$classSuffix;
}
if (!$instance instanceof $base_class) {
throw new ProgrammingError(
'%s is not an instance of %s',

View File

@ -5,26 +5,30 @@
namespace Icinga\Web\Hook;
use Icinga\Exception\ProgrammingError;
use Icinga\Module\Monitoring\Object\MonitoredObject;
/**
* Icinga Web Grapher Hook base class
*
* Extend this class if you want to integrate your graphing solution nicely into
* Icinga Web
*
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
* @author Icinga-Web Team <info@icinga.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
* Icinga Web.
*/
abstract class GrapherHook
abstract class GrapherHook extends WebBaseHook
{
/**
* Whether this grapher provides preview images
* Whether this grapher provides previews
*
* @var bool
*/
protected $hasPreviews = false;
/**
* Whether this grapher provides tiny previews
*
* @var bool
*/
protected $hasTinyPreviews = false;
/**
* Constructor must live without arguments right now
*
@ -36,16 +40,6 @@ abstract class GrapherHook
$this->init();
}
/**
* Whether this grapher provides preview images
*
* @return bool
*/
public function hasPreviews()
{
return $this->hasPreviews;
}
/**
* Overwrite this function if you want to do some initialization stuff
*
@ -56,75 +50,63 @@ abstract class GrapherHook
}
/**
* Whether a graph for the given host[, service [, plot]] exists
*
* @param string $host
* @param string $service
* @param string $plot
* Whether this grapher provides previews
*
* @return bool
*/
public function has($host, $service = null, $plot = null)
public function hasPreviews()
{
return false;
return $this->hasPreviews;
}
/**
* Get a preview image for the given host[, service [, plot]] exists
* Whether this grapher provides tiny previews
*
* @param string $host
* @param string $service
* @param string $plot
*
* @return string
*
* @throws ProgrammingError
* @return bool
*/
public function getPreviewHtml($host, $service = null, $plot = null)
public function hasTinyPreviews()
{
throw new ProgrammingError('This backend has no preview images');
return $this->hasTinyPreviews;
}
/**
* Whether a tiny graph for the given host[, service [, plot]] exists
* Whether a graph for the monitoring object exist
*
* @param string $host
* @param string $service
* @param string $plot
* @param MonitoredObject $object
*
* @return bool
*/
public function hasTinyPreview($host, $service = null, $plot = null)
{
return false;
}
abstract public function has(MonitoredObject $object);
/**
* Get a tiny preview image for the given host[, service [, plot]] exists
* Get a preview for the given object
*
* @param string $host
* @param string $service
* @param string $plot
* This function must return an empty string if no graph exists.
*
* @param MonitoredObject $object
*
* @return string
* @throws ProgrammingError
*
*/
public function getPreviewHtml(MonitoredObject $object)
{
throw new ProgrammingError('This hook provide previews but it is not implemented');
}
/**
* Get a tiny preview for the given object
*
* This function must return an empty string if no graph exists.
*
* @param MonitoredObject $object
*
* @return string
* @throws ProgrammingError
*/
public function getTinyPreviewHtml($host, $service = null, $plot = null)
public function getTinyPreviewHtml(MonitoredObject $object)
{
throw new ProgrammingError('This backend has no tiny preview images');
throw new ProgrammingError('This hook provide tiny previews but it is not implemented');
}
/**
* Get URL pointing to the grapher
*
* WARNING: We are not sure yet whether this will remain as is
*
* @param string $host
* @param string $service
* @param string $plot
*
* @return string
*/
abstract function getGraphUrl($host, $service = null, $plot = null);
}

View File

@ -21,6 +21,7 @@ class JavaScript
'js/icinga/events.js',
'js/icinga/history.js',
'js/icinga/module.js',
'js/icinga/timezone.js',
);
protected static $vendorFiles = array(

View File

@ -86,16 +86,6 @@ class LessCompiler
public function compile()
{
//TODO:
/* $tmpfile = '/tmp/icinga.less';
$cssfile = '/tmp/icinga.css';
if (! file_exists($tmpfile)) {
file_put_contents($tmpfile, $this->source);
}
if ($this->lessc->checkedCompile($tmpfile, $cssfile)) {
}
return file_get_contents($cssfile);
*/
return $this->lessc->compile($this->source);
}

View File

@ -4,12 +4,15 @@
namespace Icinga\Web;
use Icinga\Exception\ConfigurationError;
use Zend_Config;
use Icinga\Web\Menu\MenuItemRenderer;
use RecursiveIterator;
use Zend_Config;
use Icinga\Application\Config;
use Icinga\Application\Icinga;
use Icinga\Logger\Logger;
use Icinga\Exception\ConfigurationError;
use Icinga\Exception\ProgrammingError;
use Icinga\Web\Url;
class Menu implements RecursiveIterator
{
@ -59,15 +62,32 @@ class Menu implements RecursiveIterator
*/
protected $subMenus = array();
/**
* A custom item renderer used instead of the default rendering logic
*
* @var MenuItemRenderer
*/
protected $itemRenderer = null;
/*
* Parent menu
*
* @var Menu
*/
protected $parent;
/**
* Create a new menu
*
* @param int $id The id of this menu
* @param Zend_Config $config The configuration for this menu
*/
public function __construct($id, Zend_Config $config = null)
public function __construct($id, Zend_Config $config = null, Menu $parent = null)
{
$this->id = $id;
if ($parent !== null) {
$this->parent = $parent;
}
$this->setProperties($config);
}
@ -81,6 +101,15 @@ class Menu implements RecursiveIterator
if ($props !== null) {
foreach ($props as $key => $value) {
$method = 'set' . implode('', array_map('ucfirst', explode('_', strtolower($key))));
if ($key === 'renderer') {
$class = '\Icinga\Web\Menu\\' . $value;
if (!class_exists($class)) {
throw new ConfigurationError(
sprintf('ItemRenderer with class "%s" does not exist', $class)
);
}
$value = new $class;
}
if (method_exists($this, $method)) {
$this->{$method}($value);
} else {
@ -194,10 +223,13 @@ class Menu implements RecursiveIterator
'url' => 'config/modules',
'priority' => 400
));
$section->add(t('ApplicationLog'), array(
'url' => 'list/applicationlog',
'priority' => 500
));
if (Logger::writesToFile()) {
$section->add(t('Application Log'), array(
'url' => 'list/applicationlog',
'priority' => 500
));
}
$this->add(t('Logout'), array(
'url' => 'authentication/logout',
@ -229,6 +261,30 @@ class Menu implements RecursiveIterator
return $this->id;
}
/**
* Get our ID without invalid characters
*
* @return string the ID
*/
protected function getSafeHtmlId()
{
return preg_replace('/[^a-zA-Z0-9]/', '_', $this->getId());
}
/**
* Get a unique menu item id
*
* @return string the ID
*/
public function getUniqueId()
{
if ($this->parent === null) {
return 'menuitem-' . $this->getSafeHtmlId();
} else {
return $this->parent->getUniqueId() . '-' . $this->getSafeHtmlId();
}
}
/**
* Set the title of this menu
*
@ -278,13 +334,17 @@ class Menu implements RecursiveIterator
/**
* Set the url of this menu
*
* @param string $url The url to set for this menu
* @param Url|string $url The url to set for this menu
*
* @return self
*/
public function setUrl($url)
{
$this->url = $url;
if ($url instanceof Url) {
$this->url = $url;
} else {
$this->url = Url::fromPath($url);
}
return $this;
}
@ -321,6 +381,26 @@ class Menu implements RecursiveIterator
return $this->icon;
}
/**
* Get the class that renders the current menu item
*
* @return MenuItemRenderer
*/
public function getRenderer()
{
return $this->itemRenderer;
}
/**
* Set the class that renders the current menu item
*
* @param MenuItemRenderer $renderer
*/
public function setRenderer(MenuItemRenderer $renderer)
{
$this->itemRenderer = $renderer;
}
/**
* Return whether this menu has any sub menus
*
@ -342,7 +422,7 @@ class Menu implements RecursiveIterator
public function addSubMenu($id, Zend_Config $menuConfig = null)
{
if (false === ($pos = strpos($id, '.'))) {
$subMenu = new self($id, $menuConfig);
$subMenu = new self($id, $menuConfig, $this);
$this->subMenus[$id] = $subMenu;
} else {
list($parentId, $id) = explode('.', $id, 2);
@ -609,4 +689,12 @@ class Menu implements RecursiveIterator
{
next($this->subMenus);
}
/**
* PHP 5.3 GC should not leak, but just to be on the safe side...
*/
public function __destruct()
{
$this->parent = null;
}
}

View File

@ -0,0 +1,14 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Web\Menu;
use Icinga\Web\Menu;
/**
* Renders the html content of a single menu item
*/
interface MenuItemRenderer {
public function render(Menu $menu);
}

View File

@ -0,0 +1,39 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Web\Menu;
use Icinga\Module\Monitoring\Backend;
use Icinga\Web\Menu;
use Icinga\Web\Url;
class ProblemMenuItemRenderer implements MenuItemRenderer {
public function render(Menu $menu)
{
$statusSummary = Backend::createBackend()
->select()->from(
'statusSummary',
array(
'hosts_down_unhandled',
'services_critical_unhandled'
)
)->getQuery()->fetchRow();
$unhandled = $statusSummary->hosts_down_unhandled + $statusSummary->services_critical_unhandled;
$badge = '';
if ($unhandled) {
$badge = sprintf(
'<div class="badge-container"><span class="badge badge-critical">%s</span></div>',
$unhandled
);
}
return sprintf(
'<a href="%s">%s%s %s</a>',
$menu->getUrl() ?: '#',
$menu->getIcon() ? '<img src="' . Url::fromPath($menu->getIcon()) . '" class="icon" /> ' : '',
htmlspecialchars($menu->getTitle()),
$badge
);
}
}

View File

@ -0,0 +1,37 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Web\Menu;
use Icinga\Module\Monitoring\Backend;
use Icinga\Web\Menu;
use Icinga\Web\Url;
class UnhandledHostMenuItemRenderer implements MenuItemRenderer {
public function render(Menu $menu)
{
$statusSummary = Backend::createBackend()
->select()->from(
'statusSummary',
array(
'hosts_down_unhandled'
)
)->getQuery()->fetchRow();
$badge = '';
if ($statusSummary->hosts_down_unhandled) {
$badge = sprintf(
'<div class="badge-container"><span class="badge badge-critical">%s</span></div>',
$statusSummary->hosts_down_unhandled
);
}
return sprintf(
'<a href="%s">%s%s %s</a>',
$menu->getUrl() ?: '#',
$menu->getIcon() ? '<img src="' . Url::fromPath($menu->getIcon()) . '" class="icon" /> ' : '',
htmlspecialchars($menu->getTitle()),
$badge
);
}
}

View File

@ -0,0 +1,37 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Web\Menu;
use Icinga\Module\Monitoring\Backend;
use Icinga\Web\Menu;
use Icinga\Web\Url;
class UnhandledServiceMenuItemRenderer implements MenuItemRenderer {
public function render(Menu $menu)
{
$statusSummary = Backend::createBackend()
->select()->from(
'statusSummary',
array(
'services_critical_unhandled'
)
)->getQuery()->fetchRow();
$badge = '';
if ($statusSummary->services_critical_unhandled) {
$badge = sprintf(
'<div class="badge-container"><span class="badge badge-critical">%s</span></div>',
$statusSummary->services_critical_unhandled
);
}
return sprintf(
'<a href="%s">%s%s %s</a>',
$menu->getUrl() ?: '#',
$menu->getIcon() ? '<img src="' . Url::fromPath($menu->getIcon()) . '" class="icon" /> ' : '',
htmlspecialchars($menu->getTitle()),
$badge
);
}
}

View File

@ -33,7 +33,11 @@ class MenuRenderer extends RecursiveIteratorIterator
*/
public function __construct(Menu $menu, $url = null)
{
$this->url = $url;
if ($url instanceof Url) {
$this->url = $url;
} else {
$this->url = Url::fromPath($url);
}
parent::__construct($menu, RecursiveIteratorIterator::CHILD_FIRST);
}
@ -87,9 +91,12 @@ class MenuRenderer extends RecursiveIteratorIterator
*/
public function renderChild(Menu $child)
{
if ($child->getRenderer() !== null) {
return $child->getRenderer()->render($child);
}
return sprintf(
'<a href="%s">%s%s</a>',
$child->getUrl() ? Url::fromPath($child->getUrl()) : '#',
$child->getUrl() ?: '#',
$child->getIcon() ? '<img src="' . Url::fromPath($child->getIcon()) . '" class="icon" /> ' : '',
htmlspecialchars($child->getTitle())
);
@ -111,9 +118,9 @@ class MenuRenderer extends RecursiveIteratorIterator
if ($childIsActive || ($passedActiveChild && $this->getDepth() === 0)) {
$passedActiveChild &= $this->getDepth() !== 0;
$openTag = '<li class="active">';
$openTag = '<li class="active" id="' . $child->getUniqueId() . '">';
} else {
$openTag = '<li>';
$openTag = '<li id="' . $child->getUniqueId() . '">';
}
$content = $this->renderChild($child);
$closingTag = '</li>';
@ -146,6 +153,9 @@ class MenuRenderer extends RecursiveIteratorIterator
*/
protected function isActive(Menu $child)
{
return html_entity_decode(rawurldecode($this->url)) === html_entity_decode(rawurldecode($child->getUrl()));
if (! $this->url) return false;
if (! ($childUrl = $child->getUrl())) return false;
return $this->url && $this->url->matches($childUrl);
}
}

View File

@ -45,6 +45,15 @@ class StyleSheet
self::send(true);
}
protected static function fixModuleLayoutCss($css)
{
return preg_replace(
'/(\.icinga-module\.module-[^\s]+) (#layout\.[^\s]+)/m',
'\2 \1',
$css
);
}
public static function send($minified = false)
{
$app = Icinga::app();
@ -84,7 +93,7 @@ class StyleSheet
if ($minified) {
$less->compress();
}
$out = $less->compile();
$out = self::fixModuleLayoutCss($less->compile());
$cache->store($cacheFile, $out);
echo $out;
}

View File

@ -5,6 +5,7 @@
namespace Icinga\Web;
use Icinga\Application\Icinga;
use Icinga\Cli\FakeRequest;
use Icinga\Exception\ProgrammingError;
use Icinga\Web\UrlParams;
@ -98,7 +99,12 @@ class Url
*/
protected static function getRequest()
{
return Icinga::app()->getFrontController()->getRequest();
$app = Icinga::app();
if ($app->isCli()) {
return new FakeRequest();
} else {
return $app->getFrontController()->getRequest();
}
}
/**
@ -385,6 +391,23 @@ class Url
return $this->params->shift($param, $default);
}
/**
* Whether the given URL matches this URL object
*
* This does an exact match, parameters MUST be in the same order
*
* @param Url|string $url the URL to compare against
*
* @return bool whether the URL matches
*/
public function matches($url)
{
if (! $url instanceof Url) {
$url = Url::fromPath($url);
}
return (string) $url === (string) $this;
}
/**
* Return a copy of this url without the parameter given
*
@ -407,6 +430,24 @@ class Url
return $url;
}
/**
* Return a copy of this url with the given parameter(s)
*
* The argument can be either a single query parameter name or an array of parameter names to
* remove from the query list
*
* @param string|array $param A single string or an array containing parameter names
* @param array $values an optional values array
*
* @return Url
*/
public function with($param, $values = null)
{
$url = clone($this);
$url->params->mergeValues($param, $values);
return $url;
}
public function __clone()
{
$this->params = clone $this->params;

View File

@ -181,6 +181,9 @@ class UrlParams
$this->set($k, $v);
}
} else {
if (! is_array($values)) {
$values = array($values);
}
foreach ($values as $value) {
$this->set($param, $value);
}

View File

@ -22,7 +22,7 @@ $this->addHelperFunction('url', function ($path = null, $params = null) {
$url = Url::fromPath($path);
}
if ($params !== null) {
$url->setParams($params);
$url->overwriteParams($params);
}
return $url;

View File

@ -273,7 +273,7 @@ class HistoryColorGrid extends AbstractWidget {
$sun = DateTimeFactory::create('last Sunday');
$interval = new DateInterval('P' . $weekday . 'D');
$sun->add($interval);
return $sun->format('D');
return substr($sun->format('D'), 0, 2);
}
/**

View File

@ -135,6 +135,24 @@ class Pane extends AbstractWidget
return $this;
}
/**
* Removes all or a given list of components from this pane
*
* @param array $components Optional list of component titles
* @return Pane $this
*/
public function removeComponents(array $components = null)
{
if ($components === null) {
$this->components = array();
} else {
foreach ($components as $component) {
$this->removeComponent($component);
}
}
return $this;
}
/**
* Return all components added at this pane
*

View File

@ -88,7 +88,7 @@ class FilterWidget extends AbstractWidget
$editorUrl->setParam('modifyFilter', true);
if ($this->filter->isEmpty()) {
$title = t('Filter this list');
$txt = $view->icon('create.png', $title);
$txt = $view->icon('create.png');
$remove = '';
} else {
$txt = t('Filtered');
@ -98,7 +98,7 @@ class FilterWidget extends AbstractWidget
. '" title="'
. t('Remove this filter')
. '">'
. $view->icon('remove.png', $title)
. $view->icon('remove.png')
. '</a>';
}
$filter = $this->filter->isEmpty() ? '' : ': ' . $this->filter;

View File

@ -0,0 +1,101 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Web\Widget;
use Icinga\Application\Icinga;
use Icinga\Application\Modules\Module;
use Icinga\Web\Url;
use Icinga\Web\Widget\Dashboard\Pane;
use Zend_Controller_Action_Exception as ActionError;
/**
* Class SearchDashboard display multiple search views on a single search page
*
* @package Icinga\Web\Widget
*/
class SearchDashboard extends Dashboard
{
const SEARCH_PANE = 'search';
/**
* All searchUrls provided by Modules
*
* @var array
*/
protected $searchUrls = array();
/**
* Load all available search dashlets from modules
*
* @param string $searchString
* @return Dashboard|SearchDashboard
*/
public static function search($searchString = '')
{
/** @var $dashboard SearchDashboard */
$dashboard = new static('searchDashboard');
$dashboard->loadSearchDashlets($searchString);
return $dashboard;
}
/**
* Renders the output
*
* @return string
* @throws \Zend_Controller_Action_Exception
*/
public function render()
{
if (! $this->getPane(self::SEARCH_PANE)->hasComponents()) {
throw new ActionError('Site not found', 404);
}
return parent::render();
}
/**
* Loads search dashlets
*
* @param string $searchString
*/
protected function loadSearchDashlets($searchString)
{
$pane = $this->createPane(self::SEARCH_PANE)->getPane(self::SEARCH_PANE)->setTitle(t('Search'));
$this->activate(self::SEARCH_PANE);
$manager = Icinga::app()->getModuleManager();
foreach ($manager->getLoadedModules() as $module) {
$this->addSearchDashletsFromModule($searchString, $module, $pane);
}
if ($searchString === '' && $pane->hasComponents()) {
$pane->removeComponents();
$pane->add('Ready to search', 'search/hint');
return;
}
}
/**
* Add available search dashlets to the pane
*
* @param string $searchString
* @param Module $module
* @param Pane $pane
*/
protected function addSearchDashletsFromModule($searchString, $module, $pane)
{
$searchUrls = $module->getSearchUrls();
if (! empty($searchUrls)) {
$this->searchUrls[] = $module->getSearchUrls();
foreach ($searchUrls as $search) {
$pane->addComponent(
$search->title . ': ' . $searchString,
Url::fromPath($search->url, array('q' => $searchString))
);
}
}
}
}

View File

@ -2,7 +2,7 @@
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
use Zend_Controller_Action_Exception;
use \Zend_Controller_Action_Exception;
use Icinga\Application\Icinga;
use Icinga\Module\Doc\DocController;

View File

@ -2,7 +2,7 @@
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
use Zend_Controller_Action_Exception;
use \Zend_Controller_Action_Exception;
use Icinga\Application\Icinga;
use Icinga\Module\Doc\DocController;
use Icinga\Module\Doc\Exception\DocException;

View File

@ -4,7 +4,6 @@
use Icinga\Module\Monitoring\Controller;
use Icinga\Module\Monitoring\Backend;
use Icinga\Module\Monitoring\DataView\DataView;
use Icinga\Web\Url;
use Icinga\Web\Hook;
use Icinga\Web\Widget\Tabextension\DashboardAction;
@ -50,13 +49,30 @@ class Monitoring_ListController extends Controller
}
$q = $this->getRequest()->getPost('q');
if ($q) {
list($k, $v) = preg_split('/=/', $q);
$url->addParams(array($k => $v));
return $url;
}
} else {
$q = $url->shift('q');
}
if ($q) {
list($k, $v) = preg_split('/=/', $q);
$url->addParams(array($k => $v));
return $url;
if ($q !== null) {
$action = $this->_request->getActionName();
switch($action) {
case 'services':
$this->params->remove('q')->set('service_description', '*' . $q . '*');
break;
case 'hosts':
$this->params->remove('q')->set('host_name', '*' . $q . '*');
break;
case 'hostgroups':
$this->params->remove('q')->set('hostgroup', '*' . $q . '*');
break;
case 'servicegroups':
$this->params->remove('q')->set('servicegroup', '*' . $q . '*');
break;
}
}
}
return false;
}
@ -219,6 +235,25 @@ class Monitoring_ListController extends Controller
// TODO: Workaround, paginate should be able to fetch limit from new params
$this->view->services = $query->paginate($this->params->get('limit'));
}
$this->view->stats = $this->backend->select()->from('statusSummary', array(
'services_total',
'services_ok',
'services_problem',
'services_problem_handled',
'services_problem_unhandled',
'services_critical',
'services_critical_unhandled',
'services_critical_handled',
'services_warning',
'services_warning_unhandled',
'services_warning_handled',
'services_unknown',
'services_unknown_unhandled',
'services_unknown_handled',
'services_pending',
))->getQuery()->fetchRow();
}
/**
@ -395,6 +430,9 @@ class Monitoring_ListController extends Controller
public function servicegroupsAction()
{
if ($url = $this->hasBetterUrl()) {
return $this->redirectNow($url);
}
$this->addTitleTab('servicegroups');
$this->setAutorefreshInterval(12);
$query = $this->backend->select()->from('groupsummary', array(
@ -423,6 +461,9 @@ class Monitoring_ListController extends Controller
public function hostgroupsAction()
{
if ($url = $this->hasBetterUrl()) {
return $this->redirectNow($url);
}
$this->addTitleTab('hostgroups');
$this->setAutorefreshInterval(12);
$query = $this->backend->select()->from('groupsummary', array(
@ -467,7 +508,7 @@ class Monitoring_ListController extends Controller
));
$this->setupSortControl(array(
'raw_timestamp' => 'Occurence'
'timestamp' => 'Occurence'
));
$this->applyFilters($query);
$this->view->history = $query->paginate();
@ -477,19 +518,19 @@ class Monitoring_ListController extends Controller
{
$this->addTitleTab('servicematrix');
$this->setAutorefreshInterval(15);
$dataview = $this->backend->select()->from('serviceStatus', array(
$query = $this->backend->select()->from('serviceStatus', array(
'host_name',
'service_description',
'service_state',
'service_output',
'service_handled'
));
$this->applyFilters($dataview);
$this->applyFilters($query);
$this->setupSortControl(array(
'host_name' => 'Hostname',
'service_description' => 'Service description'
));
$pivot = $dataview->pivot('service_description', 'host_name');
$pivot = $query->pivot('service_description', 'host_name');
$this->view->pivot = $pivot;
$this->view->horizontalPaginator = $pivot->paginateXAxis();
$this->view->verticalPaginator = $pivot->paginateYAxis();
@ -549,10 +590,6 @@ class Monitoring_ListController extends Controller
/**
* Apply current user's `monitoring/filter' restrictions on the given data view
*
* @param DataView $dataView
*
* @return DataView
*/
protected function applyRestrictions($query)
{

View File

@ -3,13 +3,13 @@
// {{{ICINGA_LICENSE_HEADER}}}
use Icinga\Application\Benchmark;
use Icinga\Module\Monitoring\Object\MonitoredObject;
use Icinga\Web\Hook;
use Icinga\Web\Widget\Tabs;
use Icinga\Web\Widget\Tabextension\OutputFormat;
use Icinga\Web\Widget\Tabextension\DashboardAction;
use Icinga\Module\Monitoring\Backend;
use Icinga\Module\Monitoring\Controller;
use Icinga\Module\Monitoring\Object\AbstractObject;
use Icinga\Module\Monitoring\Object\Host;
use Icinga\Module\Monitoring\Object\Service;
@ -41,13 +41,16 @@ class Monitoring_ShowController extends Controller
$this->view->object = new Service($this->params);
} else {
// TODO: Well... this could be done better
$this->view->object = AbstractObject::fromParams($this->params);
$this->view->object = MonitoredObject::fromParams($this->params);
}
if (Hook::has('ticket')) {
$this->view->tickets = Hook::first('ticket');
}
if (Hook::has('grapher')) {
$this->grapher = Hook::first('grapher');
if ($this->grapher && ! $this->grapher->hasPreviews()) {
$this->grapher = null;
}
}
$this->createTabs();
@ -64,9 +67,10 @@ class Monitoring_ShowController extends Controller
. ' on ' . $o->host_name;
$this->getTabs()->activate('service');
$o->populate();
if ($this->grapher && $this->grapher->hasPreviews($o->host_name, $o->service_description)) {
$this->view->grapherHtml = $this->grapher->getPreviewHtml($o->host_name, $o->service_description);
if ($this->grapher) {
$this->view->grapherHtml = $this->grapher->getPreviewHtml($o);
}
$this->fetchHostStats();
}
/**
@ -79,9 +83,10 @@ class Monitoring_ShowController extends Controller
$this->getTabs()->activate('host');
$this->view->title = $o->host_name;
$o->populate();
if ($this->grapher && $this->grapher->hasPreviews($o->host_name)) {
$this->view->grapherHtml = $this->grapher->getPreviewHtml($o->host_name);
if ($this->grapher) {
$this->view->grapherHtml = $this->grapher->getPreviewHtml($o);
}
$this->fetchHostStats();
}
public function historyAction()
@ -91,6 +96,7 @@ class Monitoring_ShowController extends Controller
$this->view->object->fetchEventHistory();
$this->view->history = $this->view->object->eventhistory->paginate($this->params->get('limit', 50));
$this->handleFormatRequest($this->view->object->eventhistory);
$this->fetchHostStats();
}
public function servicesAction()
@ -103,6 +109,28 @@ class Monitoring_ShowController extends Controller
'view' => 'compact',
'sort' => 'service_description',
));
$this->fetchHostStats();
}
protected function fetchHostStats()
{
$this->view->stats = $this->backend->select()->from('statusSummary', array(
'services_total',
'services_ok',
'services_problem',
'services_problem_handled',
'services_problem_unhandled',
'services_critical',
'services_critical_unhandled',
'services_critical_handled',
'services_warning',
'services_warning_unhandled',
'services_warning_handled',
'services_unknown',
'services_unknown_unhandled',
'services_unknown_handled',
'services_pending',
))->where('service_host_name', $this->params->get('host'))->getQuery()->fetchRow();
}
public function contactAction()

View File

@ -278,9 +278,8 @@ class Monitoring_TimelineController extends Controller
*/
private function getTimeFormat()
{
$globalConfig = $this->getGlobalConfiguration();
$preferences = $this->getRequest()->getUser()->getPreferences();
return $preferences->get('app.timeFormat', $globalConfig->get('timeFormat', 'g:i A'));
// TODO(mh): Missing localized format (#6077)
return 'g:i A';
}
/**
@ -290,8 +289,7 @@ class Monitoring_TimelineController extends Controller
*/
private function getDateFormat()
{
$globalConfig = $this->getGlobalConfiguration();
$preferences = $this->getRequest()->getUser()->getPreferences();
return $preferences->get('app.dateFormat', $globalConfig->get('dateFormat', 'd/m/Y'));
// TODO(mh): Missing localized format (#6077)
return 'd/m/Y';
}
}

View File

@ -4,6 +4,7 @@
namespace Icinga\Module\Monitoring\Form\Command;
use Icinga\Module\Monitoring\Object\MonitoredObject;
use Zend_Form_Element_Text;
use Zend_Validate_GreaterThan;
use Zend_Validate_Digits;
@ -12,7 +13,6 @@ use Icinga\Protocol\Commandpipe\Comment;
use Icinga\Util\DateTimeFactory;
use Icinga\Module\Monitoring\Backend;
use Icinga\Module\Monitoring\Command\ScheduleDowntimeCommand;
use Icinga\Module\Monitoring\Object\AbstractObject;
use Icinga\Module\Monitoring\Object\Service;
use Icinga\Web\Url;
@ -67,7 +67,7 @@ class ScheduleDowntimeForm extends WithChildrenCommandForm
$cfg = $this->getConfiguration();
$preferences = $this->getUserPreferences();
$object = AbstractObject::fromParams(Url::fromRequest()->getParams());
$object = MonitoredObject::fromParams(Url::fromRequest()->getParams());
$object->fetchDowntimes();
$downtimes = $object->downtimes;
/*

View File

@ -1493,7 +1493,7 @@ msgstr "UP"
#: /usr/local/src/bugfix.master/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml:23
#, php-format
msgid "Unandled services with state %s"
msgid "Unhandled services with state %s"
msgstr "Unbestätigte Services mit Status %s"
#: /usr/local/src/bugfix.master/modules/monitoring/configuration.php:29

View File

@ -2,7 +2,7 @@
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
/*use Icinga\Module\Monitoring\Object\AbstractObject;*/
/* use Icinga\Module\Monitoring\Object\MonitoredObject; */
/**
* Rendering helper for object's properties which may be either enabled or disabled
@ -26,11 +26,11 @@ class Zend_View_Helper_MonitoringFlags extends Zend_View_Helper_Abstract
/**
* Retrieve flags as array with either true or false as value
*
* @param AbstractObject $object
* @param MonitoredObject $object
*
* @return array
*/
public function monitoringFlags(/*AbstractObject*/$object)
public function monitoringFlags(/*MonitoredObject*/ $object)
{
$flags = array();
foreach (self::$flags as $column => $description) {

View File

@ -3,7 +3,7 @@
// {{{ICINGA_LICENSE_HEADER}}}
use \Zend_View_Helper_Abstract;
use Icinga\Module\Monitoring\Object\AbstractObject;
use Icinga\Module\Monitoring\Object\MonitoredObject;
class Zend_View_Helper_ResolveMacros extends Zend_View_Helper_Abstract
{
@ -22,7 +22,7 @@ class Zend_View_Helper_ResolveMacros extends Zend_View_Helper_Abstract
* Return the given string with macros being resolved
*
* @param string $input The string in which to look for macros
* @param AbstractObject|stdClass $object The host or service used to resolve macros
* @param MonitoredObject|stdClass $object The host or service used to resolve macros
*
* @return string The substituted or unchanged string
*/
@ -45,7 +45,7 @@ class Zend_View_Helper_ResolveMacros extends Zend_View_Helper_Abstract
* Resolve a macro based on the given object
*
* @param string $macro The macro to resolve
* @param AbstractObject|stdClass $object The object used to resolve the macro
* @param MonitoredObject|stdClass $object The object used to resolve the macro
*
* @return string The new value or the macro if it cannot be resolved
*/

View File

@ -85,7 +85,7 @@ $helper = $this->getHelper('CommandForm');
<br>
<?= $comment->expiration ? sprintf(
$this->translate('This comment expires on %s at %s.'),
date('d.m.y', $comment-expiration),
date('d.m.y', $comment->expiration),
date('H:i', $comment->expiration)
) : $this->translate('This comment does not expire.'); ?>
</td>
@ -111,4 +111,4 @@ $helper = $this->getHelper('CommandForm');
<?php endforeach ?>
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,80 @@
<?php
use Icinga\Web\Url;
$selfUrl = 'monitoring/list/services';
$currentUrl = Url::fromRequest()->getRelativeUrl();
?><h3 class="tinystatesummary" <?= $this->compact ? ' data-base-target="col1"' : '' ?>>
<?= $this->qlink(sprintf($this->translate('%s services:'), $this->stats->services_total), $selfUrl) ?>
<span class="state ok<?= $currentUrl === Url::fromPath($selfUrl, array('service_state' => 0))->getRelativeUrl() ? ' active' : '' ?>"><?= $this->qlink(
$this->stats->services_ok,
$selfUrl,
array('service_state' => 0),
array('title' => sprintf($this->translate('Services with state %s'), strtoupper($this->translate('ok'))))
) ?></span>
<?php
foreach (array(2 => 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $state) {
$pre = 'services_' . $state;
if ($this->stats->$pre) {
$handled = $pre . '_handled';
$unhandled = $pre . '_unhandled';
$paramsHandled = array('service_state' => $stateId, 'service_handled' => 1);
$paramsUnhandled = array('service_state' => $stateId, 'service_handled' => 0);
if ($this->stats->$unhandled) {
$compareUrl = Url::fromPath($selfUrl, $paramsUnhandled)->getRelativeUrl();
} else {
$compareUrl = Url::fromPath($selfUrl, $paramsHandled)->getRelativeUrl();
}
if ($compareUrl === $currentUrl) {
$active = ' active';
} else {
$active = '';
}
echo '<span class="state ' . $state . $active . ($this->stats->$unhandled ? '' : ' handled') . '">';
if ($this->stats->$unhandled) {
echo $this->qlink(
$this->stats->$unhandled,
$selfUrl,
$paramsUnhandled,
array('title' => sprintf($this->translate('Unhandled services with state %s'), strtoupper($this->translate($state))))
);
}
if ($this->stats->$handled) {
if (Url::fromPath($selfUrl, $paramsHandled)->getRelativeUrl() === $currentUrl) {
$active = ' active';
} else {
$active = '';
}
if ($this->stats->$unhandled) {
echo '<span class="state handled ' . $state . $active . '">';
}
echo $this->qlink(
$this->stats->$handled,
$selfUrl,
$paramsHandled,
array('title' => sprintf($this->translate('Handled services with state %s'), strtoupper($this->translate($state))))
);
if ($this->stats->$unhandled) {
echo "</span>\n";
}
}
echo "</span>\n";
}
}
?>
<?php if ($this->stats->services_pending): ?>
<span class="state pending<?= $currentUrl === Url::fromPath($selfUrl, array('service_state' => 99))->getRelativeUrl() ? ' active' : '' ?>"><?= $this->qlink(
$this->stats->services_pending,
$selfUrl,
array('service_state' => 99),
array('title' => sprintf($this->translate('Services with state %s'), strtoupper($this->translate('pending'))))
) ?></span>
<?php endif ?>
</h3>

View File

@ -81,14 +81,14 @@ if ($hosts->count() === 0) {
<tr class="state <?= $hostStateName ?><?= $host->host_handled ? ' handled' : '' ?>">
<!-- State -->
<td class="state" title="<?= $helper->getStateTitle($host, 'host') ?>">
<?php if (! $this->compact): ?>
<strong><?= ucfirst($helper->monitoringState($host, 'host')) ?></strong><br />
<?php endif ?>
<?php if ((int) $host->host_state !== 99): ?>
<?= $this->prefixedTimeSince($host->host_last_state_change, true) ?>
<?php if ($host->host_state > 0 && (int) $host->host_state_type === 0): ?>
<br />
<strong>Soft <?= $host->host_attempt ?></strong>
<?php endif ?>
<?php endif ?>
</td>
<!-- Host / Status / Output -->

View File

@ -1,15 +1,13 @@
<?php if (!$this->compact): ?>
<div class="controls">
<?= $this->tabs ?>
<div class="dontprint">
<div class="controls">
<?= $this->tabs ?>
<div class="dontprint" style="margin: 1em;">
<?= $this->translate('Sort by') ?> <?= $this->sortControl->render($this) ?>
<?= $this->selectionToolbar('single') ?>
</div>
<?= $this->widget('limiter') ?>
<?php if ($notifications->count() >= 100): ?><br /><?php endif ?>
<?= $this->paginationControl($notifications, null, null, array('preserve' => $this->preserve)) ?>
</div>
</div>
<?= $this->widget('limiter') ?>
<?= $this->paginationControl($notifications, null, null, array('preserve' => $this->preserve)) ?>
</div>
<?php endif ?>
<div class="content">
@ -47,7 +45,7 @@ foreach ($notifications as $notification):
?>
<tr class="state <?= $stateName ?>">
<td class="state"><?= $this->timeSince($notification->notification_start_time) ?></td>
<td>
<td style="font-size: 0.8em">
<?php if ($isService): ?>
<a href="<?= $href ?>"><?= $notification->service ?></a> on <?= $notification->host ?>
<?php else: ?>
@ -56,12 +54,14 @@ foreach ($notifications as $notification):
<br />
<?= $this->escape(substr(strip_tags($notification->notification_output), 0, 10000)); ?>
<br />
<?php if (!$this->contact): ?>
<small>
Sent to <a href="<?= $this->href(
'monitoring/show/contact',
array('contact' => $notification->notification_contact)
) ?>"><?= $this->escape($notification->notification_contact) ?></a>
</small>
<?php endif ?>
</td>
</tr>
<?php endforeach ?>

View File

@ -1,10 +1,13 @@
<?php
$helper = $this->getHelper('MonitoringState');
$selfUrl = 'monitoring/list/services';
if (!$this->compact): ?>
<div class="controls">
<?= $this->tabs ?>
<div style="margin: 1em;" class="dontprint">
<?= $this->render('list/components/servicesummary.phtml') ?>
<?= $this->translate('Sort by') ?> <?= $this->sortControl ?>
<?php if (! $this->filterEditor): ?>
<?= $this->filterPreview ?>
@ -21,6 +24,9 @@ if (!$this->compact): ?>
<div class="content">
<?= $this->filterEditor ?>
<?php else: ?>
<div class="content">
<?php endif ?>
<table data-base-target="_next"
class="action multiselect <?php if ($this->compact): ?> compact<?php endif ?>" style="table-layout: auto;"
@ -113,6 +119,4 @@ foreach ($services as $service):
<?php endforeach ?>
</tbody>
</table>
<?php if (!$this->compact): ?>
</div>
<?php endif ?>

View File

@ -1,11 +1,15 @@
<div class="controls">
<?php
use Icinga\Data\Filter\Filter;
?><div class="controls">
<?= $this->tabs ?>
<h1>History - Critical Events</h1>
</div>
<div class="content" data-base-target="_next">
<?php
$grid->setColor('#FC0707');
$grid->setColor('#f05060');
$data = array();
if (count($summary) === 0) {
@ -16,19 +20,20 @@ foreach ($summary as $entry) {
$value = $entry->cnt_critical;
$caption = $value . ' ' .
t('critical events on ') . $this->dateFormat()->formatDate(strtotime($day));
$filter = Filter::matchAll(
Filter::expression('timestamp', '<', strtotime($day . ' 23:59:59')),
Filter::expression('timestamp', '>', strtotime($day . ' 00:00:00')),
Filter::expression('object_type', '=', 'service'),
Filter::expression('state', '=', '2'),
Filter::matchAny(
Filter::expression('type', '=', 'hard_state'),
Filter::expression('type', '=', 'hard_state')
)
);
$data[$day] = array(
'value' => $value,
'caption' => $caption,
'url' => $this->href(
'monitoring/list/eventhistory',
array(
'timestamp<' => strtotime($day . ' 23:59:59'),
'timestamp>' => strtotime($day . ' 00:00:00'),
'object_type' => 'service',
'state' => '2',
'type' => '(hard_state|soft_state)'
)
)
'url' => $this->href('monitoring/list/eventhistory?' . $filter->toQueryString())
);
}
$grid->setData($data);

View File

@ -0,0 +1,83 @@
<?php
use Icinga\Web\Url;
$selfUrl = Url::fromPath('monitoring/show/services', array('host' => $this->object->host_name));
$currentUrl = Url::fromRequest()->without('limit')->getRelativeUrl();
?>
<h3 class="tinystatesummary" <?= $this->compact ? ' data-base-target="col1"' : '' ?>>
<?= $this->qlink(sprintf($this->translate('%s service configured:'), $this->stats->services_total), $selfUrl) ?>
<?php if ($this->stats->services_ok > 0): ?>
<span class="state ok<?= $currentUrl === $selfUrl->with('service_state', 0)->getRelativeUrl() ? ' active' : '' ?>"><?= $this->qlink(
$this->stats->services_ok,
$selfUrl,
array('service_state' => 0),
array('title' => sprintf($this->translate('Services with state %s'), strtoupper($this->translate('ok'))))
) ?></span>
<?php endif ?>
<?php
foreach (array(2 => 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $state) {
$pre = 'services_' . $state;
if ($this->stats->$pre) {
$handled = $pre . '_handled';
$unhandled = $pre . '_unhandled';
$paramsHandled = array('service_state' => $stateId, 'service_handled' => 1);
$paramsUnhandled = array('service_state' => $stateId, 'service_handled' => 0);
if ($this->stats->$unhandled) {
$compareUrl = $selfUrl->with($paramsUnhandled)->getRelativeUrl();
} else {
$compareUrl = $selfUrl->with($paramsHandled)->getRelativeUrl();
}
if ($compareUrl === $currentUrl) {
$active = ' active';
} else {
$active = '';
}
echo '<span class="state ' . $state . $active . ($this->stats->$unhandled ? '' : ' handled') . '">';
if ($this->stats->$unhandled) {
echo $this->qlink(
$this->stats->$unhandled,
$selfUrl,
$paramsUnhandled,
array('title' => sprintf($this->translate('Unhandled services with state %s'), strtoupper($this->translate($state))))
);
}
if ($this->stats->$handled) {
if ($selfUrl->with($paramsHandled)->getRelativeUrl() === $currentUrl) {
$active = ' active';
} else {
$active = '';
}
if ($this->stats->$unhandled) {
echo '<span class="state handled ' . $state . $active . '">';
}
echo $this->qlink(
$this->stats->$handled,
$selfUrl,
$paramsHandled,
array('title' => sprintf($this->translate('Handled services with state %s'), strtoupper($this->translate($state))))
);
if ($this->stats->$unhandled) {
echo "</span>\n";
}
}
echo "</span>\n";
}
}
?>
<?php if ($this->stats->services_pending): ?>
<span class="state pending<?= $currentUrl === $selfUrl->with('service_state', 99)->getRelativeUrl() ? ' active' : '' ?>"><?= $this->qlink(
$this->stats->services_pending,
$selfUrl,
array('service_state' => 99),
array('title' => sprintf($this->translate('Services with state %s'), strtoupper($this->translate('pending'))))
) ?></span>
<?php endif ?>
</h3>

View File

@ -1,69 +1,59 @@
<?php
$contactHelper = $this->getHelper('ContactFlags');
?>
<div style="margin-top: 0.5em;">
<?php if ($contact): ?>
<table style="border-spacing: 0.25em; border-collapse: separate;">
<thead>
<tr>
<th colspan="2" style="text-align: left">
<?= $this->escape($contact->contact_name) ?><span style="font-weight: normal;"> (<?=
$this->escape($contact->contact_alias)
?>)</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td><?= t('Email') ?></td>
<td><?php printf(
'<a href="mailto:%1$s">%1$s</a>',
$this->escape($contact->contact_email)
); ?></td>
</tr>
<?php if ($contact->contact_pager): ?>
<tr>
<td><?= t('Pager') ?></td>
<td><?= $this->escape($contact->contact_pager) ?></td>
</tr>
<?php endif; ?>
<tr>
<td><?= t('Flags (service)') ?></td>
<td><?= $this->escape($contactHelper->contactFlags($contact, 'service')) ?></td>
</tr>
<tr>
<td><?= t('Flags (host)') ?></td>
<td><?= $this->escape($contactHelper->contactFlags($contact, 'host')) ?></td>
</tr>
<tr>
<td><?= t('Service notification period') ?></td>
<td><?= $this->escape($contact->contact_notify_service_timeperiod) ?></td>
</tr>
<tr>
<td><?= t('Host notification period') ?></td>
<td><?= $this->escape($contact->contact_notify_host_timeperiod) ?></td>
</tr>
</tbody>
</table>
<?php $contactHelper = $this->getHelper('ContactFlags') ?>
<div class="controls">
<h1><?= $this->translate('Contact details') ?></h1>
<div class="circular" style="margin-top: 1em; margin-left: 2em; width: 120px; height: 120px; float: left; background-image: url('<?=
$this->href('static/gravatar', array('email' => $contact->contact_email))
?>')"></div>
<?php if (count($commands)): ?>
<h4><?= $this->translate('Commands'); ?>:</h4>
<ul>
<?php foreach ($commands as $command): ?>
<li><?= $command->command_name; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<h4><?= $this->translate('Notifications'); ?>:</h4>
<?php if (count($notifications)): ?>
<?= $this->render('list/notifications.phtml') ?>
<?php else: ?>
<p><?= $this->translate('No notifications for this contact'); ?></p>
<?php endif; ?>
<?php else: ?>
<?= $this->translate('No such contact'); ?>: <?= $contactName; ?>
<?php endif; ?>
<?php if (! $contact): ?>
<?= $this->translate('No such contact') ?>: <?= $contactName ?>
</div>
<?php return; endif ?>
<table class="avp" style="width: 70%">
<tbody>
<tr>
<th style="width: 20%"></th>
<td><strong><?= $this->escape($contact->contact_alias) ?></strong> (<?= $contact->contact_name ?>)</td>
</tr>
<?php if ($contact->contact_email): ?>
<tr>
<th><?= t('Email') ?></th>
<td><?= sprintf('<a href="mailto:%1$s">%1$s</a>', $this->escape($contact->contact_email)) ?></td>
</tr>
<?php endif ?>
<?php if ($contact->contact_pager): ?>
<tr>
<th><?= t('Pager') ?></th>
<td><?= $this->escape($contact->contact_pager) ?></td>
</tr>
<?php endif ?>
<tr>
<th><?= t('Hosts') ?></th>
<td><?= $this->escape($contactHelper->contactFlags($contact, 'host')) ?><br />
<?= $this->escape($contact->contact_notify_host_timeperiod) ?></td>
</tr>
<tr>
<th><?= t('Services') ?></th>
<td><?= $this->escape($contactHelper->contactFlags($contact, 'service')) ?><br />
<?= $this->escape($contact->contact_notify_service_timeperiod) ?></td>
</tr>
</tbody>
</table>
<?php /* WTF?? */ ?>
<?php if (count($commands)): ?>
<h1><?= $this->translate('Commands') ?>:</h1>
<ul>
<?php foreach ($commands as $command): ?>
<li><?= $command->command_name ?></li>
<?php endforeach ?>
</ul>
<?php endif ?>
<h1><?= $this->translate('Notifications sent to this contact') ?></h1>
</div>
<?php if (count($notifications)): ?>
<?= $this->render('list/notifications.phtml') ?>
<?php else: ?>
<div class="content"><?= $this->translate('No notifications have been sent for this contact') ?></div>
<?php endif ?>

View File

@ -11,6 +11,16 @@
</div>
<?php return; endif ?>
<?php
function contactsLink($match, $self) {
$links = array();
foreach (preg_split('/,\s/', $match[1]) as $contact) {
$links[] = $self->qlink($contact, 'monitoring/show/contact', array('contact' => $contact));
}
return '[' . implode(', ', $links) . ']';
}
?>
<table data-base-target="_next" class="action objecthistory">
<tbody>
<?php foreach ($history as $event): ?>
@ -21,51 +31,61 @@
case 'notify':
$icon = 'notification';
$title = $this->translate('Notification');
$msg = $event->output;
$stateClass = (
$isService
? strtolower($this->util()->getServiceStateName($event->state))
: strtolower($this->util()->getHostStateName($event->state))
);
$msg = preg_replace_callback(
'/^\[([^\]]+)\]/',
function($match) { return contactsLink($match, $this); },
$this->escape($event->output)
);
break;
case 'comment':
$icon = 'comment';
$title = $this->translate('Comment');
$msg = $event->output;
$msg = $this->escape($event->output);
break;
case 'comment_deleted':
$icon = 'remove';
$title = $this->translate('Comment deleted');
$msg = $event->output;
$msg = $this->escape($event->output);
break;
case 'ack':
$icon = 'acknowledgement';
$title = $this->translate('Acknowledge');
$msg = $event->output;
$msg = $this->escape($event->output);
break;
case 'ack_deleted':
$icon = 'remove';
$title = $this->translate('Ack removed');
$msg = $event->output;
$msg = $this->escape($event->output);
break;
case 'dt_comment':
$icon = 'in_downtime';
$title = $this->translate('In Downtime');
$msg = $event->output;
$msg = $this->escape($event->output);
break;
case 'dt_comment_deleted':
$icon = 'remove';
$title = $this->translate('Downtime removed');
$msg = $event->output;
$msg = $this->escape($event->output);
break;
case 'flapping':
$icon = 'flapping';
$title = $this->translate('Flapping');
$msg = $event->output;
$msg = $this->escape($event->output);
break;
case 'flapping_deleted':
$icon = 'remove';
$title = $this->translate('Flapping stopped');
$msg = $event->output;
$msg = $this->escape($event->output);
break;
case 'hard_state':
$icon = $isService ? 'service' : 'host';
$msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $event->output;
$msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $this->escape($event->output);
$stateClass = (
$isService
? strtolower($this->util()->getServiceStateName($event->state))
@ -75,7 +95,7 @@
break;
case 'soft_state':
$icon = 'softstate';
$msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $event->output;
$msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $this->escape($event->output);
$stateClass = (
$isService
? strtolower($this->util()->getServiceStateName($event->state))
@ -86,12 +106,12 @@
case 'dt_start':
$icon = 'downtime_start';
$title = $this->translate('Downtime Start');
$msg = $event->output;
$msg = $this->escape($event->output);
break;
case 'dt_end':
$icon = 'downtime_end';
$title = $this->translate('Downtime End');
$msg = $event->output;
$msg = $this->escape($event->output);
break;
}
?>
@ -106,8 +126,8 @@
$output = $this->tickets ? preg_replace_callback(
$this->tickets->getPattern(),
array($this->tickets, 'createLink'),
$this->escape($msg)
) : $this->escape($msg);
$msg
) : $msg;
?>
<?php if ($isService): ?>

View File

@ -1,7 +1,7 @@
<?php if ($object->host_name !== false): ?>
<div class="controls">
<?= $this->render('show/components/header.phtml') ?>
<h1><?= $this->translate("This host's current state") ?></h1>
<?= $this->render('show/components/hostservicesummary.phtml') ?>
</div>
<div class="content" data-base-target="_next">
<?= $this->render('show/components/output.phtml') ?>

View File

@ -1,7 +1,5 @@
<div class="controls">
<?= $this->render('show/components/header.phtml') ?>
<h1><?= $this->translate('All services configured on this host') ?></h1>
<?= $this->render('show/components/hostservicesummary.phtml') ?>
</div>
<div class="content">
<?= preg_replace('~<table data-base-target="_next"~', '<table data-base-target="_self"', $services) /* TODO: find an elegant solution for this */ ?>
</div>

View File

@ -19,18 +19,29 @@ $this->provideConfigTab('security', array(
'url' => 'config/security'
));
/*
* Available Search Urls
*/
$this->provideSearchUrl($this->translate('Hosts'), 'monitoring/list/hosts?sort=host_severity&limit=10');
$this->provideSearchUrl($this->translate('Services'), 'monitoring/list/services?sort=service_severity&limit=10');
$this->provideSearchUrl($this->translate('Hostgroups'), 'monitoring/list/hostgroups?limit=10');
$this->provideSearchUrl($this->translate('Servicegroups'), 'monitoring/list/servicegroups?limit=10');
/*
* Problems Section
*/
$section = $this->menuSection($this->translate('Problems'), array(
'icon' => 'img/icons/error.png',
'priority' => 20
'renderer' => 'ProblemMenuItemRenderer',
'icon' => 'img/icons/error.png',
'priority' => 20
));
$section->add($this->translate('Unhandled Hosts'), array(
'renderer' => 'UnhandledHostMenuItemRenderer',
'url' => 'monitoring/list/hosts?host_problem=1&host_handled=0',
'priority' => 40
));
$section->add($this->translate('Unhandled Services'), array(
'renderer' => 'UnhandledServiceMenuItemRenderer',
'url' => 'monitoring/list/services?service_problem=1&service_handled=0&sort=service_severity',
'priority' => 40
));

View File

@ -42,15 +42,15 @@ class NotificationhistoryQuery extends IdoQuery
{
switch ($this->ds->getDbType()) {
case 'mysql':
$concattedContacts = "GROUP_CONCAT(c.alias ORDER BY c.alias SEPARATOR ', ')";
$concattedContacts = "GROUP_CONCAT(co.name1 ORDER BY co.name1 SEPARATOR ', ') COLLATE latin1_general_ci";
break;
case 'pgsql':
// TODO: Find a way to order the contact alias list:
$concattedContacts = "ARRAY_TO_STRING(ARRAY_AGG(c.alias), ', ')";
$concattedContacts = "ARRAY_TO_STRING(ARRAY_AGG(co.name1), ', ')";
break;
case 'oracle':
// TODO: This is only valid for Oracle >= 11g Release 2
$concattedContacts = "LISTAGG(c.alias, ', ') WITHIN GROUP (ORDER BY c.alias)";
$concattedContacts = "LISTAGG(co.name1, ', ') WITHIN GROUP (ORDER BY co.name1)";
// Alternatives:
//
// RTRIM(XMLAGG(XMLELEMENT(e, column_name, ',').EXTRACT('//text()')),
@ -74,9 +74,13 @@ class NotificationhistoryQuery extends IdoQuery
array('cn' => $this->prefix . 'contactnotifications'),
'cn.notification_id = n.notification_id',
array()
)->joinLeft(
array('co' => $this->prefix . 'objects'),
'cn.contact_object_id = co.object_id',
array()
)->joinLeft(
array('c' => $this->prefix . 'contacts'),
'cn.contact_object_id = c.contact_object_id',
'co.object_id = c.contact_object_id',
array()
)->group('cn.notification_id');

View File

@ -6,6 +6,11 @@ namespace Icinga\Module\Monitoring\Backend\Ido\Query;
class StatehistoryQuery extends IdoQuery
{
protected $types = array(
'soft_state' => 0,
'hard_state' => 1,
);
protected $columnMap = array(
'statehistory' => array(
'raw_timestamp' => 'sh.state_time',
@ -33,6 +38,8 @@ class StatehistoryQuery extends IdoQuery
{
if ($col === 'UNIX_TIMESTAMP(sh.state_time)') {
return 'sh.state_time ' . $sign . ' ' . $this->timestampForSql($this->valueToTimestamp($expression));
} elseif ($col === $this->columnMap['statehistory']['type']) {
return 'sh.state_type ' . $sign . ' ' . $this->types[$expression];
} else {
return parent::whereToSql($col, $sign, $expression);
}

View File

@ -8,7 +8,15 @@ use Zend_Db_Select;
class StatusSummaryQuery extends IdoQuery
{
protected $subHosts;
protected $subServices;
protected $columnMap = array(
'services' => array(
'service_host_name' => 'so.name1',
'service_description' => 'so.name2',
),
'hoststatussummary' => array(
'hosts_up' => 'SUM(CASE WHEN object_type = \'host\' AND state = 0 THEN 1 ELSE 0 END)',
'hosts_up_not_checked' => 'SUM(CASE WHEN object_type = \'host\' AND state = 0 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
@ -159,10 +167,24 @@ class StatusSummaryQuery extends IdoQuery
'object_type' => '(\'service\')'
));
$union = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL);
$this->subHosts = $hosts;
$this->subServices = $services;
$this->select->from(array('statussummary' => $union), array());
$this->joinedVirtualTables = array(
'servicestatussummary' => true,
'hoststatussummary' => true
'services' => true,
'servicestatussummary' => true,
'hoststatussummary' => true
);
}
public function whereToSql($col, $sign, $expression)
{
if ($col === 'so.name1') {
$this->subServices->where('so.name1 ' . $sign . ' ?', $expression);
return '';
return 'sh.state_time ' . $sign . ' ' . $this->timestampForSql($this->valueToTimestamp($expression));
} else {
return parent::whereToSql($col, $sign, $expression);
}
}
}

View File

@ -7,7 +7,7 @@ namespace Icinga\Module\Monitoring\Object;
use Icinga\Module\Monitoring\DataView\HostStatus;
use Icinga\Data\Db\DbQuery;
class Host extends AbstractObject
class Host extends MonitoredObject
{
public $type = 'host';
public $prefix = 'host_';
@ -69,7 +69,8 @@ class Host extends AbstractObject
'host_action_url',
'host_notes_url',
'host_modified_host_attributes',
'host_problem'
'host_problem',
'process_perfdata' => 'host_process_performance_data',
))->where('host_name', $this->params->get('host'));
return $this->view->getQuery()->fetchRow();
}

View File

@ -23,7 +23,7 @@ use Icinga\Web\UrlParams;
use Icinga\Application\Config;
abstract class AbstractObject
abstract class MonitoredObject
{
public $type;
public $prefix;

View File

@ -7,7 +7,7 @@ namespace Icinga\Module\Monitoring\Object;
use Icinga\Module\Monitoring\DataView\ServiceStatus;
use Icinga\Data\Db\DbQuery;
class Service extends AbstractObject
class Service extends MonitoredObject
{
public $type = 'service';
public $prefix = 'service_';
@ -114,6 +114,7 @@ class Service extends AbstractObject
'service_flap_detection_enabled',
'service_flap_detection_enabled_changed',
'service_modified_service_attributes',
'process_perfdata' => 'service_process_performance_data',
))->where('host_name', $this->params->get('host'))
->where('service_description', $this->params->get('service'));

View File

@ -53,3 +53,80 @@ div.contacts div.contact > img {
div.contacts div.notification-periods {
margin-top: 0.5em;
}
h3.tinystatesummary {
line-height: 2em;
font-size: 1em;
font-weight: bold;
padding-left: 1em;
background-color: #555;
color: white;
border: none;
border-radius: 0.2em;
-moz-border-radius: 0.2em;
-webkit-border-radius: 0.2em;
}
h3.tinystatesummary a {
color: inherit;
text-decoration: none;
padding: 1px 3px;
}
h3.tinystatesummary a:hover {
text-decoration: underline;
}
/* State badges */
span.state {
font-size: 0.8em;
color: white;
font-weight: bold;
padding: 1px 2px;
margin-right: 5px;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border: 2px solid transparent;
}
span.state.active {
border: 2px solid white;
}
span.state span.state {
font-size: 1em;
margin: 0 -3px 0 5px;
}
span.state.ok {
background: @colorOk;
}
span.state.critical {
background: @colorCritical;
}
span.state.handled.critical {
background: @colorCriticalHandled;
}
span.state.warning {
background: @colorWarning;
}
span.state.handled.warning {
background: @colorWarningHandled;
}
span.state.unknown {
background: @colorUnknown;
}
span.state.handled.unknown {
background: @colorUnknownHandled;
}
span.state.pending {
background: @colorPending;
}

View File

@ -26,7 +26,6 @@
#menu li {
margin-left: 0.5em;
margin-right: 0.5em;
}
#menu > ul > li {
@ -159,7 +158,7 @@
}
#menu li.hover > ul a {
width: 100%;
width: 95%;
display: block;
color: white;
}

View File

@ -5,6 +5,7 @@ ul.pagination {
font-size: 0.68em;
padding: 0;
display: inline;
white-space: nowrap;
-webkit-touch-callout: none;
-webkit-user-select: none;

View File

@ -179,3 +179,68 @@ ul.tree li a.error:hover {
padding: 1.5em;
height: 80vh;
}
.badge-container {
font-size: 1em;
display: inline-block;
float: right;
margin-right: 0.6em;
}
li li .badge-container {
/*
fix margin for smaller font-size of list elements
1 = 0,8em / 0.8em
*/
margin-right: 0.75em;
}
#menu > ul > li.active > a > .badge-container {
display: none;
}
#layout.hoveredmenu .hover > a > .badge-container {
margin-right: 14.15em;
}
.badge {
position: relative;
top: -0.1em;
display: inline-block;
min-width: 1em;
padding: 3px 7px;
font-size: 0.8em;
font-weight: 700;
line-height: 1.1em;
color: white;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: 1em;
background-color: @colorInvalid;
}
li li .badge {
font-size: 0.975em;
}
.badge-critical {
background-color: @colorCritical;
}
.badge-warning {
background-color: @colorWarning;
}
.badge-ok {
background-color: @colorOk;
}
.badge-pending {
background-color: @colorPending;
}
.badge-pending {
background-color: @colorUnknown;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -82,14 +82,16 @@
return false;
}
this.utils = new Icinga.Utils(this);
this.logger = new Icinga.Logger(this);
this.timer = new Icinga.Timer(this);
this.ui = new Icinga.UI(this);
this.loader = new Icinga.Loader(this);
this.events = new Icinga.Events(this);
this.history = new Icinga.History(this);
this.timezone = new Icinga.Timezone();
this.utils = new Icinga.Utils(this);
this.logger = new Icinga.Logger(this);
this.timer = new Icinga.Timer(this);
this.ui = new Icinga.UI(this);
this.loader = new Icinga.Loader(this);
this.events = new Icinga.Events(this);
this.history = new Icinga.History(this);
this.timezone.initialize();
this.timer.initialize();
this.events.initialize();
this.history.initialize();
@ -147,6 +149,7 @@
module.destroy();
});
this.timezone.destroy();
this.timer.destroy();
this.events.destroy();
this.loader.destroy();

View File

@ -10,6 +10,8 @@
'use strict';
var mouseX, mouseY;
Icinga.Events = function (icinga) {
this.icinga = icinga;
@ -114,57 +116,37 @@
$('[title]').each(function () {
var $el = $(this);
$el.attr('title', $el.attr('title-rich') || $el.attr('title'));
// $el.attr('title', null);
$el.attr('title', $el.data('title-rich') || $el.attr('title'));
});
$('svg rect.chart-data[title]', el).tipsy({ gravity: 'e', html: true });
$('svg rect.chart-data[title]', el).tipsy({ gravity: 'se', 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
// migrate 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();
}
var title = $(this).find('.tipsy-inner').html();
var atMouse = document.elementFromPoint(mouseX, mouseY);
var nearestTip = $(atMouse)
.closest('[original-title="' + title + '"]')[0];
if (nearestTip) {
console.log ('migrating orphan...');
var tipsy = $.data(nearestTip, 'tipsy');
tipsy.$tip = $(this);
$.data(this, 'tipsy-pointee', nearestTip);
} else {
// doesn't match delete
console.log ('deleting orphan...');
$(this).remove();
}
});
},
@ -221,11 +203,9 @@
// $(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);
$(document).on('mousemove', function (event) {
mouseX = event.pageX;
mouseY = event.pageY;
});
},
@ -535,7 +515,7 @@
var isMenuLink = $a.closest('#menu').length > 0;
var formerUrl;
var remote = /^(?:[a-z]+:)\/\//;
if (href.match(/^javascript:/)) {
if (href.match(/^(mailto|javascript):/)) {
return true;
}

View File

@ -0,0 +1,117 @@
(function(Icinga, $) {
'use strict';
/**
* Get the maximum timezone offset
*
* @returns {Number}
*/
Date.prototype.getStdTimezoneOffset = function() {
if (Date.maxTimezoneOffset !== undefined) {
return Date.maxTimezoneOffset;
}
var year = new Date().getYear();
var previousOffset;
for (var i=0; i<12; i++) {
var d = new Date(year, i, 1);
if (previousOffset !== undefined) {
previousOffset = Math.max(previousOffset, d.getTimezoneOffset());
} else {
previousOffset = d.getTimezoneOffset();
}
}
Date.maxTimezoneOffset = previousOffset;
return Date.maxTimezoneOffset;
};
/**
* Test for daylight saving time zone
*
* @returns {boolean}
*/
Date.prototype.isDst = function() {
return (this.getStdTimezoneOffset() === this.getTimezoneOffset()) ? false : true;
};
/**
* Write timezone information into a cookie
*
* @constructor
*/
Icinga.Timezone = function() {
this.cookieName = 'icingaweb2-tzo';
};
Icinga.Timezone.prototype = {
/**
* Initialize interface method
*/
initialize: function () {
this.writeTimezone();
},
destroy: function() {
// PASS
},
/**
* Write timezone information into cookie
*/
writeTimezone: function() {
var date = new Date();
var timezoneOffset = (date.getTimezoneOffset()*60) * -1;
var dst = date.isDst();
if (this.readCookie(this.cookieName)) {
return;
}
this.writeCookie(this.cookieName, timezoneOffset + ',' + Number(dst), 1);
},
/**
* Write cookie data
*
* @param {String} name
* @param {String} value
* @param {Number} days
*/
writeCookie: function(name, value, days) {
var expires = '';
if (days) {
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = '; expires=' + date.toGMTString();
}
document.cookie = name + '=' + value + expires + '; path=/';
},
/**
* Read cookie data
*
* @param {String} name
* @returns {*}
*/
readCookie: function(name) {
var nameEq = name + '=';
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') {
c = c.substring(1,c.length);
}
if (c.indexOf(nameEq) == 0) {
return c.substring(nameEq.length,c.length);
}
}
return null;
}
};
})(Icinga, jQuery);

View File

@ -25,9 +25,27 @@ This file contains information about how to acquire and install the source files
https://github.com/jaz303/tipsy.git
# installation
# apply hotfix (firefox SVG bound calculation)
--- jquery.tipsy.js
+++ jquery.tipsy.js
@@ -34,8 +34,8 @@
$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
+ width: this.$element[0].offsetWidth || this.$element[0].getBoundingClientRect().width,
+ height: this.$element[0].offsetHeight || this.$element[0].getBoundingClientRect().height
});
var actualWidth = $tip[0].offsetWidth,
# 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
uglifyjs src/javascript/jquery.tipsy.js ICINGAWEB/public/js/vendor/jquery.tipsy.min.js

View File

@ -34,8 +34,8 @@
$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
width: this.$element[0].offsetWidth || this.$element[0].getBoundingClientRect().width,
height: this.$element[0].offsetHeight || this.$element[0].getBoundingClientRect().height
});
var actualWidth = $tip[0].offsetWidth,

File diff suppressed because one or more lines are too long

View File

@ -1,3 +0,0 @@
[global]
dateFormat = "d-m-y"
timeFormat = "G:i a"

View File

@ -7,24 +7,14 @@ namespace Tests\Icinga\Views\Helper;
use Mockery;
use Zend_View_Helper_DateFormat;
use Icinga\Test\BaseTestCase;
use Icinga\Application\Config;
use Icinga\Util\DateTimeFactory;
require_once BaseTestCase::$appDir . '/views/helpers/DateFormat.php';
class DateFormatTest extends BaseTestCase
{
public function setUp()
{
parent::setUp();
$this->oldConfigDir = Config::$configDir;
Config::$configDir = dirname(__FILE__) . '/DateFormatTest';
}
public function tearDown()
{
parent::tearDown();
Config::$configDir = $this->oldConfigDir;
DateTimeFactory::setConfig(array('timezone' => date_default_timezone_get()));
}
@ -145,21 +135,6 @@ class DateFormatTest extends BaseTestCase
protected function getRequestMock($dateFormat = null, $timeFormat = null)
{
$mock = Mockery::mock('\Zend_Controller_Request_Abstract');
$mock->shouldReceive('getUser->getPreferences->get')
->with(Mockery::type('string'), Mockery::type('string'))
->andReturnUsing(
function ($ident, $default) use ($dateFormat, $timeFormat) {
if ($dateFormat !== null && $ident === 'app.dateFormat') {
return $dateFormat;
} elseif ($timeFormat !== null && $ident === 'app.timeFormat') {
return $timeFormat;
} else {
return $default;
}
}
);
return $mock;
return Mockery::mock('\Zend_Controller_Request_Abstract');
}
}

View File

@ -27,7 +27,7 @@ class GraphChartTest extends BaseTestCase
$xpath = new DOMXPath($doc);
$xpath->registerNamespace('x', 'http://www.w3.org/2000/svg');
$path = $xpath->query('//x:rect[@data-icinga-graph-type="bar"]');
$this->assertEquals(3, $path->length, 'Assert the correct number of datapoints being drawn as SVG bars');
$this->assertEquals(6, $path->length, 'Assert the correct number of datapoints being drawn as SVG bars');
}
public function testLineChartCreation()
@ -48,4 +48,4 @@ class GraphChartTest extends BaseTestCase
$path = $xpath->query('//x:path[@data-icinga-graph-type="line"]');
$this->assertEquals(1, $path->length, 'Assert the correct number of datapoints being drawn as SVG lines');
}
}
}

View File

@ -26,9 +26,15 @@ class TranslatorTest extends BaseTestCase
public function testWhetherGetAvailableLocaleCodesReturnsAllAvailableLocaleCodes()
{
$expected = array(Translator::DEFAULT_LOCALE, 'de_DE', 'fr_FR');
$result = Translator::getAvailableLocaleCodes();
sort($expected);
sort($result);
$this->assertEquals(
array(Translator::DEFAULT_LOCALE, 'de_DE', 'fr_FR'),
Translator::getAvailableLocaleCodes(),
$expected,
$result,
'Translator::getAvailableLocaleCodes does not return all available locale codes'
);
}
@ -44,7 +50,7 @@ class TranslatorTest extends BaseTestCase
}
/**
* @expectedException \Exception
* @expectedException Icinga\Exception\IcingaException
*/
public function testWhetherSetupLocaleThrowsAnExceptionWhenGivenAnInvalidLocale()
{

View File

@ -4,7 +4,6 @@
namespace Tests\Icinga\Web\Session;
use Exception;
use Mockery;
use Icinga\Test\BaseTestCase;
use Icinga\Web\Session\SessionNamespace;
@ -66,7 +65,7 @@ class SessionNamespaceTest extends BaseTestCase
}
/**
* @expectedException Exception
* @expectedException Icinga\Exception\IcingaException
*/
public function testFailingPropertyAccess()
{
@ -88,7 +87,7 @@ class SessionNamespaceTest extends BaseTestCase
}
/**
* @expectedException Exception
* @expectedException Icinga\Exception\IcingaException
* @expectedExceptionMessage Cannot save, session not set
*/
public function testInvalidParentWrite()

View File

@ -132,6 +132,8 @@ class UrlTest extends BaseTestCase
$url->getParam('param2', 'wrongval'),
'Url::fromPath does not properly decode aliases characters in query parameter values'
);
/*
// Temporarily disabled, no [] support right now
$this->assertEquals(
array('1', '2', '3'),
$url->getParam('param3'),
@ -142,6 +144,7 @@ class UrlTest extends BaseTestCase
$url->getParam('param4'),
'Url::fromPath does not properly reassemble query parameters as associative arrays'
);
*/
}
/**
@ -152,7 +155,7 @@ class UrlTest extends BaseTestCase
$url = Url::fromPath('/my/test/url.html?param=val&param2=val2');
$this->assertEquals(
'/my/test/url.html?param=val&amp;param2=val2',
'/my/test/url.html?param=val&param2=val2',
$url->getAbsoluteUrl(),
'Url::getAbsoluteUrl does not return the absolute url'
);
@ -166,7 +169,7 @@ class UrlTest extends BaseTestCase
$url = Url::fromPath('/my/test/url.html?param=val&param2=val2');
$this->assertEquals(
'my/test/url.html?param=val&amp;param2=val2',
'my/test/url.html?param=val&param2=val2',
$url->getRelativeUrl(),
'Url::getRelativeUrl does not return the relative url'
);
@ -251,8 +254,8 @@ class UrlTest extends BaseTestCase
$this->assertNotSame($url, $url2, 'Url::getUrlWithout does not return a new copy of the url');
$this->assertEquals(
array('param3' => 'val3'),
$url2->getParams(),
array(array('param3', 'val3')),
$url2->getParams()->toArray(),
'Url::getUrlWithout does not remove a given set of parameters from the url'
);
}
@ -271,9 +274,14 @@ class UrlTest extends BaseTestCase
'Url::addParams does not add new parameters'
);
$this->assertEquals(
'val3',
'newval3',
$url->getParam('param3', 'wrongval'),
'Url::addParams overwrites existing parameters'
'Url::addParams does not overwrite existing existing parameters'
);
$this->assertEquals(
array('val3', 'newval3'),
$url->getParams()->getValues('param3'),
'Url::addParams does not overwrite existing existing parameters'
);
}
@ -297,15 +305,35 @@ class UrlTest extends BaseTestCase
);
}
public function testWhetherEqualUrlMaches()
{
$url1 = '/whatever/is/here?a=b&c=d';
$url2 = Url::fromPath('whatever/is/here', array('a' => 'b', 'c' => 'd'));
$this->assertEquals(
true,
$url2->matches($url1)
);
}
public function testWhetherDifferentUrlDoesNotMatch()
{
$url1 = '/whatever/is/here?a=b&d=d';
$url2 = Url::fromPath('whatever/is/here', array('a' => 'b', 'c' => 'd'));
$this->assertEquals(
false,
$url2->matches($url1)
);
}
/**
* @depends testWhetherGetAbsoluteUrlReturnsTheAbsoluteUrl
* @depends testWhetherGetAbsoluteUrlReturnsTheAbsoluteUrlForHtmlAttributes
*/
public function testWhetherToStringConversionReturnsTheAbsoluteUrl()
public function testWhetherToStringConversionReturnsTheAbsoluteUrlForHtmlAttribures()
{
$url = Url::fromPath('/my/test/url.html?param=val&param2=val2&param3=val3');
$this->assertEquals(
$url->getAbsoluteUrl(),
'my/test/url.html?param=val&amp;param2=val2&amp;param3=val3',
(string) $url,
'Converting a url to string does not return the absolute url'
);

View File

@ -0,0 +1,73 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Tests\Icinga\Web;
use Mockery;
use Icinga\Application\Icinga;
use Icinga\Web\Widget\SearchDashboard;
use Icinga\Test\BaseTestCase;
class SearchDashboardTest extends BaseTestCase
{
public function tearDown()
{
parent::tearDown();
Mockery::close();
}
protected function setupIcingaMock(\Zend_Controller_Request_Abstract $request)
{
$moduleMock = Mockery::mock('Icinga\Application\Modules\Module');
$searchUrl = (object) array(
'title' => 'Hosts',
'url' => 'monitoring/list/hosts?sort=host_severity&limit=10'
);
$moduleMock->shouldReceive('getSearchUrls')->andReturn(array(
$searchUrl
));
$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);
}
/**
* @expectedException Zend_Controller_Action_Exception
*/
public function testWhetherRenderThrowsAnExceptionWhenHasNoComponents()
{
$dashboard = SearchDashboard::search('pending');
$dashboard->getPane('search')->removeComponents();
$dashboard->render();
}
public function testWhetherSearchLoadsSearchDashletsFromModules()
{
$dashboard = SearchDashboard::search('pending');
$result = $dashboard->getPane('search')->hasComponent('Hosts: pending');
$this->assertTrue($result, 'Dashboard::search() could not load search dashlets from modules');
}
public function testWhetherSearchProvidesHintWhenSearchStringIsEmpty()
{
$dashboard = SearchDashboard::search();
$result = $dashboard->getPane('search')->hasComponent('Ready to search');
$this->assertTrue($result, 'Dashboard::search() could not get hint for search');
}
}