Merge branch 'master' into bugfix/rebuild-form-builder-5525
Conflicts: application/forms/Preference/GeneralForm.php
This commit is contained in:
commit
ae9e5a40cc
|
@ -4,6 +4,7 @@
|
|||
|
||||
// namespace Icinga\Application\Controllers;
|
||||
|
||||
use Icinga\Logger\Logger;
|
||||
use Icinga\Web\Controller\ActionController;
|
||||
use Icinga\Application\Icinga;
|
||||
|
||||
|
@ -21,6 +22,10 @@ class ErrorController extends ActionController
|
|||
{
|
||||
$error = $this->_getParam('error_handler');
|
||||
$exception = $error->exception;
|
||||
|
||||
Logger::error($exception);
|
||||
Logger::error('Stacktrace: %s', $exception->getTraceAsString());
|
||||
|
||||
switch ($error->type) {
|
||||
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
|
||||
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
|
||||
|
|
|
@ -4,9 +4,12 @@
|
|||
|
||||
use Icinga\Module\Monitoring\Controller;
|
||||
use Icinga\Web\Hook;
|
||||
use Icinga\Application\Config as IcingaConfig;
|
||||
use Icinga\Web\Url;
|
||||
use Icinga\Data\ResourceFactory;
|
||||
use Icinga\Logger\Logger;
|
||||
use Icinga\Logger\Writer\FileWriter;
|
||||
use Icinga\Protocol\File\FileReader;
|
||||
use \Zend_Controller_Action_Exception as ActionError;
|
||||
|
||||
/**
|
||||
* Class ListController
|
||||
|
@ -36,19 +39,21 @@ class ListController extends Controller
|
|||
*/
|
||||
public function applicationlogAction()
|
||||
{
|
||||
$this->addTitleTab('application log');
|
||||
$config_ini = IcingaConfig::app()->toArray();
|
||||
if (!in_array('logging', $config_ini) || (
|
||||
in_array('type', $config_ini['logging']) &&
|
||||
$config_ini['logging']['type'] === 'file' &&
|
||||
in_array('target', $config_ini['logging']) &&
|
||||
file_exists($config_ini['logging']['target'])
|
||||
)
|
||||
) {
|
||||
$resource = ResourceFactory::create('logfile');
|
||||
$this->view->logData = $resource->select()->order('DESC')->paginate();
|
||||
} else {
|
||||
$this->view->logData = null;
|
||||
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();
|
||||
$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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
use Zend_Controller_Action_Exception as ActionException;
|
||||
use Icinga\Web\Controller\ActionController;
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Logger\Logger;
|
||||
use Icinga\Web\FileCache;
|
||||
use Zend_Controller_Action_Exception as ActionException;
|
||||
|
||||
/**
|
||||
* Delivery static content to clients
|
||||
|
@ -30,8 +31,25 @@ class StaticController extends ActionController
|
|||
|
||||
public function gravatarAction()
|
||||
{
|
||||
$cache = FileCache::instance();
|
||||
$filename = md5(strtolower(trim($this->_request->getParam('email'))));
|
||||
$cacheFile = 'gravatar-' . $filename;
|
||||
header('Cache-Control: public');
|
||||
header('Pragma: cache');
|
||||
if ($etag = $cache->etagMatchesCachedFile($cacheFile)) {
|
||||
header("HTTP/1.1 304 Not Modified");
|
||||
return;
|
||||
}
|
||||
|
||||
header('Content-Type: image/jpg');
|
||||
$img = file_get_contents('http://www.gravatar.com/avatar/' . md5(strtolower(trim($this->_request->getParam('email')))) . '?s=200&d=mm');
|
||||
if ($cache->has($cacheFile)) {
|
||||
header('ETag: "' . $cache->etagForCachedFile($cacheFile) . '"');
|
||||
$cache->send($cacheFile);
|
||||
return;
|
||||
}
|
||||
$img = file_get_contents('http://www.gravatar.com/avatar/' . $filename . '?s=120&d=mm');
|
||||
$cache->store($cacheFile, $img);
|
||||
header('ETag: "' . $cache->etagForCachedFile($cacheFile) . '"');
|
||||
echo $img;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ use Icinga\Web\Request;
|
|||
use Icinga\Web\Session;
|
||||
use Icinga\Web\Notification;
|
||||
use Icinga\Util\Translator;
|
||||
use Icinga\Util\TimezoneDetect;
|
||||
use Icinga\User\Preferences;
|
||||
use Icinga\User\Preferences\PreferencesStore;
|
||||
|
||||
|
@ -189,7 +190,7 @@ class PreferenceForm extends Form
|
|||
'label' => t('Your Current Timezone'),
|
||||
'description' => t('Use the following timezone for dates and times'),
|
||||
'multiOptions' => $tzList,
|
||||
'value' => date_default_timezone_get()
|
||||
'value' => $this->getDefaultTimezone()
|
||||
)
|
||||
);
|
||||
if ($useLocalTimezone) {
|
||||
|
@ -208,4 +209,19 @@ class PreferenceForm extends Form
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current default timezone
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultTimezone()
|
||||
{
|
||||
$detect = new TimezoneDetect();
|
||||
if ($detect->success()) {
|
||||
return $detect->getTimezoneName();
|
||||
} else {
|
||||
return date_default_timezone_get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
use Icinga\Web\Form\Validator\DateTimeValidator;
|
||||
|
||||
|
@ -109,10 +108,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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -122,10 +119,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
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
</div>
|
||||
|
||||
<div class="content">
|
||||
<?php if ($this->logData !== null): ?>
|
||||
<table class="action">
|
||||
<tbody>
|
||||
<?php foreach ($this->logData as $value): ?>
|
||||
|
@ -21,4 +22,5 @@
|
|||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -238,6 +238,77 @@ the labels to show you can use the 'disableLegend()' call on the GridChart objec
|
|||
|
||||
![Various Line Graph Options][graph7]
|
||||
|
||||
|
||||
### Tooltips
|
||||
|
||||
It is possible to specify custom tooltip format strings when creating bar charts.
|
||||
Tooltips provide information about the points of each bar chart column, by aggregating
|
||||
the values of all data sets with the same x-coordinate.
|
||||
|
||||
When no custom format string is given, a sane default format string is used, but its usually
|
||||
clearer for the user to describe the data of each chart more accurately with a custom one.
|
||||
|
||||
|
||||
**Example #9.1: Bar Charts with custom tooltips**
|
||||
|
||||
$this->chart->drawBars(
|
||||
array(
|
||||
'label' => 'Hosts critical',
|
||||
'palette' => Palette::PROBLEM,
|
||||
'stack' => 'stack1',
|
||||
'data' => $data2,
|
||||
'tooltip' => '{title}<br/> {value} of {sum} hosts are ok.'
|
||||
),
|
||||
array(
|
||||
'label' => 'Hosts warning',
|
||||
'stack' => 'stack1',
|
||||
'palette' => Palette::WARNING,
|
||||
'data' => $data,
|
||||
'tooltip' => '{title}<br/> Oh no, {value} of {sum} hosts are down!'
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
As you can see, you can specify a format string for each data set, which allows you to
|
||||
pass a custom message for all "down" hosts, one custom message for all "Ok" hosts and so on.
|
||||
In contrast to that, the aggregation of values works on a column basis and will give you the
|
||||
sum of all y-values with the same x-coordinate and not the aggregation of all values of the data set.
|
||||
|
||||
#### Rich Tooltips
|
||||
|
||||
It is also possible to use HTML in the tooltip strings to create rich tooltip markups, which can
|
||||
be useful to provide extended output that spans over multiple lines. Please keep in mind that
|
||||
users without JavaScript will see the tooltip with all of its html-tags stripped.
|
||||
|
||||
![Various Line Graph Options][graph7.1]
|
||||
|
||||
#### Available replacements
|
||||
|
||||
The available replacements depend on the used chart type, since the tooltip data is
|
||||
instantiated and populated by the chart. All bar graphs have the following replacements available:
|
||||
|
||||
Aggregated values, are calculated from the data points of each column:
|
||||
|
||||
- sum: The amount of all Y-values of the current column
|
||||
- max: The biggest occurring Y-value of the current column
|
||||
- min: The smallest occurring Y-value of the current column
|
||||
|
||||
|
||||
Column values are also defined by the current column, but are not
|
||||
the product of any aggregation
|
||||
|
||||
- title: The x-value of the current column
|
||||
|
||||
|
||||
Row values are defined by the properties the current data set, and are only useful for rendering the
|
||||
generic tooltip correctly, since you could also just directly write
|
||||
those values into your custom tooltip.
|
||||
|
||||
- label: The name of the current data set
|
||||
- color: The color of this data set
|
||||
|
||||
|
||||
|
||||
## Pie Charts
|
||||
|
||||
### The PieChart Object
|
||||
|
@ -317,5 +388,6 @@ Rendering is straightforward, assuming $svg is the PieChart/GridChart object, yo
|
|||
[graph5]: res/GraphExample#5.png
|
||||
[graph6]: res/GraphExample#6.png
|
||||
[graph7]: res/GraphExample#7.png
|
||||
[graph7.1]: res/GraphExample#7.1.png
|
||||
[graph8]: res/GraphExample#8.png
|
||||
[graph9]: res/GraphExample#9.png
|
||||
[graph9]: res/GraphExample#9.png
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 7.3 KiB |
|
@ -452,9 +452,8 @@ abstract class ApplicationBootstrap
|
|||
*/
|
||||
protected function setupInternationalization()
|
||||
{
|
||||
$localeDir = $this->getApplicationDir('locale');
|
||||
if (file_exists($localeDir) && is_dir($localeDir)) {
|
||||
Translator::registerDomain(Translator::DEFAULT_DOMAIN, $localeDir);
|
||||
if ($this->hasLocales()) {
|
||||
Translator::registerDomain(Translator::DEFAULT_DOMAIN, $this->getLocaleDir());
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -469,4 +468,48 @@ abstract class ApplicationBootstrap
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Our locale directory
|
||||
*/
|
||||
public function getLocaleDir()
|
||||
{
|
||||
return $this->getApplicationDir('locale');
|
||||
}
|
||||
|
||||
/**
|
||||
* return bool Whether Icinga Web has translations
|
||||
*/
|
||||
public function hasLocales()
|
||||
{
|
||||
$localedir = $this->getLocaleDir();
|
||||
return file_exists($localedir) && is_dir($localedir);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all available locales
|
||||
*
|
||||
* NOTE: Might be a candidate for a static function in Translator
|
||||
*
|
||||
* return array Locale list
|
||||
*/
|
||||
public function listLocales()
|
||||
{
|
||||
$locales = array();
|
||||
if (! $this->hasLocales()) {
|
||||
return $locales;
|
||||
}
|
||||
$localedir = $this->getLocaleDir();
|
||||
|
||||
$dh = opendir($localedir);
|
||||
while (false !== ($file = readdir($dh))) {
|
||||
$filename = $localedir . DIRECTORY_SEPARATOR . $file;
|
||||
if (preg_match('/^[a-z]{2}_[A-Z]{2}$/', $file) && is_dir($filename)) {
|
||||
$locales[] = $file;
|
||||
}
|
||||
}
|
||||
closedir($dh);
|
||||
sort($locales);
|
||||
return $locales;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,6 +82,18 @@ class Config extends Zend_Config
|
|||
return self::$app[$configname];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set module config
|
||||
*
|
||||
* @param string $moduleName
|
||||
* @param string $configName
|
||||
* @param Zend_Config $config
|
||||
*/
|
||||
public static function setModuleConfig($moduleName, $configName, Zend_Config $config)
|
||||
{
|
||||
self::$modules[$moduleName][$configName] = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a module config instance
|
||||
*
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
@ -177,7 +202,6 @@ class Module
|
|||
/**
|
||||
* Add a pane to dashboard
|
||||
*
|
||||
* @param $id
|
||||
* @param $name
|
||||
* @return Pane
|
||||
*/
|
||||
|
@ -201,21 +225,19 @@ class Module
|
|||
/**
|
||||
* Add a menu Section to the Sidebar menu
|
||||
*
|
||||
* @param string $id
|
||||
* @param string $name
|
||||
* @param $name
|
||||
* @param array $properties
|
||||
* @return mixed
|
||||
*/
|
||||
protected function menuSection($id, $name, array $properties = array())
|
||||
protected function menuSection($name, array $properties = array())
|
||||
{
|
||||
if (array_key_exists($id, $this->menuItems)) {
|
||||
$this->menuItems[$id]->setProperties($properties);
|
||||
if (array_key_exists($name, $this->menuItems)) {
|
||||
$this->menuItems[$name]->setProperties($properties);
|
||||
} else {
|
||||
$this->menuItems[$id] = new Menu($id, new Zend_Config($properties));
|
||||
$this->menuItems[$id]->setTitle($name);
|
||||
$this->menuItems[$name] = new Menu($name, new Zend_Config($properties));
|
||||
}
|
||||
|
||||
return $this->menuItems[$id];
|
||||
return $this->menuItems[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -711,12 +733,44 @@ class Module
|
|||
*/
|
||||
protected function registerLocales()
|
||||
{
|
||||
if (file_exists($this->localedir) && is_dir($this->localedir)) {
|
||||
if ($this->hasLocales()) {
|
||||
Translator::registerDomain($this->name, $this->localedir);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* return bool Whether this module has translations
|
||||
*/
|
||||
public function hasLocales()
|
||||
{
|
||||
return file_exists($this->localedir) && is_dir($this->localedir);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all available locales
|
||||
*
|
||||
* return array Locale list
|
||||
*/
|
||||
public function listLocales()
|
||||
{
|
||||
$locales = array();
|
||||
if (! $this->hasLocales()) {
|
||||
return $locales;
|
||||
}
|
||||
|
||||
$dh = opendir($this->localedir);
|
||||
while (false !== ($file = readdir($dh))) {
|
||||
$filename = $this->localedir . DIRECTORY_SEPARATOR . $file;
|
||||
if (preg_match('/^[a-z]{2}_[A-Z]{2}$/', $file) && is_dir($filename)) {
|
||||
$locales[] = $file;
|
||||
}
|
||||
}
|
||||
closedir($dh);
|
||||
sort($locales);
|
||||
return $locales;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register web integration
|
||||
*
|
||||
|
|
|
@ -10,11 +10,10 @@ 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;
|
||||
use Icinga\Web\Session\Session as BaseSession;
|
||||
use Icinga\Web\Session;
|
||||
use Icinga\User;
|
||||
use Icinga\Util\Translator;
|
||||
use Icinga\Util\DateTimeFactory;
|
||||
|
@ -59,13 +58,6 @@ class Web extends ApplicationBootstrap
|
|||
*/
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* Session object
|
||||
*
|
||||
* @var BaseSession
|
||||
*/
|
||||
private $session;
|
||||
|
||||
/**
|
||||
* User object
|
||||
*
|
||||
|
@ -92,7 +84,6 @@ class Web extends ApplicationBootstrap
|
|||
->setupErrorHandling()
|
||||
->loadConfig()
|
||||
->setupResourceFactory()
|
||||
->setupSession()
|
||||
->setupUser()
|
||||
->setupTimezone()
|
||||
->setupLogger()
|
||||
|
@ -172,7 +163,6 @@ class Web extends ApplicationBootstrap
|
|||
|
||||
$this->setupFrontController();
|
||||
$this->setupViewRenderer();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -192,17 +182,6 @@ class Web extends ApplicationBootstrap
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a session provider
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function setupSession()
|
||||
{
|
||||
$this->session = Session::create();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject dependencies into request
|
||||
*
|
||||
|
@ -295,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 {
|
||||
|
|
|
@ -16,6 +16,13 @@ use Icinga\Chart\Render\RenderContext;
|
|||
*/
|
||||
class BarGraph extends Styleable implements Drawable
|
||||
{
|
||||
/**
|
||||
* The dataset order
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $order = 0;
|
||||
|
||||
/**
|
||||
* The width of the bars.
|
||||
*
|
||||
|
@ -30,14 +37,37 @@ class BarGraph extends Styleable implements Drawable
|
|||
*/
|
||||
private $dataSet;
|
||||
|
||||
/**
|
||||
* The tooltips
|
||||
*
|
||||
* @var
|
||||
*/
|
||||
private $tooltips;
|
||||
|
||||
/**
|
||||
* All graphs
|
||||
*
|
||||
* @var
|
||||
*/
|
||||
private $graphs;
|
||||
|
||||
/**
|
||||
* Create a new BarGraph with the given dataset
|
||||
*
|
||||
* @param array $dataSet An array of datapoints
|
||||
* @param array $dataSet An array of data points
|
||||
* @param int $order The graph number displayed by this BarGraph
|
||||
* @param array $tooltips The tooltips to display for each value
|
||||
*/
|
||||
public function __construct(array $dataSet)
|
||||
{
|
||||
public function __construct(
|
||||
array $dataSet,
|
||||
array &$graphs,
|
||||
$order,
|
||||
array $tooltips = null
|
||||
) {
|
||||
$this->order = $order;
|
||||
$this->dataSet = $dataSet;
|
||||
$this->tooltips = $tooltips;
|
||||
$this->graphs = $graphs;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,6 +86,30 @@ class BarGraph extends Styleable implements Drawable
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a single rectangle
|
||||
*
|
||||
* @param array $point The
|
||||
* @param null $index
|
||||
* @param string $fill The fill color to use
|
||||
* @param $strokeWidth
|
||||
*
|
||||
* @return Rect
|
||||
*/
|
||||
private function drawSingleBar($point, $index = null, $fill, $strokeWidth)
|
||||
{
|
||||
$rect = new Rect($point[0] - ($this->barWidth / 2), $point[1], $this->barWidth, 100 - $point[1]);
|
||||
$rect->setFill($fill);
|
||||
$rect->setStrokeWidth($strokeWidth);
|
||||
$rect->setStrokeColor('black');
|
||||
if (isset($index)) {
|
||||
$rect->setAttribute('data-icinga-graph-index', $index);
|
||||
}
|
||||
$rect->setAttribute('data-icinga-graph-type', 'bar');
|
||||
$rect->setAdditionalStyle('clip-path: url(#clip);');
|
||||
return $rect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render this BarChart
|
||||
*
|
||||
|
@ -68,23 +122,33 @@ class BarGraph extends Styleable implements Drawable
|
|||
$doc = $ctx->getDocument();
|
||||
$group = $doc->createElement('g');
|
||||
$idx = 0;
|
||||
foreach ($this->dataSet as $point) {
|
||||
$rect = new Rect($point[0] - 2, $point[1], 4, 100 - $point[1]);
|
||||
$rect->setFill($this->fill);
|
||||
$rect->setStrokeWidth($this->strokeWidth);
|
||||
$rect->setStrokeColor('black');
|
||||
$rect->setAttribute('data-icinga-graph-index', $idx++);
|
||||
$rect->setAttribute('data-icinga-graph-type', 'bar');
|
||||
$rect->setAdditionalStyle('clip-path: url(#clip);');
|
||||
/*$rect->setAnimation(
|
||||
new Animation(
|
||||
'y',
|
||||
$ctx->yToAbsolute(100),
|
||||
$ctx->yToAbsolute($point[1]),
|
||||
rand(1, 1.5)/2
|
||||
)
|
||||
);*/
|
||||
$group->appendChild($rect->toSvg($ctx));
|
||||
foreach ($this->dataSet as $x => $point) {
|
||||
// add white background bar, to prevent other bars from altering transparency effects
|
||||
$bar = $this->drawSingleBar($point, $idx++, 'white', $this->strokeWidth, $idx)->toSvg($ctx);
|
||||
$group->appendChild($bar);
|
||||
|
||||
// draw actual bar
|
||||
$bar = $this->drawSingleBar($point, null, $this->fill, $this->strokeWidth, $idx)->toSvg($ctx);
|
||||
$bar->setAttribute('class', 'chart-data');
|
||||
if (isset($this->tooltips[$x])) {
|
||||
$data = array(
|
||||
'label' => isset($this->graphs[$this->order]['label']) ?
|
||||
strtolower($this->graphs[$this->order]['label']) : '',
|
||||
'color' => isset($this->graphs[$this->order]['color']) ?
|
||||
strtolower($this->graphs[$this->order]['color']) : '#fff'
|
||||
);
|
||||
$format = isset($this->graphs[$this->order]['tooltip'])
|
||||
? $this->graphs[$this->order]['tooltip'] : null;
|
||||
$bar->setAttribute(
|
||||
'title',
|
||||
$this->tooltips[$x]->renderNoHtml($this->order, $data, $format)
|
||||
);
|
||||
$bar->setAttribute(
|
||||
'data-title-rich',
|
||||
$this->tooltips[$x]->render($this->order, $data, $format)
|
||||
);
|
||||
}
|
||||
$group->appendChild($bar);
|
||||
}
|
||||
return $group;
|
||||
}
|
||||
|
|
|
@ -41,6 +41,10 @@ class StackedGraph implements Drawable
|
|||
if (!isset($this->points[$x])) {
|
||||
$this->points[$x] = 0;
|
||||
}
|
||||
// store old y-value for displaying the actual (non-aggregated)
|
||||
// value in the tooltip
|
||||
$point[2] = $point[1];
|
||||
|
||||
$this->points[$x] += $point[1];
|
||||
$point[1] = $this->points[$x];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Chart\Graph;
|
||||
|
||||
/**
|
||||
* A tooltip that stores and aggregates information about displayed data
|
||||
* points of a graph and replaces them in a format string to render the description
|
||||
* for specific data points of the graph.
|
||||
*
|
||||
* When render() is called, placeholders for the keys for each data entry will be replaced by
|
||||
* the current value of this data set and the formatted string will be returned.
|
||||
* The content of the replaced keys can change for each data set and depends on how the data
|
||||
* is passed to this class. There are several types of properties:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Global properties</li>: Key-value pairs that stay the same every time render is called, and are
|
||||
* passed to an instance in the constructor.
|
||||
* <li>Aggregated properties</li>: Global properties that are created automatically from
|
||||
* all attached data points.
|
||||
* <li>Local properties</li>: Key-value pairs that only apply to a single data point and
|
||||
* are passed to the render-function.
|
||||
* </ul>
|
||||
*/
|
||||
class Tooltip
|
||||
{
|
||||
/**
|
||||
* The default format string used
|
||||
* when no other format is specified
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $defaultFormat;
|
||||
|
||||
/**
|
||||
* All aggregated points
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $points = array();
|
||||
|
||||
/**
|
||||
* Contains all static replacements
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $data = array(
|
||||
'sum' => 0
|
||||
);
|
||||
|
||||
/**
|
||||
* Used to format the displayed tooltip.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tooltipFormat;
|
||||
|
||||
/**
|
||||
* Create a new tooltip with the specified default format string
|
||||
*
|
||||
* Allows you to set the global data for this tooltip, that is displayed every
|
||||
* time render is called.
|
||||
*
|
||||
* @param array $data Map of global properties
|
||||
* @param string $format The default format string
|
||||
*/
|
||||
public function __construct (
|
||||
$data = array(),
|
||||
$format = '<b>{title}</b></b><br> {value} of {sum} {label}'
|
||||
) {
|
||||
$this->data = array_merge($this->data, $data);
|
||||
$this->defaultFormat = $format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single data point to update the aggregated properties for this tooltip
|
||||
*
|
||||
* @param $point array Contains the (x,y) values of the data set
|
||||
*/
|
||||
public function addDataPoint($point)
|
||||
{
|
||||
// set x-value
|
||||
if (!isset($this->data['title'])) {
|
||||
$this->data['title'] = $point[0];
|
||||
}
|
||||
|
||||
// aggregate y-values
|
||||
$y = (int)$point[1];
|
||||
if (isset($point[2])) {
|
||||
// load original value in case value already aggregated
|
||||
$y = (int)$point[2];
|
||||
}
|
||||
|
||||
if (!isset($this->data['min']) || $this->data['min'] > $y) {
|
||||
$this->data['min'] = $y;
|
||||
}
|
||||
if (!isset($this->data['max']) || $this->data['max'] < $y) {
|
||||
$this->data['max'] = $y;
|
||||
}
|
||||
$this->data['sum'] += $y;
|
||||
$this->points[] = $y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the tooltip for a certain data point
|
||||
*
|
||||
* @param array $order Which data set to render
|
||||
* @param array $data The local data for this tooltip
|
||||
* @param string $format Use a custom format string for this data set
|
||||
*
|
||||
* @return mixed|string The tooltip value
|
||||
*/
|
||||
public function render($order, $data = array(), $format = null)
|
||||
{
|
||||
if (isset($format)) {
|
||||
$str = $format;
|
||||
} else {
|
||||
$str = $this->defaultFormat;
|
||||
}
|
||||
$data['value'] = $this->points[$order];
|
||||
foreach (array_merge($this->data, $data) as $key => $value) {
|
||||
$str = str_replace('{' . $key . '}', $value, $str);
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the tooltip for a certain data point but remove all
|
||||
* occurring html tags
|
||||
*
|
||||
* This is useful for rendering clean tooltips on client without JavaScript
|
||||
*
|
||||
* @param array $order Which data set to render
|
||||
* @param array $data The local data for this tooltip
|
||||
* @param string $format Use a custom format string for this data set
|
||||
*
|
||||
* @return mixed|string The tooltip value, without any HTML tags
|
||||
*/
|
||||
public function renderNoHtml($order, $data, $format)
|
||||
{
|
||||
return strip_tags($this->render($order, $data, $format));
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ use Icinga\Chart\Axis;
|
|||
use Icinga\Chart\Graph\BarGraph;
|
||||
use Icinga\Chart\Graph\LineGraph;
|
||||
use Icinga\Chart\Graph\StackedGraph;
|
||||
use Icinga\Chart\Graph\Tooltip;
|
||||
use Icinga\Chart\Primitive\Canvas;
|
||||
use Icinga\Chart\Primitive\Rect;
|
||||
use Icinga\Chart\Primitive\Path;
|
||||
|
@ -74,6 +75,16 @@ class GridChart extends Chart
|
|||
*/
|
||||
private $stacks = array();
|
||||
|
||||
/**
|
||||
* An associative array containing all Tooltips used to render the titles
|
||||
*
|
||||
* Each tooltip represents the summary for all y-values of a certain x-value
|
||||
* in the grid chart
|
||||
*
|
||||
* @var Tooltip
|
||||
*/
|
||||
private $tooltips = array();
|
||||
|
||||
/**
|
||||
* Check if the current dataset has the proper structure for this chart.
|
||||
*
|
||||
|
@ -169,6 +180,26 @@ class GridChart extends Chart
|
|||
$this->legend->addDataset($graph);
|
||||
}
|
||||
}
|
||||
$this->initTooltips($data);
|
||||
}
|
||||
|
||||
|
||||
private function initTooltips($data)
|
||||
{
|
||||
foreach ($data as &$graph) {
|
||||
foreach ($graph['data'] as $x => $point) {
|
||||
if (!array_key_exists($x, $this->tooltips)) {
|
||||
$this->tooltips[$x] = new Tooltip(
|
||||
array(
|
||||
'color' => $graph['color'],
|
||||
|
||||
)
|
||||
|
||||
);
|
||||
}
|
||||
$this->tooltips[$x]->addDataPoint($point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -353,11 +384,16 @@ class GridChart extends Chart
|
|||
foreach ($this->graphs as $axisName => $graphs) {
|
||||
$axis = $this->axis[$axisName];
|
||||
$graphObj = null;
|
||||
foreach ($graphs as $graph) {
|
||||
foreach ($graphs as $dataset => $graph) {
|
||||
// determine the type and create a graph object for it
|
||||
switch ($graph['graphType']) {
|
||||
case self::TYPE_BAR:
|
||||
$graphObj = new BarGraph($axis->transform($graph['data']));
|
||||
$graphObj = new BarGraph(
|
||||
$axis->transform($graph['data']),
|
||||
$graphs,
|
||||
$dataset,
|
||||
$this->tooltips
|
||||
);
|
||||
break;
|
||||
case self::TYPE_LINE:
|
||||
$graphObj = new LineGraph($axis->transform($graph['data']));
|
||||
|
|
|
@ -89,6 +89,17 @@ class DbQuery extends SimpleQuery
|
|||
public function getSelectQuery()
|
||||
{
|
||||
$select = $this->dbSelect();
|
||||
|
||||
// Add order fields to select for postgres distinct queries (#6351)
|
||||
if ($this->hasOrder()
|
||||
&& $this->getDatasource()->getDbType() === 'pgsql'
|
||||
&& $select->getPart(Zend_Db_Select::DISTINCT) === true) {
|
||||
foreach ($this->getOrder() as $fieldAndDirection) {
|
||||
list($alias, $field) = explode('.', $fieldAndDirection[0]);
|
||||
$this->columns[$field] = $fieldAndDirection[0];
|
||||
}
|
||||
}
|
||||
|
||||
$select->columns($this->columns);
|
||||
$this->applyFilterSql($select);
|
||||
|
||||
|
@ -102,6 +113,7 @@ class DbQuery extends SimpleQuery
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $select;
|
||||
}
|
||||
|
||||
|
|
|
@ -132,12 +132,12 @@ abstract class Filter
|
|||
public static function expression($col, $op, $expression)
|
||||
{
|
||||
switch ($op) {
|
||||
case '=': return new FilterEqual($col, $op, $expression);
|
||||
case '=': return new FilterMatch($col, $op, $expression);
|
||||
case '<': return new FilterLessThan($col, $op, $expression);
|
||||
case '>': return new FilterGreaterThan($col, $op, $expression);
|
||||
case '>=': return new FilterEqualOrGreaterThan($col, $op, $expression);
|
||||
case '<=': return new FilterEqualOrLessThan($col, $op, $expression);
|
||||
case '!=': return new FilterNotEqual($col, $op, $expression);
|
||||
case '!=': return new FilterMatchNot($col, $op, $expression);
|
||||
default: throw new ProgrammingError(
|
||||
'There is no such filter sign: %s',
|
||||
$op
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Data\Filter;
|
||||
|
||||
class FilterMatch extends FilterExpression
|
||||
{
|
||||
public function matches($row)
|
||||
{
|
||||
$expression = (string) $this->expression;
|
||||
if (strpos($expression, '*') === false) {
|
||||
return (string) $row->{$this->column} === $expression;
|
||||
} else {
|
||||
$parts = array();
|
||||
foreach (preg_split('/\*/', $expression) as $part) {
|
||||
$parts[] = preg_quote($part);
|
||||
}
|
||||
return preg_match('/^' . implode('.*', $parts) . '$/', $row->{$this->column});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Data\Filter;
|
||||
|
||||
class FilterMatchNot extends FilterExpression
|
||||
{
|
||||
public function matches($row)
|
||||
{
|
||||
$expression = (string) $this->expression;
|
||||
if (strpos($expression, '*') === false) {
|
||||
return (string) $row->{$this->column} !== $expression;
|
||||
} else {
|
||||
$parts = array();
|
||||
foreach (preg_split('/\*/', $expression) as $part) {
|
||||
$parts[] = preg_quote($part);
|
||||
}
|
||||
return ! preg_match('/^' . implode('.*', $parts) . '$/', $row->{$this->column});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Data\Filter;
|
||||
|
||||
class FilterNotEqual extends FilterExpression
|
||||
{
|
||||
public function matches($row)
|
||||
{
|
||||
return (string) $row->{$this->column} !== (string) $this->expression;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -24,10 +24,17 @@ class Logger
|
|||
/**
|
||||
* The log writer to use
|
||||
*
|
||||
* @var LogWriter
|
||||
* @var \Icinga\Logger\LogWriter
|
||||
*/
|
||||
protected $writer;
|
||||
|
||||
/**
|
||||
* The configured type
|
||||
*
|
||||
* @string Type (syslog, file)
|
||||
*/
|
||||
protected $type = 'none';
|
||||
|
||||
/**
|
||||
* The maximum severity to emit
|
||||
*
|
||||
|
@ -54,7 +61,7 @@ class Logger
|
|||
$this->verbosity = $config->level;
|
||||
|
||||
if ($config->enable) {
|
||||
$this->writer = $this->getWriter($config);
|
||||
$this->writer = $this->createWriter($config);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,11 +80,10 @@ class Logger
|
|||
*
|
||||
* @param Zend_Config $config The configuration to initialize the writer with
|
||||
*
|
||||
* @return LogWriter The requested log writer
|
||||
*
|
||||
* @throws ConfigurationError In case the requested writer cannot be found
|
||||
* @return \Icinga\Logger\LogWriter The requested log writer
|
||||
* @throws ConfigurationError If the requested writer cannot be found
|
||||
*/
|
||||
protected function getWriter(Zend_Config $config)
|
||||
protected function createWriter(Zend_Config $config)
|
||||
{
|
||||
$class = 'Icinga\\Logger\\Writer\\' . ucfirst(strtolower($config->type)) . 'Writer';
|
||||
if (!class_exists($class)) {
|
||||
|
@ -86,6 +92,7 @@ class Logger
|
|||
$config->type
|
||||
);
|
||||
}
|
||||
$this->type = $config->type;
|
||||
|
||||
return new $class($config);
|
||||
}
|
||||
|
@ -202,4 +209,34 @@ class Logger
|
|||
static::$instance->log(static::formatMessage(func_get_args()), static::$DEBUG);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the log writer to use
|
||||
*
|
||||
* @return \Icinga\Logger\LogWriter
|
||||
*/
|
||||
public function getWriter()
|
||||
{
|
||||
return $this->writer;
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
* @return Logger
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
return static::$instance;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,4 +103,12 @@ class FileWriter extends LogWriter
|
|||
$file->fwrite($text);
|
||||
$file->fflush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
namespace Icinga\Protocol\File;
|
||||
|
||||
use RuntimeException;
|
||||
use Icinga\Exception\IcingaException;
|
||||
|
||||
/**
|
||||
* Exception thrown if a file reader specific error occurs
|
||||
*/
|
||||
class FileReaderException extends RuntimeException {}
|
||||
class FileReaderException extends IcingaException {}
|
||||
|
|
|
@ -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})) {
|
||||
throw new FileReaderException('The directive `' . $key . '\' is required');
|
||||
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) {
|
||||
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,32 +97,32 @@ 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)
|
||||
{
|
||||
$skipLines = $query->getOffset();
|
||||
$readLines = $query->getLimit();
|
||||
if ($skipLines === null) {
|
||||
$skipLines = 0;
|
||||
$skip = $query->getOffset();
|
||||
$read = $query->getLimit();
|
||||
if ($skip === null) {
|
||||
$skip = 0;
|
||||
}
|
||||
$lines = array();
|
||||
if ($query->sortDesc()) {
|
||||
$count = $this->count($query);
|
||||
if ($count <= $skipLines) {
|
||||
if ($count <= $skip) {
|
||||
return $lines;
|
||||
} else if ($count < ($skipLines + $readLines)) {
|
||||
$readLines = $count - $skipLines;
|
||||
$skipLines = 0;
|
||||
} else if ($count < ($skip + $read)) {
|
||||
$read = $count - $skip;
|
||||
$skip = 0;
|
||||
} else {
|
||||
$skipLines = $count - ($skipLines + $readLines);
|
||||
$skip = $count - ($skip + $read);
|
||||
}
|
||||
}
|
||||
foreach ($this as $index => $line) {
|
||||
if ($index >= $skipLines) {
|
||||
if ($index >= $skipLines + $readLines) {
|
||||
foreach ($this->iterate() as $index => $line) {
|
||||
if ($index >= $skip) {
|
||||
if ($index >= $skip + $read) {
|
||||
break;
|
||||
}
|
||||
$lines[] = $line;
|
||||
|
@ -171,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])) {
|
||||
|
@ -187,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) {
|
||||
|
@ -206,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])) {
|
|
@ -307,7 +307,7 @@ class Connection
|
|||
$results = @ldap_search(
|
||||
$this->ds,
|
||||
$base,
|
||||
(string) $query,
|
||||
$query->create(),
|
||||
$fields,
|
||||
0, // Attributes and values
|
||||
0 // No limit - at least where possible
|
||||
|
@ -619,7 +619,7 @@ class Connection
|
|||
$result = @ldap_read(
|
||||
$ds,
|
||||
'',
|
||||
(string) $query,
|
||||
$query->create(),
|
||||
$query->listFields()
|
||||
);
|
||||
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
|
||||
namespace Icinga\Protocol\Ldap;
|
||||
|
||||
use Icinga\Exception\IcingaException;
|
||||
|
||||
/**
|
||||
* Search class
|
||||
*
|
||||
|
@ -84,7 +82,7 @@ class Query
|
|||
public function limit($count = null, $offset = null)
|
||||
{
|
||||
if (! preg_match('~^\d+~', $count . $offset)) {
|
||||
throw new IcingaException(
|
||||
throw new Exception(
|
||||
'Got invalid limit: %s, %s',
|
||||
$count,
|
||||
$offset
|
||||
|
@ -302,21 +300,11 @@ class Query
|
|||
*
|
||||
* @string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the LDAP filter that will be applied
|
||||
*
|
||||
* @string
|
||||
*/
|
||||
protected function render()
|
||||
public function create()
|
||||
{
|
||||
$parts = array();
|
||||
if (! isset($this->filters['objectClass']) || $this->filters['objectClass'] === null) {
|
||||
// throw new IcingaException('Object class is mandatory');
|
||||
throw new Exception('Object class is mandatory');
|
||||
}
|
||||
foreach ($this->filters as $key => $value) {
|
||||
$parts[] = sprintf(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -348,7 +348,9 @@ class ActionController extends Zend_Controller_Action
|
|||
// Cast preference app.show_benchmark to bool because preferences loaded from a preferences storage are
|
||||
// always strings
|
||||
if ((bool) $user->getPreferences()->get('app.show_benchmark', false) === true) {
|
||||
$layout->benchmark = $this->renderBenchmark();
|
||||
if (!$this->_helper->viewRenderer->getNoRender()) {
|
||||
$layout->benchmark = $this->renderBenchmark();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,275 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Web;
|
||||
|
||||
class FileCache
|
||||
{
|
||||
/**
|
||||
* FileCache singleton instances
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $instances = array();
|
||||
|
||||
/**
|
||||
* Cache instance base directory
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $basedir;
|
||||
|
||||
/**
|
||||
* Instance name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* Whether the cache is enabled
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $enabled = false;
|
||||
|
||||
/**
|
||||
* The protected constructor creates a new instance with the given name
|
||||
*
|
||||
* @param string $name Cache instance name
|
||||
*/
|
||||
protected function __construct($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
$tmpdir = sys_get_temp_dir();
|
||||
$basedir = $tmpdir . '/FileCache_' . $name;
|
||||
|
||||
if (file_exists($basedir) && is_writeable($basedir)) {
|
||||
|
||||
$this->basedir = $basedir;
|
||||
$this->enabled = true;
|
||||
|
||||
} elseif (file_exists($tmpdir) && is_writeable($tmpdir)) {
|
||||
|
||||
if (mkdir($basedir, '0750', true)) {
|
||||
$this->enabled = true;
|
||||
$this->basedir = $basedir;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the given content to the desired file name
|
||||
*
|
||||
* @param string $file new (relative) filename
|
||||
* @param string $content the content to be stored
|
||||
*
|
||||
* @return bool whether the file has been stored
|
||||
*/
|
||||
public function store($file, $content)
|
||||
{
|
||||
if (! $this->enabled) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return file_put_contents($this->filename($file), $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find out whether a given file exists
|
||||
*
|
||||
* @param string $file the (relative) filename
|
||||
* @param int $newerThan optional timestamp to compare against
|
||||
*
|
||||
* @return bool whether such file exists
|
||||
*/
|
||||
public function has($file, $newerThan = null)
|
||||
{
|
||||
if (! $this->enabled) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$filename = $this->filename($file);
|
||||
|
||||
if (! file_exists($filename) || ! is_readable($filename)) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($newerThan === null) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$info = stat($file);
|
||||
|
||||
if ($info === false) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return (int) $newerThan < $info['mtime'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific file or false if no such file available
|
||||
*
|
||||
* @param string $file the disired file name
|
||||
*
|
||||
* @return string|bool Filename content or false
|
||||
*/
|
||||
public function get($file)
|
||||
{
|
||||
if ($this->has($file)) {
|
||||
return file_get_contents($this->filename($file));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a specific file to the browser (output)
|
||||
*
|
||||
* @param string $file the disired file name
|
||||
*
|
||||
* @return bool Whether the file has been sent
|
||||
*/
|
||||
public function send($file)
|
||||
{
|
||||
if ($this->has($file)) {
|
||||
readfile($this->filename($file));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get absolute filename for a given file
|
||||
*
|
||||
* @param string $file the disired file name
|
||||
*
|
||||
* @return string absolute filename
|
||||
*/
|
||||
protected function filename($file)
|
||||
{
|
||||
return $this->basedir . '/' . $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given ETag matches a cached file
|
||||
*
|
||||
* If no ETag is given we'll try to fetch the one from the current
|
||||
* HTTP request.
|
||||
*
|
||||
* @param string $file The cached file you want to check
|
||||
* @param string $match The ETag to match against
|
||||
*
|
||||
* @return string|bool ETag on match, otherwise false
|
||||
*/
|
||||
public function etagMatchesCachedFile($file, $match = null)
|
||||
{
|
||||
return self::etagMatchesFiles($this->filename($file), $match);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ETag for the given file
|
||||
*
|
||||
* @param string $file The desired cache file
|
||||
*
|
||||
* @return string your ETag
|
||||
*/
|
||||
public function etagForCachedFile($file)
|
||||
{
|
||||
return self::etagForFiles($this->filename($file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given ETag matchesspecific file(s) on disk
|
||||
*
|
||||
* If no ETag is given we'll try to fetch the one from the current
|
||||
* HTTP request.
|
||||
*
|
||||
* @param string|array $files file(s) to check
|
||||
* @param string $match ETag to match against
|
||||
*
|
||||
* @return string|bool ETag on match, otherwise false
|
||||
*/
|
||||
public static function etagMatchesFiles($files, $match = null)
|
||||
{
|
||||
if ($match === null) {
|
||||
$match = isset($_SERVER['HTTP_IF_NONE_MATCH'])
|
||||
? trim($_SERVER['HTTP_IF_NONE_MATCH'], '"')
|
||||
: false;
|
||||
}
|
||||
if (! $match) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$etag = self::etagForFiles($files);
|
||||
return $match === $etag ? $etag : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create ETag for the given files
|
||||
*
|
||||
* Custom algorithm creating an ETag based on filenames, mtimes
|
||||
* and file sizes. Supports single files or a list of files. This
|
||||
* way we are able to create ETags for virtual files depending on
|
||||
* multiple source files (e.g. compressed JS, CSS).
|
||||
*
|
||||
* @param string|array $files Single file or a list of such
|
||||
*
|
||||
* @return string The generated ETag
|
||||
*/
|
||||
public static function etagForFiles($files)
|
||||
{
|
||||
if (is_string($files)) {
|
||||
$files = array($files);
|
||||
}
|
||||
|
||||
$sizes = array();
|
||||
$mtimes = array();
|
||||
|
||||
foreach ($files as $file) {
|
||||
$file = realpath($file);
|
||||
if ($file !== false && $info = stat($file)) {
|
||||
$mtimes[] = $info['mtime'];
|
||||
$sizes[] = $info['size'];
|
||||
} else {
|
||||
$mtimes[] = time();
|
||||
$sizes[] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'%s-%s-%s',
|
||||
hash('crc32', implode('|', $files)),
|
||||
hash('crc32', implode('|', $sizes)),
|
||||
hash('crc32', implode('|', $mtimes))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory creating your cache instance
|
||||
*
|
||||
* @param string $name Instance name
|
||||
*
|
||||
* @return FileCache
|
||||
*/
|
||||
public static function instance($name = 'icingaweb')
|
||||
{
|
||||
if ($name !== 'icingaweb') {
|
||||
$name = 'icingaweb/modules/' . $name;
|
||||
}
|
||||
|
||||
if (!array_key_exists($name, self::$instances)) {
|
||||
self::$instances[$name] = new static($name);
|
||||
}
|
||||
|
||||
return self::$instances[$name];
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
namespace Icinga\Web;
|
||||
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Web\FileCache;
|
||||
use JShrink\Minifier;
|
||||
|
||||
class JavaScript
|
||||
|
@ -20,16 +21,19 @@ class JavaScript
|
|||
'js/icinga/events.js',
|
||||
'js/icinga/history.js',
|
||||
'js/icinga/module.js',
|
||||
'js/icinga/timezone.js',
|
||||
);
|
||||
|
||||
protected static $vendorFiles = array(
|
||||
'js/vendor/jquery-2.1.0',
|
||||
'js/vendor/jquery.sparkline'
|
||||
'js/vendor/jquery.sparkline',
|
||||
'js/vendor/jquery.tipsy'
|
||||
);
|
||||
|
||||
protected static $ie8VendorFiles = array(
|
||||
'js/vendor/jquery-1.11.0',
|
||||
'js/vendor/jquery.sparkline'
|
||||
'js/vendor/jquery.sparkline',
|
||||
'js/vendor/jquery.tipsy'
|
||||
);
|
||||
|
||||
public static function listModuleFiles()
|
||||
|
@ -62,36 +66,58 @@ class JavaScript
|
|||
$js = $out = '';
|
||||
$min = $minified ? '.min' : '';
|
||||
|
||||
// TODO: Cache header
|
||||
header('Content-Type: application/javascript');
|
||||
$cacheFile = '/tmp/cache_icinga' . $min . '.js';
|
||||
if (file_exists($cacheFile)) {
|
||||
readfile($cacheFile);
|
||||
exit;
|
||||
}
|
||||
|
||||
// We do not minify vendor files
|
||||
// Prepare vendor file list
|
||||
$vendorFiles = array();
|
||||
foreach (self::$vendorFiles as $file) {
|
||||
$out .= file_get_contents($basedir . '/' . $file . $min . '.js');
|
||||
$vendorFiles[] = $basedir . '/' . $file . $min . '.js';
|
||||
}
|
||||
|
||||
// Prepare Icinga JS file list
|
||||
$jsFiles = array();
|
||||
foreach (self::$jsFiles as $file) {
|
||||
$js .= file_get_contents($basedir . '/' . $file);
|
||||
$jsFiles[] = $basedir . '/' . $file;
|
||||
}
|
||||
|
||||
foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $name => $module) {
|
||||
if ($module->hasJs()) {
|
||||
$js .= file_get_contents($module->getJsFilename());
|
||||
$jsFiles[] = $module->getJsFilename();
|
||||
}
|
||||
}
|
||||
$files = array_merge($vendorFiles, $jsFiles);
|
||||
|
||||
if ($etag = FileCache::etagMatchesFiles($files)) {
|
||||
header("HTTP/1.1 304 Not Modified");
|
||||
return;
|
||||
} else {
|
||||
$etag = FileCache::etagForFiles($files);
|
||||
}
|
||||
header('Cache-Control: public');
|
||||
header('ETag: "' . $etag . '"');
|
||||
header('Content-Type: application/javascript');
|
||||
|
||||
$cacheFile = 'icinga-' . $etag . $min . '.js';
|
||||
$cache = FileCache::instance();
|
||||
if ($cache->has($cacheFile)) {
|
||||
$cache->send($cacheFile);
|
||||
return;
|
||||
}
|
||||
|
||||
// We do not minify vendor files
|
||||
foreach ($vendorFiles as $file) {
|
||||
$out .= file_get_contents($file);
|
||||
}
|
||||
|
||||
foreach ($jsFiles as $file) {
|
||||
$js .= file_get_contents($file);
|
||||
}
|
||||
|
||||
if ($minified) {
|
||||
require_once 'IcingaVendor/JShrink/Minifier.php';
|
||||
$out .= Minifier::minify($js, array('flaggedComments' => false));
|
||||
} else {
|
||||
$out .= $js;
|
||||
}
|
||||
// Not yet, this is for tests only. Waiting for Icinga\Web\Cache
|
||||
// file_put_contents($cacheFile, $out);
|
||||
$cache->store($cacheFile, $out);
|
||||
echo $out;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,13 +4,14 @@
|
|||
|
||||
namespace Icinga\Web;
|
||||
|
||||
use Icinga\Exception\ConfigurationError;
|
||||
use Icinga\Logger\Logger;
|
||||
use Zend_Config;
|
||||
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
|
||||
{
|
||||
|
@ -173,34 +174,37 @@ class Menu implements RecursiveIterator
|
|||
*/
|
||||
protected function addMainMenuItems()
|
||||
{
|
||||
$this->add('dashboard', t('Dashboard'), array(
|
||||
$this->add(t('Dashboard'), array(
|
||||
'url' => 'dashboard',
|
||||
'icon' => 'img/icons/dashboard.png',
|
||||
'priority' => 10
|
||||
));
|
||||
|
||||
$section = $this->add('system', t('System'), array(
|
||||
$section = $this->add(t('System'), array(
|
||||
'icon' => 'img/icons/configuration.png',
|
||||
'priority' => 200
|
||||
));
|
||||
$section->add('preferences', t('Preferences'), array(
|
||||
$section->add(t('Preferences'), array(
|
||||
'url' => 'preference',
|
||||
'priority' => 200
|
||||
));
|
||||
$section->add('configuration', t('Configuration'), array(
|
||||
$section->add(t('Configuration'), array(
|
||||
'url' => 'config',
|
||||
'priority' => 300
|
||||
));
|
||||
$section->add('modules', t('Modules'), array(
|
||||
$section->add(t('Modules'), array(
|
||||
'url' => 'config/modules',
|
||||
'priority' => 400
|
||||
));
|
||||
$section->add('applicationlog', t('ApplicationLog'), array(
|
||||
'url' => 'list/applicationlog',
|
||||
'priority' => 500
|
||||
));
|
||||
|
||||
$this->add('logout', t('Logout'), array(
|
||||
if (Logger::writesToFile()) {
|
||||
$section->add(t('Application Log'), array(
|
||||
'url' => 'list/applicationlog',
|
||||
'priority' => 500
|
||||
));
|
||||
}
|
||||
|
||||
$this->add(t('Logout'), array(
|
||||
'url' => 'authentication/logout',
|
||||
'icon' => 'img/icons/logout.png',
|
||||
'priority' => 300
|
||||
|
@ -279,13 +283,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;
|
||||
}
|
||||
|
||||
|
@ -428,10 +436,9 @@ class Menu implements RecursiveIterator
|
|||
* @param array $config
|
||||
* @return Menu
|
||||
*/
|
||||
public function add($id, $name, $config = array())
|
||||
public function add($name, $config = array())
|
||||
{
|
||||
$config['title'] = $name;
|
||||
return $this->addSubMenu($id, new Zend_Config($config));
|
||||
return $this->addSubMenu($name, new Zend_Config($config));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -89,7 +93,7 @@ class MenuRenderer extends RecursiveIteratorIterator
|
|||
{
|
||||
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())
|
||||
);
|
||||
|
@ -146,6 +150,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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ class Session
|
|||
public static function getSession()
|
||||
{
|
||||
if (self::$session === null) {
|
||||
throw new ProgrammingError('No session created yet');
|
||||
self::create();
|
||||
}
|
||||
|
||||
return self::$session;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
namespace Icinga\Web;
|
||||
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Web\FileCache;
|
||||
use Icinga\Web\LessCompiler;
|
||||
|
||||
class StyleSheet
|
||||
|
@ -23,6 +24,8 @@ class StyleSheet
|
|||
'css/icinga/monitoring-colors.less',
|
||||
'css/icinga/selection-toolbar.less',
|
||||
'css/icinga/login.less',
|
||||
'css/icinga/charts.less',
|
||||
'css/vendor/tipsy.css'
|
||||
);
|
||||
|
||||
public static function compileForPdf()
|
||||
|
@ -42,30 +45,56 @@ 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();
|
||||
$basedir = $app->getBootstrapDirecory();
|
||||
foreach (self::$lessFiles as $file) {
|
||||
$lessFiles[] = $basedir . '/' . $file;
|
||||
}
|
||||
$files = $lessFiles;
|
||||
foreach ($app->getModuleManager()->getLoadedModules() as $name => $module) {
|
||||
if ($module->hasCss()) {
|
||||
$files[] = $module->getCssFilename();
|
||||
}
|
||||
}
|
||||
|
||||
if ($etag = FileCache::etagMatchesFiles($files)) {
|
||||
header("HTTP/1.1 304 Not Modified");
|
||||
return;
|
||||
} else {
|
||||
$etag = FileCache::etagForFiles($files);
|
||||
}
|
||||
header('Cache-Control: public');
|
||||
header('ETag: "' . $etag . '"');
|
||||
header('Content-Type: text/css');
|
||||
|
||||
$min = $minified ? '.min' : '';
|
||||
$cacheFile = '/tmp/cache_icinga' . $min . '.css';
|
||||
if (file_exists($cacheFile)) {
|
||||
readfile($cacheFile);
|
||||
exit;
|
||||
$cacheFile = 'icinga-' . $etag . $min . '.css';
|
||||
$cache = FileCache::instance();
|
||||
if ($cache->has($cacheFile)) {
|
||||
$cache->send($cacheFile);
|
||||
return;
|
||||
}
|
||||
|
||||
$less = new LessCompiler();
|
||||
$basedir = Icinga::app()->getBootstrapDirecory();
|
||||
foreach (self::$lessFiles as $file) {
|
||||
$less->addFile($basedir . '/' . $file);
|
||||
foreach ($lessFiles as $file) {
|
||||
$less->addFile($file);
|
||||
}
|
||||
$less->addLoadedModules();
|
||||
if ($minified) {
|
||||
$less->compress();
|
||||
}
|
||||
$out = $less->compile();
|
||||
// Not yet, this is for tests only. Waiting for Icinga\Web\Cache
|
||||
// file_put_contents($cacheFile, $out);
|
||||
$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);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ use Icinga\Application\Icinga;
|
|||
use Icinga\Application\Config as IcingaConfig;
|
||||
use Icinga\Exception\ConfigurationError;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\Web\Widget\AbstractWidget;
|
||||
use Icinga\Web\Widget\Dashboard\Pane;
|
||||
use Icinga\Web\Widget\Dashboard\Component as DashboardComponent;
|
||||
use Icinga\Web\Url;
|
||||
|
@ -96,7 +95,7 @@ class Dashboard extends AbstractWidget
|
|||
$current = $this->panes[$pane->getName()];
|
||||
$current->addComponents($pane->getComponents());
|
||||
} else {
|
||||
$this->panes = array_filter(array_merge($this->panes, $panes));
|
||||
$this->panes[$pane->getName()] = $pane;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,6 +127,16 @@ class Dashboard extends AbstractWidget
|
|||
return $this->tabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all panes of this dashboard
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPanes()
|
||||
{
|
||||
return $this->panes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate this dashboard via the given configuration file
|
||||
*
|
||||
|
@ -164,9 +173,9 @@ class Dashboard extends AbstractWidget
|
|||
*
|
||||
* @TODO: Should only allow component objects to be added directly as soon as we store more information
|
||||
*
|
||||
* @param string $pane The pane to add the component to
|
||||
* @param Component|string $component The component to add or the title of the newly created component
|
||||
* @param $url The url to use for the component
|
||||
* @param string $pane The pane to add the component to
|
||||
* @param Component|string $component The component to add or the title of the newly created component
|
||||
* @param string|null $url The url to use for the component
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
|
@ -198,20 +207,14 @@ class Dashboard extends AbstractWidget
|
|||
}
|
||||
|
||||
/**
|
||||
* Return true if a pane doesn't exist or doesn't have any components in it
|
||||
*
|
||||
* @param string $pane The name of the pane to check for emptyness
|
||||
* Check if this dashboard has a specific pane
|
||||
*
|
||||
* @param $pane string The name of the pane
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmptyPane($pane)
|
||||
public function hasPane($pane)
|
||||
{
|
||||
$paneObj = $this->getPane($pane);
|
||||
if ($paneObj === null) {
|
||||
return true;
|
||||
}
|
||||
$cmps = $paneObj->getComponents();
|
||||
return !empty($cmps);
|
||||
return array_key_exists($pane, $this->panes);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -305,11 +308,11 @@ class Dashboard extends AbstractWidget
|
|||
return $active;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see determineActivePane()
|
||||
*/
|
||||
public function getActivePane()
|
||||
{
|
||||
if ($active = $this->getTabs()->getActiveName()) {
|
||||
return $this->getPane($active);
|
||||
}
|
||||
return $this->determineActivePane();
|
||||
}
|
||||
|
||||
|
@ -323,10 +326,12 @@ class Dashboard extends AbstractWidget
|
|||
$active = $this->getTabs()->getActiveName();
|
||||
if (! $active) {
|
||||
if ($active = Url::fromRequest()->getParam($this->tabParam)) {
|
||||
if ($this->isEmptyPane($active)) {
|
||||
$active = $this->setDefaultPane();
|
||||
} else {
|
||||
if ($this->hasPane($active)) {
|
||||
$this->activate($active);
|
||||
} else {
|
||||
throw new ProgrammingError(
|
||||
'Try to get an inexistent pane.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$active = $this->setDefaultPane();
|
||||
|
|
|
@ -30,13 +30,6 @@ class Component extends AbstractWidget
|
|||
*/
|
||||
private $url;
|
||||
|
||||
/**
|
||||
* The id of this Component
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* The title being displayed on top of the component
|
||||
* @var
|
||||
|
@ -49,6 +42,13 @@ class Component extends AbstractWidget
|
|||
*/
|
||||
private $pane;
|
||||
|
||||
/**
|
||||
* The disabled option is used to "delete" default dashlets provided by modules
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $disabled = false;
|
||||
|
||||
/**
|
||||
* The template string used for rendering this widget
|
||||
*
|
||||
|
@ -67,14 +67,12 @@ EOD;
|
|||
/**
|
||||
* Create a new component displaying the given url in the provided pane
|
||||
*
|
||||
* @param string $id The id to use for this component
|
||||
* @param string $title The title to use for this component
|
||||
* @param Url|string $url The url this component uses for displaying information
|
||||
* @param Pane $pane The pane this Component will be added to
|
||||
*/
|
||||
public function __construct($id, $title, $url, Pane $pane)
|
||||
public function __construct($title, $url, Pane $pane)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->title = $title;
|
||||
$this->pane = $pane;
|
||||
if ($url instanceof Url) {
|
||||
|
@ -126,6 +124,26 @@ EOD;
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the disabled property
|
||||
*
|
||||
* @param boolean $disabled
|
||||
*/
|
||||
public function setDisabled($disabled)
|
||||
{
|
||||
$this->disabled = $disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the disabled property
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function getDisabled()
|
||||
{
|
||||
return $this->disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this component's structure as array
|
||||
*
|
||||
|
@ -145,6 +163,10 @@ EOD;
|
|||
*/
|
||||
public function render()
|
||||
{
|
||||
if ($this->disabled === true) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$view = $this->view();
|
||||
$url = clone($this->url);
|
||||
$url->setParam('view', 'compact');
|
||||
|
@ -195,14 +217,14 @@ EOD;
|
|||
|
||||
/**
|
||||
* Create a @see Component instance from the given Zend config, using the provided title
|
||||
* @param $id The id for this component
|
||||
*
|
||||
* @param $title The title for this component
|
||||
* @param Zend_Config $config The configuration defining url, parameters, height, width, etc.
|
||||
* @param Pane $pane The pane this component belongs to
|
||||
*
|
||||
* @return Component A newly created Component for use in the Dashboard
|
||||
*/
|
||||
public static function fromIni($id, $title, Zend_Config $config, Pane $pane)
|
||||
public static function fromIni($title, Zend_Config $config, Pane $pane)
|
||||
{
|
||||
$height = null;
|
||||
$width = null;
|
||||
|
@ -210,27 +232,7 @@ EOD;
|
|||
$parameters = $config->toArray();
|
||||
unset($parameters['url']); // otherwise there's an url = parameter in the Url
|
||||
|
||||
$cmp = new Component($id, $title, Url::fromPath($url, $parameters), $pane);
|
||||
$cmp = new Component($title, Url::fromPath($url, $parameters), $pane);
|
||||
return $cmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the components id
|
||||
*
|
||||
* @param $id string
|
||||
*/
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the components id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ class Pane extends AbstractWidget
|
|||
/**
|
||||
* Create a new pane
|
||||
*
|
||||
* @param $name The pane to create
|
||||
* @param string $name The pane to create
|
||||
*/
|
||||
public function __construct($name)
|
||||
{
|
||||
|
@ -83,44 +83,72 @@ class Pane extends AbstractWidget
|
|||
/**
|
||||
* Return true if a component with the given title exists in this pane
|
||||
*
|
||||
* @param string $id The id of the component to check for existence
|
||||
* @param string $title The title of the component to check for existence
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasComponent($id)
|
||||
public function hasComponent($title)
|
||||
{
|
||||
return array_key_exists($id, $this->components);
|
||||
return array_key_exists($title, $this->components);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current pane has any components
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasComponents()
|
||||
{
|
||||
return ! empty($this->components);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a component with the given name if existing
|
||||
*
|
||||
* @param string $id The id of the component to return
|
||||
* @param string $title The title of the component to return
|
||||
*
|
||||
* @return Component The component with the given title
|
||||
* @throws ProgrammingError If the component doesn't exist
|
||||
*/
|
||||
public function getComponent($id)
|
||||
public function getComponent($title)
|
||||
{
|
||||
if ($this->hasComponent($id)) {
|
||||
return $this->components[$id];
|
||||
if ($this->hasComponent($title)) {
|
||||
return $this->components[$title];
|
||||
}
|
||||
throw new ProgrammingError(
|
||||
'Trying to access invalid component: %s',
|
||||
$id
|
||||
$title
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the component with the given id if it exists in this pane
|
||||
* Removes the component with the given title if it exists in this pane
|
||||
*
|
||||
* @param string $id The pane
|
||||
* @param string $title The pane
|
||||
* @return Pane $this
|
||||
*/
|
||||
public function removeComponent($id)
|
||||
public function removeComponent($title)
|
||||
{
|
||||
if ($this->hasComponent($id)) {
|
||||
unset($this->components[$id]);
|
||||
if ($this->hasComponent($title)) {
|
||||
unset($this->components[$title]);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
@ -146,7 +174,6 @@ class Pane extends AbstractWidget
|
|||
/**
|
||||
* Add a component to this pane, optionally creating it if $component is a string
|
||||
*
|
||||
* @param string $id An unique Identifier
|
||||
* @param string|Component $component The component object or title
|
||||
* (if a new component will be created)
|
||||
* @param string|null $url An Url to be used when component is a string
|
||||
|
@ -154,12 +181,12 @@ class Pane extends AbstractWidget
|
|||
* @return self
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
*/
|
||||
public function addComponent($id, $component, $url = null)
|
||||
public function addComponent($component, $url = null)
|
||||
{
|
||||
if ($component instanceof Component) {
|
||||
$this->components[$component->getId()] = $component;
|
||||
} elseif (is_string($id) && is_string($component) && $url !== null) {
|
||||
$this->components[$id] = new Component($id, $component, $url, $this);
|
||||
$this->components[$component->getTitle()] = $component;
|
||||
} elseif (is_string($component) && $url !== null) {
|
||||
$this->components[$component] = new Component($component, $url, $this);
|
||||
} else {
|
||||
throw new ConfigurationError('Invalid component added: %s', $component);
|
||||
}
|
||||
|
@ -176,15 +203,15 @@ class Pane extends AbstractWidget
|
|||
{
|
||||
/* @var $component Component */
|
||||
foreach ($components as $component) {
|
||||
if (array_key_exists($component->getId(), $this->components)) {
|
||||
if (preg_match('/-(\d+)$/', $component->getId(), $m)) {
|
||||
$name = preg_replace('/-\d+$/', $m[1]++, $component->getId());
|
||||
if (array_key_exists($component->getTitle(), $this->components)) {
|
||||
if (preg_match('/_(\d+)$/', $component->getTitle(), $m)) {
|
||||
$name = preg_replace('/_\d+$/', $m[1]++, $component->getTitle());
|
||||
} else {
|
||||
$name = $component->getId() . '-2';
|
||||
$name = $component->getTitle() . '_2';
|
||||
}
|
||||
$this->components[$name] = $component;
|
||||
} else {
|
||||
$this->components[$component->getId()] = $component;
|
||||
$this->components[$component->getTitle()] = $component;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,18 +221,17 @@ class Pane extends AbstractWidget
|
|||
/**
|
||||
* Add a component to the current pane
|
||||
*
|
||||
* @param $id
|
||||
* @param $title
|
||||
* @param null $url
|
||||
* @return mixed
|
||||
* @param $url
|
||||
* @return Component
|
||||
*
|
||||
* @see addComponent()
|
||||
*/
|
||||
public function add($id, $title, $url = null)
|
||||
public function add($title, $url = null)
|
||||
{
|
||||
$this->addComponent($id, $title, $url);
|
||||
$this->addComponent($title, $url);
|
||||
|
||||
return $this->components[$id];
|
||||
return $this->components[$title];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -183,9 +183,10 @@ class FilterEditor extends AbstractWidget
|
|||
{
|
||||
$name = 'sign_' . $filter->getId();
|
||||
$signs = array(
|
||||
'=' => '=',
|
||||
'>' => '>',
|
||||
'<' => '<',
|
||||
'=' => '=',
|
||||
'!=' => '!=',
|
||||
'>' => '>',
|
||||
'<' => '<',
|
||||
'>=' => '>=',
|
||||
'<=' => '<=',
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,8 @@
|
|||
|
||||
/* @var $this \Icinga\Application\Modules\Module */
|
||||
|
||||
$section = $this->menuSection('documentation', $this->translate('Documentation'), array(
|
||||
$section = $this->menuSection($this->translate('Documentation'), array(
|
||||
'title' => 'Documentation',
|
||||
'icon' => 'img/icons/comment.png',
|
||||
'url' => 'doc',
|
||||
'priority' => 80
|
||||
|
|
|
@ -92,7 +92,7 @@ class Monitoring_ChartController extends Controller
|
|||
'services_warning_unhandled',
|
||||
'services_pending'
|
||||
)
|
||||
)->getQuery()->fetchAll();
|
||||
)->order('hostgroup')->getQuery()->fetchAll();
|
||||
$this->view->height = intval($this->getParam('height', 500));
|
||||
$this->view->width = intval($this->getParam('width', 500));
|
||||
if (count($query) === 1) {
|
||||
|
@ -117,7 +117,7 @@ class Monitoring_ChartController extends Controller
|
|||
'services_warning_unhandled',
|
||||
'services_pending'
|
||||
)
|
||||
)->getQuery()->fetchAll();
|
||||
)->order('servicegroup')->getQuery()->fetchAll();
|
||||
$this->view->height = intval($this->getParam('height', 500));
|
||||
$this->view->width = intval($this->getParam('width', 500));
|
||||
|
||||
|
@ -147,30 +147,35 @@ class Monitoring_ChartController extends Controller
|
|||
->setXAxis(new StaticAxis())
|
||||
->setAxisMin(null, 0);
|
||||
|
||||
$tooltip = t('<b>{title}:</b><br />{value} of {sum} services are {label}');
|
||||
$this->view->chart->drawBars(
|
||||
array(
|
||||
'label' => t('Ok'),
|
||||
'color' => '#44bb77',
|
||||
'stack' => 'stack1',
|
||||
'data' => $okBars
|
||||
'data' => $okBars,
|
||||
'tooltip' => $tooltip
|
||||
),
|
||||
array(
|
||||
'label' => t('Warning'),
|
||||
'color' => '#ffaa44',
|
||||
'stack' => 'stack1',
|
||||
'data' => $warningBars
|
||||
'data' => $warningBars,
|
||||
'tooltip' => $tooltip
|
||||
),
|
||||
array(
|
||||
'label' => t('Critical'),
|
||||
'color' => '#ff5566',
|
||||
'stack' => 'stack1',
|
||||
'data' => $critBars
|
||||
'data' => $critBars,
|
||||
'tooltip' => $tooltip
|
||||
),
|
||||
array(
|
||||
'label' => t('Unknown'),
|
||||
'color' => '#dd66ff',
|
||||
'stack' => 'stack1',
|
||||
'data' => $unknownBars
|
||||
'data' => $unknownBars,
|
||||
'tooltip' => $tooltip
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -194,6 +199,7 @@ class Monitoring_ChartController extends Controller
|
|||
$hostgroup->hosts_unreachable_unhandled
|
||||
);
|
||||
}
|
||||
$tooltip = t('<b>{title}:</b><br /> {value} of {sum} hosts are {label}');
|
||||
$this->view->chart = new GridChart();
|
||||
$this->view->chart->alignTopLeft();
|
||||
$this->view->chart->setAxisLabel('', t('Hosts'))
|
||||
|
@ -204,19 +210,22 @@ class Monitoring_ChartController extends Controller
|
|||
'label' => t('Up'),
|
||||
'color' => '#44bb77',
|
||||
'stack' => 'stack1',
|
||||
'data' => $upBars
|
||||
'data' => $upBars,
|
||||
'tooltip' => $tooltip
|
||||
),
|
||||
array(
|
||||
'label' => t('Down'),
|
||||
'color' => '#ff5566',
|
||||
'stack' => 'stack1',
|
||||
'data' => $downBars
|
||||
'data' => $downBars,
|
||||
'tooltip' => $tooltip
|
||||
),
|
||||
array(
|
||||
'label' => t('Unreachable'),
|
||||
'color' => '#dd66ff',
|
||||
'stack' => 'stack1',
|
||||
'data' => $unreachableBars
|
||||
'data' => $unreachableBars,
|
||||
'tooltip' => $tooltip
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -34,16 +34,46 @@ class Monitoring_ListController extends Controller
|
|||
|
||||
protected function hasBetterUrl()
|
||||
{
|
||||
$request = $this->getRequest();
|
||||
$url = clone($this->url);
|
||||
|
||||
if ($this->getRequest()->isPost()) {
|
||||
|
||||
if ($request->getPost('sort')) {
|
||||
$url->setParam('sort', $request->getPost('sort'));
|
||||
if ($request->getPost('dir')) {
|
||||
$url->setParam('dir', $request->getPost('dir'));
|
||||
} else {
|
||||
$url->removeParam('dir');
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
$q = $this->getRequest()->getPost('q');
|
||||
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;
|
||||
}
|
||||
|
@ -382,6 +412,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(
|
||||
|
@ -410,6 +443,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(
|
||||
|
@ -488,15 +524,8 @@ class Monitoring_ListController extends Controller
|
|||
$request = $this->getRequest();
|
||||
|
||||
$limit = $params->shift('limit');
|
||||
|
||||
$sort = null;
|
||||
$dir = null;
|
||||
if ($request->isPost()) {
|
||||
$sort = $request->getPost('sort', null);
|
||||
$dir = $request->getPost('dir', null);
|
||||
}
|
||||
$sort = $params->shift('sort', $sort);
|
||||
$dir = $params->shift('dir', $dir);
|
||||
$sort = $params->shift('sort');
|
||||
$dir = $params->shift('dir');
|
||||
$page = $params->shift('page');
|
||||
$format = $params->shift('format');
|
||||
$view = $params->shift('view');
|
||||
|
@ -533,7 +562,9 @@ class Monitoring_ListController extends Controller
|
|||
$query->applyFilter($filter);
|
||||
}
|
||||
$this->view->filter = $filter;
|
||||
$query->order($sort, $dir);
|
||||
if ($sort) {
|
||||
$query->order($sort, $dir);
|
||||
}
|
||||
$this->applyRestrictions($query);
|
||||
$this->handleFormatRequest($query);
|
||||
return $query;
|
||||
|
|
|
@ -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,8 +67,8 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,8 +82,8 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,9 +92,8 @@ class Monitoring_ShowController extends Controller
|
|||
$this->getTabs()->activate('history');
|
||||
//$this->view->object->populate();
|
||||
$this->view->object->fetchEventHistory();
|
||||
$this->view->history = $this->view->object->eventhistory->paginate($this->params->get('limit', 50));
|
||||
$this->handleFormatRequest($this->view->object->eventhistory);
|
||||
$this->view->history = $this->view->object->eventhistory
|
||||
->paginate($this->params->get('limit', 50));
|
||||
}
|
||||
|
||||
public function servicesAction()
|
||||
|
|
|
@ -277,9 +277,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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -289,8 +288,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';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -115,21 +115,8 @@ abstract class CommandForm extends Form
|
|||
*/
|
||||
public function getValidDateTimeFormats()
|
||||
{
|
||||
$config = $this->getConfiguration();
|
||||
$global = $config->global;
|
||||
if ($global === null) {
|
||||
$global = new Zend_Config(array());
|
||||
}
|
||||
$preferences = $this->getUserPreferences();
|
||||
return array(
|
||||
implode(
|
||||
' ',
|
||||
array(
|
||||
$preferences->get('app.dateFormat', $global->get('dateFormat', 'd/m/Y')),
|
||||
$preferences->get('app.timeFormat', $global->get('timeFormat', 'g:i A'))
|
||||
)
|
||||
)
|
||||
);
|
||||
// TODO(mh): Missing localized format (#6077)
|
||||
return array('d/m/Y g:i A');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
/*
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -1,284 +0,0 @@
|
|||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
use Icinga\Module\Monitoring\Object\AbstractObject;
|
||||
|
||||
/**
|
||||
* Class Zend_View_Helper_MonitoringProperties
|
||||
*/
|
||||
class Zend_View_Helper_MonitoringProperties extends Zend_View_Helper_Abstract
|
||||
{
|
||||
/**
|
||||
* Value for check type active
|
||||
*/
|
||||
const CHECK_ACTIVE = 'ACTIVE';
|
||||
|
||||
/**
|
||||
* Value for check type passive
|
||||
*/
|
||||
const CHECK_PASSIVE = 'PASSIVE';
|
||||
|
||||
/**
|
||||
* Value for check type disabled
|
||||
*/
|
||||
const CHECK_DISABLED = 'DISABLED';
|
||||
|
||||
/**
|
||||
* Return value for not available
|
||||
*/
|
||||
const VALUE_NA = 'N/A';
|
||||
|
||||
/**
|
||||
* Return value for "YES"
|
||||
*/
|
||||
const VALUE_YES = 'YES';
|
||||
|
||||
/**
|
||||
* Return value for "NO"
|
||||
*/
|
||||
const VALUE_NO = 'NO';
|
||||
|
||||
/**
|
||||
* Label / value mapping for object keys
|
||||
*
|
||||
* Keys can be callables in this object
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $keys = array(
|
||||
'buildAttempt' => 'Current Attempt',
|
||||
'buildCheckType' => 'Check Type',
|
||||
'buildLatency' => 'Check Latency / Duration',
|
||||
'buildLastStateChange' => 'Last State Change',
|
||||
'buildLastNotification' => 'Last Notification',
|
||||
'buildFlapping' => 'Is This %s Flapping?',
|
||||
'buildScheduledDowntime' => 'In Scheduled Downtime?',
|
||||
'status_update_time' => 'Last Update'
|
||||
);
|
||||
|
||||
private static $notificationReasons = array(
|
||||
0 => 'NORMAL',
|
||||
1 => 'ACKNOWLEDGEMENT',
|
||||
2 => 'FLAPPING START',
|
||||
3 => 'FLAPPING STOP',
|
||||
4 => 'FLAPPING DISABLED',
|
||||
5 => 'DOWNTIME START',
|
||||
6 => 'DOWNTIME END',
|
||||
7 => 'DOWNTIME CANCELLED',
|
||||
8 => 'CUSTOM',
|
||||
9 => 'STALKING'
|
||||
);
|
||||
|
||||
/**
|
||||
* Return the object type
|
||||
* @param stdClass $object
|
||||
* @return mixed
|
||||
*/
|
||||
private function getObjectType($object)
|
||||
{
|
||||
$keys = array_keys(get_object_vars($object));
|
||||
$keyParts = explode('_', array_shift($keys), 2);
|
||||
return array_shift($keyParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop all object specific attribute prefixes
|
||||
* @param stdClass $object
|
||||
* @param $type
|
||||
* @return object
|
||||
*/
|
||||
private function dropObjectType($object, $type)
|
||||
{
|
||||
$vars = get_object_vars($object);
|
||||
$out = array();
|
||||
foreach ($vars as $name => $value) {
|
||||
$name = str_replace($type. '_', '', $name);
|
||||
$out[$name] = $value;
|
||||
}
|
||||
return (object)$out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string for attempt
|
||||
* @param stdClass $object
|
||||
* @return string
|
||||
*/
|
||||
private function buildAttempt($object)
|
||||
{
|
||||
return sprintf(
|
||||
'%s/%s (%s state)',
|
||||
$object->current_check_attempt,
|
||||
$object->max_check_attempts,
|
||||
($object->state_type === '1') ? 'HARD' : 'SOFT'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic fomatter for float values
|
||||
* @param $value
|
||||
* @return string
|
||||
*/
|
||||
private function floatFormatter($value)
|
||||
{
|
||||
return sprintf('%.4f', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string for check type
|
||||
* @param stdClass $object
|
||||
* @return string
|
||||
*/
|
||||
private function buildCheckType($object)
|
||||
{
|
||||
if ($object->passive_checks_enabled === '1' && $object->active_checks_enabled === '0') {
|
||||
return self::CHECK_PASSIVE;
|
||||
} elseif ($object->passive_checks_enabled === '0' && $object->active_checks_enabled === '0') {
|
||||
return self::CHECK_DISABLED;
|
||||
}
|
||||
|
||||
return self::CHECK_ACTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string for latency
|
||||
* @param stdClass $object
|
||||
* @return string
|
||||
*/
|
||||
private function buildLatency($object)
|
||||
{
|
||||
$val = '';
|
||||
if ($this->buildCheckType($object) === self::CHECK_PASSIVE) {
|
||||
$val .= self::VALUE_NA;
|
||||
} else {
|
||||
$val .= $this->floatFormatter(
|
||||
(isset($object->check_latency)) ? $object->check_latency : 0
|
||||
);
|
||||
}
|
||||
|
||||
$val .= ' / '. $this->floatFormatter(
|
||||
isset($object->check_execution_time) ? $object->check_execution_time : 0
|
||||
). ' seconds';
|
||||
|
||||
return $val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string for next check
|
||||
* @param stdClass $object
|
||||
* @return string
|
||||
*/
|
||||
private function buildNextCheck($object)
|
||||
{
|
||||
if ($this->buildCheckType($object) === self::CHECK_PASSIVE) {
|
||||
return self::VALUE_NA;
|
||||
} else {
|
||||
return $object->next_check;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get date for last state change
|
||||
* @param stdClass $object
|
||||
* @return string
|
||||
*/
|
||||
private function buildLastStateChange($object)
|
||||
{
|
||||
return strftime('%Y-%m-%d %H:%M:%S', $object->last_state_change);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string for "last notification"
|
||||
* @param stdClass $object
|
||||
* @return string
|
||||
*/
|
||||
private function buildLastNotification($object)
|
||||
{
|
||||
$val = '';
|
||||
|
||||
if ($object->last_notification === '0000-00-00 00:00:00') {
|
||||
$val .= self::VALUE_NA;
|
||||
} else {
|
||||
$val .= $object->last_notification;
|
||||
}
|
||||
|
||||
$val .= sprintf(' (notification %d)', $object->current_notification_number);
|
||||
|
||||
return $val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string for "is flapping"
|
||||
* @param stdClass $object
|
||||
* @return string
|
||||
*/
|
||||
private function buildFlapping($object)
|
||||
{
|
||||
$val = '';
|
||||
|
||||
if ($object->is_flapping === '0') {
|
||||
$val .= self::VALUE_NO;
|
||||
} else {
|
||||
$val .= self::VALUE_YES;
|
||||
}
|
||||
|
||||
$val .= sprintf(' (%.2f%% state change)', $object->percent_state_change);
|
||||
|
||||
return $val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string for scheduled downtime
|
||||
* @param stdClass $object
|
||||
* @return string
|
||||
*/
|
||||
private function buildScheduledDowntime($object)
|
||||
{
|
||||
if ($object->in_downtime === '1') {
|
||||
return self::VALUE_YES;
|
||||
}
|
||||
|
||||
return self::VALUE_NO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array which represent monitoring properties
|
||||
*
|
||||
* @param stdClass $object
|
||||
* @return array
|
||||
*/
|
||||
public function monitoringProperties($object)
|
||||
{
|
||||
$type = $this->getObjectType($object);
|
||||
//$object = $this->dropObjectType($object, $type);
|
||||
|
||||
$out = array();
|
||||
foreach (self::$keys as $property => $label) {
|
||||
$label = sprintf($label, ucfirst($type));
|
||||
if (is_callable(array(&$this, $property))) {
|
||||
$out[$label] = $this->$property($object);
|
||||
} elseif (isset($object->{$property})) {
|
||||
$out[$label] = $object->{$property};
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function getNotificationType($notification)
|
||||
{
|
||||
$reason = intval($notification->notification_reason);
|
||||
if (!isset(self::$notificationReasons[$reason])) {
|
||||
return 'N/A';
|
||||
}
|
||||
$type = self::$notificationReasons[$reason];
|
||||
if ($reason === 8) {
|
||||
if (intval($notification->notification_type) === 0) {
|
||||
$type .= '(UP)';
|
||||
} else {
|
||||
$type .= '(OK)';
|
||||
}
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -19,76 +19,83 @@ $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('problems', $this->translate('Problems'), array(
|
||||
$section = $this->menuSection($this->translate('Problems'), array(
|
||||
'icon' => 'img/icons/error.png',
|
||||
'priority' => 20
|
||||
));
|
||||
$section->add('unhandled hosts', $this->translate('Unhandled Hosts'), array(
|
||||
$section->add($this->translate('Unhandled Hosts'), array(
|
||||
'url' => 'monitoring/list/hosts?host_problem=1&host_handled=0',
|
||||
'priority' => 40
|
||||
));
|
||||
$section->add('unhandled services', $this->translate('Unhandled Services'), array(
|
||||
$section->add($this->translate('Unhandled Services'), array(
|
||||
'url' => 'monitoring/list/services?service_problem=1&service_handled=0&sort=service_severity',
|
||||
'priority' => 40
|
||||
));
|
||||
$section->add('host problems', $this->translate('Host Problems'), array(
|
||||
$section->add($this->translate('Host Problems'), array(
|
||||
'url' => 'monitoring/list/hosts?host_problem=1&sort=host_severity',
|
||||
'priority' => 50
|
||||
));
|
||||
$section->add('service prolems', $this->translate('Service Problems'), array(
|
||||
$section->add($this->translate('Service Problems'), array(
|
||||
'url' => 'monitoring/list/services?service_problem=1&sort=service_severity&dir=desc',
|
||||
'priority' => 50
|
||||
));
|
||||
$section->add('current downtimes', $this->translate('Current Downtimes'))
|
||||
->setUrl('monitoring/list/downtimes?downtime_is_in_effect=1');
|
||||
$section->add($this->translate('Current Downtimes'))->setUrl('monitoring/list/downtimes?downtime_is_in_effect=1');
|
||||
|
||||
/*
|
||||
* Overview Section
|
||||
*/
|
||||
$section = $this->menuSection('overview', $this->translate('Overview'), array(
|
||||
$section = $this->menuSection($this->translate('Overview'), array(
|
||||
'icon' => 'img/icons/hostgroup.png',
|
||||
'priority' => 30
|
||||
));
|
||||
$section->add('tactical overview', $this->translate('Tactical Overview'), array(
|
||||
$section->add($this->translate('Tactical Overview'), array(
|
||||
'url' => 'monitoring/tactical',
|
||||
'priority' => 40
|
||||
));
|
||||
$section->add('hosts', $this->translate('Hosts'), array(
|
||||
$section->add($this->translate('Hosts'), array(
|
||||
'url' => 'monitoring/list/hosts',
|
||||
'priority' => 50
|
||||
));
|
||||
$section->add('services', $this->translate('Services'), array(
|
||||
$section->add($this->translate('Services'), array(
|
||||
'url' => 'monitoring/list/services',
|
||||
'priority' => 50
|
||||
));
|
||||
$section->add('servicematrix', $this->translate('Servicematrix'), array(
|
||||
$section->add($this->translate('Servicematrix'), array(
|
||||
'url' => 'monitoring/list/servicematrix?service_problem=1',
|
||||
'priority' => 51
|
||||
));
|
||||
$section->add('servicegroups', $this->translate('Servicegroups'), array(
|
||||
$section->add($this->translate('Servicegroups'), array(
|
||||
'url' => 'monitoring/list/servicegroups',
|
||||
'priority' => 60
|
||||
));
|
||||
$section->add('hostgroups', $this->translate('Hostgroups'), array(
|
||||
$section->add($this->translate('Hostgroups'), array(
|
||||
'url' => 'monitoring/list/hostgroups',
|
||||
'priority' => 60
|
||||
));
|
||||
$section->add('contactgroups', $this->translate('Contactgroups'), array(
|
||||
$section->add($this->translate('Contactgroups'), array(
|
||||
'url' => 'monitoring/list/contactgroups',
|
||||
'priority' => 61
|
||||
));
|
||||
$section->add('downtimes', $this->translate('Downtimes'), array(
|
||||
$section->add($this->translate('Downtimes'), array(
|
||||
'url' => 'monitoring/list/downtimes',
|
||||
'priority' => 71
|
||||
));
|
||||
$section->add('comments', $this->translate('Comments'), array(
|
||||
$section->add($this->translate('Comments'), array(
|
||||
'url' => 'monitoring/list/comments?comment_type=(comment|ack)',
|
||||
'priority' => 70
|
||||
));
|
||||
$section->add('contacts', $this->translate('Contacts'), array(
|
||||
$section->add($this->translate('Contacts'), array(
|
||||
'url' => 'monitoring/list/contacts',
|
||||
'priority' => 70
|
||||
));
|
||||
|
@ -96,33 +103,31 @@ $section->add('contacts', $this->translate('Contacts'), array(
|
|||
/*
|
||||
* History Section
|
||||
*/
|
||||
$section = $this->menuSection('history', $this->translate('History'), array(
|
||||
'title' => $this->translate('History'),
|
||||
$section = $this->menuSection($this->translate('History'), array(
|
||||
'icon' => 'img/icons/history.png'
|
||||
));
|
||||
$section->add('critical events', $this->translate('Critical Events'), array(
|
||||
'title' => $this->translate('Critical Events'),
|
||||
$section->add($this->translate('Critical Events'), array(
|
||||
'url' => 'monitoring/list/statehistorysummary',
|
||||
'priority' => 50
|
||||
));
|
||||
$section->add('notifications', $this->translate('Notifications'), array(
|
||||
$section->add($this->translate('Notifications'), array(
|
||||
'url' => 'monitoring/list/notifications'
|
||||
));
|
||||
$section->add('events', $this->translate('Events'), array(
|
||||
$section->add($this->translate('Events'), array(
|
||||
'title' => $this->translate('All Events'),
|
||||
'url' => 'monitoring/list/eventhistory?timestamp>=-7%20days'
|
||||
));
|
||||
$section->add('timeline', $this->translate('Timeline'))->setUrl('monitoring/timeline');
|
||||
$section->add($this->translate('Timeline'))->setUrl('monitoring/timeline');
|
||||
|
||||
/*
|
||||
* System Section
|
||||
*/
|
||||
$section = $this->menuSection('system', $this->translate('System'));
|
||||
$section->add('process info', $this->translate('Process Info'), array(
|
||||
$section = $this->menuSection($this->translate('System'));
|
||||
$section->add($this->translate('Process Info'), array(
|
||||
'url' => 'monitoring/process/info',
|
||||
'priority' => 120
|
||||
));
|
||||
$section->add('performance info', $this->translate('Performance Info'), array(
|
||||
$section->add($this->translate('Performance Info'), array(
|
||||
'url' => 'monitoring/process/performance',
|
||||
'priority' => 130
|
||||
));
|
||||
|
@ -130,19 +135,16 @@ $section->add('performance info', $this->translate('Performance Info'), array(
|
|||
/*
|
||||
* Dashboard
|
||||
*/
|
||||
$dashboard = $this->dashboard('current incidents')->setTitle($this->translate('Current Incidents'));
|
||||
$dashboard = $this->dashboard($this->translate('Current Incidents'));
|
||||
$dashboard->add(
|
||||
'service problems',
|
||||
$this->translate('Service Problems'),
|
||||
'monitoring/list/services?service_problem=1&limit=10&sort=service_severity'
|
||||
);
|
||||
$dashboard->add(
|
||||
'recently recovered services',
|
||||
$this->translate('Recently Recovered Services'),
|
||||
'monitoring/list/services?service_state=0&limit=10&sort=service_last_state_change&dir=desc'
|
||||
);
|
||||
$dashboard->add(
|
||||
'host problems',
|
||||
$this->translate('Host Problems'),
|
||||
'monitoring/list/hosts?host_problem=1&sort=host_severity'
|
||||
);
|
||||
|
|
|
@ -69,33 +69,31 @@ class Backend implements Selectable, Queryable, ConnectionInterface
|
|||
*/
|
||||
public static function createBackend($backendName = null)
|
||||
{
|
||||
$allBackends = array();
|
||||
$defaultBackend = null;
|
||||
foreach (IcingaConfig::module('monitoring', 'backends') as $name => $config) {
|
||||
if (!(bool) $config->get('disabled', false) && $defaultBackend === null) {
|
||||
$defaultBackend = $config;
|
||||
}
|
||||
$allBackends[$name] = $config;
|
||||
$config = IcingaConfig::module('monitoring', 'backends');
|
||||
if ($config->count() === 0) {
|
||||
throw new ConfigurationError(t('No backend has been configured'));
|
||||
}
|
||||
if (empty($allBackends)) {
|
||||
throw new ConfigurationError('No backend has been configured');
|
||||
}
|
||||
if ($defaultBackend === null) {
|
||||
throw new ConfigurationError('All backends are disabled');
|
||||
}
|
||||
if ($backendName === null) {
|
||||
$backendConfig = $defaultBackend;
|
||||
} else {
|
||||
if (!array_key_exists($backendName, $allBackends)) {
|
||||
if ($backendName !== null) {
|
||||
$backendConfig = $config->get($backendName);
|
||||
if ($backendConfig === null) {
|
||||
throw new ConfigurationError('No configuration for backend %s', $backendName);
|
||||
}
|
||||
$backendConfig = $allBackends[$backendName];
|
||||
if ((bool) $backendConfig->get('disabled', false)) {
|
||||
if ((bool) $backendConfig->get('disabled', false) === true) {
|
||||
throw new ConfigurationError(
|
||||
'Configuration for backend %s available but backend is disabled',
|
||||
t('Configuration for backend %s available but backend is disabled'),
|
||||
$backendName
|
||||
);
|
||||
}
|
||||
} else {
|
||||
foreach ($config as $name => $backendConfig) {
|
||||
if ((bool) $backendConfig->get('disabled', false) === false) {
|
||||
$backendName = $name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($backendName === null) {
|
||||
throw new ConfigurationError(t('All backends are disabled'));
|
||||
}
|
||||
}
|
||||
$resource = ResourceFactory::create($backendConfig->resource);
|
||||
if ($backendConfig->type === 'ido' && $resource->getDbType() !== 'oracle') {
|
||||
|
|
|
@ -51,10 +51,12 @@ class CommandQuery extends IdoQuery
|
|||
{
|
||||
$this->select->join(
|
||||
array('cnc' => $this->prefix . 'contact_notificationcommands'),
|
||||
'cnc.command_object_id = co.object_id'
|
||||
'cnc.command_object_id = co.object_id',
|
||||
array()
|
||||
)->join(
|
||||
array('con' => $this->prefix . 'contacts'),
|
||||
'con.contact_id = cnc.contact_id'
|
||||
'con.contact_id = cnc.contact_id',
|
||||
array()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -45,7 +45,7 @@ class CommentdeletionhistoryQuery extends IdoQuery
|
|||
array()
|
||||
)->join(
|
||||
array('h' => $this->prefix . 'commenthistory'),
|
||||
'o.' . $this->object_id . ' = h.' . $this->object_id . " AND o.is_active = 1 AND h.deletion_time > '1970-01-01 00:00:00' AND h.entry_type <> 2",
|
||||
'o.' . $this->object_id . ' = h.' . $this->object_id . " AND o.is_active = 1 AND h.deletion_time > '1970-01-02 00:00:00' AND h.entry_type <> 2",
|
||||
array()
|
||||
);
|
||||
$this->joinedVirtualTables = array('commenthistory' => true);
|
||||
|
|
|
@ -23,8 +23,8 @@ class DowntimeQuery extends IdoQuery
|
|||
'downtime_triggered_by_id' => 'sd.triggered_by_id',
|
||||
'downtime_scheduled_start' => 'UNIX_TIMESTAMP(sd.scheduled_start_time)',
|
||||
'downtime_scheduled_end' => 'UNIX_TIMESTAMP(sd.scheduled_end_time)',
|
||||
'downtime_start' => "UNIX_TIMESTAMP(CASE WHEN sd.trigger_time != '0000-00-00 00:00:00' then sd.trigger_time ELSE sd.scheduled_start_time END)",
|
||||
'downtime_end' => 'CASE WHEN sd.is_fixed THEN UNIX_TIMESTAMP(sd.scheduled_end_time) ELSE UNIX_TIMESTAMP(sd.trigger_time) + sd.duration END',
|
||||
'downtime_start' => "UNIX_TIMESTAMP(CASE WHEN UNIX_TIMESTAMP(sd.trigger_time) > 0 then sd.trigger_time ELSE sd.scheduled_start_time END)",
|
||||
'downtime_end' => 'CASE WHEN sd.is_fixed > 0 THEN UNIX_TIMESTAMP(sd.scheduled_end_time) ELSE UNIX_TIMESTAMP(sd.trigger_time) + sd.duration END',
|
||||
'downtime_duration' => 'sd.duration',
|
||||
'downtime_is_in_effect' => 'sd.is_in_effect',
|
||||
'downtime_internal_id' => 'sd.internal_downtime_id',
|
||||
|
|
|
@ -47,7 +47,7 @@ class DowntimeendhistoryQuery extends IdoQuery
|
|||
array('h' => $this->prefix . 'downtimehistory'),
|
||||
'o.' . $this->object_id . ' = h.' . $this->object_id . ' AND o.is_active = 1',
|
||||
array()
|
||||
)->where('h.actual_end_time > ?', '1970-01-01 00:00:00');
|
||||
)->where('h.actual_end_time > ?', '1970-01-02 00:00:00');
|
||||
$this->joinedVirtualTables = array('downtimehistory' => true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ class DowntimestarthistoryQuery extends IdoQuery
|
|||
array('h' => $this->prefix . 'downtimehistory'),
|
||||
'o.' . $this->object_id . ' = h.' . $this->object_id . ' AND o.is_active = 1',
|
||||
array()
|
||||
)->where('h.actual_start_time > ?', '1970-01-01 00:00:00');
|
||||
)->where('h.actual_start_time > ?', '1970-01-02 00:00:00');
|
||||
$this->joinedVirtualTables = array('downtimehistory' => true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
|
||||
use Icinga\Logger\Logger;
|
||||
use Zend_Db_Select;
|
||||
|
||||
class GroupSummaryQuery extends IdoQuery
|
||||
|
@ -69,8 +70,15 @@ class GroupSummaryQuery extends IdoQuery
|
|||
)
|
||||
);
|
||||
|
||||
$groupColumn = 'hostgroup';
|
||||
|
||||
if (in_array('servicegroup', $this->desiredColumns)) {
|
||||
$groupColumn = 'servicegroup';
|
||||
}
|
||||
|
||||
$union = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL);
|
||||
$this->select->from(array('statussummary' => $union), '*')->group($columns[0]);
|
||||
$this->select->from(array('statussummary' => $union), array($groupColumn))->group(array($groupColumn));
|
||||
|
||||
$this->joinedVirtualTables = array(
|
||||
'servicestatussummary' => true,
|
||||
'hoststatussummary' => true
|
||||
|
|
|
@ -85,7 +85,8 @@ class NotificationhistoryQuery extends IdoQuery
|
|||
$this->select->group('n.object_id')
|
||||
->group('n.start_time')
|
||||
->group('n.output')
|
||||
->group('n.state');
|
||||
->group('n.state')
|
||||
->group('o.objecttype_id');
|
||||
}
|
||||
|
||||
$this->joinedVirtualTables = array('history' => true);
|
||||
|
|
|
@ -41,7 +41,7 @@ class StatusQuery extends IdoQuery
|
|||
'host_next_check' => 'CASE hs.should_be_scheduled WHEN 1 THEN UNIX_TIMESTAMP(hs.next_check) ELSE NULL END',
|
||||
'host_check_execution_time' => 'hs.execution_time',
|
||||
'host_check_latency' => 'hs.latency',
|
||||
'host_problem' => 'CASE WHEN hs.current_state = 0 THEN 0 ELSE 1 END',
|
||||
'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END',
|
||||
|
||||
'host_notifications_enabled' => 'hs.notifications_enabled',
|
||||
|
||||
|
@ -278,20 +278,41 @@ class StatusQuery extends IdoQuery
|
|||
ELSE 0
|
||||
END'
|
||||
),
|
||||
|
||||
'serviceproblemsummary' => array(
|
||||
'host_unhandled_services' => 'sps.unhandled_services_count'
|
||||
),
|
||||
'lasthostcomment' => array(
|
||||
'host_last_comment' => 'hlc.last_comment_data',
|
||||
'host_last_downtime' => 'hlc.last_downtime_data',
|
||||
'host_last_flapping' => 'hlc.last_flapping_data',
|
||||
'host_last_ack' => 'hlc.last_ack_data',
|
||||
|
||||
'lasthostcommentgeneric' => array(
|
||||
'host_last_comment' => 'hlcg.last_comment_data'
|
||||
),
|
||||
'lastservicecomment' => array(
|
||||
'service_last_comment' => 'slc.last_comment_data',
|
||||
'service_last_downtime' => 'slc.last_downtime_data',
|
||||
'service_last_flapping' => 'slc.last_flapping_data',
|
||||
'service_last_ack' => 'slc.last_ack_data',
|
||||
|
||||
'lasthostcommentdowntime' => array(
|
||||
'host_last_downtime' => 'hlcd.last_downtime_data'
|
||||
),
|
||||
|
||||
'lasthostcommentflapping' => array(
|
||||
'host_last_flapping' => 'hlcf.last_flapping_data'
|
||||
),
|
||||
|
||||
'lasthostcommentack' => array(
|
||||
'host_last_ack' => 'hlca.last_ack_data'
|
||||
),
|
||||
|
||||
'lastservicecommentgeneric' => array(
|
||||
'service_last_comment' => 'slcg.last_comment_data'
|
||||
),
|
||||
|
||||
'lastservicecommentdowntime' => array(
|
||||
'service_last_downtime' => 'slcd.last_downtime_data'
|
||||
),
|
||||
|
||||
'lastservicecommentflapping' => array(
|
||||
'service_last_flapping' => 'slcf.last_flapping_data'
|
||||
),
|
||||
|
||||
'lastservicecommentack' => array(
|
||||
'service_last_ack' => 'slca.last_ack_data'
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -483,41 +504,117 @@ class StatusQuery extends IdoQuery
|
|||
);
|
||||
}
|
||||
|
||||
protected function getLastCommentSubQuery()
|
||||
/**
|
||||
* Create a subquery to join comments into status query
|
||||
* @param int $entryType
|
||||
* @param string $fieldName
|
||||
* @return Zend_Db_Expr
|
||||
*/
|
||||
protected function getLastCommentSubQuery($entryType, $fieldName)
|
||||
{
|
||||
$sub = '(SELECT'
|
||||
. ' lc.object_id,'
|
||||
. " CASE WHEN lc.entry_type = 1 THEN CONCAT('[' || c.author_name || '] ' || c.comment_data) ELSE NULL END AS last_comment_data,"
|
||||
. " CASE WHEN lc.entry_type = 2 THEN CONCAT('[' || c.author_name || '] ' || c.comment_data) ELSE NULL END AS last_downtime_data,"
|
||||
. " CASE WHEN lc.entry_type = 3 THEN CONCAT('[' || c.author_name || '] ' || c.comment_data) ELSE NULL END AS last_flapping_data,"
|
||||
. " CASE WHEN lc.entry_type = 4 THEN CONCAT('[' || c.author_name || '] ' || c.comment_data) ELSE NULL END AS last_ack_data"
|
||||
. ' FROM icinga_comments c'
|
||||
. ' JOIN (SELECT'
|
||||
. ' MAX(comment_id) as comment_id,'
|
||||
. ' object_id,'
|
||||
. ' entry_type'
|
||||
. ' FROM icinga_comments'
|
||||
. ' WHERE entry_type = 1 OR entry_type = 4'
|
||||
. ' GROUP BY object_id, entry_type'
|
||||
. ') lc ON lc.comment_id = c.comment_id GROUP BY lc.object_id)';
|
||||
. ' c.object_id,'
|
||||
. " '[' || c.author_name || '] ' || c.comment_data AS $fieldName"
|
||||
. ' FROM icinga_comments c JOIN ('
|
||||
. ' SELECT MAX(comment_id) AS comment_id, object_id FROM icinga_comments'
|
||||
. ' WHERE entry_type = ' . $entryType . ' GROUP BY object_id'
|
||||
. ' ) lc ON c.comment_id = lc.comment_id)';
|
||||
|
||||
return new Zend_Db_Expr($sub);
|
||||
}
|
||||
|
||||
protected function joinLasthostcomment()
|
||||
/**
|
||||
* Join last host comment
|
||||
*/
|
||||
protected function joinLasthostcommentgeneric()
|
||||
{
|
||||
$this->select->joinLeft(
|
||||
array('hlc' => $this->getLastCommentSubQuery()),
|
||||
'hlc.object_id = hs.host_object_id',
|
||||
array('hlcg' => $this->getLastCommentSubQuery(1, 'last_comment_data')),
|
||||
'hlcg.object_id = hs.host_object_id',
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Terribly slow. As I have no idea of how to fix this we should remove it.
|
||||
protected function joinLastservicecomment()
|
||||
/**
|
||||
* Join last host downtime comment
|
||||
*/
|
||||
protected function joinLasthostcommentdowntime()
|
||||
{
|
||||
$this->select->joinLeft(
|
||||
array('slc' => $this->getLastCommentSubQuery()),
|
||||
'slc.object_id = ss.service_object_id',
|
||||
array('hlcd' => $this->getLastCommentSubQuery(2, 'last_downtime_data')),
|
||||
'hlcg.object_id = hs.host_object_id',
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join last host flapping comment
|
||||
*/
|
||||
protected function joinLastHostcommentflapping()
|
||||
{
|
||||
$this->select->joinLeft(
|
||||
array('hlcf' => $this->getLastCommentSubQuery(3, 'last_flapping_data')),
|
||||
'hlcg.object_id = hs.host_object_id',
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join last host acknowledgement comment
|
||||
*/
|
||||
protected function joinLasthostcommentack()
|
||||
{
|
||||
$this->select->joinLeft(
|
||||
array('hlca' => $this->getLastCommentSubQuery(4, 'last_ack_data')),
|
||||
'hlca.object_id = hs.host_object_id',
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join last service comment
|
||||
*/
|
||||
protected function joinLastservicecommentgeneric()
|
||||
{
|
||||
$this->select->joinLeft(
|
||||
array('slcg' => $this->getLastCommentSubQuery(1, 'last_comment_data')),
|
||||
'slcg.object_id = ss.service_object_id',
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join last service downtime comment
|
||||
*/
|
||||
protected function joinLastservicecommentdowntime()
|
||||
{
|
||||
$this->select->joinLeft(
|
||||
array('slcd' => $this->getLastCommentSubQuery(2, 'last_downtime_data')),
|
||||
'slcd.object_id = ss.service_object_id',
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join last service flapping comment
|
||||
*/
|
||||
protected function joinLastservicecommentflapping()
|
||||
{
|
||||
$this->select->joinLeft(
|
||||
array('slcf' => $this->getLastCommentSubQuery(3, 'last_flapping_data')),
|
||||
'slcf.object_id = ss.service_object_id',
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join last service acknowledgement comment
|
||||
*/
|
||||
protected function joinLastservicecommentack()
|
||||
{
|
||||
$this->select->joinLeft(
|
||||
array('slca' => $this->getLastCommentSubQuery(4, 'last_ack_data')),
|
||||
'slca.object_id = ss.service_object_id',
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -33,23 +33,27 @@ class StatusSummaryQuery extends IdoQuery
|
|||
'hosts_flapping' => 'SUM(CASE WHEN object_type = \'host\' AND is_flapping = 1 THEN 1 ELSE 0 END)'
|
||||
),
|
||||
'servicestatussummary' => array(
|
||||
'services_total' => 'SUM(CASE WHEN object_type = \'service\' THEN 1 ELSE 0 END)',
|
||||
'services_problem' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 THEN 1 ELSE 0 END)',
|
||||
'services_problem_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 AND (acknowledged + in_downtime + host_problem) > 0 THEN 1 ELSE 0 END)',
|
||||
'services_problem_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 AND (acknowledged + in_downtime + host_problem) = 0 THEN 1 ELSE 0 END)',
|
||||
'services_ok' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 THEN 1 ELSE 0 END)',
|
||||
'services_ok_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
|
||||
'services_pending' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 THEN 1 ELSE 0 END)',
|
||||
'services_pending_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
|
||||
'services_warning' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 THEN 1 ELSE 0 END)',
|
||||
'services_warning_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) > 0 THEN 1 ELSE 0 END)',
|
||||
'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) = 0 THEN 1 ELSE 0 END)',
|
||||
'services_warning_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND (acknowledged + in_downtime + host_problem) > 0 THEN 1 ELSE 0 END)',
|
||||
'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND (acknowledged + in_downtime + host_problem) = 0 THEN 1 ELSE 0 END)',
|
||||
'services_warning_passive' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
|
||||
'services_warning_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
|
||||
'services_critical' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 THEN 1 ELSE 0 END)',
|
||||
'services_critical_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) > 0 THEN 1 ELSE 0 END)',
|
||||
'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) = 0 THEN 1 ELSE 0 END)',
|
||||
'services_critical_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND (acknowledged + in_downtime + host_problem) > 0 THEN 1 ELSE 0 END)',
|
||||
'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND (acknowledged + in_downtime + host_problem) = 0 THEN 1 ELSE 0 END)',
|
||||
'services_critical_passive' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
|
||||
'services_critical_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
|
||||
'services_unknown' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 THEN 1 ELSE 0 END)',
|
||||
'services_unknown_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) > 0 THEN 1 ELSE 0 END)',
|
||||
'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) = 0 THEN 1 ELSE 0 END)',
|
||||
'services_unknown_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND (acknowledged + in_downtime + host_problem) > 0 THEN 1 ELSE 0 END)',
|
||||
'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND (acknowledged + in_downtime + host_problem) = 0 THEN 1 ELSE 0 END)',
|
||||
'services_unknown_passive' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
|
||||
'services_unknown_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
|
||||
'services_active' => 'SUM(CASE WHEN object_type = \'service\' AND is_active_checked = 1 THEN 1 ELSE 0 END)',
|
||||
|
@ -131,6 +135,7 @@ class StatusSummaryQuery extends IdoQuery
|
|||
'acknowledged' => 'hs.problem_has_been_acknowledged',
|
||||
'in_downtime' => 'CASE WHEN (hs.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END',
|
||||
'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END',
|
||||
'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END',
|
||||
'is_passive_checked' => 'CASE WHEN hs.active_checks_enabled = 0 AND hs.passive_checks_enabled = 1 THEN 1 ELSE 0 END',
|
||||
'is_active_checked' => 'hs.active_checks_enabled',
|
||||
'is_processing_events' => 'hs.event_handler_enabled',
|
||||
|
@ -144,6 +149,7 @@ class StatusSummaryQuery extends IdoQuery
|
|||
'acknowledged' => 'ss.problem_has_been_acknowledged',
|
||||
'in_downtime' => 'CASE WHEN (ss.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END',
|
||||
'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END',
|
||||
'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END',
|
||||
'is_passive_checked' => 'CASE WHEN ss.active_checks_enabled = 0 AND ss.passive_checks_enabled = 1 THEN 1 ELSE 0 END',
|
||||
'is_active_checked' => 'ss.active_checks_enabled',
|
||||
'is_processing_events' => 'ss.event_handler_enabled',
|
||||
|
|
|
@ -31,6 +31,8 @@ abstract class DataView implements Browsable, Filterable, Sortable
|
|||
|
||||
protected $connection;
|
||||
|
||||
protected $isSorted = false;
|
||||
|
||||
/**
|
||||
* Create a new view
|
||||
*
|
||||
|
@ -99,6 +101,7 @@ public function dump()
|
|||
protected function applyUrlFilter($request = null)
|
||||
{
|
||||
$url = Url::fromRequest();
|
||||
|
||||
$limit = $url->shift('limit');
|
||||
$sort = $url->shift('sort');
|
||||
$dir = $url->shift('dir');
|
||||
|
@ -132,20 +135,19 @@ public function dump()
|
|||
}
|
||||
}
|
||||
|
||||
$order = isset($params['order']) ? $params['order'] : null;
|
||||
if ($order !== null) {
|
||||
if (strtolower($order) === 'desc') {
|
||||
$order = self::SORT_DESC;
|
||||
} else {
|
||||
$order = self::SORT_ASC;
|
||||
if (isset($params['sort'])) {
|
||||
|
||||
$order = isset($params['order']) ? $params['order'] : null;
|
||||
if ($order !== null) {
|
||||
if (strtolower($order) === 'desc') {
|
||||
$order = self::SORT_DESC;
|
||||
} else {
|
||||
$order = self::SORT_ASC;
|
||||
}
|
||||
}
|
||||
|
||||
$view->sort($params['sort'], $order);
|
||||
}
|
||||
|
||||
$view->sort(
|
||||
isset($params['sort']) ? $params['sort'] : null,
|
||||
$order
|
||||
);
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
|
@ -226,6 +228,7 @@ public function dump()
|
|||
foreach ($sortColumns['columns'] as $column) {
|
||||
$this->query->order($column, $order);
|
||||
}
|
||||
$this->isSorted = true;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
@ -285,6 +288,7 @@ public function dump()
|
|||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
if (! $this->isSorted) { $this->sort(); }
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,20 +37,4 @@ class Groupsummary extends DataView
|
|||
'services_pending'
|
||||
);
|
||||
}
|
||||
|
||||
public function getSortRules()
|
||||
{
|
||||
if (in_array('servicegroup', $this->getQuery()->getColumns())) {
|
||||
return array(
|
||||
'servicegroup' => array(
|
||||
'order' => self::SORT_ASC
|
||||
)
|
||||
);
|
||||
}
|
||||
return array(
|
||||
'hostgroup' => array(
|
||||
'order' => self::SORT_ASC
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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_';
|
||||
|
|
|
@ -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_';
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Test\Modules\Monitoring\Application\Views\Helpers;
|
||||
|
||||
use Zend_View_Helper_MonitoringProperties;
|
||||
use Icinga\Test\BaseTestCase;
|
||||
|
||||
require_once realpath(BaseTestCase::$moduleDir . '/monitoring/application/views/helpers/MonitoringProperties.php');
|
||||
|
||||
class HostStruct4Properties
|
||||
{
|
||||
public $host_name = 'localhost';
|
||||
public $host_address = '127.0.0.1';
|
||||
public $host_state = '1';
|
||||
public $host_handled = '1';
|
||||
public $in_downtime = '1';
|
||||
public $acknowledged = '1';
|
||||
public $check_command = 'check-host-alive';
|
||||
public $last_state_change = '1372937083';
|
||||
public $host_alias = 'localhost';
|
||||
public $output = 'DDD';
|
||||
public $long_output = '';
|
||||
public $perfdata = '';
|
||||
public $current_check_attempt = '1';
|
||||
public $max_check_attempts = '10';
|
||||
public $attempt = '1/10';
|
||||
public $last_check = '2013-07-04 11:24:42';
|
||||
public $next_check = '2013-07-04 11:29:43';
|
||||
public $heck_type = '1';
|
||||
public $last_hard_state_change = '2013-07-04 11:24:43';
|
||||
public $last_hard_state = '0';
|
||||
public $last_time_up = '2013-07-04 11:20:23';
|
||||
public $last_time_down = '2013-07-04 11:24:43';
|
||||
public $last_time_unreachable = '0000-00-00 00:00:00';
|
||||
public $state_type = '1';
|
||||
public $last_notification = '0000-00-00 00:00:00';
|
||||
public $next_notification = '0000-00-00 00:00:00';
|
||||
public $no_more_notifications = '0';
|
||||
public $host_notifications_enabled = '1';
|
||||
public $host_problem_has_been_acknowledged = '1';
|
||||
public $host_acknowledgement_type = '2';
|
||||
public $current_notification_number = '0';
|
||||
public $passive_checks_enabled = '1';
|
||||
public $active_checks_enabled = '0';
|
||||
public $event_handler_enabled = '0';
|
||||
public $flap_detection_enabled = '1';
|
||||
public $is_flapping = '0';
|
||||
public $percent_state_change = '12.36842';
|
||||
public $check_latency = '0.12041';
|
||||
public $check_execution_time = '0';
|
||||
public $scheduled_downtime_depth = '1';
|
||||
public $host_failure_prediction_enabled = '1';
|
||||
public $host_process_performance_data = '1';
|
||||
public $host_obsessing = '1';
|
||||
public $host_modified_host_attributes = '14';
|
||||
public $host_event_handler = '';
|
||||
public $host_normal_check_interval = '5';
|
||||
public $host_retry_check_interval = '1';
|
||||
public $host_check_timeperiod_object_id = '27';
|
||||
public $host_status_update_time = '2013-07-08 10:10:10';
|
||||
}
|
||||
|
||||
/**
|
||||
* @TODO(el): This test is subject to bug #4679
|
||||
*/
|
||||
class MonitoringPropertiesTest extends BaseTestCase
|
||||
{
|
||||
public function testOutput1()
|
||||
{
|
||||
$host = new HostStruct4Properties();
|
||||
$host->current_check_attempt = '5';
|
||||
|
||||
$propertyHelper = new Zend_View_Helper_MonitoringProperties();
|
||||
$items = $propertyHelper->monitoringProperties($host);
|
||||
|
||||
$this->assertEquals('5/10 (HARD state)', $items['Current Attempt']);
|
||||
}
|
||||
|
||||
public function testOutput2()
|
||||
{
|
||||
$host = new HostStruct4Properties();
|
||||
$host->current_check_attempt = '5';
|
||||
$host->active_checks_enabled = '1';
|
||||
$host->passive_checks_enabled = '0';
|
||||
$host->is_flapping = '1';
|
||||
|
||||
$propertyHelper = new Zend_View_Helper_MonitoringProperties();
|
||||
$items = $propertyHelper->monitoringProperties($host);
|
||||
|
||||
$test = array(
|
||||
'Current Attempt' => "5/10 (HARD state)",
|
||||
'Check Type' => "ACTIVE",
|
||||
'Check Latency / Duration' => "0.1204 / 0.0000 seconds",
|
||||
'Last State Change' => "2013-07-04 11:24:43",
|
||||
'Last Notification' => "N/A (notification 0)",
|
||||
'Is This Host Flapping?' => "YES (12.37% state change)",
|
||||
'In Scheduled Downtime?' => "YES"
|
||||
);
|
||||
|
||||
$this->assertEquals($test, $items);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Icinga\Module\Monitoring\Regression;
|
||||
|
||||
// Necessary as some of these tests disable phpunit's preservation
|
||||
// of the global state (e.g. autoloaders are in the global state)
|
||||
require_once realpath(dirname(__FILE__) . '/../../../../../test/php/bootstrap.php');
|
||||
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Module\Monitoring\Backend;
|
||||
use Icinga\Test\BaseTestCase;
|
||||
use Mockery;
|
||||
use Zend_Config;
|
||||
|
||||
class Bug7043Test extends BaseTestCase
|
||||
{
|
||||
public function tearDown()
|
||||
{
|
||||
parent::tearDown();
|
||||
Mockery::close(); // Necessary because some tests run in a separate process
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
public function testBackendDefaultName()
|
||||
{
|
||||
Mockery::mock('alias:Icinga\Data\ResourceFactory')
|
||||
->shouldReceive('create')
|
||||
->andReturn(
|
||||
Mockery::mock('Icinga\Data\Db\DbConnection')
|
||||
->shouldReceive('getDbType')
|
||||
->andReturn('mysql')
|
||||
->shouldReceive('setTablePrefix')
|
||||
->getMock()
|
||||
);
|
||||
|
||||
Config::setModuleConfig('monitoring', 'backends', new Zend_Config(array(
|
||||
'backendName' => array(
|
||||
'type' => 'ido',
|
||||
'resource' => 'ido'
|
||||
)
|
||||
)));
|
||||
|
||||
$defaultBackend = Backend::createBackend();
|
||||
|
||||
$this->assertEquals('backendName', $defaultBackend->getName(), 'Default backend has name set');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
/* Add hover effects to chart data */
|
||||
.chart-data:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.pie-data:hover {
|
||||
opacity: 0.6;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/* tipsy, facebook style tooltips for jquery
|
||||
version 1.0.0a
|
||||
(c) 2008-2010 jason frame [jason@onehackoranother.com]
|
||||
released under the MIT license */
|
||||
|
||||
.tipsy { font-size: 14px; position: absolute; padding: 5px; z-index: 100000; }
|
||||
.tipsy-inner { background-color: #000; color: #FFF; max-width: 200px; padding: 5px 8px 4px 8px; text-align: center; }
|
||||
|
||||
/* Rounded corners */
|
||||
.tipsy-inner { border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; }
|
||||
|
||||
/* Uncomment for shadow */
|
||||
/*.tipsy-inner { box-shadow: 0 0 5px #000000; -webkit-box-shadow: 0 0 5px #000000; -moz-box-shadow: 0 0 5px #000000; }*/
|
||||
|
||||
.tipsy-arrow { position: absolute; width: 0; height: 0; line-height: 0; border: 5px dashed #000; }
|
||||
|
||||
/* Rules to colour arrows */
|
||||
.tipsy-arrow-n { border-bottom-color: #000; }
|
||||
.tipsy-arrow-s { border-top-color: #000; }
|
||||
.tipsy-arrow-e { border-left-color: #000; }
|
||||
.tipsy-arrow-w { border-right-color: #000; }
|
||||
|
||||
.tipsy-n .tipsy-arrow { top: 0px; left: 50%; margin-left: -5px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent; }
|
||||
.tipsy-nw .tipsy-arrow { top: 0; left: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;}
|
||||
.tipsy-ne .tipsy-arrow { top: 0; right: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;}
|
||||
.tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -5px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
|
||||
.tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
|
||||
.tipsy-se .tipsy-arrow { bottom: 0; right: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
|
||||
.tipsy-e .tipsy-arrow { right: 0; top: 50%; margin-top: -5px; border-left-style: solid; border-right: none; border-top-color: transparent; border-bottom-color: transparent; }
|
||||
.tipsy-w .tipsy-arrow { left: 0; top: 50%; margin-top: -5px; border-right-style: solid; border-left: none; border-top-color: transparent; border-bottom-color: transparent; }
|
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;
|
||||
|
||||
|
@ -53,6 +55,8 @@
|
|||
}
|
||||
});
|
||||
|
||||
$('td.state span.timesince').attr('title', null);
|
||||
|
||||
var moduleName = el.data('icingaModule');
|
||||
if (moduleName) {
|
||||
if (icinga.hasModule(moduleName)) {
|
||||
|
@ -109,6 +113,42 @@
|
|||
if (searchField.length && searchField.val().length) {
|
||||
this.searchValue = searchField.val();
|
||||
}
|
||||
|
||||
$('[title]').each(function () {
|
||||
var $el = $(this);
|
||||
$el.attr('title', $el.data('title-rich') || $el.attr('title'));
|
||||
});
|
||||
$('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 });
|
||||
|
||||
// migrate or remove all orphaned tooltips
|
||||
$('.tipsy').each(function () {
|
||||
var arrow = $('.tipsy-arrow', this)[0];
|
||||
if (!icinga.utils.elementsOverlap(arrow, $('#main')[0])) {
|
||||
$(this).remove();
|
||||
return;
|
||||
}
|
||||
if (!icinga.utils.elementsOverlap(arrow, el)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -162,6 +202,11 @@
|
|||
// $(document).on('keyup', 'form.auto input', this.formChangeDelayed);
|
||||
// $(document).on('change', 'form.auto input', this.formChanged);
|
||||
// $(document).on('change', 'form.auto select', this.submitForm);
|
||||
|
||||
$(document).on('mousemove', function (event) {
|
||||
mouseX = event.pageX;
|
||||
mouseY = event.pageY;
|
||||
});
|
||||
},
|
||||
|
||||
menuTitleHovered: function (event) {
|
||||
|
@ -470,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;
|
||||
}
|
||||
|
||||
|
|
|
@ -329,8 +329,7 @@
|
|||
this.icinga.ui.reloadCss();
|
||||
}
|
||||
|
||||
var redirect = req.getResponseHeader('X-Icinga-Redirect');
|
||||
if (this.processRedirectHeader(req)) return;
|
||||
if (req.getResponseHeader('X-Icinga-Redirect')) return;
|
||||
|
||||
// div helps getting an XML tree
|
||||
var $resp = $('<div>' + req.responseText + '</div>');
|
||||
|
@ -567,6 +566,7 @@
|
|||
delete this.requests[req.$target.attr('id')];
|
||||
this.icinga.ui.fadeNotificationsAway();
|
||||
|
||||
this.processRedirectHeader(req);
|
||||
|
||||
if (typeof req.loadNext !== 'undefined') {
|
||||
if ($('#col2').length) {
|
||||
|
@ -665,7 +665,11 @@
|
|||
var self = this;
|
||||
var containerId = $container.attr('id');
|
||||
if (typeof containerId !== 'undefined') {
|
||||
scrollPos = $container.scrollTop();
|
||||
if (autorefresh) {
|
||||
scrollPos = $container.scrollTop();
|
||||
} else {
|
||||
scrollPos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var origFocus = document.activeElement;
|
||||
|
|
|
@ -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);
|
|
@ -10,6 +10,9 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
// The currently hovered tooltip
|
||||
var tooltip = null;
|
||||
|
||||
// Stores the icinga-data-url of the last focused table.
|
||||
var focusedTableDataUrl = null;
|
||||
|
||||
|
@ -767,7 +770,6 @@
|
|||
this.debugTimer = null;
|
||||
this.timeCounterTimer = null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}(Icinga, jQuery));
|
||||
|
|
|
@ -200,6 +200,40 @@
|
|||
return params;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check whether two HTMLElements overlap
|
||||
*
|
||||
* @param a {HTMLElement}
|
||||
* @param b {HTMLElement}
|
||||
*
|
||||
* @returns {Boolean} whether elements overlap, will return false when one
|
||||
* element is not in the DOM
|
||||
*/
|
||||
elementsOverlap: function(a, b)
|
||||
{
|
||||
// a bounds
|
||||
var aoff = $(a).offset();
|
||||
if (!aoff) {
|
||||
return false;
|
||||
}
|
||||
var at = aoff.top;
|
||||
var ah = a.offsetHeight;
|
||||
var al = aoff.left;
|
||||
var aw = a.offsetWidth;
|
||||
|
||||
// b bounds
|
||||
var boff = $(b).offset();
|
||||
if (!boff) {
|
||||
return false;
|
||||
}
|
||||
var bt = boff.top;
|
||||
var bh = b.offsetHeight;
|
||||
var bl = boff.left;
|
||||
var bw = b.offsetWidth;
|
||||
|
||||
return !(at > (bt + bh) || bt > (at + ah)) && !(bl > (al + aw) || al > (bl + bw));
|
||||
},
|
||||
|
||||
/**
|
||||
* Cleanup
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
jquery.tipsy.js SOURCE
|
||||
======================
|
||||
|
||||
This file contains information about how to acquire and install the source files for jquery.tipsy
|
||||
|
||||
|
||||
# version
|
||||
|
||||
1.0.0a
|
||||
|
||||
|
||||
# license
|
||||
|
||||
MIT license
|
||||
|
||||
|
||||
# used files
|
||||
|
||||
src/javascript/tipsy.css
|
||||
src/javascript/jquery.tipsy.js
|
||||
|
||||
|
||||
# source
|
||||
|
||||
https://github.com/jaz303/tipsy.git
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
|
|
@ -0,0 +1,262 @@
|
|||
// tipsy, facebook style tooltips for jquery
|
||||
// version 1.0.0a
|
||||
// (c) 2008-2010 jason frame [jason@onehackoranother.com]
|
||||
// released under the MIT license
|
||||
|
||||
(function($) {
|
||||
|
||||
function maybeCall(thing, ctx) {
|
||||
return (typeof thing == 'function') ? (thing.call(ctx)) : thing;
|
||||
};
|
||||
|
||||
function isElementInDOM(ele) {
|
||||
while (ele = ele.parentNode) {
|
||||
if (ele == document) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
function Tipsy(element, options) {
|
||||
this.$element = $(element);
|
||||
this.options = options;
|
||||
this.enabled = true;
|
||||
this.fixTitle();
|
||||
};
|
||||
|
||||
Tipsy.prototype = {
|
||||
show: function() {
|
||||
var title = this.getTitle();
|
||||
if (title && this.enabled) {
|
||||
var $tip = this.tip();
|
||||
|
||||
$tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
|
||||
$tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity
|
||||
$tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body);
|
||||
|
||||
var pos = $.extend({}, this.$element.offset(), {
|
||||
width: this.$element[0].offsetWidth || this.$element[0].getBoundingClientRect().width,
|
||||
height: this.$element[0].offsetHeight || this.$element[0].getBoundingClientRect().height
|
||||
});
|
||||
|
||||
var actualWidth = $tip[0].offsetWidth,
|
||||
actualHeight = $tip[0].offsetHeight,
|
||||
gravity = maybeCall(this.options.gravity, this.$element[0]);
|
||||
|
||||
var tp;
|
||||
switch (gravity.charAt(0)) {
|
||||
case 'n':
|
||||
tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
|
||||
break;
|
||||
case 's':
|
||||
tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
|
||||
break;
|
||||
case 'e':
|
||||
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset};
|
||||
break;
|
||||
case 'w':
|
||||
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset};
|
||||
break;
|
||||
}
|
||||
|
||||
if (gravity.length == 2) {
|
||||
if (gravity.charAt(1) == 'w') {
|
||||
tp.left = pos.left + pos.width / 2 - 15;
|
||||
} else {
|
||||
tp.left = pos.left + pos.width / 2 - actualWidth + 15;
|
||||
}
|
||||
}
|
||||
|
||||
$tip.css(tp).addClass('tipsy-' + gravity);
|
||||
$tip.find('.tipsy-arrow')[0].className = 'tipsy-arrow tipsy-arrow-' + gravity.charAt(0);
|
||||
if (this.options.className) {
|
||||
$tip.addClass(maybeCall(this.options.className, this.$element[0]));
|
||||
}
|
||||
|
||||
if (this.options.fade) {
|
||||
$tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity});
|
||||
} else {
|
||||
$tip.css({visibility: 'visible', opacity: this.options.opacity});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
if (this.options.fade) {
|
||||
this.tip().stop().fadeOut(function() { $(this).remove(); });
|
||||
} else {
|
||||
this.tip().remove();
|
||||
}
|
||||
},
|
||||
|
||||
fixTitle: function() {
|
||||
var $e = this.$element;
|
||||
if ($e.attr('title') || typeof($e.attr('original-title')) != 'string') {
|
||||
$e.attr('original-title', $e.attr('title') || '').removeAttr('title');
|
||||
}
|
||||
},
|
||||
|
||||
getTitle: function() {
|
||||
var title, $e = this.$element, o = this.options;
|
||||
this.fixTitle();
|
||||
var title, o = this.options;
|
||||
if (typeof o.title == 'string') {
|
||||
title = $e.attr(o.title == 'title' ? 'original-title' : o.title);
|
||||
} else if (typeof o.title == 'function') {
|
||||
title = o.title.call($e[0]);
|
||||
}
|
||||
title = ('' + title).replace(/(^\s*|\s*$)/, "");
|
||||
return title || o.fallback;
|
||||
},
|
||||
|
||||
tip: function() {
|
||||
if (!this.$tip) {
|
||||
this.$tip = $('<div class="tipsy"></div>').html('<div class="tipsy-arrow"></div><div class="tipsy-inner"></div>');
|
||||
this.$tip.data('tipsy-pointee', this.$element[0]);
|
||||
}
|
||||
return this.$tip;
|
||||
},
|
||||
|
||||
validate: function() {
|
||||
if (!this.$element[0].parentNode) {
|
||||
this.hide();
|
||||
this.$element = null;
|
||||
this.options = null;
|
||||
}
|
||||
},
|
||||
|
||||
enable: function() { this.enabled = true; },
|
||||
disable: function() { this.enabled = false; },
|
||||
toggleEnabled: function() { this.enabled = !this.enabled; }
|
||||
};
|
||||
|
||||
$.fn.tipsy = function(options) {
|
||||
|
||||
if (options === true) {
|
||||
return this.data('tipsy');
|
||||
} else if (typeof options == 'string') {
|
||||
var tipsy = this.data('tipsy');
|
||||
if (tipsy) tipsy[options]();
|
||||
return this;
|
||||
}
|
||||
|
||||
options = $.extend({}, $.fn.tipsy.defaults, options);
|
||||
|
||||
function get(ele) {
|
||||
var tipsy = $.data(ele, 'tipsy');
|
||||
if (!tipsy) {
|
||||
tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options));
|
||||
$.data(ele, 'tipsy', tipsy);
|
||||
}
|
||||
return tipsy;
|
||||
}
|
||||
|
||||
function enter() {
|
||||
var tipsy = get(this);
|
||||
var ele = this;
|
||||
tipsy.hoverState = 'in';
|
||||
if (options.delayIn == 0) {
|
||||
tipsy.show();
|
||||
} else {
|
||||
tipsy.fixTitle();
|
||||
setTimeout(function() {
|
||||
if (tipsy.hoverState == 'in' && ele && isElementInDOM(ele))
|
||||
tipsy.show();
|
||||
}, options.delayIn);
|
||||
}
|
||||
};
|
||||
|
||||
function leave() {
|
||||
var tipsy = get(this);
|
||||
tipsy.hoverState = 'out';
|
||||
if (options.delayOut == 0) {
|
||||
tipsy.hide();
|
||||
} else {
|
||||
setTimeout(function() { if (tipsy.hoverState == 'out') tipsy.hide(); }, options.delayOut);
|
||||
}
|
||||
};
|
||||
|
||||
if (!options.live) this.each(function() { get(this); });
|
||||
|
||||
if (options.trigger != 'manual') {
|
||||
var binder = options.live ? 'live' : 'bind',
|
||||
eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus',
|
||||
eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur';
|
||||
this[binder](eventIn, enter)[binder](eventOut, leave);
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
$.fn.tipsy.defaults = {
|
||||
className: null,
|
||||
delayIn: 0,
|
||||
delayOut: 0,
|
||||
fade: false,
|
||||
fallback: '',
|
||||
gravity: 'n',
|
||||
html: false,
|
||||
live: false,
|
||||
offset: 0,
|
||||
opacity: 0.8,
|
||||
title: 'title',
|
||||
trigger: 'hover'
|
||||
};
|
||||
|
||||
$.fn.tipsy.revalidate = function() {
|
||||
$('.tipsy').each(function() {
|
||||
var pointee = $.data(this, 'tipsy-pointee');
|
||||
if (!pointee || !isElementInDOM(pointee)) {
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Overwrite this method to provide options on a per-element basis.
|
||||
// For example, you could store the gravity in a 'tipsy-gravity' attribute:
|
||||
// return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' });
|
||||
// (remember - do not modify 'options' in place!)
|
||||
$.fn.tipsy.elementOptions = function(ele, options) {
|
||||
return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
|
||||
};
|
||||
|
||||
$.fn.tipsy.autoNS = function() {
|
||||
return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
|
||||
};
|
||||
|
||||
$.fn.tipsy.autoWE = function() {
|
||||
return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
|
||||
};
|
||||
|
||||
/**
|
||||
* yields a closure of the supplied parameters, producing a function that takes
|
||||
* no arguments and is suitable for use as an autogravity function like so:
|
||||
*
|
||||
* @param margin (int) - distance from the viewable region edge that an
|
||||
* element should be before setting its tooltip's gravity to be away
|
||||
* from that edge.
|
||||
* @param prefer (string, e.g. 'n', 'sw', 'w') - the direction to prefer
|
||||
* if there are no viewable region edges effecting the tooltip's
|
||||
* gravity. It will try to vary from this minimally, for example,
|
||||
* if 'sw' is preferred and an element is near the right viewable
|
||||
* region edge, but not the top edge, it will set the gravity for
|
||||
* that element's tooltip to be 'se', preserving the southern
|
||||
* component.
|
||||
*/
|
||||
$.fn.tipsy.autoBounds = function(margin, prefer) {
|
||||
return function() {
|
||||
var dir = {ns: prefer[0], ew: (prefer.length > 1 ? prefer[1] : false)},
|
||||
boundTop = $(document).scrollTop() + margin,
|
||||
boundLeft = $(document).scrollLeft() + margin,
|
||||
$this = $(this);
|
||||
|
||||
if ($this.offset().top < boundTop) dir.ns = 'n';
|
||||
if ($this.offset().left < boundLeft) dir.ew = 'w';
|
||||
if ($(window).width() + $(document).scrollLeft() - $this.offset().left < margin) dir.ew = 'e';
|
||||
if ($(window).height() + $(document).scrollTop() - $this.offset().top < margin) dir.ns = 's';
|
||||
|
||||
return dir.ns + (dir.ew ? dir.ew : '');
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery);
|
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue