Merge branch 'master' into feature/navigation-unhandled-badges-7114

Conflicts:
	library/Icinga/Web/Menu.php
This commit is contained in:
Matthias Jentsch 2014-09-05 18:14:29 +02:00
commit 51d14af154
31 changed files with 746 additions and 64 deletions

View File

@ -9,6 +9,7 @@ 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
@ -38,20 +39,21 @@ class ListController extends Controller
*/
public function applicationlogAction()
{
if (! Logger::writesToFile()) {
throw new ActionError('Site not found', 404);
}
$this->addTitleTab('application log');
$pattern = '/^(?<datetime>[0-9]{4}(-[0-9]{2}){2}' // date
. 'T[0-9]{2}(:[0-9]{2}){2}([\\+\\-][0-9]{2}:[0-9]{2})?)' // time
. ' - (?<loglevel>[A-Za-z]+)' // loglevel
. ' - (?<message>.*)$/'; // message
$loggerWriter = Logger::getInstance()->getWriter();
if ($loggerWriter instanceof FileWriter) {
$resource = new FileReader(new Zend_Config(array(
'filename' => $loggerWriter->getPath(),
'fields' => '/^(?<datetime>[0-9]{4}(-[0-9]{2}){2}' // date
. 'T[0-9]{2}(:[0-9]{2}){2}([\\+\\-][0-9]{2}:[0-9]{2})?)' // time
. ' - (?<loglevel>[A-Za-z]+)' // loglevel
. ' - (?<message>.*)$/' // message
)));
$this->view->logData = $resource->select()->order('DESC')->paginate();
} else {
$this->view->logData = null;
}
$resource = new FileReader(new Zend_Config(array(
'filename' => $loggerWriter->getPath(),
'fields' => $pattern
)));
$this->view->logData = $resource->select()->order('DESC')->paginate();
}
}

View File

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

View File

@ -5,6 +5,7 @@
namespace Icinga\Form\Preference;
use DateTimeZone;
use Icinga\Util\TimezoneDetect;
use Zend_Config;
use Zend_Form_Element_Text;
use Zend_Form_Element_Select;
@ -69,13 +70,22 @@ class GeneralForm extends Form
*/
private function addTimezoneSelection(Zend_Config $cfg)
{
$prefs = $this->getUserPreferences();
$useGlobalTimezone = $this->getRequest()->getParam('default_timezone', !$prefs->has('app.timezone'));
$detect = new TimezoneDetect();
$tzList = array();
foreach (DateTimeZone::listIdentifiers() as $tz) {
$tzList[$tz] = $tz;
}
$helptext = 'Use the following timezone for dates and times';
$prefs = $this->getUserPreferences();
$useGlobalTimezone = $this->getRequest()->getParam('default_timezone', !$prefs->has('app.timezone'));
if ($useGlobalTimezone && $detect->success() === true) {
$helptext .= '<br />' . t('Currently your time was detected to be')
. ': '
. '<strong>' . $detect->getTimezoneName() . '</strong>';
}
$selectTimezone = new Zend_Form_Element_Select(
array(
@ -87,6 +97,7 @@ class GeneralForm extends Form
'value' => $prefs->get('app.timezone', $cfg->get('timezone', date_default_timezone_get()))
)
);
$this->addElement(
'checkbox',
'default_timezone',
@ -99,7 +110,9 @@ class GeneralForm extends Form
if ($useGlobalTimezone) {
$selectTimezone->setAttrib('disabled', 1);
}
$this->addElement($selectTimezone);
$this->enableAutoSubmit(array('default_timezone'));
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ namespace Icinga\Protocol\File;
use Icinga\Data\Selectable;
use Countable;
use Icinga\Util\Enumerate;
use Zend_Config;
/**
@ -52,7 +53,9 @@ class FileReader implements Selectable, Countable
*/
public function iterate()
{
return new FileIterator($this->filename, $this->fields);
return new Enumerate(
new FileIterator($this->filename, $this->fields)
);
}
/**
@ -117,15 +120,13 @@ class FileReader implements Selectable, Countable
$skip = $count - ($skip + $read);
}
}
$index = 0;
foreach ($this->iterate() as $line) {
foreach ($this->iterate() as $index => $line) {
if ($index >= $skip) {
if ($index >= $skip + $read) {
break;
}
$lines[] = $line;
}
++$index;
}
if ($query->sortDesc()) {
$lines = array_reverse($lines);

View File

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

View File

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

View File

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

View File

@ -9,6 +9,7 @@ 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;
@ -67,6 +68,13 @@ class Menu implements RecursiveIterator
* @var MenuItemRenderer
*/
protected $itemRenderer = null;
/*
* Parent menu
*
* @var Menu
*/
protected $parent;
/**
* Create a new menu
@ -74,9 +82,12 @@ class Menu implements RecursiveIterator
* @param int $id The id of this menu
* @param Zend_Config $config The configuration for this menu
*/
public function __construct($id, Zend_Config $config = null)
public function __construct($id, Zend_Config $config = null, Menu $parent = null)
{
$this->id = $id;
if ($parent !== null) {
$this->parent = $parent;
}
$this->setProperties($config);
}
@ -212,10 +223,13 @@ class Menu implements RecursiveIterator
'url' => 'config/modules',
'priority' => 400
));
$section->add(t('ApplicationLog'), array(
'url' => 'list/applicationlog',
'priority' => 500
));
if (Logger::writesToFile()) {
$section->add(t('Application Log'), array(
'url' => 'list/applicationlog',
'priority' => 500
));
}
$this->add(t('Logout'), array(
'url' => 'authentication/logout',
@ -247,6 +261,30 @@ class Menu implements RecursiveIterator
return $this->id;
}
/**
* Get our ID without invalid characters
*
* @return string the ID
*/
protected function getSafeHtmlId()
{
return preg_replace('/[^a-zA-Z0-9]/', '_', $this->getId());
}
/**
* Get a unique menu item id
*
* @return string the ID
*/
public function getUniqueId()
{
if ($this->parent === null) {
return 'menuitem-' . $this->getSafeHtmlId();
} else {
return $this->parent->getUniqueId() . '-' . $this->getSafeHtmlId();
}
}
/**
* Set the title of this menu
*
@ -384,7 +422,7 @@ class Menu implements RecursiveIterator
public function addSubMenu($id, Zend_Config $menuConfig = null)
{
if (false === ($pos = strpos($id, '.'))) {
$subMenu = new self($id, $menuConfig);
$subMenu = new self($id, $menuConfig, $this);
$this->subMenus[$id] = $subMenu;
} else {
list($parentId, $id) = explode('.', $id, 2);
@ -651,4 +689,12 @@ class Menu implements RecursiveIterator
{
next($this->subMenus);
}
/**
* PHP 5.3 GC should not leak, but just to be on the safe side...
*/
public function __destruct()
{
$this->parent = null;
}
}

View File

@ -118,9 +118,9 @@ class MenuRenderer extends RecursiveIteratorIterator
if ($childIsActive || ($passedActiveChild && $this->getDepth() === 0)) {
$passedActiveChild &= $this->getDepth() !== 0;
$openTag = '<li class="active">';
$openTag = '<li class="active" id="' . $child->getUniqueId() . '">';
} else {
$openTag = '<li>';
$openTag = '<li id="' . $child->getUniqueId() . '">';
}
$content = $this->renderChild($child);
$closingTag = '</li>';

View File

@ -5,6 +5,7 @@
namespace Icinga\Web;
use Icinga\Application\Icinga;
use Icinga\Cli\FakeRequest;
use Icinga\Exception\ProgrammingError;
use Icinga\Web\UrlParams;
@ -98,7 +99,12 @@ class Url
*/
protected static function getRequest()
{
return Icinga::app()->getFrontController()->getRequest();
$app = Icinga::app();
if ($app->isCli()) {
return new FakeRequest();
} else {
return $app->getFrontController()->getRequest();
}
}
/**
@ -424,6 +430,24 @@ class Url
return $url;
}
/**
* Return a copy of this url with the given parameter(s)
*
* The argument can be either a single query parameter name or an array of parameter names to
* remove from the query list
*
* @param string|array $param A single string or an array containing parameter names
* @param array $values an optional values array
*
* @return Url
*/
public function with($param, $values = null)
{
$url = clone($this);
$url->params->mergeValues($param, $values);
return $url;
}
public function __clone()
{
$this->params = clone $this->params;

View File

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

View File

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

View File

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

View File

@ -4,7 +4,6 @@
use Icinga\Module\Monitoring\Controller;
use Icinga\Module\Monitoring\Backend;
use Icinga\Module\Monitoring\DataView\DataView;
use Icinga\Web\Url;
use Icinga\Web\Hook;
use Icinga\Web\Widget\Tabextension\DashboardAction;
@ -236,6 +235,25 @@ class Monitoring_ListController extends Controller
// TODO: Workaround, paginate should be able to fetch limit from new params
$this->view->services = $query->paginate($this->params->get('limit'));
}
$this->view->stats = $this->backend->select()->from('statusSummary', array(
'services_total',
'services_ok',
'services_problem',
'services_problem_handled',
'services_problem_unhandled',
'services_critical',
'services_critical_unhandled',
'services_critical_handled',
'services_warning',
'services_warning_unhandled',
'services_warning_handled',
'services_unknown',
'services_unknown_unhandled',
'services_unknown_handled',
'services_pending',
))->getQuery()->fetchRow();
}
/**
@ -500,19 +518,19 @@ class Monitoring_ListController extends Controller
{
$this->addTitleTab('servicematrix');
$this->setAutorefreshInterval(15);
$dataview = $this->backend->select()->from('serviceStatus', array(
$query = $this->backend->select()->from('serviceStatus', array(
'host_name',
'service_description',
'service_state',
'service_output',
'service_handled'
));
$this->applyFilters($dataview);
$this->applyFilters($query);
$this->setupSortControl(array(
'host_name' => 'Hostname',
'service_description' => 'Service description'
));
$pivot = $dataview->pivot('service_description', 'host_name');
$pivot = $query->pivot('service_description', 'host_name');
$this->view->pivot = $pivot;
$this->view->horizontalPaginator = $pivot->paginateXAxis();
$this->view->verticalPaginator = $pivot->paginateYAxis();
@ -572,10 +590,6 @@ class Monitoring_ListController extends Controller
/**
* Apply current user's `monitoring/filter' restrictions on the given data view
*
* @param DataView $dataView
*
* @return DataView
*/
protected function applyRestrictions($query)
{

View File

@ -70,6 +70,7 @@ class Monitoring_ShowController extends Controller
if ($this->grapher) {
$this->view->grapherHtml = $this->grapher->getPreviewHtml($o);
}
$this->fetchHostStats();
}
/**
@ -85,6 +86,7 @@ class Monitoring_ShowController extends Controller
if ($this->grapher) {
$this->view->grapherHtml = $this->grapher->getPreviewHtml($o);
}
$this->fetchHostStats();
}
public function historyAction()
@ -94,6 +96,7 @@ class Monitoring_ShowController extends Controller
$this->view->object->fetchEventHistory();
$this->view->history = $this->view->object->eventhistory->paginate($this->params->get('limit', 50));
$this->handleFormatRequest($this->view->object->eventhistory);
$this->fetchHostStats();
}
public function servicesAction()
@ -106,6 +109,28 @@ class Monitoring_ShowController extends Controller
'view' => 'compact',
'sort' => 'service_description',
));
$this->fetchHostStats();
}
protected function fetchHostStats()
{
$this->view->stats = $this->backend->select()->from('statusSummary', array(
'services_total',
'services_ok',
'services_problem',
'services_problem_handled',
'services_problem_unhandled',
'services_critical',
'services_critical_unhandled',
'services_critical_handled',
'services_warning',
'services_warning_unhandled',
'services_warning_handled',
'services_unknown',
'services_unknown_unhandled',
'services_unknown_handled',
'services_pending',
))->where('service_host_name', $this->params->get('host'))->getQuery()->fetchRow();
}
public function contactAction()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -515,7 +515,7 @@
var isMenuLink = $a.closest('#menu').length > 0;
var formerUrl;
var remote = /^(?:[a-z]+:)\/\//;
if (href.match(/^javascript:/)) {
if (href.match(/^(mailto|javascript):/)) {
return true;
}

View File

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