Merge branch 'bugfix/service-grid-grouping-postgres-9333'

Conflicts:
	modules/monitoring/application/controllers/ListController.php
	modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php

fixes #9333
fixes #9538
This commit is contained in:
Eric Lippmann 2015-08-27 12:58:49 +02:00
commit c294283636
14 changed files with 514 additions and 448 deletions

View File

@ -4,23 +4,23 @@
namespace Icinga\Application\Modules; namespace Icinga\Application\Modules;
use Exception; use Exception;
use Zend_Controller_Router_Route;
use Zend_Controller_Router_Route_Abstract; use Zend_Controller_Router_Route_Abstract;
use Zend_Controller_Router_Route as Route; use Zend_Controller_Router_Route_Regex;
use Zend_Controller_Router_Route_Regex as RegexRoute;
use Icinga\Application\ApplicationBootstrap; use Icinga\Application\ApplicationBootstrap;
use Icinga\Application\Config; use Icinga\Application\Config;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
use Icinga\Application\Logger; use Icinga\Application\Logger;
use Icinga\Data\ConfigObject; use Icinga\Data\ConfigObject;
use Icinga\Exception\IcingaException;
use Icinga\Exception\ProgrammingError;
use Icinga\Module\Setup\SetupWizard;
use Icinga\Util\File;
use Icinga\Util\Translator; use Icinga\Util\Translator;
use Icinga\Web\Hook; use Icinga\Web\Hook;
use Icinga\Web\Menu; use Icinga\Web\Menu;
use Icinga\Web\Widget; use Icinga\Web\Widget;
use Icinga\Web\Widget\Dashboard\Pane; use Icinga\Web\Widget\Dashboard\Pane;
use Icinga\Module\Setup\SetupWizard;
use Icinga\Util\File;
use Icinga\Exception\ProgrammingError;
use Icinga\Exception\IcingaException;
/** /**
* Module handling * Module handling
@ -188,7 +188,7 @@ class Module
/** /**
* A set of menu elements * A set of menu elements
* *
* @var array * @var Menu[]
*/ */
protected $menuItems = array(); protected $menuItems = array();
@ -770,6 +770,7 @@ class Module
{ {
$this->launchConfigScript(); $this->launchConfigScript();
$tabs = Widget::create('tabs'); $tabs = Widget::create('tabs');
/** @var \Icinga\Web\Widget\Tabs $tabs */
$tabs->add('info', array( $tabs->add('info', array(
'url' => 'config/module', 'url' => 'config/module',
'urlParams' => array('name' => $this->getName()), 'urlParams' => array('name' => $this->getName()),
@ -1039,12 +1040,13 @@ class Module
protected function registerRoutes() protected function registerRoutes()
{ {
$router = $this->app->getFrontController()->getRouter(); $router = $this->app->getFrontController()->getRouter();
/** @var \Zend_Controller_Router_Rewrite $router */
foreach ($this->routes as $name => $route) { foreach ($this->routes as $name => $route) {
$router->addRoute($name, $route); $router->addRoute($name, $route);
} }
$router->addRoute( $router->addRoute(
$this->name . '_jsprovider', $this->name . '_jsprovider',
new Route( new Zend_Controller_Router_Route(
'js/' . $this->name . '/:file', 'js/' . $this->name . '/:file',
array( array(
'controller' => 'static', 'controller' => 'static',
@ -1055,7 +1057,7 @@ class Module
); );
$router->addRoute( $router->addRoute(
$this->name . '_img', $this->name . '_img',
new RegexRoute( new Zend_Controller_Router_Route_Regex(
'img/' . $this->name . '/(.+)', 'img/' . $this->name . '/(.+)',
array( array(
'controller' => 'static', 'controller' => 'static',

View File

@ -4,12 +4,11 @@
namespace Icinga\Data; namespace Icinga\Data;
use Icinga\Data\Filter\Filter; use Icinga\Data\Filter\Filter;
use Icinga\Data\SimpleQuery;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
use Icinga\Web\Paginator\Adapter\QueryAdapter; use Icinga\Web\Paginator\Adapter\QueryAdapter;
use Zend_Paginator; use Zend_Paginator;
class PivotTable class PivotTable implements Sortable
{ {
/** /**
* The query to fetch as pivot table * The query to fetch as pivot table
@ -19,53 +18,74 @@ class PivotTable
protected $baseQuery; protected $baseQuery;
/** /**
* The query to fetch the x axis labels * X-axis pivot column
*
* @var SimpleQuery
*/
protected $xAxisQuery;
/**
* The query to fetch the y axis labels
*
* @var SimpleQuery
*/
protected $yAxisQuery;
/**
* The column that contains the labels for the x axis
* *
* @var string * @var string
*/ */
protected $xAxisColumn; protected $xAxisColumn;
/** /**
* The column that contains the labels for the y axis * Y-axis pivot column
* *
* @var string * @var string
*/ */
protected $yAxisColumn; protected $yAxisColumn;
/** /**
* The filter being applied on the query for the x axis * Column for sorting the result set
*
* @var array
*/
protected $order = array();
/**
* The filter being applied on the query for the x-axis
* *
* @var Filter * @var Filter
*/ */
protected $xAxisFilter; protected $xAxisFilter;
/** /**
* The filter being applied on the query for the y axis * The filter being applied on the query for the y-axis
* *
* @var Filter * @var Filter
*/ */
protected $yAxisFilter; protected $yAxisFilter;
/**
* The query to fetch the leading x-axis rows and their headers
*
* @var SimpleQuery
*/
protected $xAxisQuery;
/**
* The query to fetch the leading y-axis rows and their headers
*
* @var SimpleQuery
*/
protected $yAxisQuery;
/**
* X-axis header column
*
* @var string|null
*/
protected $xAxisHeader;
/**
* Y-axis header column
*
* @var string|null
*/
protected $yAxisHeader;
/** /**
* Create a new pivot table * Create a new pivot table
* *
* @param SimpleQuery $query The query to fetch as pivot table * @param SimpleQuery $query The query to fetch as pivot table
* @param string $xAxisColumn The column that contains the labels for the x axis * @param string $xAxisColumn X-axis pivot column
* @param string $yAxisColumn The column that contains the labels for the y axis * @param string $yAxisColumn Y-axis pivot column
*/ */
public function __construct(SimpleQuery $query, $xAxisColumn, $yAxisColumn) public function __construct(SimpleQuery $query, $xAxisColumn, $yAxisColumn)
{ {
@ -75,7 +95,32 @@ class PivotTable
} }
/** /**
* Set the filter to apply on the query for the x axis * {@inheritdoc}
*/
public function getOrder()
{
return $this->order;
}
/**
* {@inheritdoc}
*/
public function hasOrder()
{
return ! empty($this->order);
}
/**
* {@inheritdoc}
*/
public function order($field, $direction = null)
{
$this->order[$field] = $direction;
return $this;
}
/**
* Set the filter to apply on the query for the x-axis
* *
* @param Filter $filter * @param Filter $filter
* *
@ -88,7 +133,7 @@ class PivotTable
} }
/** /**
* Set the filter to apply on the query for the y axis * Set the filter to apply on the query for the y-axis
* *
* @param Filter $filter * @param Filter $filter
* *
@ -100,6 +145,56 @@ class PivotTable
return $this; return $this;
} }
/**
* Get the x-axis header
*
* Defaults to {@link $xAxisColumn} in case no x-axis header has been set using {@link setXAxisHeader()}
*
* @return string
*/
public function getXAxisHeader()
{
return $this->xAxisHeader !== null ? $this->xAxisHeader : $this->xAxisColumn;
}
/**
* Set the x-axis header
*
* @param string $xAxisHeader
*
* @return $this
*/
public function setXAxisHeader($xAxisHeader)
{
$this->xAxisHeader = (string) $xAxisHeader;
return $this;
}
/**
* Get the y-axis header
*
* Defaults to {@link $yAxisColumn} in case no x-axis header has been set using {@link setYAxisHeader()}
*
* @return string
*/
public function getYAxisHeader()
{
return $this->yAxisHeader !== null ? $this->yAxisHeader : $this->yAxisColumn;
}
/**
* Set the y-axis header
*
* @param string $yAxisHeader
*
* @return $this
*/
public function setYAxisHeader($yAxisHeader)
{
$this->yAxisHeader = (string) $yAxisHeader;
return $this;
}
/** /**
* Return the value for the given request parameter * Return the value for the given request parameter
* *
@ -131,17 +226,19 @@ class PivotTable
{ {
if ($this->xAxisQuery === null) { if ($this->xAxisQuery === null) {
$this->xAxisQuery = clone $this->baseQuery; $this->xAxisQuery = clone $this->baseQuery;
$this->xAxisQuery->group($this->xAxisColumn); $xAxisHeader = $this->getXAxisHeader();
$this->xAxisQuery->columns(array($this->xAxisColumn)); $columns = array($this->xAxisColumn, $xAxisHeader);
$this->xAxisQuery->setUseSubqueryCount(); $this->xAxisQuery->group(array_unique($columns)); // xAxisColumn and header may be the same column
$this->xAxisQuery->columns($columns);
if ($this->xAxisFilter !== null) { if ($this->xAxisFilter !== null) {
$this->xAxisQuery->addFilter($this->xAxisFilter); $this->xAxisQuery->addFilter($this->xAxisFilter);
} }
if (! $this->xAxisQuery->hasOrder($this->xAxisColumn)) { $this->xAxisQuery->order(
$this->xAxisQuery->order($this->xAxisColumn, 'asc'); $xAxisHeader,
} isset($this->order[$xAxisHeader]) ? $this->order[$xAxisHeader] : self::SORT_ASC
);
} }
return $this->xAxisQuery; return $this->xAxisQuery;
@ -156,24 +253,25 @@ class PivotTable
{ {
if ($this->yAxisQuery === null) { if ($this->yAxisQuery === null) {
$this->yAxisQuery = clone $this->baseQuery; $this->yAxisQuery = clone $this->baseQuery;
$this->yAxisQuery->group($this->yAxisColumn); $yAxisHeader = $this->getYAxisHeader();
$this->yAxisQuery->columns(array($this->yAxisColumn)); $columns = array($this->yAxisColumn, $yAxisHeader);
$this->yAxisQuery->setUseSubqueryCount(); $this->yAxisQuery->group(array_unique($columns)); // yAxisColumn and header may be the same column
$this->yAxisQuery->columns($columns);
if ($this->yAxisFilter !== null) { if ($this->yAxisFilter !== null) {
$this->yAxisQuery->addFilter($this->yAxisFilter); $this->yAxisQuery->addFilter($this->yAxisFilter);
} }
if (! $this->yAxisQuery->hasOrder($this->yAxisColumn)) { $this->yAxisQuery->order(
$this->yAxisQuery->order($this->yAxisColumn, 'asc'); $yAxisHeader,
isset($this->order[$yAxisHeader]) ? $this->order[$yAxisHeader] : self::SORT_ASC
);
} }
}
return $this->yAxisQuery; return $this->yAxisQuery;
} }
/** /**
* Return a pagination adapter for the x axis query * Return a pagination adapter for the x-axis query
* *
* $limit and $page are taken from the current request if not given. * $limit and $page are taken from the current request if not given.
* *
@ -204,7 +302,7 @@ class PivotTable
} }
/** /**
* Return a pagination adapter for the y axis query * Return a pagination adapter for the y-axis query
* *
* $limit and $page are taken from the current request if not given. * $limit and $page are taken from the current request if not given.
* *
@ -235,7 +333,7 @@ class PivotTable
} }
/** /**
* Return the pivot table as array * Return the pivot table as an array of pivot data and pivot header
* *
* @return array * @return array
*/ */
@ -245,33 +343,39 @@ class PivotTable
($this->xAxisFilter === null && $this->yAxisFilter === null) ($this->xAxisFilter === null && $this->yAxisFilter === null)
|| ($this->xAxisFilter !== null && $this->yAxisFilter !== null) || ($this->xAxisFilter !== null && $this->yAxisFilter !== null)
) { ) {
$xAxis = $this->queryXAxis()->fetchColumn(); $xAxis = $this->queryXAxis()->fetchPairs();
$yAxis = $this->queryYAxis()->fetchColumn(); $yAxis = $this->queryYAxis()->fetchPairs();
} else { } else {
if ($this->xAxisFilter !== null) { if ($this->xAxisFilter !== null) {
$xAxis = $this->queryXAxis()->fetchColumn(); $xAxis = $this->queryXAxis()->fetchPairs();
$yAxis = $this->queryYAxis()->where($this->xAxisColumn, $xAxis)->fetchColumn(); $yAxis = $this->queryYAxis()->where($this->xAxisColumn, $xAxis)->fetchPairs();
} else { // $this->yAxisFilter !== null } else { // $this->yAxisFilter !== null
$yAxis = $this->queryYAxis()->fetchColumn(); $yAxis = $this->queryYAxis()->fetchPairs();
$xAxis = $this->queryXAxis()->where($this->yAxisColumn, $yAxis)->fetchColumn(); $xAxis = $this->queryXAxis()->where($this->yAxisColumn, $yAxis)->fetchPairs();
} }
} }
$pivotData = array();
$pivot = array(); $pivotHeader = array(
'cols' => $xAxis,
'rows' => $yAxis
);
if (! empty($xAxis) && ! empty($yAxis)) { if (! empty($xAxis) && ! empty($yAxis)) {
$this->baseQuery->where($this->xAxisColumn, $xAxis)->where($this->yAxisColumn, $yAxis); $xAxisKeys = array_keys($xAxis);
$yAxisKeys = array_keys($yAxis);
$this->baseQuery
->where($this->xAxisColumn, $xAxisKeys)
->where($this->yAxisColumn, $yAxisKeys);
foreach ($yAxis as $yLabel) { foreach ($yAxisKeys as $yAxisKey) {
foreach ($xAxis as $xLabel) { foreach ($xAxisKeys as $xAxisKey) {
$pivot[$yLabel][$xLabel] = null; $pivotData[$yAxisKey][$xAxisKey] = null;
} }
} }
foreach ($this->baseQuery as $row) { foreach ($this->baseQuery as $row) {
$pivot[$row->{$this->yAxisColumn}][$row->{$this->xAxisColumn}] = $row; $pivotData[$row->{$this->yAxisColumn}][$row->{$this->xAxisColumn}] = $row;
} }
} }
return array($pivotData, $pivotHeader);
return $pivot;
} }
} }

View File

@ -12,6 +12,7 @@ class StyleSheet
protected static $lessFiles = array( protected static $lessFiles = array(
'../application/fonts/fontello-ifont/css/ifont-embedded.css', '../application/fonts/fontello-ifont/css/ifont-embedded.css',
'css/vendor/tipsy.css', 'css/vendor/tipsy.css',
'css/icinga/mixins.less',
'css/icinga/defaults.less', 'css/icinga/defaults.less',
'css/icinga/animation.less', 'css/icinga/animation.less',
'css/icinga/layout-colors.less', 'css/icinga/layout-colors.less',

View File

@ -118,12 +118,12 @@ class SortBox extends AbstractWidget
if ($request === null) { if ($request === null) {
$request = Icinga::app()->getRequest(); $request = Icinga::app()->getRequest();
} }
if (null === $sort = $request->getParam('sort')) {
if (($sort = $request->getParam('sort'))) { list($sort, $dir) = $this->getSortDefaults();
$this->query->order($sort, $request->getParam('dir')); } else {
} elseif (($dir = $request->getParam('dir'))) { list($_, $dir) = $this->getSortDefaults($sort);
$this->query->order(null, $dir);
} }
$this->query->order($sort, $request->getParam('dir', $dir));
} }
return $this; return $this;
@ -148,8 +148,10 @@ class SortBox extends AbstractWidget
if ($column !== null && isset($sortRules[$column]['order'])) { if ($column !== null && isset($sortRules[$column]['order'])) {
$direction = strtoupper($sortRules[$column]['order']) === Sortable::SORT_DESC ? 'desc' : 'asc'; $direction = strtoupper($sortRules[$column]['order']) === Sortable::SORT_DESC ? 'desc' : 'asc';
} }
} elseif ($column === null) {
reset($this->sortFields);
$column = key($this->sortFields);
} }
return array($column, $direction); return array($column, $direction);
} }

View File

@ -556,29 +556,36 @@ class Monitoring_ListController extends Controller
{ {
$this->addTitleTab('servicegrid', $this->translate('Service Grid'), $this->translate('Show the Service Grid')); $this->addTitleTab('servicegrid', $this->translate('Service Grid'), $this->translate('Show the Service Grid'));
$this->setAutorefreshInterval(15); $this->setAutorefreshInterval(15);
$problems = (bool) $this->params->shift('problems', 0);
$query = $this->backend->select()->from('servicestatus', array( $query = $this->backend->select()->from('servicestatus', array(
'host_display_name',
'host_name', 'host_name',
'service_description', 'service_description',
'service_state', 'service_display_name',
'service_handled',
'service_output', 'service_output',
'service_handled' 'service_state'
)); ));
$this->applyRestriction('monitoring/filter/objects', $query); $this->applyRestriction('monitoring/filter/objects', $query);
$this->filterQuery($query); $this->filterQuery($query);
$this->setupSortControl(array( $filter = (bool) $this->params->shift('problems', false) ? Filter::where('service_problem', 1) : null;
'host_name' => $this->translate('Hostname'), $pivot = $query
'service_description' => $this->translate('Service description') ->pivot(
), $query);
$pivot = $query->pivot(
'service_description', 'service_description',
'host_name', 'host_name',
$problems ? Filter::where('service_problem', 1) : null, $filter,
$problems ? Filter::where('service_problem', 1) : null $filter ? clone $filter : null
); )
$this->view->pivot = $pivot; ->setXAxisHeader('service_display_name')
->setYAxisHeader('host_display_name');
$this->setupSortControl(array(
'host_display_name' => $this->translate('Hostname'),
'service_display_name' => $this->translate('Service Name')
), $pivot);
$this->view->horizontalPaginator = $pivot->paginateXAxis(); $this->view->horizontalPaginator = $pivot->paginateXAxis();
$this->view->verticalPaginator = $pivot->paginateYAxis(); $this->view->verticalPaginator = $pivot->paginateYAxis();
list($pivotData, $pivotHeader) = $pivot->toArray();
$this->view->pivotData = $pivotData;
$this->view->pivotHeader = $pivotHeader;
} }
/** /**

View File

@ -12,19 +12,14 @@ if (! $this->compact): ?>
<?php endif ?> <?php endif ?>
<div class="content" data-base-target="_next"> <div class="content" data-base-target="_next">
<?php <?php
if (empty($pivotData)) {
$hasHeader = false;
$pivotData = $this->pivot->toArray();
if (count($pivotData) === 0) {
echo $this->translate('No services found matching the filter') . '</div>'; echo $this->translate('No services found matching the filter') . '</div>';
return; return;
} }
$hostFilter = '(host_name=' . implode('|host_name=', array_keys($pivotData)) . ')'; $hostFilter = '(host_name=' . implode('|host_name=', array_keys($pivotData)) . ')';
?> ?>
<table class="pivot servicestates">
<?php foreach ($pivotData as $host_name => $serviceStates): ?> <table class="service-grid-table">
<?php if (!$hasHeader): ?>
<thead> <thead>
<tr> <tr>
<th><?= $this->partial( <th><?= $this->partial(
@ -35,71 +30,63 @@ $hostFilter = '(host_name=' . implode('|host_name=', array_keys($pivotData)) . '
'yAxisPaginator' => $verticalPaginator 'yAxisPaginator' => $verticalPaginator
) )
); ?></th> ); ?></th>
<th colspan="<?= count($serviceStates); ?>"> <?php foreach ($pivotHeader['cols'] as $serviceDescription => $serviceDisplayName): ?>
<div> <th class="rotate-45"><div><span><?= $this->qlink(
<?php $this->ellipsis($serviceDisplayName, 18),
$serviceDescriptions = array_keys($serviceStates);
$serviceFilter = '(service_description=' . implode('|service_description=', $serviceDescriptions) . ')';
foreach ($serviceDescriptions as $service_description): ?>
<span>
<?= $this->qlink(
'<abbr>' . (strlen($service_description) > 18 ? substr($service_description, 0, 18) . '...' : $service_description) . '</abbr>',
'monitoring/list/services?' . $hostFilter, 'monitoring/list/services?' . $hostFilter,
array( array('service_description' => $serviceDescription),
'service_description' => $service_description array('title' => sprintf(
), $this->translate('List all services with the name "%s" on all reported hosts'),
array( $serviceDisplayName
'title' => sprintf($this->translate('List all services with the name "%s" on all reported hosts'), $service_description) )),
),
false false
); ?> ) ?></span></div></th>
</span>
<?php endforeach ?> <?php endforeach ?>
</div>
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php $hasHeader = true; ?> <?php foreach ($pivotHeader['rows'] as $hostName => $hostDisplayName): ?>
<?php endif ?>
<tr> <tr>
<th> <th><?php
<?= $this->qlink( $services = $pivotData[$hostName];
$host_name, $serviceFilter = '(service_description=' . implode('|service_description=', array_keys($services)) . ')';
echo $this->qlink(
$hostDisplayName,
'monitoring/list/services?' . $serviceFilter, 'monitoring/list/services?' . $serviceFilter,
array('host' => $host_name), array('host_name' => $hostName),
array('title' => sprintf($this->translate('List all reported services on host %s'), $host_name)) array('title' => sprintf($this->translate('List all reported services on host %s'), $hostDisplayName))
); ?> );
</th> ?></th>
<?php foreach (array_values($serviceStates) as $service): ?> <?php foreach (array_keys($pivotHeader['cols']) as $serviceDescription): ?>
<?php if ($service !== null): ?>
<td> <td>
<span class="sr-only" id="<?= $service->host_name . '_' . $service->service_description . '_desc'; ?>"> <?php
<?= $this->escape($service->service_output); ?> $service = $pivotData[$hostName][$serviceDescription];
if ($service === null): ?>
<span aria-hidden="true">&middot;</span>
<?php continue; endif ?>
<?php $ariaDescribedById = $this->protectId($service->host_name . '_' . $service->service_description . '_desc') ?>
<span class="sr-only" id="<?= $ariaDescribedById ?>">
<?= $this->escape($service->service_output) ?>
</span> </span>
<?= $this->qlink( <?= $this->qlink(
'', '',
'monitoring/show/service', 'monitoring/show/service',
array( array(
'host' => $service->host_name, 'host' => $hostName,
'service' => $service->service_description 'service' => $serviceDescription
), ),
array( array(
'aria-describedby' => $service->host_name . '_' . $service->service_description . '_desc', 'aria-describedby' => $ariaDescribedById,
'class' => 'state_' . Service::getStateText($service->service_state). ($service->service_handled ? ' handled' : ''), 'class' => 'bg-state-' . Service::getStateText($service->service_state) . ($service->service_handled ? ' handled' : ''),
'title' => $this->escape($service->service_output), 'title' => $this->escape($service->service_output),
'aria-label' => sprintf( 'aria-label' => sprintf(
$this->translate('Show detailed information for service %s on host %s'), $this->translate('Show detailed information for service %s on host %s'),
$service->service_description, $service->service_display_name,
$service->host_name $service->host_display_name
) )
) )
); ?> ); ?>
</td> </td>
<?php else: ?>
<td><span aria-hidden="true">&middot;</span></td>
<?php endif ?>
<?php endforeach ?> <?php endforeach ?>
</tr> </tr>
<?php endforeach ?> <?php endforeach ?>

View File

@ -240,3 +240,9 @@ $dashboard->add(
$this->translate('Host Problems'), $this->translate('Host Problems'),
'monitoring/list/hosts?host_problem=1&sort=host_severity' 'monitoring/list/hosts?host_problem=1&sort=host_severity'
); );
/*
* CSS
*/
$this->provideCssFile('colors.less');
$this->provideCssFile('service-grid.less');

View File

@ -257,62 +257,65 @@ class HoststatusQuery extends IdoQuery
*/ */
public function getGroup() public function getGroup()
{ {
$group = array(); $group = parent::getGroup() ?: array();
if ($this->hasJoinedVirtualTable('hostgroups') || $this->hasJoinedVirtualTable('services')) { if (! is_array($group)) {
$group = array('h.host_id', 'ho.object_id'); $group = array($group);
if ($this->hasJoinedVirtualTable('hoststatus')) {
$group[] = 'hs.hoststatus_id';
} }
$groupedTables = array();
if ($this->hasJoinedVirtualTable('serviceproblemsummary')) {
$group[] = 'sps.unhandled_services_count';
}
if ($this->hasJoinedVirtualTable('hostgroups')) {
$selected = false;
foreach ($this->columns as $alias => $column) {
if ($column instanceof Zend_Db_Expr) {
continue;
}
$table = $this->aliasToTableName(
$this->hasAliasName($alias) ? $alias : $this->customAliasToAlias($alias)
);
if ($table === 'hostgroups') {
$selected = true;
break;
}
}
if ($selected) {
$group[] = 'hg.hostgroup_id';
$group[] = 'hgo.object_id';
}
}
if ($this->hasJoinedVirtualTable('servicegroups')) { if ($this->hasJoinedVirtualTable('servicegroups')) {
$selected = false; $serviceGroupColumns = array_keys($this->columnMap['servicegroups']);
$selectedServiceGroupColumns = array_intersect($serviceGroupColumns, array_keys($this->columns));
if (! empty($selectedServiceGroupColumns)) {
$group[] = 'ho.object_id';
$group[] = 'h.host_id';
$group[] = 'sgo.object_id';
$group[] = 'sg.servicegroup_id';
$groupedTables['hosts'] = true;
$groupedTables['servicegroups'] = true;
}
}
if ($this->hasJoinedVirtualTable('hostgroups')) {
$hostGroupColumns = array_keys($this->columnMap['hostgroups']);
$selectedHostGroupColumns = array_intersect($hostGroupColumns, array_keys($this->columns));
if (! empty($selectedHostGroupColumns)) {
if (! isset($groupedTables['hosts'])) {
$group[] = 'ho.object_id';
$group[] = 'h.host_id';
$groupedTables['hosts'] = true;
}
$group[] = 'hgo.object_id';
$group[] = 'hg.hostgroup_id';
$groupedTables['hostgroups'] = true;
}
}
if (! empty($groupedTables)) {
foreach ($this->columns as $alias => $column) { foreach ($this->columns as $alias => $column) {
if ($column instanceof Zend_Db_Expr) { if ($column instanceof Zend_Db_Expr || $column === '(NULL)') {
continue; continue;
} }
$tableName = $this->aliasToTableName(
$table = $this->aliasToTableName(
$this->hasAliasName($alias) ? $alias : $this->customAliasToAlias($alias) $this->hasAliasName($alias) ? $alias : $this->customAliasToAlias($alias)
); );
if ($table === 'servicegroups') { if (isset($groupedTables[$tableName])) {
$selected = true; continue;
}
switch ($tableName) {
case 'hoststatus':
$group[] = 'hs.hoststatus_id';
break; break;
case 'serviceproblemsummary':
$group[] = 'sps.unhandled_services_count';
break;
case 'services':
$group[] = 'so.object_id';
$group[] = 's.service_id';
break;
default:
continue 2;
}
$groupedTables[$tableName] = true;
} }
} }
if ($selected) {
$group[] = 'sg.servicegroup_id';
$group[] = 'sgo.object_id';
}
}
}
return $group; return $group;
} }

View File

@ -461,17 +461,23 @@ abstract class IdoQuery extends DbQuery
if ($filter->getExpression() === '*') { if ($filter->getExpression() === '*') {
return; // Wildcard only filters are ignored so stop early here to avoid joining a table for nothing return; // Wildcard only filters are ignored so stop early here to avoid joining a table for nothing
} }
$alias = $filter->getColumn();
$col = $filter->getColumn(); $this->requireColumn($alias);
$this->requireColumn($col); if ($this->isCustomvar($alias)) {
$column = $this->getCustomvarColumnName($alias);
if ($this->isCustomvar($col)) {
$col = $this->getCustomvarColumnName($col);
} else { } else {
$col = $this->aliasToColumnName($col); $column = $this->aliasToColumnName($alias);
} }
if (isset($this->columnsWithoutCollation[$alias])) {
$expression = $filter->getExpression();
if (is_array($expression)) {
$filter->setExpression(array_map('strtolower', $expression));
} else {
$filter->setExpression(strtolower($expression));
$filter->setColumn($col); }
}
$filter->setColumn($column);
} else { } else {
foreach ($filter->filters() as $filter) { foreach ($filter->filters() as $filter) {
$this->requireFilterColumns($filter); $this->requireFilterColumns($filter);
@ -489,48 +495,11 @@ abstract class IdoQuery extends DbQuery
return parent::addFilter($filter); return parent::addFilter($filter);
} }
/**
* Recurse the given filter and ensure that any string conversion is case-insensitive
*
* @param Filter $filter
*/
protected function lowerColumnsWithoutCollation(Filter $filter)
{
if ($filter instanceof FilterExpression) {
if (
in_array($filter->getColumn(), $this->columnsWithoutCollation)
&& strpos($filter->getColumn(), 'LOWER') !== 0
) {
$filter->setColumn('LOWER(' . $filter->getColumn() . ')');
$expression = $filter->getExpression();
if (is_array($expression)) {
$filter->setExpression(array_map('strtolower', $expression));
} else {
$filter->setExpression(strtolower($expression));
}
}
} else {
foreach ($filter->filters() as $chainedFilter) {
$this->lowerColumnsWithoutCollation($chainedFilter);
}
}
}
protected function applyFilterSql($select)
{
if (! empty($this->columnsWithoutCollation)) {
$this->lowerColumnsWithoutCollation($this->filter);
}
parent::applyFilterSql($select);
}
public function where($condition, $value = null) public function where($condition, $value = null)
{ {
if ($value === '*') { if ($value === '*') {
return $this; // Wildcard only filters are ignored so stop early here to avoid joining a table for nothing return $this; // Wildcard only filters are ignored so stop early here to avoid joining a table for nothing
} }
$this->requireColumn($condition); $this->requireColumn($condition);
$col = $this->getMappedField($condition); $col = $this->getMappedField($condition);
if ($col === null) { if ($col === null) {
@ -578,7 +547,6 @@ abstract class IdoQuery extends DbQuery
if (! empty($this->columnsWithoutCollation)) { if (! empty($this->columnsWithoutCollation)) {
return in_array($column, $this->columnsWithoutCollation) || strpos($column, 'LOWER') !== 0; return in_array($column, $this->columnsWithoutCollation) || strpos($column, 'LOWER') !== 0;
} }
return preg_match('/ COLLATE .+$/', $column) === 1; return preg_match('/ COLLATE .+$/', $column) === 1;
} }
@ -603,27 +571,27 @@ abstract class IdoQuery extends DbQuery
} }
/** /**
* Apply postgresql specific query initialization * Apply PostgreSQL specific query initialization
*/ */
private function initializeForPostgres() private function initializeForPostgres()
{ {
$this->customVarsJoinTemplate = $this->customVarsJoinTemplate =
'%1$s = %2$s.object_id AND LOWER(%2$s.varname) = %3$s'; '%1$s = %2$s.object_id AND LOWER(%2$s.varname) = %3$s';
foreach ($this->columnMap as $table => &$columns) { foreach ($this->columnMap as $table => &$columns) {
foreach ($columns as $key => & $value) { foreach ($columns as $alias => &$column) {
$value = preg_replace('/ COLLATE .+$/', '', $value, -1, $count); if (false !== $pos = strpos($column, ' COLLATE')) {
if ($count > 0) { $column = 'LOWER(' . substr($column, 0, $pos) . ')';
$this->columnsWithoutCollation[] = $this->getMappedField($key); $this->columnsWithoutCollation[$alias] = true;
} }
$value = preg_replace( $column = preg_replace(
'/inet_aton\(([[:word:].]+)\)/i', '/inet_aton\(([[:word:].]+)\)/i',
'(CASE WHEN $1 ~ \'(?:[0-9]{1,3}\\\\.){3}[0-9]{1,3}\' THEN $1::inet - \'0.0.0.0\' ELSE NULL END)', '(CASE WHEN $1 ~ \'(?:[0-9]{1,3}\\\\.){3}[0-9]{1,3}\' THEN $1::inet - \'0.0.0.0\' ELSE NULL END)',
$value $column
); );
$value = preg_replace( $column = preg_replace(
'/UNIX_TIMESTAMP(\((?>[^()]|(?-1))*\))/i', '/UNIX_TIMESTAMP(\((?>[^()]|(?-1))*\))/i',
'CASE WHEN ($1 < \'1970-01-03 00:00:00+00\'::timestamp with time zone) THEN 0 ELSE UNIX_TIMESTAMP($1) END', 'CASE WHEN ($1 < \'1970-01-03 00:00:00+00\'::timestamp with time zone) THEN 0 ELSE UNIX_TIMESTAMP($1) END',
$value $column
); );
} }
} }

View File

@ -381,68 +381,60 @@ class ServicestatusQuery extends IdoQuery
if (! is_array($group)) { if (! is_array($group)) {
$group = array($group); $group = array($group);
} }
$groupedTables = array();
if ($this->hasJoinedVirtualTable('hostgroups') || $this->hasJoinedVirtualTable('servicegroups')) {
$group[] = 's.service_id';
$group[] = 'so.object_id';
if ($this->hasJoinedVirtualTable('hosts')) {
$group[] = 'h.host_id';
}
if ($this->hasJoinedVirtualTable('hoststatus')) {
$group[] = 'hs.hoststatus_id';
}
if ($this->hasJoinedVirtualTable('servicestatus')) {
$group[] = 'ss.servicestatus_id';
}
if ($this->hasJoinedVirtualTable('hostgroups')) {
$selected = false;
foreach ($this->columns as $alias => $column) {
if ($column instanceof Zend_Db_Expr) {
continue;
}
$table = $this->aliasToTableName(
$this->hasAliasName($alias) ? $alias : $this->customAliasToAlias($alias)
);
if ($table === 'hostgroups') {
$selected = true;
break;
}
}
if ($selected) {
$group[] = 'hg.hostgroup_id';
$group[] = 'hgo.object_id';
}
}
if ($this->hasJoinedVirtualTable('servicegroups')) { if ($this->hasJoinedVirtualTable('servicegroups')) {
$selected = false; $serviceGroupColumns = array_keys($this->columnMap['servicegroups']);
$selectedServiceGroupColumns = array_intersect($serviceGroupColumns, array_keys($this->columns));
if (! empty($selectedServiceGroupColumns)) {
$group[] = 'so.object_id';
$group[] = 's.service_id';
$group[] = 'sgo.object_id';
$group[] = 'sg.servicegroup_id';
$groupedTables['services'] = true;
$groupedTables['servicegroups'] = true;
}
}
if ($this->hasJoinedVirtualTable('hostgroups')) {
$hostGroupColumns = array_keys($this->columnMap['hostgroups']);
$selectedHostGroupColumns = array_intersect($hostGroupColumns, array_keys($this->columns));
if (! empty($selectedHostGroupColumns)) {
if (! isset($groupedTables['services'])) {
$group[] = 'so.object_id';
$group[] = 's.service_id';
$groupedTables['services'] = true;
}
$group[] = 'hgo.object_id';
$group[] = 'hg.hostgroup_id';
$groupedTables['hostgroups'] = true;
}
}
if (! empty($groupedTables)) {
foreach ($this->columns as $alias => $column) { foreach ($this->columns as $alias => $column) {
if ($column instanceof Zend_Db_Expr) { if ($column instanceof Zend_Db_Expr || $column === '(NULL)') {
continue; continue;
} }
$tableName = $this->aliasToTableName(
$table = $this->aliasToTableName(
$this->hasAliasName($alias) ? $alias : $this->customAliasToAlias($alias) $this->hasAliasName($alias) ? $alias : $this->customAliasToAlias($alias)
); );
if ($table === 'servicegroups') { if (isset($groupedTables[$tableName])) {
$selected = true; continue;
}
switch ($tableName) {
case 'hosts':
$group[] = 'h.host_id';
break; break;
case 'hoststatus':
$group[] = 'hs.hoststatus_id';
break;
case 'servicestatus':
$group[] = 'ss.servicestatus_id';
break;
default:
continue 2;
}
$groupedTables[$tableName] = true;
} }
} }
if ($selected) {
$group[] = 'sg.servicegroup_id';
$group[] = 'sgo.object_id';
}
}
}
return $group; return $group;
} }
} }

View File

@ -0,0 +1,43 @@
/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
.bg-state-ok,
.bg-state-up {
background-color: @colorOk;
}
.bg-state-warning {
background-color: @colorWarning;
&.handled {
background-color: @colorWarningHandled;
}
}
.bg-state-critical,
.bg-state-down {
background-color: @colorCritical;
&.handled {
background-color: @colorCriticalHandled;
}
}
.bg-state-unreachable {
background-color: @colorUnreachable;
&.handled {
background-color: @colorUnreachableHandled;
}
}
.bg-state-unknown {
background-color: @colorUnknown;
&.handled {
background-color: @colorUnknownHandled;
}
}
.bg-state-pending {
background-color: @colorPending;
}

View File

@ -842,112 +842,6 @@ table.joystick-pagination {
} }
} }
table.pivot {
a {
text-decoration: none;
color: black;
&:hover {
color: @colorTextDefault;
}
}
& > thead {
th {
height: 6em;
div {
margin-right: -1.5em;
padding-left: 1.3em;
span {
width: 1.5em;
margin-right: 0.25em;
margin-top: 4em;
line-height: 2em;
white-space: nowrap;
display: block;
float: left;
transform: rotate(-45deg);
transform-origin: bottom left;
-o-transform: rotate(-45deg);
-o-transform-origin: bottom left;
-ms-transform: rotate(-45deg);
-ms-transform-origin: bottom left;
-moz-transform: rotate(-45deg);
-moz-transform-origin: bottom left;
-webkit-transform: rotate(-45deg);
-webkit-transform-origin: bottom left;
//filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
abbr {
border: 0; // Remove highlighting in firefox
font-size: 0.8em;
}
}
}
}
}
& > tbody {
th {
padding: 0 14px 0 0;
white-space: nowrap;
a {
font-size: 0.8em;
}
}
td {
padding: 2px;
min-width: 1.5em;
line-height: 1.5em;
text-align: center;
a {
width: 1.5em;
height: 1.5em;
display: block;
border-radius: 0.5em;
&.state_ok {
background-color: @colorOk;
}
&.state_pending {
background-color: @colorPending;
}
&.state_warning {
background-color: @colorWarning;
&.handled {
background-color: @colorWarningHandled;
}
}
&.state_critical {
background-color: @colorCritical;
&.handled {
background-color: @colorCriticalHandled;
}
}
&.state_unknown {
background-color: @colorUnknown;
&.handled {
background-color: @colorUnknownHandled;
}
}
}
}
}
}
/* End of monitoring pivot table styles */ /* End of monitoring pivot table styles */
/* Monitoring timeline styles */ /* Monitoring timeline styles */

View File

@ -0,0 +1,38 @@
/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
table.service-grid-table {
white-space: nowrap;
th {
a {
color: @colorMainLink;
font-weight: normal;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
td {
text-align: center;
width: 1.5em;
a {
.rounded-corners(0.4em);
display: block;
height: 1.5em;
width: 1.5em;
}
}
th.rotate-45 {
height: 6em;
div {
.transform(translate(0.4em, 2.1em) rotate(315deg));
width: 1.5em;
}
}
}

View File

@ -0,0 +1,19 @@
/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
.transform(@transform) {
-webkit-transform: @transform;
-moz-transform: @transform;
-ms-transform: @transform;
-o-transform: @transform;
transform: @transform;
}
.rounded-corners(@border-radius: 0.4em) {
-webkit-border-radius: @border-radius;
-moz-border-radius: @border-radius;
border-radius: @border-radius;
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
background-clip: padding-box;
}