mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-04-08 17:15:08 +02:00
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:
commit
0e7ca591ec
2
.gitignore
vendored
2
.gitignore
vendored
@ -11,6 +11,8 @@
|
||||
|
||||
build/
|
||||
|
||||
development/
|
||||
|
||||
# ./configure output
|
||||
config.log
|
||||
autom4te.cache
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div class="controls">
|
||||
<?= $this->tabs ?>
|
||||
<?= $this->dashboard->getTabs() ?>
|
||||
</div>
|
||||
|
||||
<div class="content dashboard">
|
||||
|
@ -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
0
doc/api/.gitkeep
Normal 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
|
||||
*
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
*
|
||||
|
82
library/Icinga/Protocol/File/FileIterator.php
Normal file
82
library/Icinga/Protocol/File/FileIterator.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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)
|
||||
{
|
@ -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])) {
|
62
library/Icinga/Util/Enumerate.php
Normal file
62
library/Icinga/Util/Enumerate.php
Normal 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;
|
||||
}
|
||||
}
|
108
library/Icinga/Util/TimezoneDetect.php
Normal file
108
library/Icinga/Util/TimezoneDetect.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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',
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
14
library/Icinga/Web/Menu/MenuItemRenderer.php
Normal file
14
library/Icinga/Web/Menu/MenuItemRenderer.php
Normal 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);
|
||||
}
|
39
library/Icinga/Web/Menu/ProblemMenuItemRenderer.php
Normal file
39
library/Icinga/Web/Menu/ProblemMenuItemRenderer.php
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
37
library/Icinga/Web/Menu/UnhandledHostMenuItemRenderer.php
Normal file
37
library/Icinga/Web/Menu/UnhandledHostMenuItemRenderer.php
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
37
library/Icinga/Web/Menu/UnhandledServiceMenuItemRenderer.php
Normal file
37
library/Icinga/Web/Menu/UnhandledServiceMenuItemRenderer.php
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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;
|
||||
|
101
library/Icinga/Web/Widget/SearchDashboard.php
Normal file
101
library/Icinga/Web/Widget/SearchDashboard.php
Normal 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))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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()
|
||||
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
/*
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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 -->
|
||||
|
@ -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 ?>
|
||||
|
@ -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 ?>
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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 ?>
|
||||
|
@ -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): ?>
|
||||
|
@ -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') ?>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
));
|
||||
|
@ -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');
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ use Icinga\Web\UrlParams;
|
||||
use Icinga\Application\Config;
|
||||
|
||||
|
||||
abstract class AbstractObject
|
||||
abstract class MonitoredObject
|
||||
{
|
||||
public $type;
|
||||
public $prefix;
|
@ -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'));
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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 |
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
117
public/js/icinga/timezone.js
Normal file
117
public/js/icinga/timezone.js
Normal 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);
|
22
public/js/vendor/SOURCE.jquery.tipsy
vendored
22
public/js/vendor/SOURCE.jquery.tipsy
vendored
@ -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
|
||||
|
||||
|
||||
|
4
public/js/vendor/jquery.tipsy.js
vendored
4
public/js/vendor/jquery.tipsy.js
vendored
@ -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,
|
||||
|
8
public/js/vendor/jquery.tipsy.min.js
vendored
8
public/js/vendor/jquery.tipsy.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,3 +0,0 @@
|
||||
[global]
|
||||
dateFormat = "d-m-y"
|
||||
timeFormat = "G:i a"
|
@ -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');
|
||||
}
|
||||
}
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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()
|
||||
|
@ -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¶m2=val2');
|
||||
|
||||
$this->assertEquals(
|
||||
'/my/test/url.html?param=val&param2=val2',
|
||||
'/my/test/url.html?param=val¶m2=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¶m2=val2');
|
||||
|
||||
$this->assertEquals(
|
||||
'my/test/url.html?param=val&param2=val2',
|
||||
'my/test/url.html?param=val¶m2=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¶m2=val2¶m3=val3');
|
||||
|
||||
$this->assertEquals(
|
||||
$url->getAbsoluteUrl(),
|
||||
'my/test/url.html?param=val&param2=val2&param3=val3',
|
||||
(string) $url,
|
||||
'Converting a url to string does not return the absolute url'
|
||||
);
|
||||
|
73
test/php/library/Icinga/Web/Widget/SearchDashboardTest.php
Normal file
73
test/php/library/Icinga/Web/Widget/SearchDashboardTest.php
Normal 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');
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user